/****************************************************************************
 *
 *  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 com.dalsemi.fs.*;
import java.io.*;
import java.net.*;

/* This class is an implementation of the com.dalsemi.fs.FileSystemDriver interface.
 * It uses sockets to communicate with a file server that performs most of the work.
 * This driver just maintains the connection and sends simple commands to the server
 * and parses the results when necessary.  The server program is implemented in the 
 * NetworkFileSystemHost.java file.  To use these classes see the readme.html file that
 * is included with the distribution.
 */
public class NetworkFileSystemDriver implements FileSystemDriver
{
    //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;

    //Communication streams.
    Socket sck;
    DataInputStream in;
    DataOutputStream out;
    
    public NetworkFileSystemDriver()
    {
        //No work needs to be done here.  Everything will be taken care of during the init method.
    }
    
    public void init(String[] args) throws Exception
    {
        //arg[0] is ip address of file system server.  args[1] is the port to connect to.
        int port = Integer.parseInt(args[1]);
        int newPort = 0;
        try
        {
            sck = new Socket(args[0], port);
            
            //Once connected the server will send back a new port to connect to.  This way
            //the original server port will remain available for additional connections.
            in = new DataInputStream(sck.getInputStream());
            newPort = in.readInt();
        }
        finally
        {
            if(in != null)
            {
                in.close();
            }
            
            if(sck != null)
            {
                sck.close();
            }
        }

        //Open the new socket and get its input and output streams.
        //We'll wrap them in Data streams for convenience.
        sck = new Socket(args[0], newPort);
        in = new DataInputStream(sck.getInputStream());
        out = new DataOutputStream(sck.getOutputStream());
    }
    
