2005-12-15 jrandom

* Added a first pass to the I2PSnark web UI (see /i2psnark/)
This commit is contained in:
jrandom
2005-12-16 03:00:48 +00:00
committed by zzz
parent b37bb9372e
commit 3ec92c8b62
16 changed files with 930 additions and 107 deletions

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="i2psnark">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="build" depends="builddep, jar, war" />
<target name="builddep">
<ant dir="../../jetty/" target="build" />
<ant dir="../../ministreaming/java/" target="build" />
<!-- ministreaming will build core -->
</target>
@ -13,18 +14,24 @@
srcdir="./src"
debug="true" deprecation="on" source="1.3" target="1.3"
destdir="./build/obj"
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
</target>
<target name="jar" depends="builddep, compile">
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class">
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
</manifest>
</jar>
</target>
<target name="war" depends="jar">
<war destfile="../i2psnark.war" webxml="../web.xml">
<classes dir="./build/obj" includes="**/*" />
</war>
</target>
<target name="clean">
<delete dir="./build" />
<delete file="../i2psnark.war" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../ministreaming/java/" target="distclean" />

View File

@ -32,11 +32,12 @@ import net.i2p.client.streaming.I2PSocket;
*/
public class ConnectionAcceptor implements Runnable
{
private final I2PServerSocket serverSocket;
private I2PServerSocket serverSocket;
private final PeerAcceptor peeracceptor;
private Thread thread;
private boolean stop;
private boolean socketChanged;
public ConnectionAcceptor(I2PServerSocket serverSocket,
PeerAcceptor peeracceptor)
@ -44,8 +45,10 @@ public class ConnectionAcceptor implements Runnable
this.serverSocket = serverSocket;
this.peeracceptor = peeracceptor;
socketChanged = false;
stop = false;
thread = new Thread(this);
thread = new Thread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
@ -65,6 +68,14 @@ public class ConnectionAcceptor implements Runnable
if (t != null)
t.interrupt();
}
public void restart() {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
socketChanged = true;
Thread t = thread;
if (t != null)
t.interrupt();
}
public int getPort()
{
@ -75,57 +86,35 @@ public class ConnectionAcceptor implements Runnable
{
while(!stop)
{
if (socketChanged) {
// ok, already updated
socketChanged = false;
}
if (serverSocket == null) {
Snark.debug("Server socket went away.. boo hiss", Snark.ERROR);
stop = true;
return;
}
try
{
final I2PSocket socket = serverSocket.accept();
Thread t = new Thread("Connection-" + socket)
{
public void run()
{
try
{
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
// See what kind of connection it is.
/*
if (httpacceptor != null)
{
byte[] scratch = new byte[4];
bis.mark(4);
int len = bis.read(scratch);
if (len != 4)
throw new IOException("Need at least 4 bytes");
bis.reset();
if (scratch[0] == 19 && scratch[1] == 'B'
&& scratch[2] == 'i' && scratch[3] == 't')
peeracceptor.connection(socket, bis, bos);
else if (scratch[0] == 'G' && scratch[1] == 'E'
&& scratch[2] == 'T' && scratch[3] == ' ')
httpacceptor.connection(socket, bis, bos);
}
else
*/
peeracceptor.connection(socket, bis, bos);
}
catch (IOException ioe)
{
try
{
socket.close();
}
catch (IOException ignored) { }
}
I2PSocket socket = serverSocket.accept();
if (socket == null) {
if (socketChanged) {
continue;
} else {
Snark.debug("Null socket accepted, but socket wasn't changed?", Snark.ERROR);
}
};
t.start();
} else {
Thread t = new Thread(new Handler(socket), "Connection-" + socket);
t.start();
}
}
catch (I2PException ioe)
{
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
if (!socketChanged) {
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
catch (IOException ioe)
{
@ -140,4 +129,23 @@ public class ConnectionAcceptor implements Runnable
}
catch (I2PException ignored) { }
}
private class Handler implements Runnable {
private I2PSocket _socket;
public Handler(I2PSocket socket) {
_socket = socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
OutputStream out = _socket.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
peeracceptor.connection(_socket, bis, bos);
} catch (IOException ioe) {
try { _socket.close(); } catch (IOException ignored) { }
}
}
}
}

View File

@ -13,7 +13,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.Log;
import java.io.*;
import java.util.Properties;
import java.util.*;
/**
* I2P specific helpers for I2PSnark
@ -29,14 +29,17 @@ public class I2PSnarkUtil {
private int _proxyPort;
private String _i2cpHost;
private int _i2cpPort;
private Properties _opts;
private Map _opts;
private I2PSocketManager _manager;
private boolean _configured;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(Snark.class);
_opts = new HashMap();
setProxy("127.0.0.1", 4444);
setI2CPConfig("127.0.0.1", 7654, null);
_configured = false;
}
/**
@ -54,25 +57,55 @@ public class I2PSnarkUtil {
_proxyHost = null;
_proxyPort = -1;
}
_configured = true;
}
public void setI2CPConfig(String i2cpHost, int i2cpPort, Properties opts) {
public boolean configured() { return _configured; }
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
if (opts != null)
_opts = opts;
_opts.putAll(opts);
_configured = true;
}
public String getI2CPHost() { return _i2cpHost; }
public int getI2CPPort() { return _i2cpPort; }
public Map getI2CPOptions() { return _opts; }
public String getEepProxyHost() { return _proxyHost; }
public int getEepProxyPort() { return _proxyPort; }
public boolean getEepProxySet() { return _shouldProxy; }
/**
* Connect to the router, if we aren't already
*/
boolean connect() {
public boolean connect() {
if (_manager == null) {
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, _opts);
Properties opts = new Properties();
if (_opts != null) {
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
opts.setProperty(key, _opts.get(key).toString());
}
}
if (opts.getProperty("inbound.nickname") == null)
opts.setProperty("inbound.nickname", "I2PSnark");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
}
return (_manager != null);
}
public boolean connected() { return _manager != null; }
/**
* Destroy the destination itself
*/
public void disconnect() {
I2PSocketManager mgr = _manager;
_manager = null;
mgr.destroySocketManager();
}
/** connect to the given destination */
I2PSocket connect(PeerID peer) throws IOException {
try {
@ -85,7 +118,8 @@ public class I2PSnarkUtil {
/**
* fetch the given URL, returning the file it is stored in, or null on error
*/
File get(String url) {
public File get(String url) { return get(url, true); }
public File get(String url, boolean rewrite) {
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
File out = null;
try {
@ -95,8 +129,10 @@ public class I2PSnarkUtil {
out.delete();
return null;
}
String fetchURL = rewriteAnnounce(url);
_log.debug("Rewritten url [" + fetchURL + "]");
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);
//_log.debug("Rewritten url [" + fetchURL + "]");
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), fetchURL);
if (get.fetch()) {
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
@ -108,7 +144,7 @@ public class I2PSnarkUtil {
}
}
I2PServerSocket getServerSocket() {
public I2PServerSocket getServerSocket() {
return _manager.getServerSocket();
}

View File

@ -37,7 +37,7 @@ public class Peer implements Comparable
private final PeerID peerID;
private final byte[] my_id;
private final MetaInfo metainfo;
final MetaInfo metainfo;
// The data in/output streams set during the handshake and used by
// the actual connections.

View File

@ -70,6 +70,7 @@ class PeerCheckerTask extends TimerTask
{
it.remove();
coordinator.removePeerFromPieces(peer);
coordinator.peerCount = coordinator.peers.size();
continue;
}
@ -185,6 +186,7 @@ class PeerCheckerTask extends TimerTask
// Put it at the back of the list
coordinator.peers.remove(worstDownloader);
coordinator.peerCount = coordinator.peers.size();
removed.add(worstDownloader);
}
@ -193,6 +195,7 @@ class PeerCheckerTask extends TimerTask
// Put peers back at the end of the list that we removed earlier.
coordinator.peers.addAll(removed);
coordinator.peerCount = coordinator.peers.size();
}
}
}

View File

@ -24,8 +24,11 @@ import java.io.*;
import java.net.*;
import java.util.*;
import net.i2p.util.Log;
class PeerConnectionIn implements Runnable
{
private Log _log = new Log(PeerConnectionIn.class);
private final Peer peer;
private final DataInputStream din;
@ -72,6 +75,8 @@ class PeerConnectionIn implements Runnable
if (i == 0)
{
ps.keepAliveMessage();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
continue;
}
@ -82,30 +87,44 @@ class PeerConnectionIn implements Runnable
{
case 0:
ps.chokeMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
break;
case 1:
ps.chokeMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
break;
case 2:
ps.interestedMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
break;
case 3:
ps.interestedMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
break;
case 4:
piece = din.readInt();
ps.haveMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
break;
case 5:
byte[] bitmap = new byte[i-1];
din.readFully(bitmap);
ps.bitfieldMessage(bitmap);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName());
break;
case 6:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.requestMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
break;
case 7:
piece = din.readInt();
@ -118,12 +137,16 @@ class PeerConnectionIn implements Runnable
piece_bytes = req.bs;
din.readFully(piece_bytes, begin, len);
ps.pieceMessage(req);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
else
{
// XXX - Consume but throw away afterwards.
piece_bytes = new byte[len];
din.readFully(piece_bytes);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
}
break;
case 8:
@ -131,11 +154,15 @@ class PeerConnectionIn implements Runnable
begin = din.readInt();
len = din.readInt();
ps.cancelMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
break;
default:
byte[] bs = new byte[i-1];
din.readFully(bs);
ps.unknownMessage(b, bs);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
}
}
}

