/* * 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) {} } }