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