diff --git a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java index 2f01fe62c2abe95437bd8173155f69cc967b4101..3fc4977f6e92ee74420099b46d9b5ed6ff37a57d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java +++ b/apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java @@ -54,28 +54,30 @@ public class MetaInfo private final byte[] info_hash; private final String name; private final String name_utf8; - private final List files; - private final List files_utf8; - private final List lengths; + private final List<List<String>> files; + private final List<List<String>> files_utf8; + private final List<Long> lengths; private final int piece_length; private final byte[] piece_hashes; private final long length; - private Map infoMap; + private Map<String, BEValue> infoMap; /** * Called by Storage when creating a new torrent from local data * * @param announce may be null + * @param files null for single-file torrent + * @param lengths null for single-file torrent */ - MetaInfo(String announce, String name, String name_utf8, List files, List lengths, + MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths, int piece_length, byte[] piece_hashes, long length) { this.announce = announce; this.name = name; this.name_utf8 = name_utf8; - this.files = files; + this.files = files == null ? null : Collections.unmodifiableList(files); this.files_utf8 = null; - this.lengths = lengths; + this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths); this.piece_length = piece_length; this.piece_hashes = piece_hashes; this.length = length; @@ -131,7 +133,7 @@ public class MetaInfo if (val == null) throw new InvalidBEncodingException("Missing info map"); Map info = val.getMap(); - infoMap = info; + infoMap = Collections.unmodifiableMap(info); val = (BEValue)info.get("name"); if (val == null) @@ -171,39 +173,39 @@ public class MetaInfo throw new InvalidBEncodingException ("Missing length number and/or files list"); - List list = val.getList(); + List<BEValue> list = val.getList(); int size = list.size(); if (size == 0) throw new InvalidBEncodingException("zero size files list"); - files = new ArrayList(size); - files_utf8 = new ArrayList(size); - lengths = new ArrayList(size); + List<List<String>> m_files = new ArrayList(size); + List<List<String>> m_files_utf8 = new ArrayList(size); + List<Long> m_lengths = new ArrayList(size); long l = 0; for (int i = 0; i < list.size(); i++) { - Map desc = ((BEValue)list.get(i)).getMap(); - val = (BEValue)desc.get("length"); + Map<String, BEValue> desc = list.get(i).getMap(); + val = desc.get("length"); if (val == null) throw new InvalidBEncodingException("Missing length number"); long len = val.getLong(); - lengths.add(new Long(len)); + m_lengths.add(new Long(len)); l += len; val = (BEValue)desc.get("path"); if (val == null) throw new InvalidBEncodingException("Missing path list"); - List path_list = val.getList(); + List<BEValue> path_list = val.getList(); int path_length = path_list.size(); if (path_length == 0) throw new InvalidBEncodingException("zero size file path list"); - List file = new ArrayList(path_length); - Iterator it = path_list.iterator(); + List<String> file = new ArrayList(path_length); + Iterator<BEValue> it = path_list.iterator(); while (it.hasNext()) - file.add(((BEValue)it.next()).getString()); + file.add(it.next().getString()); - files.add(file); + m_files.add(Collections.unmodifiableList(file)); val = (BEValue)desc.get("path.utf-8"); if (val != null) { @@ -213,11 +215,14 @@ public class MetaInfo file = new ArrayList(path_length); it = path_list.iterator(); while (it.hasNext()) - file.add(((BEValue)it.next()).getString()); - files_utf8.add(file); + file.add(it.next().getString()); + m_files_utf8.add(Collections.unmodifiableList(file)); } } } + files = Collections.unmodifiableList(m_files); + files_utf8 = Collections.unmodifiableList(m_files_utf8); + lengths = Collections.unmodifiableList(m_lengths); length = l; } @@ -265,9 +270,8 @@ public class MetaInfo * a single name. It has the same size as the list returned by * getLengths(). */ - public List getFiles() + public List<List<String>> getFiles() { - // XXX - Immutable? return files; } @@ -276,9 +280,8 @@ public class MetaInfo * files, or null if it is a single file. It has the same size as * the list returned by getFiles(). */ - public List getLengths() + public List<Long> getLengths() { - // XXX - Immutable? return lengths; } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index 0f13d1470d49787b9d1fcf6950d8027dd5516c07..b31ba65e2f315ff3b46fc8935bd16b109955f78b 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -32,6 +32,7 @@ import java.util.Map; import net.i2p.client.streaming.I2PSocket; import net.i2p.data.DataHelper; +import net.i2p.data.Destination; import net.i2p.util.Log; import org.klomp.snark.bencode.BEValue; @@ -384,6 +385,13 @@ public class Peer implements Comparable return options; } + /** @since 0.8.4 */ + public Destination getDestination() { + if (sock == null) + return null; + return sock.getPeerDestination(); + } + /** * Shared state across all peers, callers must sync on returned object * @return non-null diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java index 65a869845322de0bfdb4a9a5e482cd51651fa1cb..6370eaca646983d0729fceb3f97aea0a7c889d7f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java @@ -37,7 +37,8 @@ class PeerCheckerTask extends TimerTask private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000); private final PeerCoordinator coordinator; - public I2PSnarkUtil _util; + private final I2PSnarkUtil _util; + private int _runCount; PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator) { @@ -49,6 +50,7 @@ class PeerCheckerTask extends TimerTask public void run() { + _runCount++; List<Peer> peerList = coordinator.peerList(); if (peerList.isEmpty() || coordinator.halted()) { coordinator.setRateHistory(0, 0); @@ -204,6 +206,10 @@ class PeerCheckerTask extends TimerTask } peer.retransmitRequests(); peer.keepAlive(); + // announce them to local tracker (TrackerClient does this too) + if (_util.getDHT() != null && (_runCount % 5) == 0) { + _util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash()); + } } // Resync actual uploaders value @@ -245,8 +251,13 @@ class PeerCheckerTask extends TimerTask // close out unused files, but we don't need to do it every time Storage storage = coordinator.getStorage(); - if (storage != null && random.nextInt(4) == 0) { + if (storage != null && (_runCount % 4) == 0) { storage.cleanRAFs(); } + + // announce ourselves to local tracker (TrackerClient does this too) + if (_util.getDHT() != null && (_runCount % 16) == 0) { + _util.getDHT().announce(coordinator.getInfoHash()); + } } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 0ad2c1e37261745101f07a8e7bca5677fb824308..504749b6255d9aae9d5ae11fbce49aa903507ca2 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -36,6 +36,8 @@ import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.SimpleTimer2; +import org.klomp.snark.dht.KRPC; + /** * Coordinates what peer does what. */ @@ -1186,10 +1188,13 @@ public class PeerCoordinator implements PeerListener /** * PeerListener callback + * Tell the DHT to ping it, this will get back the node info * @since 0.8.4 */ public void gotPort(Peer peer, int port) { - // send to DHT + KRPC krpc = _util.getDHT(); + if (krpc != null) + krpc.ping(peer.getDestination(), port); } /** Return number of allowed uploaders for this torrent. diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index bd3d0d20dd29238da2afc621d75c0b425ddca592..45231965fcc493396a939f03c1d685b21f604c60 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -776,7 +776,7 @@ public class Snark } /** - * @return needed of all torrent files, or total of metainfo file if fetching magnet, or -1 + * @return number of pieces still needed (magnet mode or not), or -1 if unknown * @since 0.8.4 */ public long getNeeded() { @@ -786,7 +786,7 @@ public class Snark // FIXME subtract chunks we have return meta.getTotalLength(); // FIXME fake - return 16 * 16 * 1024; + return -1; } /** @@ -800,6 +800,17 @@ public class Snark 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 diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index c1410e7fa47fc76a876888ec05cab46e4741e368..27640d53baacf2442e310f79fb5cdcc06cbfe8e6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -68,6 +68,7 @@ public class SnarkManager implements Snark.CompleteListener { public static final String PROP_META_PREFIX = "i2psnark.zmeta."; public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield"; public static final String PROP_META_PRIORITY_SUFFIX = ".priority"; + public static final String PROP_META_MAGNET_SUFFIX = ".magnet"; private static final String CONFIG_FILE = "i2psnark.config"; public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops @@ -1031,6 +1032,9 @@ public class SnarkManager implements Snark.CompleteListener { } } +//start magnets + + // here because we need to delay until I2CP is up // although the user will see the default until then getBWLimit(); @@ -1245,6 +1249,7 @@ public class SnarkManager implements Snark.CompleteListener { if ( (snark != null) && (!snark.isStopped()) ) snark.stopTorrent(); } +//save magnets } } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index dcd78620d9704d3bdc17ac4ec6af9c2579316b12..77012de77dd57e34fd5be166e8888c017e924999 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -100,7 +100,7 @@ public class Storage getFiles(baseFile); long total = 0; - ArrayList lengthsList = new ArrayList(); + ArrayList<Long> lengthsList = new ArrayList(); for (int i = 0; i < lengths.length; i++) { long length = lengths[i]; @@ -122,10 +122,10 @@ public class Storage bitfield = new BitField(pieces); needed = 0; - List files = new ArrayList(); + List<List<String>> files = new ArrayList(); for (int i = 0; i < names.length; i++) { - List file = new ArrayList(); + List<String> file = new ArrayList(); StringTokenizer st = new StringTokenizer(names[i], File.separator); while (st.hasMoreTokens()) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index f437beeeb63666cab4b34b34da44f2cd75a23fcc..720a662b3a83a6bbdc8a99088be942fa5e05253f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -277,18 +277,26 @@ public class TrackerClient extends I2PAppThread runStarted = true; tr.started = true; - Set peers = info.getPeers(); + Set<Peer> peers = info.getPeers(); tr.seenPeers = info.getPeerCount(); if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly snark.setTrackerSeenPeers(tr.seenPeers); + + // pass everybody over to our tracker + if (_util.getDHT() != null) { + for (Peer peer : peers) { + _util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash()); + } + } + if ( (left > 0) && (!completed) ) { // we only want to talk to new people if we need things // from them (duh) - List ordered = new ArrayList(peers); + List<Peer> ordered = new ArrayList(peers); Collections.shuffle(ordered, r); - Iterator it = ordered.iterator(); + Iterator<Peer> it = ordered.iterator(); while ((!stop) && it.hasNext()) { - Peer cur = (Peer)it.next(); + Peer cur = it.next(); // FIXME if id == us || dest == us continue; // only delay if we actually make an attempt to add peer if(coordinator.addPeer(cur)) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index fefc808485ea99f698d4f953d4defb466e55a80e..61c9c8e572a3637c217125329e96f00f98b2e44c 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -6,6 +6,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.text.Collator; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -875,8 +876,7 @@ public class I2PSnarkServlet extends Default { else if (isValid) icon = toIcon(meta.getName()); else - // todo get a nice magnet icon? - icon = "page_white"; + icon = "magnet"; if (remaining == 0 || isMultiFile) { out.write(toImg(icon, _("Open"))); out.write("</a>"); @@ -1375,6 +1375,7 @@ public class I2PSnarkServlet extends Default { if (ihash.length() == 32) { ih = Base32.decode(ihash); } else if (ihash.length() == 40) { + // Like DataHelper.fromHexString() but ensures no loss of leading zero bytes ih = new byte[20]; try { for (int i = 0; i < 20; i++) { @@ -1539,6 +1540,7 @@ public class I2PSnarkServlet extends Default { if (title.endsWith("/")) title = title.substring(0, title.length() - 1); + String directory = title; title = _("Torrent") + ": " + title; buf.append(title); buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" + @@ -1550,10 +1552,40 @@ public class I2PSnarkServlet extends Default { boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete(); if (showPriority) buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n"); - buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" >" + - "<thead><tr><th>") + buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" ><thead>"); + if (snark != null) { + // first row - torrent info + // FIXME center + buf.append("<tr><th colspan=\"" + (showPriority ? '4' : '3') + "\"><div>") + .append(_("Torrent")).append(": ").append(snark.getBaseName()); + int pieces = snark.getPieces(); + double completion = (pieces - snark.getNeeded()) / (double) pieces; + if (completion < 1.0) + buf.append("<br>").append(_("Completion")).append(": ").append((new DecimalFormat("0.00%")).format(completion)); + else + buf.append("<br>").append(_("Complete")); + // else unknown + buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength())); + MetaInfo meta = snark.getMetaInfo(); + if (meta != null) { + List files = meta.getFiles(); + int fileCount = files != null ? files.size() : 1; + buf.append("<br>").append(_("Files")).append(": ").append(fileCount); + } + buf.append("<br>").append(_("Pieces")).append(": ").append(pieces); + buf.append("<br>").append(_("Piece size")).append(": ").append(formatSize(snark.getPieceLength(0))); + String hex = toHex(snark.getInfoHash()); + buf.append("<br>").append(_("Magnet link")).append(": <a href=\"").append(MAGNET).append(hex).append("\">") + .append(MAGNET).append(hex).append("</a>"); + // We don't have the hash of the torrent file + //buf.append("<br>").append(_("Maggot link")).append(": <a href=\"").append(MAGGOT).append(hex).append(':').append(hex).append("\">") + // .append(MAGGOT).append(hex).append(':').append(hex).append("</a>"); + buf.append("</div></th></tr>"); + } + // second row - dir info + buf.append("<tr><th>") .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > ") - .append(title).append("</th><th align=\"right\">") + .append(_("Directory")).append(": ").append(directory).append("</th><th align=\"right\">") .append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" > ") .append(_("Size")); buf.append("</th><th class=\"headerstatus\">") @@ -1767,6 +1799,21 @@ public class I2PSnarkServlet extends Default { return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\">"; } + /** + * Like DataHelper.toHexString but ensures no loss of leading zero bytes + * @since 0.8.4 + */ + private static String toHex(byte[] b) { + StringBuilder buf = new StringBuilder(40); + for (int i = 0; i < b.length; i++) { + int bi = b[i] & 0xff; + if (bi < 16) + buf.append('0'); + buf.append(Integer.toHexString(bi)); + } + return buf.toString(); + } + /** @since 0.8.1 */ private void savePriorities(Snark snark, Map postParams) { Storage storage = snark.getStorage();