/* Snark - Main snark program startup class. Copyright (C) 2003 Mark J. Wielaard This file is part of Snark. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.klomp.snark; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Random; import java.util.StringTokenizer; import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.data.Destination; import net.i2p.util.I2PThread; /** * Main Snark program startup class. * * @author Mark Wielaard (mark@klomp.org) */ public class Snark implements StorageListener, CoordinatorListener, ShutdownListener { private final static int MIN_PORT = 6881; private final static int MAX_PORT = 6889; // Error messages (non-fatal) public final static int ERROR = 1; // Warning messages public final static int WARNING = 2; // Notices (peer level) public final static int NOTICE = 3; // Info messages (protocol policy level) public final static int INFO = 4; // Debug info (protocol level) public final static int DEBUG = 5; // Very low level stuff (network level) public final static int ALL = 6; /** * What level of debug info to show. */ //public static int debug = NOTICE; // Whether or not to ask the user for commands while sharing private static boolean command_interpreter = true; private static final String newline = System.getProperty("line.separator"); private static final String copyright = "The Hunting of the Snark Project - Copyright (C) 2003 Mark J. Wielaard" + 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." + newline + newline + "This is the I2P port, allowing anonymous bittorrent (http://www.i2p.net/)" + newline + "It will not work with normal torrents, so don't even try ;)"; private static final String usage = "Press return for help. Type \"quit\" and return to stop."; private static final String help = "Commands: 'info', 'list', 'quit'."; // String indicating main activity String activity = "Not started"; private static class OOMListener implements I2PThread.OOMEventListener { public void outOfMemory(OutOfMemoryError err) { try { err.printStackTrace(); System.out.println("OOM in the snark" + err); } catch (Throwable t) { System.out.println("OOM in the OOM"); } //System.exit(0); } } /******** No, not maintaining a command-line client public static void main(String[] args) { System.out.println(copyright); System.out.println(); if ( (args.length > 0) && ("--config".equals(args[0])) ) { I2PThread.addOOMEventListener(new OOMListener()); SnarkManager sm = SnarkManager.instance(); if (args.length > 1) sm.loadConfig(args[1]); System.out.println("Running in multitorrent mode"); while (true) { try { synchronized (sm) { sm.wait(); } } catch (InterruptedException ie) {} } } // Parse debug, share/ip and torrent file options. Snark snark = parseArguments(args); SnarkShutdown snarkhook = new SnarkShutdown(snark.storage, snark.coordinator, snark.acceptor, snark.trackerclient, snark); //Runtime.getRuntime().addShutdownHook(snarkhook); Timer timer = new Timer(true); TimerTask monitor = new PeerMonitorTask(snark.coordinator); timer.schedule(monitor, PeerMonitorTask.MONITOR_PERIOD, PeerMonitorTask.MONITOR_PERIOD); // Start command interpreter if (Snark.command_interpreter) { 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 ("".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) { System.out.println("ERROR while reading stdin: " + ioe); } // Explicit shutdown. //Runtime.getRuntime().removeShutdownHook(snarkhook); snarkhook.start(); } } ***********/ public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections"; /** most of these used to be public, use accessors below instead */ private String torrent; private MetaInfo meta; private Storage storage; private PeerCoordinator coordinator; private ConnectionAcceptor acceptor; private TrackerClient trackerclient; private String rootDataDir = "."; private final CompleteListener completeListener; private boolean stopped; private byte[] id; private byte[] infoHash; private String additionalTrackerURL; private final I2PSnarkUtil _util; private final PeerCoordinatorSet _peerCoordinatorSet; private String trackerProblems; private int trackerSeenPeers; /** from main() via parseArguments() single torrent */ Snark(I2PSnarkUtil util, String torrent, String ip, int user_port, StorageListener slistener, CoordinatorListener clistener) { this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, "."); } /** single torrent - via router */ public Snark(I2PAppContext ctx, Properties opts, String torrent, StorageListener slistener, boolean start, String rootDir) { this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir); String host = opts.getProperty("i2cp.hostname"); int port = 0; String s = opts.getProperty("i2cp.port"); if (s != null) { try { port = Integer.parseInt(s); } catch (NumberFormatException nfe) {} } _util.setI2CPConfig(host, port, opts); s = opts.getProperty(SnarkManager.PROP_UPBW_MAX); if (s != null) { try { int v = Integer.parseInt(s); _util.setMaxUpBW(v); } catch (NumberFormatException nfe) {} } s = opts.getProperty(PROP_MAX_CONNECTIONS); if (s != null) { try { int v = Integer.parseInt(s); _util.setMaxConnections(v); } catch (NumberFormatException nfe) {} } if (start) this.startTorrent(); } /** multitorrent */ public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port, StorageListener slistener, CoordinatorListener clistener, CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet, ConnectionAcceptor connectionAcceptor, boolean start, String rootDir) { if (slistener == null) slistener = this; completeListener = complistener; _util = util; _peerCoordinatorSet = peerCoordinatorSet; acceptor = connectionAcceptor; this.torrent = torrent; this.rootDataDir = rootDir; stopped = true; activity = "Network setup"; id = generateID(); debug("My peer id: " + PeerID.idencode(id), Snark.INFO); /* * Don't start a tunnel if the torrent isn't going to be started. * If we are starting, * startTorrent() will force a connect. * boolean ok = util.connect(); if (!ok) fatal("Unable to connect to I2P"); I2PServerSocket serversocket = util.getServerSocket(); if (serversocket == null) fatal("Unable to listen for I2P connections"); else { Destination d = serversocket.getManager().getSession().getMyDestination(); debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE); } */ // Figure out what the torrent argument represents. meta = null; File f = null; InputStream in = null; try { f = new File(torrent); if (f.exists()) in = new FileInputStream(f); else { activity = "Getting torrent"; File torrentFile = _util.get(torrent, 3); if (torrentFile == null) { fatal("Unable to fetch " + torrent); if (false) return; // never reached - fatal(..) throws } else { torrentFile.deleteOnExit(); in = new FileInputStream(torrentFile); } } meta = new MetaInfo(in); infoHash = meta.getInfoHash(); } catch(IOException ioe) { // OK, so it wasn't a torrent metainfo file. if (f != null && f.exists()) if (ip == null) fatal("'" + torrent + "' exists," + " but is not a valid torrent metainfo file." + System.getProperty("line.separator"), ioe); else fatal("I2PSnark does not support creating and tracking a torrent at the moment"); /* { // Try to create a new metainfo file debug ("Trying to create metainfo torrent for '" + torrent + "'", NOTICE); try { activity = "Creating torrent"; storage = new Storage (f, "http://" + ip + ":" + port + "/announce", slistener); storage.create(); meta = storage.getMetaInfo(); } catch (IOException ioe2) { fatal("Could not create torrent for '" + torrent + "'", ioe2); } } */ else fatal("Cannot open '" + torrent + "'", ioe); } catch (OutOfMemoryError oom) { fatal("ERROR - Out of memory, cannot create torrent " + torrent + ": " + oom.getMessage()); } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} } debug(meta.toString(), INFO); // When the metainfo torrent was created from an existing file/dir // it already exists. if (storage == null) { try { activity = "Checking storage"; storage = new Storage(_util, meta, slistener); if (completeListener != null) { storage.check(rootDataDir, completeListener.getSavedTorrentTime(this), completeListener.getSavedTorrentBitField(this)); } else { storage.check(rootDataDir); } // have to figure out when to reopen // if (!start) // storage.close(); } catch (IOException ioe) { try { storage.close(); } catch (IOException ioee) { ioee.printStackTrace(); } fatal("Could not check or create storage", ioe); } } /* * see comment above * activity = "Collecting pieces"; coordinator = new PeerCoordinator(id, meta, storage, clistener, this); PeerCoordinatorSet set = PeerCoordinatorSet.instance(); set.add(coordinator); ConnectionAcceptor acceptor = ConnectionAcceptor.instance(); acceptor.startAccepting(set, serversocket); trackerclient = new TrackerClient(meta, coordinator); */ if (start) startTorrent(); } /** * multitorrent, magnet * * @param torrent a fake name for now (not a file name) * @param ih 20-byte info hash * @param trackerURL may be null * @since 0.8.4 */ public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL, CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet, ConnectionAcceptor connectionAcceptor, boolean start, String rootDir) { completeListener = complistener; _util = util; _peerCoordinatorSet = peerCoordinatorSet; acceptor = connectionAcceptor; this.torrent = torrent; this.infoHash = ih; this.additionalTrackerURL = trackerURL; this.rootDataDir = rootDir; stopped = true; id = generateID(); // All we have is an infoHash // meta remains null // storage remains null if (start) startTorrent(); } private static byte[] generateID() { // "Taking Three as the subject to reason about-- // A convenient number to state-- // We add Seven, and Ten, and then multiply out // By One Thousand diminished by Eight. // // "The result we proceed to divide, as you see, // By Nine Hundred and Ninety Two: // Then subtract Seventeen, and the answer must be // Exactly and perfectly true. // Create a new ID and fill it with something random. First nine // zeros bytes, then three bytes filled with snark and then // sixteen random bytes. byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17; byte[] rv = new byte[20]; Random random = I2PAppContext.getGlobalContext().random(); int i; for (i = 0; i < 9; i++) rv[i] = 0; rv[i++] = snark; rv[i++] = snark; rv[i++] = snark; while (i < 20) rv[i++] = (byte)random.nextInt(256); return rv; } /** * Start up contacting peers and querying the tracker */ public void startTorrent() { boolean ok = _util.connect(); if (!ok) fatal("Unable to connect to I2P"); if (coordinator == null) { I2PServerSocket serversocket = _util.getServerSocket(); if (serversocket == null) fatal("Unable to listen for I2P connections"); else { Destination d = serversocket.getManager().getSession().getMyDestination(); debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE); } debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE); activity = "Collecting pieces"; coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this); if (_peerCoordinatorSet != null) { // multitorrent _peerCoordinatorSet.add(coordinator); if (acceptor != null) { acceptor.startAccepting(_peerCoordinatorSet, serversocket); } else { // error } } else { // single torrent acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator)); } // TODO pass saved closest DHT nodes to the tracker? or direct to the coordinator? trackerclient = new TrackerClient(_util, meta, additionalTrackerURL, coordinator, this); } stopped = false; boolean coordinatorChanged = false; if (coordinator.halted()) { // ok, we have already started and stopped, but the coordinator seems a bit annoying to // restart safely, so lets build a new one to replace the old if (_peerCoordinatorSet != null) _peerCoordinatorSet.remove(coordinator); PeerCoordinator newCoord = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this); if (_peerCoordinatorSet != null) _peerCoordinatorSet.add(newCoord); coordinator = newCoord; coordinatorChanged = true; } if (!trackerclient.started() && !coordinatorChanged) { trackerclient.start(); } else if (trackerclient.halted() || coordinatorChanged) { if (storage != null) { try { storage.reopen(rootDataDir); } catch (IOException ioe) { try { storage.close(); } catch (IOException ioee) { ioee.printStackTrace(); } fatal("Could not reopen storage", ioe); } } TrackerClient newClient = new TrackerClient(_util, meta, additionalTrackerURL, coordinator, this); if (!trackerclient.halted()) trackerclient.halt(); trackerclient = newClient; trackerclient.start(); } else { debug("NOT starting TrackerClient???", NOTICE); } } /** * Stop contacting the tracker and talking with peers */ public void stopTorrent() { stopped = true; TrackerClient tc = trackerclient; if (tc != null) tc.halt(); PeerCoordinator pc = coordinator; if (pc != null) pc.halt(); Storage st = storage; if (st != null) { boolean changed = storage.isChanged(); try { storage.close(); } catch (IOException ioe) { System.out.println("Error closing " + torrent); ioe.printStackTrace(); } if (changed && completeListener != null) completeListener.updateStatus(this); } if (pc != null && _peerCoordinatorSet != null) _peerCoordinatorSet.remove(pc); if (_peerCoordinatorSet == null) _util.disconnect(); } private static Snark parseArguments(String[] args) { return parseArguments(args, null, null); } // Accessors /** * @return file name of .torrent file (should be full absolute path), or a fake name if in magnet mode. * @since 0.8.4 */ public String getName() { return torrent; } /** * @return base name of torrent [filtered version of getMetaInfo.getName()], or a fake name if in magnet mode * @since 0.8.4 */ public String getBaseName() { if (storage != null) return storage.getBaseName(); return torrent; } /** * @return always will be valid even in magnet mode * @since 0.8.4 */ public byte[] getID() { return id; } /** * @return always will be valid even in magnet mode * @since 0.8.4 */ public byte[] getInfoHash() { // should always be the same if (meta != null) return meta.getInfoHash(); return infoHash; } /** * @return may be null if in magnet mode * @since 0.8.4 */ public MetaInfo getMetaInfo() { return meta; } /** * @return may be null if in magnet mode * @since 0.8.4 */ public Storage getStorage() { return storage; } /** * @since 0.8.4 */ public boolean isStopped() { return stopped; } /** * @since 0.8.4 */ public long getDownloadRate() { PeerCoordinator coord = coordinator; if (coord != null) return coord.getDownloadRate(); return 0; } /** * @since 0.8.4 */ public long getUploadRate() { PeerCoordinator coord = coordinator; if (coord != null) return coord.getUploadRate(); return 0; } /** * @since 0.8.4 */ public long getDownloaded() { PeerCoordinator coord = coordinator; if (coord != null) return coord.getDownloaded(); return 0; } /** * @since 0.8.4 */ public long getUploaded() { PeerCoordinator coord = coordinator; if (coord != null) return coord.getUploaded(); return 0; } /** * @since 0.8.4 */ public int getPeerCount() { PeerCoordinator coord = coordinator; if (coord != null) return coord.getPeerCount(); return 0; } /** * @since 0.8.4 */ public List getPeerList() { PeerCoordinator coord = coordinator; if (coord != null) return coord.peerList(); return Collections.EMPTY_LIST; } /** * @return String returned from tracker, or null if no error * @since 0.8.4 */ public String getTrackerProblems() { return trackerProblems; } /** * @param p tracker error string or null * @since 0.8.4 */ public void setTrackerProblems(String p) { trackerProblems = p; } /** * @return count returned from tracker * @since 0.8.4 */ public int getTrackerSeenPeers() { return trackerSeenPeers; } /** * @since 0.8.4 */ public void setTrackerSeenPeers(int p) { trackerSeenPeers = p; } /** * @since 0.8.4 */ public void updatePiecePriorities() { PeerCoordinator coord = coordinator; if (coord != null) coord.updatePiecePriorities(); } /** * @return total of all torrent files, or total of metainfo file if fetching magnet, or -1 * @since 0.8.4 */ public long getTotalLength() { if (meta != null) return meta.getTotalLength(); // FIXME else return metainfo length if available return -1; } /** * @return exact value. or -1 if no storage yet. * getNeeded() * pieceLength(0) isn't accurate if last piece * is still needed. * @since 0.8.9 */ public long getRemainingLength() { if (meta != null && storage != null) { long needed = storage.needed(); long length0 = meta.getPieceLength(0); long remaining = needed * length0; // fixup if last piece is needed int last = meta.getPieces() - 1; if (last != 0 && !storage.getBitField().get(last)) remaining -= length0 - meta.getPieceLength(last); return remaining; } return -1; } /** * @return number of pieces still needed (magnet mode or not), or -1 if unknown * @since 0.8.4 */ public long getNeeded() { if (storage != null) return storage.needed(); if (meta != null) // FIXME subtract chunks we have return meta.getTotalLength(); // FIXME fake return -1; } /** * @param p the piece number * @return metainfo piece length or 16K if fetching magnet * @since 0.8.4 */ public int getPieceLength(int p) { if (meta != null) return meta.getPieceLength(p); return 16*1024; } /** * @return number of pieces * @since 0.8.4 */ public int getPieces() { if (meta != null) return meta.getPieces(); // FIXME else return metainfo pieces if available return -1; } /** * @return true if restarted * @since 0.8.4 */ public boolean restartAcceptor() { if (acceptor == null) return false; acceptor.restart(); return true; } /** * @return trackerURL string from magnet-mode constructor, may be null * @since 0.8.4 */ public String getTrackerURL() { return additionalTrackerURL; } /** * 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. */ private static Snark parseArguments(String[] args, StorageListener slistener, CoordinatorListener clistener) { int user_port = -1; String ip = null; String torrent = null; I2PSnarkUtil util = new I2PSnarkUtil(I2PAppContext.getGlobalContext()); boolean configured = util.configured(); int i = 0; while (i < args.length) { /* if (args[i].equals("--debug")) { debug = INFO; i++; // Try if there is an level argument. if (i < args.length) { try { int level = Integer.parseInt(args[i]); if (level >= 0) { debug = level; i++; } } catch (NumberFormatException nfe) { } } } 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("--no-commands")) { command_interpreter = false; i++; } //else if (args[i].equals("--eepproxy")) // { // String proxyHost = args[i+1]; // String proxyPort = args[i+2]; // if (!configured) // util.setProxy(proxyHost, Integer.parseInt(proxyPort)); // i += 3; // } else if (args[i].equals("--i2cp")) { String i2cpHost = args[i+1]; String i2cpPort = args[i+2]; Properties opts = null; if (i+3 < args.length) { if (!args[i+3].startsWith("--")) { opts = new Properties(); StringTokenizer tok = new StringTokenizer(args[i+3], " \t"); while (tok.hasMoreTokens()) { String str = tok.nextToken(); int split = str.indexOf('='); if (split > 0) { opts.setProperty(str.substring(0, split), str.substring(split+1)); } } } } if (!configured) util.setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts); i += 3 + (opts != null ? 1 : 0); } else { torrent = args[i]; i++; break; } } if (torrent == null || i != args.length) if (torrent != null && torrent.startsWith("-")) usage("Unknow option '" + torrent + "'."); else usage("Need exactly one , or ."); return new Snark(util, torrent, ip, user_port, slistener, clistener); } private static void usage(String s) { System.out.println("snark: " + s); usage(); } private static void usage() { System.out.println ("Usage: snark [--debug [level]] [--no-commands] [--port ]"); System.out.println (" [--eepproxy hostname portnum]"); System.out.println (" [--i2cp routerHost routerPort ['name=val name=val name=val']]"); System.out.println (" (|)"); System.out.println (" --debug\tShows some extra info and stacktraces"); System.out.println (" level\tHow much debug details to show"); System.out.println (" \t(defaults to " + NOTICE + ", with --debug to " + INFO + ", highest level is " + ALL + ")."); System.out.println (" --no-commands\tDon't read interactive commands or show usage info."); System.out.println (" --port\tThe port to listen on for incomming connections"); System.out.println (" \t(if not given defaults to first free port between " + MIN_PORT + "-" + MAX_PORT + ")."); System.out.println (" --share\tStart torrent tracker on address or name."); System.out.println (" --eepproxy\thttp proxy to use (default of 127.0.0.1 port 4444)"); System.out.println (" --i2cp\tlocation of your I2P router (default of 127.0.0.1 port 7654)"); System.out.println (" \toptional settings may be included, such as"); System.out.println (" \tinbound.length=2 outbound.length=2 inbound.lengthVariance=-1 "); System.out.println (" \tURL pointing to .torrent metainfo file to download/share."); System.out.println (" \tEither a local .torrent metainfo file to download"); System.out.println (" \tor (with --share) a file to share."); } /** * Aborts program abnormally. */ private void fatal(String s) { fatal(s, null); } /** * Aborts program abnormally. */ private void fatal(String s, Throwable t) { _util.debug(s, ERROR, t); //System.err.println("snark: " + s + ((t == null) ? "" : (": " + t))); //if (debug >= INFO && t != null) // t.printStackTrace(); stopTorrent(); if (t != null) s += ": " + t; if (completeListener != null) completeListener.fatal(this, s); throw new RuntimeException(s, t); } /** * Show debug info if debug is true. */ private void debug(String s, int level) { _util.debug(s, level, null); } /** CoordinatorListener - this does nothing */ public void peerChange(PeerCoordinator coordinator, Peer peer) { // System.out.println(peer.toString()); } /** * Called when the PeerCoordinator got the MetaInfo via magnet. * CoordinatorListener. * Create the storage, tell SnarkManager, and give the storage * back to the coordinator. * * @throws RuntimeException via fatal() * @since 0.8.4 */ public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) { try { // The following two may throw IOE... storage = new Storage(_util, metainfo, this); storage.check(rootDataDir); // ... so don't set meta until here meta = metainfo; if (completeListener != null) { String newName = completeListener.gotMetaInfo(this); if (newName != null) torrent = newName; // else some horrible problem } coordinator.setStorage(storage); } catch (IOException ioe) { if (storage != null) { try { storage.close(); } catch (IOException ioee) {} } fatal("Could not check or create storage", ioe); } } private boolean allocating = false; public void storageCreateFile(Storage storage, String name, long length) { //if (allocating) // System.out.println(); // Done with last file. //System.out.print("Creating file '" + name // + "' of length " + length + ": "); allocating = true; } // How much storage space has been allocated private long allocated = 0; public void storageAllocated(Storage storage, long length) { allocating = true; //System.out.print("."); allocated += length; //if (allocated == meta.getTotalLength()) // System.out.println(); // We have all the disk space we need. } private boolean allChecked = false; private boolean checking = false; private boolean prechecking = true; public void storageChecked(Storage storage, int num, boolean checked) { allocating = false; if (!allChecked && !checking) { // Use the MetaInfo from the storage since our own might not // yet be setup correctly. //MetaInfo meta = storage.getMetaInfo(); //if (meta != null) // System.out.print("Checking existing " // + meta.getPieces() // + " pieces: "); checking = true; } if (!checking) debug("Got " + (checked ? "" : "BAD ") + "piece: " + num, Snark.INFO); } public void storageAllChecked(Storage storage) { //if (checking) // System.out.println(); allChecked = true; checking = false; if (storage.isChanged() && completeListener != null) completeListener.updateStatus(this); } public void storageCompleted(Storage storage) { debug("Completely received " + torrent, Snark.INFO); //storage.close(); //System.out.println("Completely received: " + torrent); if (completeListener != null) completeListener.torrentComplete(this); } public void setWantedPieces(Storage storage) { coordinator.setWantedPieces(); } /** SnarkSnutdown callback unused */ 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); } public interface CompleteListener { public void torrentComplete(Snark snark); public void updateStatus(Snark snark); /** * We transitioned from magnet mode, we have now initialized our * metainfo and storage. The listener should now call getMetaInfo() * and save the data to disk. * * @return the new name for the torrent or null on error * @since 0.8.4 */ public String gotMetaInfo(Snark snark); /** * @since 0.9 */ public void fatal(Snark snark, String error); // not really listeners but the easiest way to get back to an optional SnarkManager public long getSavedTorrentTime(Snark snark); public BitField getSavedTorrentBitField(Snark snark); } /** Maintain a configurable total uploader cap * coordinatorListener */ final static int MIN_TOTAL_UPLOADERS = 4; final static int MAX_TOTAL_UPLOADERS = 10; public boolean overUploadLimit(int uploaders) { if (_peerCoordinatorSet == null || uploaders <= 0) return false; int totalUploaders = 0; for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) { PeerCoordinator c = (PeerCoordinator)iter.next(); if (!c.halted()) totalUploaders += c.uploaders; } int limit = _util.getMaxUploaders(); // debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG); return totalUploaders > limit; } public boolean overUpBWLimit() { if (_peerCoordinatorSet == null) return false; long total = 0; for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) { PeerCoordinator c = (PeerCoordinator)iter.next(); if (!c.halted()) total += c.getCurrentUploadRate(); } long limit = 1024l * _util.getMaxUpBW(); debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING); return total > limit; } public boolean overUpBWLimit(long total) { long limit = 1024l * _util.getMaxUpBW(); return total > limit; } }