package org.klomp.snark.cmd;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.Timer;
import java.util.TimerTask;
import org.klomp.snark.CoordinatorListener;
import org.klomp.snark.Peer;
import org.klomp.snark.PeerMonitorTask;
import org.klomp.snark.ShutdownListener;
import org.klomp.snark.Snark;
import org.klomp.snark.SnarkShutdown;
import org.klomp.snark.StorageListener;
/**
* A basic command line interface to the Snark library.
*
* @author Elizabeth Fong (elizabeth@threerings.net)
*/
public class SnarkApplication
{
public static void main (String[] args)
{
System.out.println(copyright);
System.out.println();
// Parse debug, share/ip and torrent file options.
Snark snark = parseArguments(args, new ConsoleStorageReporter(), null);
boolean interactive = true;
boolean showPeers = false;
for (String arg : args) {
if (arg.equals("--no-commands")) {
interactive = false;
}
if (arg.equals("--show-peers") || arg.equals("--share")) {
showPeers = true;
}
};
// Set things up to exit gracefully
ShutdownListener listener = new ShutdownListener() {
// documentation inherited from interface ShutdownListener
public void shutdown ()
{
// Should not be necessary since all non-deamon threads should
// have died. But in reality this does not always happen.
System.exit(0);
}
};
SnarkShutdown hook = new SnarkShutdown(snark, listener);
Runtime.getRuntime().addShutdownHook(hook);
// Let's start grabbing files!
try {
snark.setupNetwork();
snark.collectPieces();
} catch (IOException ioe) {
System.exit(-1);
}
// If requested, periodically monitor progress.
if (showPeers) {
Timer timer = new Timer(true);
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
timer.schedule(monitor, PeerMonitorTask.MONITOR_PERIOD,
PeerMonitorTask.MONITOR_PERIOD);
}
// Start interactive command interpreter if desired
if (interactive) {
doInteractive(snark, hook);
}
}
/**
* Initializes the user-interactive readline interface to Snark
*/
protected static void doInteractive (Snark snark, SnarkShutdown hook)
{
boolean quit = false;
System.out.println();
System.out.println(usage);
System.out.println();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(
System.in));
String line = br.readLine();
while (!quit && line != null) {
line = line.toLowerCase();
if ("quit".equals(line)) {
quit = true;
} else if ("list".equals(line)) {
synchronized (snark.coordinator.peers) {
System.out.println(snark.coordinator.peers.size()
+ " peers -" + " (i)nterested," + " (I)nteresting,"
+ " (c)hoking," + " (C)hoked:");
Iterator it = snark.coordinator.peers.iterator();
while (it.hasNext()) {
Peer peer = (Peer)it.next();
System.out.println(peer);
System.out.println("\ti: " + peer.isInterested()
+ " I: " + peer.isInteresting() + " c: "
+ peer.isChoking() + " C: " + peer.isChoked());
}
}
} else if ("info".equals(line)) {
System.out.println("Name: " + snark.meta.getName());
System.out.println("Torrent: " + snark.torrent);
System.out.println("Tracker: " + snark.meta.getAnnounce());
List files = snark.meta.getFiles();
System.out.println("Files: "
+ ((files == null) ? 1 : files.size()));
System.out.println("Pieces: " + snark.meta.getPieces());
System.out.println("Piece size: "
+ snark.meta.getPieceLength(0) / 1024 + " KB");
System.out.println("Total size: "
+ snark.meta.getTotalLength() / (1024 * 1024) + " MB");
} else if ("state".equals(line)) {
System.out.println(
snark.storage.getBitField().getHumanReadable());
System.out.println("Total peers: "
+ snark.coordinator.getPeers());
System.out.println("Total size: "
+ snark.meta.getTotalLength() / (1024 * 1024) + " MB");
System.out.println("Total remaining: "
+ snark.coordinator.getLeft() / (1024 * 1024) + " MB");
System.out.println("Total downloaded: "
+ snark.coordinator.getDownloaded());
System.out.println("Total uploaded: "
+ snark.coordinator.getUploaded());
} else if ("".equals(line) || "help".equals(line)) {
System.out.println(usage);
System.out.println(help);
} else {
System.out.println("Unknown command: " + line);
System.out.println(usage);
}
if (!quit) {
System.out.println();
line = br.readLine();
}
}
} catch (IOException ioe) {
log.log(Level.SEVERE, "Unable to read stdin", ioe);
}
// Explicit shutdown.
Runtime.getRuntime().removeShutdownHook(hook);
hook.start();
}
/**
* Prints messages about proper usage of the Snark application.
*/
protected static void usage (String s)
{
PrintStream stream = System.out;
if (s != null) {
stream = System.err;
stream.println("snark: " + s);
}
stream.println("Usage: snark [--debug [level]] [--no-commands] [--port <port>]");
stream.println(" [--show-peers] [--share (<ip>|<host>)] (<url>|<file>|<dir>)");
stream.println(" --debug\tShows some extra info and stacktraces");
stream.println(" level\tHow much debug details to show");
stream.println(" \t(defaults to " + Level.SEVERE
+ ", with --debug to " + Level.INFO + ", highest level is "
+ Level.ALL + ").");
stream.println(" --no-commands\tDon't read interactive commands or show usage info.");
stream.println(" --port\tThe port to listen on for incomming connections");
stream.println(" \t(if not given defaults to first free port between "
+ Snark.MIN_PORT + "-" + Snark.MAX_PORT + ").");
stream.println(" --show-peers\tIf enabled, periodically prints peer information.");
stream.println(" --share\tStart torrent tracker on <ip> address or <host> name.");
stream.println(" <url> \tURL pointing to .torrent metainfo file to download/share.");
stream.println(" <file> \tEither a local .torrent metainfo file to download");
stream.println(" \tor (with --share) a file to share.");
stream.println(" <dir> \tA directory with files to share (needs --share).");
System.exit(-1);
}
/**
* A convenience method for parsing arguments passed via the command line
* where no overriding of the listeners is required.
*/
public static Snark parseArguments (String[] args)
{
return parseArguments(args, null, null);
}
/**
* Sets debug, ip and torrent variables then creates a Snark instance. Calls
* usage(), which terminates the program, if non-valid argument list. The
* given listeners will be passed to all components that take one.
*/
public static Snark parseArguments (String[] args,
StorageListener slistener, CoordinatorListener clistener)
{
int user_port = -1;
String ip = null;
String torrent = null;
Level level = Level.INFO;
int i = 0;
while (i < args.length) {
if (args[i].equals("--debug")) {
level = Level.FINE;
i++;
// Try if there is an level argument.
if (i < args.length) {
try {
level = Level.parse(args[i]);
} catch (IllegalArgumentException iae) {
// continue parsing arguments
}
i++;
}
} else if (args[i].equals("--port")) {
if (args.length - 1 < i + 1) {
usage("--port needs port number to listen on");
}
try {
user_port = Integer.parseInt(args[i + 1]);
} catch (NumberFormatException nfe) {
usage("--port argument must be a number (" + nfe + ")");
}
i += 2;
} else if (args[i].equals("--share")) {
if (args.length - 1 < i + 1) {
usage("--share needs local ip-address or host-name");
}
ip = args[i + 1];
i += 2;
} else if (args[i].equals("--no-commands") ||
args[i].equals("--show-peers")) {
// ignore, processed elsewhere.
i++;
} else if (args[i].equals("--help")) {
usage(null);
} else {
torrent = args[i];
i++;
break;
}
}
log.setLevel(level);
Snark.setLogLevel(level);
if (torrent == null || i != args.length) {
if (torrent != null && torrent.startsWith("-")) {
usage("Unknown option '" + torrent + "'.");
} else {
usage("Need exactly one <url>, <file> or <dir>.");
}
}
Snark snark = new Snark(torrent, ip, user_port, slistener, clistener);
return snark;
}
protected static final String newline = System.getProperty("line.separator");
protected static final String copyright =
"The Hunting of the Snark Project - "
+ "Copyright (C) 2003 Mark J. Wielaard, (c) 2006 Three Rings Design"
+ newline
+ newline
+ "Snark comes with ABSOLUTELY NO WARRANTY. This is free software, and"
+ newline
+ "you are welcome to redistribute it under certain conditions; read the"
+ newline + "COPYING file for details.";
/** The message displayed when entering the interactive interface */
protected static final String usage =
"Press return for help. Type \"quit\" and return to stop.";
/** A list of commands that the interactive interface accepts */
protected static final String help = "Commands: 'info', 'list', 'quit'.";
/** The Java logger used to process our log events. */
protected static final Logger log = Logger.getLogger("org.klomp.snark.cmd");
}