View File

@ -24,8 +24,11 @@ import java.io.*;
import java.net.*;
import java.util.*;
import net.i2p.util.Log;
class PeerConnectionOut implements Runnable
{
private Log _log = new Log(PeerConnectionOut.class);
private final Peer peer;
private final DataOutputStream dout;
@ -34,14 +37,18 @@ class PeerConnectionOut implements Runnable
// Contains Messages.
private List sendQueue = new ArrayList();
private static long __id = 0;
private long _id;
public PeerConnectionOut(Peer peer, DataOutputStream dout)
{
this.peer = peer;
this.dout = dout;
_id = ++__id;
quit = false;
thread = new Thread(this);
thread = new Thread(this, "Snark sender " + _id);
thread.start();
}
@ -64,6 +71,8 @@ class PeerConnectionOut implements Runnable
try
{
// Make sure everything will reach the other side.
// i2p flushes passively, no need to force it
// ... maybe not though
dout.flush();
// Wait till more data arrives.
@ -114,6 +123,8 @@ class PeerConnectionOut implements Runnable
{
if (Snark.debug >= Snark.ALL)
Snark.debug("Send " + peer + ": " + m, Snark.ALL);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
m.sendMessage(dout);
// Remove all piece messages after sending a choke message.

View File

@ -51,6 +51,8 @@ public class PeerCoordinator implements PeerListener
// synchronize on this when changing peers or downloaders
final List peers = new ArrayList();
/** estimate of the peers, without requiring any synchronization */
volatile int peerCount;
/** Timer to handle all periodical tasks. */
private final Timer timer = new Timer(true);
@ -63,6 +65,9 @@ public class PeerCoordinator implements PeerListener
private boolean halted = false;
private final CoordinatorListener listener;
public String trackerProblems = null;
public int trackerSeenPeers = 0;
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
CoordinatorListener listener)
@ -97,12 +102,15 @@ public class PeerCoordinator implements PeerListener
return storage.complete();
}
public int getPeerCount() { return peerCount; }
public int getPeers()
{
synchronized(peers)
{
return peers.size();
int rv = peers.size();
peerCount = rv;
return rv;
}
}
@ -163,6 +171,7 @@ public class PeerCoordinator implements PeerListener
it.remove();
removePeerFromPieces(peer);
}
peerCount = peers.size();
}
}
@ -187,9 +196,11 @@ public class PeerCoordinator implements PeerListener
if (Snark.debug >= Snark.INFO)
Snark.debug("New connection to peer: " + peer, Snark.INFO);
_log.info("New connection to peer " + peer + " for " + metainfo.getName());
// Add it to the beginning of the list.
// And try to optimistically make it a uploader.
peers.add(0, peer);
peerCount = peers.size();
unchokePeer();
if (listener != null)
@ -223,7 +234,7 @@ public class PeerCoordinator implements PeerListener
if (need_more)
{
_log.debug("Addng a peer " + peer.getPeerID().getAddress().calculateHash().toBase64(), new Exception("add/run"));
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
// Run the peer with us as listener and the current bitfield.
final PeerListener listener = this;
@ -281,6 +292,7 @@ public class PeerCoordinator implements PeerListener
// Put peer back at the end of the list.
peers.remove(peer);
peers.add(peer);
peerCount = peers.size();
}
}
@ -422,8 +434,10 @@ public class PeerCoordinator implements PeerListener
*/
public boolean gotPiece(Peer peer, int piece, byte[] bs)
{
if (halted)
if (halted) {
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
return true; // We don't actually care anymore.
}
synchronized(wantedPieces)
{
@ -433,6 +447,8 @@ public class PeerCoordinator implements PeerListener
if (Snark.debug >= Snark.INFO)
Snark.debug(peer + " piece " + piece + " no longer needed",
Snark.INFO);
_log.info("Got unwanted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
// No need to announce have piece to peers.
// Assume we got a good piece, we don't really care anymore.
@ -445,6 +461,7 @@ public class PeerCoordinator implements PeerListener
{
if (Snark.debug >= Snark.INFO)
Snark.debug("Recv p" + piece + " " + peer, Snark.INFO);
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
}
else
{
@ -453,6 +470,7 @@ public class PeerCoordinator implements PeerListener
if (Snark.debug >= Snark.NOTICE)
Snark.debug("Got BAD piece " + piece + " from " + peer,
Snark.NOTICE);
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
return false; // No need to announce BAD piece to peers.
}
}
@ -524,6 +542,7 @@ public class PeerCoordinator implements PeerListener
unchokePeer();
removePeerFromPieces(peer);
}
peerCount = peers.size();
}
if (listener != null)

