Download | Plain Text | Line Numbers


/*
 * Copyright (c) 2010, Manuel Mausz. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - The names of the authors may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
import static java.lang.System.err;
import static java.lang.System.out;
 
import java.util.Arrays;
 
import java.net.*;
import java.io.*;
 
/*
 * Client implementation for Lab#1 of DSLab WS10
 * See angabe.pdf for details
 *
 * This code is not documented at all. This is volitional
 *
 * @author Manuel Mausz (0728348)
 */
public class Client
{
  public class ProxyConnection
    extends CommandNetwork
    implements Runnable
  {
    private final ObjectInputStream oin;
    private final String downloadDir;
    private final Object mainLock;
    private final Object intLock;
 
    ProxyConnection(InputStream in, String downloadDir,
        Object mainLock, Object intLock)
      throws NoSuchMethodException, IOException
    {
      this.oin         = new ObjectInputStream(new BufferedInputStream(in));
      this.downloadDir = downloadDir;
      this.mainLock    = mainLock;
      this.intLock     = intLock;
 
      cmdHandler.register("!error",    this, "cmdError");
      cmdHandler.register("!output",   this, "cmdOutput");
      cmdHandler.register("!download", this, "cmdDownload");
      cmdHandler.register("unknown",   this, "cmdUnknown");
    }
 
    /*------------------------------------------------------------------------*/
 
    public void notifyInteractive()
    {
      synchronized(intLock)
      {
        intLock.notify();
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdPrintOutput(PrintStream out, String cmd, String[] args)
      throws IOException
    {
      long num;
      if ((num = Utils.parseHeaderNum(args, 0)) < 0)
        return;
 
      String msg;
      for (; num > 0 && (msg = oin.readUTF()) != null; --num)
        out.println(msg);
      notifyInteractive();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdError(String cmd, String[] args)
      throws IOException
    {
      cmdPrintOutput(err, cmd, args);
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdOutput(String cmd, String[] args)
      throws IOException
    {
      cmdPrintOutput(out, cmd, args);
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdDownload(String cmd, String[] args)
    {
      if (args.length < 2 || args[1].length() <= 0)
      {
        err.println("Error: Invalid " + cmd + "-command paket from proxy. Ignoring...");
        notifyInteractive();
        return;
      }
 
      long filesize;
      if ((filesize = Utils.parseHeaderNum(args, 1)) < 0)
        return;
 
      File file = new File(downloadDir, args[0]);
      file.delete();
      try
      {
        FileOutputStream fout = null;
        try
        {
          fout = new FileOutputStream(file);
        }
        catch(FileNotFoundException e)
        {
          err.println("Error: Unable to write to file '" + file + "': " + e.getMessage());
        }
 
        byte[] buffer = new byte[8 * 1024];
        int toread = buffer.length;
        while(filesize > 0)
        {
          if (filesize < toread)
            toread = (int) filesize;
          int count = oin.read(buffer, 0, toread);
          if (count == -1)
            throw new IOException("Connection reset by peer");
          if (fout != null)
            fout.write(buffer, 0, count);
          filesize -= count;
        }
 
        if (fout != null)
          out.println("File '" + file + "' successfully downloaded.");
      }
      catch(IOException e)
      {
        err.println("Error: Error during file transfer: " + e.getMessage());
      }
      notifyInteractive();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdUnknown(String cmd, String[] args)
    {
      err.println("Error: Unknown data from proxy: " + cmd + " "
          + Utils.join(Arrays.asList(args), " "));
      notifyInteractive();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void run()
    {
      try
      {
        run(oin);
      }
      catch(CommandHandler.Exception e)
      {
        err.println("Internal Error: " + e.getMessage());
      }
      catch(IOException e)
      {
        /* ignore that exception
         * thread will shutdown and unlock the main thread
         * which will shutdown the application
         */
      }
 
      notifyInteractive();
      synchronized(mainLock)
      {
        mainLock.notify();
      }
    }
  }
 
  /*==========================================================================*/
 
  public class Interactive
    extends CommandInteractive
    implements Runnable
  {
    private final InputStream sin;
    private final OutputStream sout;
    private final ObjectOutputStream oout;
    private final Object mainLock;
    private final Object intLock;
 
    Interactive(InputStream sin, OutputStream sout,
        Object mainLock, Object intLock)
      throws NoSuchMethodException, IOException
    {
      this.sin      = sin;
      this.sout     = sout;
      this.mainLock = mainLock;
      this.intLock  = intLock;
      this.oout     = new ObjectOutputStream(sout);
 
      cmdHandler.register("unknown", this, "cmdUnknown");
      cmdHandler.register("!exit",   this, "cmdExit");
    }
 
    /*------------------------------------------------------------------------*/
 
    public void waitForSocket()
    {
      synchronized(intLock)
      {
        try
        {
          intLock.wait(1000);
        }
        catch(InterruptedException e)
        {
          /* if we get interrupted -> ignore */
        }
      }
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdUnknown(String cmd, String[] args)
      throws IOException
    {
      oout.writeUTF(cmd + " " + Utils.join(Arrays.asList(args), " "));
      oout.flush();
      waitForSocket();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void cmdExit(String cmd, String[] args)
    {
      stop();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void printPrompt()
    {
      out.print(">: ");
      out.flush();
    }
 
    /*------------------------------------------------------------------------*/
 
    public void shutdown()
    {
      try
      {
        oout.flush();
      }
      catch(IOException e)
      {}
    }
 
    /*------------------------------------------------------------------------*/
 
    public void run()
    {
      try
      {
        run(sin);
      }
      catch(CommandHandler.Exception e)
      {
        err.println("Internal Error: " + e.getMessage());
      }
      catch(IOException e)
      {
        /* ignore that exception
         * thread will shutdown and unlock the main thread
         * which will shutdown the application
         */
      }
 
      shutdown();
      synchronized(mainLock)
      {
        mainLock.notify();
      }
    }
  }
 
  /*==========================================================================*/
 
  private static String downloadDir;
  private static String proxyHost;
  private static int proxyTCPPort;
  private Socket sock = null;
  private InputStream sockin = null;
  private OutputStream sockout = null;
  private Thread tPConnection = null;
  private Thread tInteractive = null;
  private InputStream stdin = null;
  private final Object interactiveLock = new Object();
  private final Object mainLock = new Object();
 
  /*--------------------------------------------------------------------------*/
 
  public static void usage()
    throws Utils.Shutdown
  {
    out.println("Usage: Client downloadDir proxyHost proxyTCPPort\n");
    out.println("downloadDir\t...the directory to put downloaded files");
    out.println("proxyHost\t...the host name or an IP address where the Proxy is running");
    out.println("proxyTCPPort\t...the TCP port where the server is listening for client connections");
 
    // Java is some piece of crap which doesn't allow me to set exitcode w/o
    // using System.exit. Maybe someday Java will be a fully functional
    // programming language, but I wouldn't bet my money
    //System.exit(1);
    throw new Utils.Shutdown("FUCK YOU JAVA");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void bailout(String error)
    throws Utils.Shutdown
  {
    err.println("Error: " + error);
    shutdown();
 
    // Java is some piece of crap which doesn't allow me to set exitcode w/o
    // using System.exit. Maybe someday Java will be a fully functional
    // programming language, but I wouldn't bet my money
    //System.exit(2);
    throw new Utils.Shutdown("FUCK YOU JAVA");
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void parseArgs(String[] args)
  {
    if (args.length != 3)
      usage();
 
    downloadDir = args[0];
    File dldir = new File(downloadDir);
    if (!dldir.isDirectory())
      bailout("downloadDir '" + downloadDir + "' is not a directory");
    if (!dldir.canWrite())
      bailout("downloadDir '" + downloadDir + "' is not writeable");
 
    proxyHost = args[1];
    if (proxyHost.length() == 0)
      bailout("proxyHost is empty");
 
    try
    {
      proxyTCPPort = Integer.parseInt(args[2]);
      if (proxyTCPPort <= 0 || proxyTCPPort > 65536)
        bailout("proxyTCPPort must be a valid port number (1 - 65535)");
    }
    catch(NumberFormatException e)
    {
      bailout("proxyTCPPort must be numeric");
    }
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void shutdown()
  {
    try
    {
      if (sockin != null)
        sockin.close();
    }
    catch(IOException e)
    {}
 
    try
    {
      if (sockout != null)
        sockout.close();
    }
    catch(IOException e)
    {}
 
    try
    {
      if (sock != null && !sock.isClosed())
        sock.close();
    }
    catch(IOException e)
    {}
 
    try
    {
      if (tPConnection != null)
        tPConnection.join();
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (tInteractive != null)
      {
        tInteractive.interrupt();
        tInteractive.join();
      }
    }
    catch(InterruptedException e)
    {}
 
    try
    {
      if (stdin != null)
        stdin.close();
    }
    catch(IOException e)
    {}
  }
 
  /*--------------------------------------------------------------------------*/
 
  public void run(String[] args)
  {
    parseArgs(args);
 
    try
    {
      out.println("Connecting to " + proxyHost + ":" + proxyTCPPort + "...");
      sock = new Socket(proxyHost, proxyTCPPort);
      sockin = sock.getInputStream();
      sockout = sock.getOutputStream();
      out.println("Connected...");
    }
    catch(UnknownHostException e)
    {
      bailout("Unable to resolve hostname: " + e.getMessage());
    }
    catch(IOException e)
    {
      bailout("Unable to connect to proxy: " + e.getMessage());
    }
 
    synchronized(mainLock)
    {
      try
      {
        tPConnection = new Thread(new ProxyConnection(sockin, downloadDir,
              mainLock, interactiveLock));
        tPConnection.start();
      }
      catch(NoSuchMethodException e)
      {
        bailout("Unable to setup remote command handler");
      }
      catch(IOException e)
      {
        bailout("Unable to create object input stream: " + e.getMessage());
      }
 
      try
      {
        InputStream stdin = java.nio.channels.Channels.newInputStream(
            new FileInputStream(FileDescriptor.in).getChannel());
        tInteractive = new Thread(new Interactive(stdin, sockout,
              mainLock, interactiveLock));
        tInteractive.start();
      }
      catch(NoSuchMethodException e)
      {
        bailout("Unable to setup interactive command handler");
      }
      catch(IOException e)
      {
        bailout("Unable to create object output stream: " + e.getMessage());
      }
 
      out.println("Client startup successful!");
      try
      {
        mainLock.wait();
      }
      catch(InterruptedException e)
      {
        /* if we get interrupted -> ignore */
      }
 
      try
      {
        /* let the threads shutdown */
        Thread.sleep(100);
      }
      catch(InterruptedException e)
      {}
    }
 
    if (tPConnection != null && !tPConnection.isAlive())
      bailout("Connection to proxy closed unexpected. Terminating...");
 
    shutdown();
  }
 
  /*--------------------------------------------------------------------------*/
 
  public static void main(String[] args)
  {
    try
    {
      Client cli = new Client();
      cli.run(args);
    }
    catch(Utils.Shutdown e)
    {}
  }
}