/****************************************************************************
 *
 *  Copyright (C) 2001 Dallas Semiconductor Corporation.
 *  All rights Reserved. Printed in U.S.A.
 *  This software is protected by copyright laws of
 *  the United States and of foreign countries.
 *  This material may also be protected by patent laws of the United States
 *  and of foreign countries.
 *  This software is furnished under a license agreement and/or a
 *  nondisclosure agreement and may only be used or copied in accordance
 *  with the terms of those agreements.
 *  The mere transfer of this software does not imply any licenses
 *  of trade secrets, proprietary technology, copyrights, patents,
 *  trademarks, maskwork rights, or any other form of intellectual
 *  property whatsoever. Dallas Semiconductor retains all ownership rights.
 *
 *****************************************************************************/

import java.net.*;
import java.io.*;
import java.util.*;

/* This class implements a server that can be used to mount an external file
 * system for TINI.  This class should be used with the driver that is
 * implemented in the NetworkFileSystemDriver.java file.  To use these classes
 * see the readme.html file that is included with the distribution.
 */
public class NetworkFileSystemHost
{
    //File Descriptor types.
    public static final byte READING = 0;
    public static final byte WRITING = 1;
    public static final byte RANDOM = 2;

    //Command Bytes.
    public static final byte EXISTS = 0;
    public static final byte CAN_WRITE = 1;
    public static final byte CAN_READ = 2;
    public static final byte IS_FILE = 3;
    public static final byte IS_DIRECTORY = 4;
    public static final byte LAST_MODIFIED = 5;
    public static final byte LENGTH = 6;
    public static final byte MKDIR = 7;
    public static final byte RENAME = 8;
    public static final byte LIST = 9;
    public static final byte DELETE = 10;
    public static final byte CLOSE = 11;
    public static final byte OPEN_READING = 12;
    public static final byte OPEN_WRITING = 13;
    public static final byte OPEN_RANDOM = 14;
    public static final byte WRITE = 15;
    public static final byte READ = 16;
    public static final byte CLOSE_FD = 17;
    public static final byte GET_LENGTH = 18;
    public static final byte AVAILABLE = 19;
    public static final byte SEEK = 20;
    public static final byte SKIP = 21;
    public static final byte OFFSET = 22;
    public static final byte TOUCH = 23;
    public static final byte CONTENTS = 24;

    //Top level directory for the server.
    File parent;

    public NetworkFileSystemHost(String startDir) throws Exception
    {
        //Set the mounting point of the server.
        parent = new File(startDir);
        
        //Create a socket to accept incoming connections.
        ServerSocket ss = new ServerSocket(3453);
        System.out.println("Server running... Waiting for connections...");
        
        //Continue to accept incoming connections until the program is terminated.
        while(true)
        {
            Socket sck = null;
            DataOutputStream out = null;
            
            try
            {
                sck = ss.accept();
                System.out.println("Connected.");
            
                //Open a new server socket and send its port number to the client.
                //We want to keep the original free for more connections.
                out = new DataOutputStream(sck.getOutputStream());
                ServerSocket ss2 = new ServerSocket(0);
                
                //Start a thread to handle the new connection.  The thread will
                //be responsible for all file system operations.
                new SessionThread(ss2).start();
                
                out.writeInt(ss2.getLocalPort());
                out.flush();
            }
            finally
            {
                //Clean up.
                if(out != null)
                {
                    out.close();
                }
                
                if(sck != null)
                {
                    sck.close();
                }
            }
        }
    }
    
    class SessionThread extends Thread
    {
        //Tables of opened files.  These will be indexed with a Long object.
        Hashtable openRand = new Hashtable();
        Hashtable openWrite = new Hashtable();
        Hashtable openRead = new Hashtable();
        
        //The next unused indices for the tables.
        long randIndex = 0;
        long writeIndex = 0;
        long readIndex = 0;

        //Communication Streams.
        Socket sck;
        DataInputStream in;
        DataOutputStream out;
        
        boolean closed = false;
        ServerSocket ss;

        public SessionThread(ServerSocket ss) throws Exception
        {
            this.ss = ss;
        }
        
        public void run()
        {
            try
            {
                //Open the communication socket and get the streams.
                sck = ss.accept();
                in = new DataInputStream(sck.getInputStream());
                out = new DataOutputStream(sck.getOutputStream());
                
                //Continue to accept commands until the CLOSE command is received.
                while(!closed)
                {
                    parseCommand();
                }
            }
            catch(Exception x)
            {
            }
        }