View File

@ -209,13 +209,15 @@ public class Snark
}
}
String torrent;
MetaInfo meta;
Storage storage;
PeerCoordinator coordinator;
ConnectionAcceptor acceptor;
TrackerClient trackerclient;
String rootDataDir = ".";
public String torrent;
public MetaInfo meta;
public Storage storage;
public PeerCoordinator coordinator;
public ConnectionAcceptor acceptor;
public TrackerClient trackerclient;
public String rootDataDir = ".";
public CompleteListener completeListener;
public boolean stopped;
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
@ -233,6 +235,7 @@ public class Snark
this.torrent = torrent;
this.rootDataDir = rootDir;
stopped = true;
activity = "Network setup";
// "Taking Three as the subject to reason about--
@ -363,6 +366,7 @@ public class Snark
* Start up contacting peers and querying the tracker
*/
public void startTorrent() {
stopped = false;
boolean coordinatorChanged = false;
if (coordinator.halted()) {
// ok, we have already started and stopped, but the coordinator seems a bit annoying to
@ -375,18 +379,21 @@ public class Snark
coordinator = newCoord;
coordinatorChanged = true;
}
if (trackerclient.halted() || coordinatorChanged) {
if (!trackerclient.started() && !coordinatorChanged) {
trackerclient.start();
} else if (trackerclient.halted() || coordinatorChanged) {
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
if (!trackerclient.halted())
trackerclient.halt();
trackerclient = newClient;
trackerclient.start();
}
trackerclient.start();
}
/**
* Stop contacting the tracker and talking with peers
*/
public void stopTorrent() {
stopped = true;
trackerclient.halt();
coordinator.halt();
try {
@ -417,6 +424,8 @@ public class Snark
String ip = null;
String torrent = null;
boolean configured = I2PSnarkUtil.instance().configured();
int i = 0;
while (i < args.length)
{
@ -463,7 +472,8 @@ public class Snark
{
String proxyHost = args[i+1];
String proxyPort = args[i+2];
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
if (!configured)
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
i += 3;
}
else if (args[i].equals("--i2cp"))
@ -484,7 +494,8 @@ public class Snark
}
}
}
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
if (!configured)
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
i += 3 + (opts != null ? 1 : 0);
}
else
@ -654,6 +665,8 @@ public class Snark
Snark.debug("Completely received " + torrent, Snark.INFO);
//storage.close();
System.out.println("Completely received: " + torrent);
if (completeListener != null)
completeListener.torrentComplete(this);
}
public void shutdown()
@ -662,4 +675,8 @@ public class Snark
// have died. But in reality this does not always happen.
System.exit(0);
}
public interface CompleteListener {
public void torrentComplete(Snark snark);
}
}