    public boolean exists(String fileName)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(EXISTS);
            writeString(fileName);
            
            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }
    
    public boolean canWrite(String fileName, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(CAN_WRITE);
            writeString(fileName);
            
            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }
    
    public boolean canRead(String fileName, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(CAN_READ);
            writeString(fileName);
            
            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }

    public boolean canExec(String fileName, byte uid)
    {
        //In this implementation, if a file can be read, it can be executed,
        //so just test if it is readable.
        return canRead(fileName, uid);
    }
    
    public boolean isFile(String fileName)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(IS_FILE);
            writeString(fileName);

            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }

    public boolean isDirectory(String fileName)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(IS_DIRECTORY);
            writeString(fileName);

            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }
    
    public long lastModified(String fileName)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(LAST_MODIFIED);
            writeString(fileName);

            //Get the result.
            return in.readLong();
        }
        catch(Exception s)
        {
            return 0L;
        }
    }
    
    public long length(String fileName)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(LENGTH);
            writeString(fileName);
            
            //Get the result.
            return in.readLong();
        }
        catch(Exception s)
        {
            return 0L;
        }
    }
    
    public boolean mkdir(String fileName, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(MKDIR);
            writeString(fileName);

            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }

    public boolean rename(String srcname, String destname, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(RENAME);
            writeString(srcname);
            writeString(destname);

            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }

    public String[] list(String fileName, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(LIST);
            writeString(fileName);

            //Get the result.
            return readStringArray();
        }
        catch(Exception s)
        {
            return new String[0];
        }
    }
    
    public boolean delete(String fileName, byte uid)
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(DELETE);
            writeString(fileName);

            //Get the result.
            return in.readBoolean();
        }
        catch(Exception s)
        {
            return false;
        }
    }
    
    public void unmount()
    {
        try
        {
            try
            {
                //Send the command byte.
                //This will notify the server it can close its end.
                out.writeByte(CLOSE);
            }
            finally
            {
                //We don't care what the server does, we still need to close our end.
                in.close();
                out.close();
                sck.close();
            }
        }
        catch(IOException x)
        {
        }
    }

    public int getUserPermissions(String fileName) throws FileNotFoundException
    {
        //Permissions aren't supported in this implementation, so we'll just
        //assume everyone gets all permissions.
        return 7;
    }
    
    public int getOtherPermissions(String fileName) throws FileNotFoundException
    {
        //Permissions aren't supported in this implementation, so we'll just
        //assume everyone gets all permissions.
        return 7;
    }

    public int getUser(String fileName) throws FileNotFoundException
    {
        //Users aren't supported in this implementation, so we'll just
        //assume the current user is the owner of any file.
        return com.dalsemi.system.TINIOS.getCurrentUID();
    }
    
    //To send a string to the server, we'll first send the string's length
    //followed by the string itself.
    private void writeString(String str) throws Exception
    {
        out.writeInt(str.length());
        out.writeBytes(str);
    }

    //When reading a string array back from the server, we first get the length
    //of the array.  Then each string is read by first reading the length of the
    //string followed by the string itself.
    private String[] readStringArray() throws Exception
    {
        int length = in.readInt();
        String[] ret = new String[length];
        
        for(int i = 0; i < length; i++)
        {
            int bytes = in.readInt();
            byte[] b = new byte[bytes];
            in.readFully(b);
            ret[i] = new String(b);
        }
        
        return ret;
    }
    
    public void setUserPermissions(String fileName, int perms, byte uid) throws IOException
    {
        //Permissions not supported in this implementation.
    }
    
    public void setOtherPermissions(String fileName, int perms, byte uid) throws IOException
    {
        //Permissions not supported in this implementation.
    }
    
    public void setUser(String fileName, byte newUID, byte uid) throws IOException
    {
        //Users not supported in this implementation.
    }

    public byte[] openWritingFD(String fileName, boolean append, byte uid) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(OPEN_WRITING);
            writeString(fileName);
            out.writeBoolean(append);
            
            //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 the server
            //uses to identify the file.
            byte[] res = new byte[9];
            in.readFully(res);
            return res;
        }
        catch(Exception s)
        {
            throw new IOException();
        }
    }

    public byte[] openReadingFD(String fileName, byte uid) throws FileNotFoundException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(OPEN_READING);
            writeString(fileName);

            //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 the server
            //uses to identify the file.
            byte[] res = new byte[9];
            in.readFully(res);
            return res;
        }
        catch(Exception s)
        {
            throw new FileNotFoundException();
        }
    }
    
    public byte[] openRandomFD(String fileName, byte uid) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(OPEN_RANDOM);
            writeString(fileName);

            //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 the server
            //uses to identify the file.
            byte[] res = new byte[9];
            in.readFully(res);
            return res;
        }
        catch(Exception s)
        {
            throw new IOException();
        }
    }
    
    public void writeBytes(byte[] fd, byte[] data, int start, int length) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The server will handle the rest.
            out.writeByte(WRITE);
            out.write(fd);
            out.writeInt(length);
            out.write(data, start, length);
        }
        catch(Exception s)
        {
            throw new IOException();
        }
    }

    public int readBytes(byte[] fd, byte[] data, int start, int length) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            out.writeByte(READ);
            out.write(fd);
            out.writeInt(length);
            
            //Read in the number of bytes read.
            int amount = in.readInt();
            //Get the actual bytes read.
            if(amount > 0)
            {
                in.readFully(data, start, amount);
            }
            
            //Return the result.
            return amount;
        }
        catch(Exception s)
        {
            throw new IOException();
        }
    }

    public void close(byte[] fd) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(CLOSE_FD);
        out.write(fd);
    }

    public long getLength(byte[] fd) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(GET_LENGTH);
        out.write(fd);
        
        //Get the result.
        return in.readLong();
    }

    public int available(byte[] fd) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(AVAILABLE);
        out.write(fd);
        
        //Get the result.
        return in.readInt();
    }

    public void seek(byte[] fd, long n) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(SEEK);
        out.write(fd);
        out.writeLong(n);
    }
    
    public long skipBytes(byte[] fd, long n) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(SKIP);
        out.write(fd);
        out.writeLong(n);
        
        //Get the result.
        return in.readLong();
    }

    public long getOffset(byte[] fd) throws IOException
    {
        //Send the command byte and the necessary arguments.
        out.writeByte(OFFSET);
        out.write(fd);
        
        //Get the result.
        return in.readLong();
    }
    
    public void touch(String fileName, byte uid) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(TOUCH);
            writeString(fileName);
        }
        catch(Exception x)
        {
        }
    }
    
    public byte[] getContents(String fileName, byte uid) throws IOException
    {
        try
        {
            //Send the command byte and the necessary arguments.
            //The uid is not used in this implementation.
            out.writeByte(CONTENTS);
            writeString(fileName);
        
            //Get the number of bytes returned.
            int length = in.readInt();
            if(length == -1)
            {
                //A little bit of error checking.
                throw new Exception();
            }
            
            //Create the return array and fill it.
            byte[] contents = new byte[length];
            in.readFully(contents);        
            return contents;
        }
        catch(Exception x)
        {
            throw new FileNotFoundException();
        }
    }
}
