diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java index 97deb7339..9f5789031 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java @@ -220,7 +220,9 @@ public class MetaInfo Object o = val.getValue(); // Is it supposed to be a number or a string? // i2psnark does it as a string. BEP 27 doesn't say. - // Transmission does numbers. + // Transmission does numbers. So does libtorrent. + // We handle both as of 0.9.9. + // We switch to storing as number as of 0.9.14. privateTorrent = "1".equals(o) || ((o instanceof Number) && ((Number) o).intValue() == 1); } else { @@ -621,7 +623,9 @@ public class MetaInfo info.put("name.utf-8", new BEValue(DataHelper.getUTF8(name_utf8))); // BEP 27 if (privateTorrent) - info.put("private", new BEValue(DataHelper.getUTF8("1"))); + // switched to number in 0.9.14 + //info.put("private", new BEValue(DataHelper.getUTF8("1"))); + info.put("private", new BEValue(Integer.valueOf(1))); info.put("piece length", new BEValue(Integer.valueOf(piece_length))); info.put("pieces", new BEValue(piece_hashes)); diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 14c6e79af..9a84c8d3a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -650,7 +650,9 @@ public class TrackerClient implements Runnable { numwant = 1; else numwant = _util.getMaxConnections(); - Collection hashes = dht.getPeersAndAnnounce(snark.getInfoHash(), numwant, 5*60*1000, 1, 3*60*1000); + Collection hashes = dht.getPeersAndAnnounce(snark.getInfoHash(), numwant, + 5*60*1000, 1, 3*60*1000, + coordinator.completed()); if (!hashes.isEmpty()) { runStarted = true; lastDHTAnnounce = _util.getContext().clock().now(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java b/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java index 16d2edd65..8a9977a2f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/DHT.java @@ -45,9 +45,12 @@ public interface DHT { * @param maxWait the maximum time to wait (ms) must be > 0 * @param annMax the number of peers to announce to * @param annMaxWait the maximum total time to wait for announces, may be 0 to return immediately without waiting for acks + * @param isSeed true if seed, false if leech * @return possibly empty (never null) */ - public Collection getPeersAndAnnounce(byte[] ih, int max, long maxWait, int annMax, long annMaxWait); + public Collection getPeersAndAnnounce(byte[] ih, int max, long maxWait, + int annMax, long annMaxWait, + boolean isSeed); /** * Announce to ourselves. @@ -58,7 +61,7 @@ public interface DHT { public void announce(byte[] ih); /** - * Announce somebody else we know about. + * Announce somebody else we know about to ourselves. * Non-blocking. * * @param ih the Info Hash (torrent) @@ -84,9 +87,10 @@ public interface DHT { * * @param ih the Info Hash (torrent) * @param maxWait the maximum total time to wait (ms) or 0 to do all in parallel and return immediately. + * @param isSeed true if seed, false if leech * @return the number of successful announces, not counting ourselves. */ - public int announce(byte[] ih, int max, long maxWait); + public int announce(byte[] ih, int max, long maxWait, boolean isSeed); /** * Stop everything. diff --git a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java index 1ea2c9917..8b46c613e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java +++ b/apps/i2psnark/java/src/org/klomp/snark/dht/KRPC.java @@ -315,9 +315,12 @@ public class KRPC implements I2PSessionMuxedListener, DHT { * @param maxWait the maximum time to wait (ms) must be > 0 * @param annMax the number of peers to announce to * @param annMaxWait the maximum total time to wait for announces, may be 0 to return immediately without waiting for acks + * @param isSeed true if seed, false if leech * @return possibly empty (never null) */ - public Collection getPeersAndAnnounce(byte[] ih, int max, long maxWait, int annMax, long annMaxWait) { + public Collection getPeersAndAnnounce(byte[] ih, int max, long maxWait, + int annMax, long annMaxWait, + boolean isSeed) { // check local tracker first InfoHash iHash = new InfoHash(ih); Collection rv = _tracker.getPeers(iHash, max); @@ -424,7 +427,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { if (_log.shouldLog(Log.INFO)) _log.info("Announcing to closest from get peers: " + annTo); long toWait = annMaxWait > 0 ? Math.min(annMaxWait, 60*1000) : 0; - if (announce(ih, annTo, toWait)) + if (announce(ih, annTo, toWait, isSeed)) annCnt++; if (annMaxWait > 0) { annMaxWait -= _context.clock().now() - start; @@ -437,7 +440,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { // so this is essentially just a retry if (_log.shouldLog(Log.INFO)) _log.info("Announcing to closest in kbuckets after get peers failed"); - announce(ih, annMax, annMaxWait); + announce(ih, annMax, annMaxWait, isSeed); } if (_log.shouldLog(Log.INFO)) { _log.info("Finished get Peers, returning " + rv.size()); @@ -460,7 +463,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { } /** - * Announce somebody else we know about. + * Announce somebody else we know about to ourselves. * Non-blocking. * * @param ih the Info Hash (torrent) @@ -500,9 +503,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT { * @param ih the Info Hash (torrent) * @param max maximum number of peers to announce to * @param maxWait the maximum total time to wait (ms) or 0 to do all in parallel and return immediately. + * @param isSeed true if seed, false if leech * @return the number of successful announces, not counting ourselves. */ - public int announce(byte[] ih, int max, long maxWait) { + public int announce(byte[] ih, int max, long maxWait, boolean isSeed) { announce(ih); int rv = 0; long start = _context.clock().now(); @@ -513,7 +517,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { for (NodeInfo nInfo : nodes) { if (!_isRunning) break; - if (announce(ih, nInfo, Math.min(maxWait, 60*1000))) + if (announce(ih, nInfo, Math.min(maxWait, 60*1000), isSeed)) rv++; maxWait -= _context.clock().now() - start; if (maxWait < 1000) @@ -531,9 +535,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT { * @param ih the Info Hash (torrent) * @param nInfo the peer to announce to * @param maxWait the maximum time to wait (ms) or 0 to return immediately. + * @param isSeed true if seed, false if leech * @return success */ - private boolean announce(byte[] ih, NodeInfo nInfo, long maxWait) { + private boolean announce(byte[] ih, NodeInfo nInfo, long maxWait, boolean isSeed) { InfoHash iHash = new InfoHash(ih); // it isn't clear from BEP 5 if a token is bound to a single infohash? // for now, just bind to the NID @@ -580,7 +585,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { } // send and wait on rcv msg lock unless maxWait <= 0 - ReplyWaiter waiter = sendAnnouncePeer(nInfo, iHash, token); + ReplyWaiter waiter = sendAnnouncePeer(nInfo, iHash, token, isSeed); if (waiter == null) return false; if (maxWait <= 0) @@ -743,9 +748,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT { * Non-blocking, will fail if we don't have the dest for the nodeinfo * * @param nInfo who to send it to + * @param isSeed true if seed, false if leech * @return null on error */ - private ReplyWaiter sendAnnouncePeer(NodeInfo nInfo, InfoHash ih, Token token) { + private ReplyWaiter sendAnnouncePeer(NodeInfo nInfo, InfoHash ih, Token token, boolean isSeed) { if (_log.shouldLog(Log.INFO)) _log.info("Sending announce of " + ih + " to: " + nInfo); Map map = new HashMap(); @@ -755,6 +761,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT { // port ignored args.put("port", Integer.valueOf(TrackerClient.PORT)); args.put("token", token.getData()); + args.put("seed", Integer.valueOf(isSeed ? 1 : 0)); map.put("a", args); // an announce need not be signed, we have a token ReplyWaiter rv = sendQuery(nInfo, map, false);