View File

@ -10,7 +10,7 @@ import net.i2p.util.Log;
/**
* Manage multiple snarks
*/
public class SnarkManager {
public class SnarkManager implements Snark.CompleteListener {
private static SnarkManager _instance = new SnarkManager();
public static SnarkManager instance() { return _instance; }
@ -35,6 +35,8 @@ public class SnarkManager {
_log = _context.logManager().getLog(SnarkManager.class);
_messages = new ArrayList(16);
loadConfig("i2psnark.config");
int minutes = getStartupDelayMinutes();
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
@ -50,9 +52,16 @@ public class SnarkManager {
_log.info("MSG: " + message);
}
private boolean shouldAutoStart() { return true; }
/** newest last */
public List getMessages() {
synchronized (_messages) {
return new ArrayList(_messages);
}
}
public boolean shouldAutoStart() { return true; }
private int getStartupDelayMinutes() { return 1; }
private File getDataDir() {
public File getDataDir() {
String dir = _config.getProperty(PROP_DIR);
if ( (dir == null) || (dir.trim().length() <= 0) )
dir = "i2psnark";
@ -89,14 +98,14 @@ public class SnarkManager {
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
String opts = _config.getProperty(PROP_I2CP_OPTS);
Properties i2cpOpts = new Properties();
Map i2cpOpts = new HashMap();
if (opts != null) {
StringTokenizer tok = new StringTokenizer(opts, " ");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
i2cpOpts.setProperty(pair.substring(0, split), pair.substring(split+1));
if (split > 0)
i2cpOpts.put(pair.substring(0, split), pair.substring(split+1));
}
}
if (i2cpHost != null) {
@ -122,6 +131,108 @@ public class SnarkManager {
return defaultVal;
}
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
boolean changed = false;
if (eepHost != null) {
int port = I2PSnarkUtil.instance().getEepProxyPort();
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
String host = I2PSnarkUtil.instance().getEepProxyHost();
if ( (eepHost.trim().length() > 0) && (port > 0) &&
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
I2PSnarkUtil.instance().setProxy(eepHost, port);
changed = true;
_config.setProperty(PROP_EEP_HOST, eepHost);
_config.setProperty(PROP_EEP_PORT, eepPort+"");
addMessage("EepProxy location changed to " + eepHost + ":" + port);
}
}
if (i2cpHost != null) {
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
int port = oldI2CPPort;
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
String host = oldI2CPHost;
Map opts = new HashMap();
if (i2cpOpts == null) i2cpOpts = "";
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
opts.put(pair.substring(0, split), pair.substring(split+1));
}
Map oldOpts = new HashMap();
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
if (oldI2CPOpts == null) oldI2CPOpts = "";
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int split = pair.indexOf('=');
if (split > 0)
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
}
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
((!host.equals(i2cpHost) ||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
(!oldOpts.equals(opts)))) ) {
boolean snarksActive = false;
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
Snark snark = getTorrent((String)iter.next());
if ( (snark != null) && (!snark.stopped) ) {
snarksActive = true;
break;
}
}
if (snarksActive) {
addMessage("Cannot change the I2CP settings while torrents are active");
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
+ "] oldOpts [" + oldOpts + "]");
} else {
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
addMessage("Disconnecting old I2CP destination");
}
Properties p = new Properties();
p.putAll(opts);
addMessage("I2CP settings changed to " + i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")");
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, port, p);
boolean ok = I2PSnarkUtil.instance().connect();
if (!ok) {
addMessage("Unable to connect with the new settings, reverting to the old I2CP settings");
I2PSnarkUtil.instance().setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
ok = I2PSnarkUtil.instance().connect();
if (!ok)
addMessage("Unable to reconnect with the old settings!");
} else {
addMessage("Reconnected on the new I2CP destination");
_config.setProperty(PROP_I2CP_HOST, i2cpHost.trim());
_config.setProperty(PROP_I2CP_PORT, "" + port);
_config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim());
changed = true;
// no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = getTorrent(name);
if ( (snark != null) && (snark.acceptor != null) ) {
snark.acceptor.restart();
addMessage("I2CP listener restarted for " + snark.meta.getName());
}
}
}
}
changed = true;
}
}
if (changed) {
saveConfig();
} else {
addMessage("Configuration unchanged");
}
}
public void saveConfig() {
try {
DataHelper.storeProps(_config, new File(_configFile));
@ -130,6 +241,8 @@ public class SnarkManager {
}
}
public Properties getConfig() { return _config; }
/** set of filenames that we are dealing with */
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
/**
@ -151,17 +264,19 @@ public class SnarkManager {
torrent = (Snark)_snarks.get(filename);
if (torrent == null) {
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
torrent.completeListener = this;
_snarks.put(filename, torrent);
} else {
return;
}
}
// ok, snark created, now lets start it up or configure it further
File f = new File(filename);
if (shouldAutoStart()) {
torrent.startTorrent();
addMessage("Torrent added and started: '" + filename + "'");
addMessage("Torrent added and started: '" + f.getName() + "'");
} else {
addMessage("Torrent added: '" + filename + "'");
addMessage("Torrent added: '" + f.getName() + "'");
}
}
@ -187,13 +302,15 @@ public class SnarkManager {
remaining = _snarks.size();
}
if (torrent != null) {
boolean wasStopped = torrent.stopped;
torrent.stopTorrent();
if (remaining == 0) {
// should we disconnect/reconnect here (taking care to deal with the other thread's
// I2PServerSocket.accept() call properly?)
////I2PSnarkUtil.instance().
}
addMessage("Torrent stopped: '" + filename + "'");
if (!wasStopped)
addMessage("Torrent stopped: '" + sfile.getName() + "'");
}
return torrent;
}
@ -206,7 +323,7 @@ public class SnarkManager {
if (torrent != null) {
File torrentFile = new File(filename);
torrentFile.delete();
addMessage("Torrent removed: '" + filename + "'");
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
}
}
@ -222,6 +339,13 @@ public class SnarkManager {
}
}
public void torrentComplete(Snark snark) {
File f = new File(snark.torrent);
long len = snark.meta.getTotalLength();
addMessage("Download complete of " + f.getName()
+ (len < 5*1024*1024 ? " (size: " + (len/1024) + "KB)" : " (size: " + (len/1024*1024) + "MB)"));
}
private void monitorTorrents(File dir) {
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
List foundNames = new ArrayList(0);
@ -241,7 +365,10 @@ public class SnarkManager {
if (existingNames.contains(foundNames.get(i))) {
// already known. noop
} else {
addTorrent((String)foundNames.get(i));
if (I2PSnarkUtil.instance().connect())
addTorrent((String)foundNames.get(i));
else
addMessage("Unable to connect to I2P");
}
}
// now lets see which ones have been removed...

View File

@ -48,6 +48,7 @@ public class TrackerClient extends Thread
private final int port;
private boolean stop;
private boolean started;
private long interval;
private long lastRequestTime;
@ -62,14 +63,17 @@ public class TrackerClient extends Thread
this.port = 6881; //(port == -1) ? 9 : port;
stop = false;
started = false;
}
public void start() {
stop = false;
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
super.start();
started = true;
}
public boolean halted() { return stop; }
public boolean started() { return started; }
/**
* Interrupts this Thread to stop it.
@ -107,8 +111,10 @@ public class TrackerClient extends Thread
TrackerInfo info = doRequest(announce, infoHash, peerID,
uploaded, downloaded, left,
STARTED_EVENT);
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
if (!completed) {
Iterator it = info.getPeers().iterator();
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
coordinator.addPeer(cur);
@ -118,6 +124,7 @@ public class TrackerClient extends Thread
}
}
started = true;
coordinator.trackerProblems = null;
}
catch (IOException ioe)
{
@ -125,6 +132,7 @@ public class TrackerClient extends Thread
Snark.debug
("WARNING: Could not contact tracker at '"
+ announce + "': " + ioe, Snark.WARNING);
coordinator.trackerProblems = ioe.getMessage();
}
if (!started && !stop)
@ -182,10 +190,12 @@ public class TrackerClient extends Thread
uploaded, downloaded, left,
event);
Set peers = info.getPeers();
coordinator.trackerSeenPeers = peers.size();
if ( (left > 0) && (!completed) ) {
// we only want to talk to new people if we need things
// from them (duh)
Iterator it = info.getPeers().iterator();
Iterator it = peers.iterator();
while (it.hasNext()) {
Peer cur = (Peer)it.next();
coordinator.addPeer(cur);
@ -244,21 +254,24 @@ public class TrackerClient extends Thread
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = new FileInputStream(fetched);
try {
InputStream in = new FileInputStream(fetched);
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
if (Snark.debug >= Snark.INFO)
Snark.debug("TrackerClient response: " + info, Snark.INFO);
lastRequestTime = System.currentTimeMillis();
String failure = info.getFailureReason();
if (failure != null)
throw new IOException(failure);
interval = info.getInterval() * 1000;
return info;
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
if (Snark.debug >= Snark.INFO)
Snark.debug("TrackerClient response: " + info, Snark.INFO);
lastRequestTime = System.currentTimeMillis();
String failure = info.getFailureReason();
if (failure != null)
throw new IOException(failure);
interval = info.getInterval() * 1000;
return info;
} finally {
fetched.delete();
}
}
/**

View File

@ -0,0 +1,524 @@
package org.klomp.snark.web;
import java.io.*;
import java.util.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import org.klomp.snark.*;
/**
*
*/
public class I2PSnarkServlet extends HttpServlet {
private I2PAppContext _context;
private Log _log;
private SnarkManager _manager;
private static long _nonce;
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
public void init(ServletConfig cfg) throws ServletException {
super.init(cfg);
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(I2PSnarkServlet.class);
_nonce = _context.random().nextLong();
_manager = SnarkManager.instance();
String configFile = _context.getProperty(PROP_CONFIG_FILE);
if ( (configFile == null) || (configFile.trim().length() <= 0) )
configFile = "i2psnark.config";
_manager.loadConfig(configFile);
}
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
String nonce = req.getParameter("nonce");
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
processRequest(req);
PrintWriter out = resp.getWriter();
out.write(HEADER_BEGIN);
// we want it to go to the base URI so we don't refresh with some funky action= value
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
out.write(HEADER);
out.write("<textarea class=\"snarkMessages\" rows=\"2\" cols=\"100\" wrap=\"off\" >");
List msgs = _manager.getMessages();
for (int i = msgs.size()-1; i >= 0; i--) {
String msg = (String)msgs.get(i);
out.write(msg + "\n");
}
out.write("</textarea>\n");
out.write(TABLE_HEADER);
List snarks = getSortedSnarks(req);
String uri = req.getRequestURI();
for (int i = 0; i < snarks.size(); i++) {
Snark snark = (Snark)snarks.get(i);
displaySnark(out, snark, uri, i);
}
if (snarks.size() <= 0) {
out.write(TABLE_EMPTY);
}
out.write(TABLE_FOOTER);
writeAddForm(out, req);
writeConfigForm(out, req);
out.write(FOOTER);
}
/**
* Do what they ask, adding messages to _manager.addMessage as necessary
*/
private void processRequest(HttpServletRequest req) {
String action = req.getParameter("action");
if (action == null) {
// noop
} else if ("Add torrent".equals(action)) {
String newFile = req.getParameter("newFile");
String newURL = req.getParameter("newURL");
File f = null;
if ( (newFile != null) && (newFile.trim().length() > 0) )
f = new File(newFile.trim());
if ( (f != null) && (!f.exists()) ) {
_manager.addMessage("Torrent file " + newFile +" does not exist");
}
if ( (f != null) && (f.exists()) ) {
File local = new File(_manager.getDataDir(), f.getName());
String canonical = null;
try {
canonical = local.getCanonicalPath();
if (local.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + newFile);
else
_manager.addMessage("Torrent already in the queue: " + newFile);
} else {
boolean ok = FileUtil.copy(f.getAbsolutePath(), local.getAbsolutePath(), true);
if (ok) {
_manager.addMessage("Copying torrent to " + local.getAbsolutePath());
_manager.addTorrent(canonical);
} else {
_manager.addMessage("Unable to copy the torrent to " + local.getAbsolutePath() + " from " + f.getAbsolutePath());
}
}
} catch (IOException ioe) {
_log.warn("hrm: " + local, ioe);
}
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
_manager.addMessage("Fetching " + newURL);
I2PThread fetch = new I2PThread(new FetchAndAdd(newURL), "Fetch and add");
fetch.start();
} else {
// no file or URL specified
}
} else if ("Stop".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, false);
break;
}
}
}
}
} else if ("Start".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
snark.startTorrent();
_manager.addMessage("Starting up torrent " + name);
break;
}
}
}
}
} else if ("Remove".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
// should we delete the torrent file?
break;
}
}
}
}
} else if ("Delete".equals(action)) {
String torrent = req.getParameter("torrent");
if (torrent != null) {
byte infoHash[] = Base64.decode(torrent);
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
_manager.stopTorrent(name, true);
File f = new File(name);
f.delete();
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
List files = snark.meta.getFiles();
String dataFile = snark.meta.getName();
for (int i = 0; files != null && i < files.size(); i++) {
File df = new File(_manager.getDataDir(), (String)files.get(i));
boolean deleted = FileUtil.rmdir(df, false);
if (deleted)
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
else
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
}
if (dataFile != null) {
f = new File(_manager.getDataDir(), dataFile);
boolean deleted = f.delete();
if (deleted)
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
else
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
}
break;
}
}
}
}
} else if ("Save configuration".equals(action)) {
String dataDir = req.getParameter("dataDir");
boolean autoStart = req.getParameter("autoStart") != null;
String seedPct = req.getParameter("seedPct");
String eepHost = req.getParameter("eepHost");
String eepPort = req.getParameter("eepPort");
String i2cpHost = req.getParameter("i2cpHost");
String i2cpPort = req.getParameter("i2cpPort");
String i2cpOpts = req.getParameter("i2cpOpts");
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
}
}
private class FetchAndAdd implements Runnable {
private String _url;
public FetchAndAdd(String url) {
_url = url;
}
public void run() {
_url = _url.trim();
File file = I2PSnarkUtil.instance().get(_url, false);
try {
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
_manager.addMessage("Torrent fetched from " + _url);
FileInputStream in = null;
try {
in = new FileInputStream(file);
MetaInfo info = new MetaInfo(in);
String name = info.getName();
name = name.replace('/', '_');
name = name.replace('\\', '_');
name = name.replace('&', '+');
name = name.replace('\'', '_');
name = name.replace('"', '_');
name = name.replace('`', '_');
name = name + ".torrent";
File torrentFile = new File(_manager.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage("Torrent already running: " + name);
else
_manager.addMessage("Torrent already in the queue: " + name);
} else {
FileUtil.copy(file.getAbsolutePath(), canonical, true);
_manager.addTorrent(canonical);
}
} catch (IOException ioe) {
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
} finally {
try { in.close(); } catch (IOException ioe) {}
}
} else {
_manager.addMessage("Torrent was not retrieved from " + _url);
}
} finally {
if (file != null) file.delete();
}
}
}
private List getSortedSnarks(HttpServletRequest req) {
Set files = _manager.listTorrentFiles();
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
ArrayList rv = new ArrayList(fileNames.size());
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
Snark snark = _manager.getTorrent(name);
if (snark != null)
rv.add(snark);
}
return rv;
}
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
String filename = snark.torrent;
File f = new File(filename);
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
long total = snark.meta.getTotalLength();
long remaining = snark.storage.needed() * snark.meta.getPieceLength(0);
if (remaining > total)
remaining = total;
int totalBps = 4096; // should probably grab this from the snark...
long remainingSeconds = remaining / totalBps;
long uploaded = snark.coordinator.getUploaded();
boolean isRunning = !snark.stopped;
boolean isValid = snark.meta != null;
String err = snark.coordinator.trackerProblems;
int curPeers = snark.coordinator.getPeerCount();
int knownPeers = snark.coordinator.trackerSeenPeers;
String statusString = "Unknown";
if (err != null) {
if (isRunning)
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "TrackerErr (" + err + ")";
} else if (remaining <= 0) {
if (isRunning)
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Complete";
} else {
if (isRunning)
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
else
statusString = "Stopped";
}
String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
out.write("<tr class=\"" + rowClass + "\">");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
out.write(statusString + "</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
out.write(filename + "</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
if (remaining > 0) {
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
// lets hold off on the ETA until we have rates sorted...
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
} else {
out.write(formatSize(total)); // 3GB
}
out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
+ "\">" + formatSize(uploaded) + "</td>\n\t");
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
//out.write("n/a"); //2KBps/12KBps/4KBps
//out.write("</td>\n\t");
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
if (isRunning) {
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Stop the torrent\">Stop</a>");
} else {
if (isValid)
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Start the torrent\">Start</a> ");
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
}
out.write("</td>\n</tr>\n");
}
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String newURL = req.getParameter("newURL");
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
String newFile = req.getParameter("newFile");
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
out.write("<span class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("From URL&nbsp;: <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
out.write("Alternately, you can copy .torrent files to " + _manager.getDataDir().getAbsolutePath() + "<br />\n");
out.write("</form>\n</span>\n");
}
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
String uri = req.getRequestURI();
String dataDir = _manager.getDataDir().getAbsolutePath();
boolean autoStart = _manager.shouldAutoStart();
int seedPct = 0;
out.write("<span class=\"snarkConfig\">\n");
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
out.write("<hr /><span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /><br />\n");
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
+ (autoStart ? "checked " : "")
+ "title=\"If true, automatically start torrents that are added\" disabled=\"true\" />");
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
//out.write("<br />\n");
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
if (seedPct <= 0)
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
else
out.write("<option value=\"0\">Unlimited</option>\n\t");
if (seedPct == 100)
out.write("<option value=\"100\" selected=\"true\">100%</option>\n\t");
else
out.write("<option value=\"100\">100%</option>\n\t");
if (seedPct == 150)
out.write("<option value=\"150\" selected=\"true\">150%</option>\n\t");
else
out.write("<option value=\"150\">150%</option>\n\t");
out.write("</select><br />\n");
out.write("<hr />\n");
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
out.write("EepProxy port: <input type=\"text\" name=\"eepPort\" value=\""
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /> <br />\n");
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
out.write("I2CP port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
StringBuffer opts = new StringBuffer(64);
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = (String)options.get(key);
opts.append(key).append('=').append(val).append(' ');
}
out.write("I2CP options: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
+ opts.toString() + "\" /><br />\n");
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
out.write("</form>\n</span>\n");
}
private String formatSize(long bytes) {
if (bytes < 5*1024)
return bytes + "B";
else if (bytes < 5*1024*1024)
return (bytes/1024) + "KB";
else if (bytes < 5*1024*1024*1024)
return (bytes/(1024*1024)) + "MB";
else
return (bytes/(1024*1024*1024)) + "GB";
}
private static final String HEADER_BEGIN = "<html>\n" +
"<head>\n" +
"<title>I2PSnark - anonymous bittorrent</title>\n";
private static final String HEADER = "<style>\n" +
"body {\n" +
" background-color: #C7CFB4;\n" +
"}\n" +
".snarkTitle {\n" +
" text-align: left;\n" +
" float: left;\n" +
" margin: 0px 0px 5px 5px;\n" +
" display: inline;\n" +
" font-size: 16pt;\n" +
" font-weight: bold;\n" +
"}\n" +
".snarkMessages {\n" +
" border: none;\n" +
" background-color: #CECFC6;\n" +
" font-family: monospace;\n" +
" font-size: 10pt;\n" +
" font-weight: 100;\n" +
"}\n" +
"table {\n" +
" margin: 0px 0px 0px 0px;\n" +
" border: 0px;\n" +
" padding: 0px;\n" +
" border-width: 0px;\n" +
" border-spacing: 0px;\n" +
"}\n" +
"th {\n" +
" background-color: #C7D5D5;\n" +
" margin: 0px 0px 0px 0px;\n" +
"}\n" +
".snarkTorrentEven {\n" +
" background-color: #E7E7E7;\n" +
"}\n" +
".snarkTorrentOdd {\n" +
" background-color: #DDDDCC;\n" +
"}\n" +
".snarkNewTorrent {\n" +
" font-size: 12pt;\n" +
" font-family: monospace;\n" +
" background-color: #ADAE9;\n" +
"}\n" +
".snarkConfigTitle {\n" +
" font-size: 16pt;\n" +
" font-weight: bold;\n" +
"}\n" +
"</style>\n" +
"</head>\n" +
"<body>\n" +
"<p class=\"snarkTitle\">I2PSnark&nbsp;</p>\n";
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
"<thead>\n" +
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
" <th>&nbsp;</th></tr>\n" +
"</thead>\n";
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
"<td class=\"snarkTorrentEven\" align=\"left\"" +
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
private static final String TABLE_FOOTER = "</table>\n";
private static final String FOOTER = "</body></html>";
}