        void parseCommand() throws Exception
        {
            File f;
            String fName = null;
            
            byte command = in.readByte();

            switch(command)
            {
                case EXISTS:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                    
                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.exists());
                    break;
                }
                case CAN_WRITE:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.canWrite());
                    break;
                }
                case CAN_READ:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.canRead());
                    break;
                }
                case IS_FILE:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.isFile());
                    break;
                }
                case IS_DIRECTORY:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.isDirectory());
                    break;
                }
                case LAST_MODIFIED:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeLong(f.lastModified());
                    break;
                }
                case LENGTH:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeLong(f.length());
                    break;
                }
                case MKDIR:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.mkdir());
                    break;
                }
                case RENAME:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                    String newName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    File dest = new File(parent, newName);
                    out.writeBoolean(f.renameTo(dest));
                    break;
                }
                case LIST:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    writeStringArray(f.list());
                    break;
                }
                case DELETE:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Send the result.
                    f = new File(parent, fName);
                    out.writeBoolean(f.delete());
                    break;
                }
                case CLOSE:
                {
                    //Close the communication socket as requested.
                    in.close();
                    out.close();
                    sck.close();
                    
                    //Clean up all the open files held by this thread.
                    closeOpenFiles(openRand);
                    closeOpenFiles(openRead);
                    closeOpenFiles(openWrite);

                    //Set the state to closed.  This will stop the main
                    //loop in the run() method and allow the thread to
                    //terminate.
                    closed = true;
                    break;
                }
                case OPEN_READING:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                    
                    //Open the requested file.
                    f = new File(parent, fName);
                    FileInputStream fis = new FileInputStream(f);
                    
                    //Store the stream in the appropriate table.
                    openRead.put(new Long(readIndex), fis);
                    
                    //Send the file descriptor back to the client.
                    //File descriptors can be any length and hold any information needed
                    //by a particular implementation.  In this example, a file descriptor
                    //consists of nine bytes.  The first byte is the type of descriptor
                    //(READING, WRITING, RANDOM), and the next 8 are a long that is the
                    //index into the table.
                    out.writeByte(READING);
                    out.writeLong(readIndex++);
                    break;
                }
                case OPEN_WRITING:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                    boolean append = in.readBoolean();

                    //Open the requested file.
                    f = new File(parent, fName);
                    FileOutputStream fos = new FileOutputStream(f.getCanonicalPath(), append);
                    
                    //Store the stream in the appropriate table.
                    openWrite.put(new Long(writeIndex), fos);

                    //Send the file descriptor back to the client.
                    //File descriptors can be any length and hold any information needed
                    //by a particular implementation.  In this example, a file descriptor
                    //consists of nine bytes.  The first byte is the type of descriptor
                    //(READING, WRITING, RANDOM), and the next 8 are a long that is the
                    //index into the table.
                    out.writeByte(WRITING);
                    out.writeLong(writeIndex++);
                    break;
                }
                case OPEN_RANDOM:
                {
                    //Get the parameters for this operation.
                    fName = readString();

                    //Open the requested file.
                    f = new File(parent, fName);
                    RandomAccessFile raf = new RandomAccessFile(f, "rw");

                    //Store the stream in the appropriate table.
                    openWrite.put(new Long(randIndex), raf);
                    
                    //Send the file descriptor back to the client.
                    //File descriptors can be any length and hold any information needed
                    //by a particular implementation.  In this example, a file descriptor
                    //consists of nine bytes.  The first byte is the type of descriptor
                    //(READING, WRITING, RANDOM), and the next 8 are a long that is the
                    //index into the table.
                    out.writeByte(RANDOM);
                    out.writeLong(randIndex++);
                    break;
                }
                case WRITE:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    int length = in.readInt();
                    byte[] data = new byte[length];
                    in.readFully(data);
                    
                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case WRITING:
                            FileOutputStream fos = (FileOutputStream)openWrite.get(new Long(index));
                            fos.write(data);
                            break;
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            raf.write(data);
                            break;
                    }
                    break;
                }
                case READ:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    int length = in.readInt();
                    byte[] data = new byte[length];

                    int amount = 0;
                    
                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case READING:
                            FileInputStream fis = (FileInputStream)openRead.get(new Long(index));
                            amount = fis.read(data);
                            break;
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            amount = raf.read(data);
                            break;
                    }
                    
                    //Return the amount and data that was read.
                    out.writeInt(amount);
                    if(amount > 0)
                    {
                        out.write(data, 0, amount);
                    }
                    break;
                }
                case CLOSE_FD:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case READING:
                            FileInputStream fis = (FileInputStream)openRead.get(new Long(index));
                            fis.close();
                            break;
                        case WRITING:
                            FileOutputStream fos = (FileOutputStream)openWrite.get(new Long(index));
                            fos.close();
                            break;
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            raf.close();
                            break;
                    }
                    break;
                }
                case GET_LENGTH:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();

                    long length = 0;

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            length = raf.length();
                            break;
                    }
                    
                    //Send the result.
                    out.writeLong(length);
                    break;
                }
                case AVAILABLE:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    
                    int available = 0;

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case READING:
                            FileInputStream fis = (FileInputStream)openRead.get(new Long(index));
                            available = fis.available();
                            break;
                    }
                    
                    //Send the result.
                    out.writeInt(available);
                    break;
                }
                case SEEK:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    long length = in.readLong();

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            raf.seek(length);
                            break;
                    }
                    break;
                }
                case SKIP:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    long length = in.readLong();
                    
                    long amount = 0;

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case READING:
                            FileInputStream fis = (FileInputStream)openRead.get(new Long(index));
                            amount = fis.skip(length);
                            break;
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            amount = raf.skipBytes((int)length);
                            break;
                    }
                    
                    //Send the result.
                    out.writeLong(amount);
                    break;
                }
                case OFFSET:
                {
                    //Get the parameters for this operation.
                    byte type = in.readByte();
                    long index = in.readLong();
                    
                    long offset = 0;

                    //Use the tables and the file descriptor to get the appropriate file.
                    switch(type)
                    {
                        case RANDOM:
                            RandomAccessFile raf = (RandomAccessFile)openRand.get(new Long(index));
                            offset = raf.getFilePointer();
                            break;
                    }
                    
                    //Send the result.
                    out.writeLong(offset);
                    break;
                }
                case TOUCH:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                    
                    //Perform the touch operation.
                    f = new File(parent, fName);
                    long end = f.length();
                    RandomAccessFile raf = new RandomAccessFile(f.getCanonicalPath(), "rw");
                    raf.seek(end);
                    raf.writeByte(0);
                    raf.setLength(end);
                    raf.close();
                    break;
                }
                case CONTENTS:
                {
                    //Get the parameters for this operation.
                    fName = readString();
                 
                    f = new File(parent, fName);

                    try
                    {
                        //Read in the entire contents of the file.
                        DataInputStream fis = new DataInputStream(new FileInputStream(f));
                        byte[] ba = new byte[(int)f.length()];
                        fis.readFully(ba);
                    
                        //Send back the result.
                        out.writeInt(ba.length);
                        out.write(ba);
                    }
                    catch(FileNotFoundException x)
                    {
                        out.writeInt(-1);
                    }
                    break;
                }
           }
        }
        
        //Strings are sent from the client by first sending the length
        //of the string followed by the string itself.
        private String readString() throws Exception
        {
            int length = in.readInt();
            byte[] b = new byte[length];
            in.readFully(b);
                    
            return new String(b);
        }
        
        //String arrays are sent by first sending the length of the
        //array.  The contents are then sent by first sending the
        //length of the string followed by the string itself.
        private void writeStringArray(String[] sa) throws Exception
        {
            out.writeInt(sa.length);
            for(int i = 0; i < sa.length; i++)
            {
                out.writeInt(sa[i].length());
                out.writeBytes(sa[i]);
            }
        }
    }

    private static void closeOpenFiles(Hashtable table)
    {
        Enumeration enum = table.elements();
        while(enum.hasMoreElements())
        {
            Object obj = enum.nextElement();
            if(obj instanceof RandomAccessFile)
            {
                try
                {
                    ((RandomAccessFile)obj).close();
                }
                catch(IOException x)
                {
                }
            }
            else if(obj instanceof FileInputStream)
            {
                try
                {
                    ((FileInputStream)obj).close();
                }
                catch(IOException x)
                {
                }
            }
            else if(obj instanceof FileOutputStream)
            {
                try
                {
                    ((FileOutputStream)obj).close();
                }
                catch(IOException x)
                {
                }
            }
        }
        table.clear();
    }
    
    public static void main(String[] args) throws Exception
    {
        if(args.length != 1)
        {
            System.err.println("USAGE: java NetworkFileSystemHost <StartDir>");
        }
        
        new NetworkFileSystemHost(args[0]);
    }
}