25
apps/i2psnark/web.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<servlet>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<servlet-class>org.klomp.snark.web.I2PSnarkServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- precompiled servlets -->
<servlet-mapping>
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>

View File

@ -25,6 +25,7 @@
<a href="susimail/susimail">Susimail</a> |
<a href="susidns/index.jsp">SusiDNS</a> |
<a href="syndie/">Syndie</a> |
<a href="i2psnark/">I2PSnark</a> |
<a href="http://localhost:7658/">My Eepsite</a> <br>
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |
<a href="tunnels.jsp">Tunnels</a> |

View File

@ -31,7 +31,7 @@
<ant dir="apps/susimail/" target="war" />
<ant dir="apps/susidns/src" target="all" />
<ant dir="apps/syndie/java/" target="jar" />
<ant dir="apps/i2psnark/java/" target="jar" />
<ant dir="apps/i2psnark/java/" target="war" />
</target>
<target name="buildrouter">
<ant dir="core/java/" target="distclean" />
@ -108,7 +108,7 @@
<copy file="apps/syndie/syndie.war" todir="build/" />
<copy file="apps/syndie/java/build/syndie.jar" todir="build/" />
<copy file="apps/syndie/java/build/sucker.jar" todir="build/" />
<copy file="apps/syndie/syndie.war" todir="build/" />
<copy file="apps/i2psnark/i2psnark.war" todir="build/" />
<copy file="apps/i2psnark/java/build/i2psnark.jar" todir="build/" />
<copy file="apps/jdom/jdom.jar" todir="build/" />
<copy file="apps/rome/rome-0.7.jar" todir="build/" />
@ -250,6 +250,7 @@
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
<copy file="installer/resources/clients.config" todir="pkg-temp/" />
<copy file="installer/resources/eepget" todir="pkg-temp/" />
<copy file="installer/resources/i2prouter" todir="pkg-temp/" />
@ -359,6 +360,7 @@
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
<copy file="history.txt" todir="pkg-temp/" />
<mkdir dir="pkg-temp/docs/" />
<copy file="news.xml" todir="pkg-temp/docs/" />

View File

@ -1,4 +1,7 @@
$Id: history.txt,v 1.355 2005/12/14 04:32:52 jrandom Exp $
$Id: history.txt,v 1.356 2005/12/15 03:58:31 jrandom Exp $
2005-12-15 jrandom
* Added a first pass to the I2PSnark web UI (see /i2psnark/)
2005-12-15 jrandom
* Added multitorrent support to I2PSnark, accessible currently by running