From 580bb5a6fec7c0daaaf8ac11d0f440b5ae40ade5 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Wed, 23 May 2012 16:36:37 +0000 Subject: [PATCH] * i2psnark: - Fixes when complete except for skipped files (ticket #447) status in UI, don't connect outbound, disconnect seeds when done - More classes pkg private --- .../org/klomp/snark/CoordinatorListener.java | 2 +- .../src/org/klomp/snark/PeerCoordinator.java | 68 ++++++++++++++++--- .../java/src/org/klomp/snark/Snark.java | 15 ++++ .../java/src/org/klomp/snark/Storage.java | 3 +- .../src/org/klomp/snark/StorageListener.java | 2 +- .../src/org/klomp/snark/TrackerClient.java | 12 ++-- .../org/klomp/snark/web/I2PSnarkServlet.java | 45 ++++++++---- 7 files changed, 118 insertions(+), 29 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/CoordinatorListener.java b/apps/i2psnark/java/src/org/klomp/snark/CoordinatorListener.java index 478c17bb50..767979d061 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/CoordinatorListener.java +++ b/apps/i2psnark/java/src/org/klomp/snark/CoordinatorListener.java @@ -24,7 +24,7 @@ package org.klomp.snark; /** * Callback used when some peer changes state. */ -public interface CoordinatorListener +interface CoordinatorListener { /** * Called when the PeerCoordinator notices a change in the state of a peer. diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 7a4e2bc7b0..01260f1831 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -48,7 +48,7 @@ import org.klomp.snark.dht.DHT; /** * Coordinates what peer does what. */ -public class PeerCoordinator implements PeerListener +class PeerCoordinator implements PeerListener { private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class); @@ -116,6 +116,12 @@ public class PeerCoordinator implements PeerListener */ private final List<Piece> wantedPieces; + /** The total number of bytes in wantedPieces, or -1 if not yet known. + * Sync on wantedPieces. + * @since 0.9.1 + */ + private long wantedBytes; + /** partial pieces - lock by synching on wantedPieces - TODO store Requests, not PartialPieces */ private final List<PartialPiece> partialPieces; @@ -171,16 +177,22 @@ public class PeerCoordinator implements PeerListener } } - // only called externally from Storage after the double-check fails + /** + * Only called externally from Storage after the double-check fails. + * Sets wantedBytes too. + */ public void setWantedPieces() { - if (metainfo == null || storage == null) + if (metainfo == null || storage == null) { + wantedBytes = -1; return; + } // Make a list of pieces synchronized(wantedPieces) { wantedPieces.clear(); BitField bitfield = storage.getBitField(); int[] pri = storage.getPiecePriorities(); + long count = 0; for (int i = 0; i < metainfo.getPieces(); i++) { // only add if we don't have and the priority is >= 0 if ((!bitfield.get(i)) && @@ -189,8 +201,10 @@ public class PeerCoordinator implements PeerListener if (pri != null) p.setPriority(pri[i]); wantedPieces.add(p); + count += metainfo.getPieceLength(i); } } + wantedBytes = count; Collections.shuffle(wantedPieces, _random); } } @@ -233,7 +247,9 @@ public class PeerCoordinator implements PeerListener } /** - * Returns how many bytes are still needed to get the complete file. + * Bytes not yet in storage. Does NOT account for skipped files. + * Not exact (does not adjust for last piece size). + * Returns how many bytes are still needed to get the complete torrent. * @return -1 if in magnet mode */ public long getLeft() @@ -244,6 +260,15 @@ public class PeerCoordinator implements PeerListener return ((long) storage.needed()) * metainfo.getPieceLength(0); } + /** + * Bytes still wanted. DOES account for skipped files. + * @return exact value. or -1 if no storage yet. + * @since 0.9.1 + */ + public long getNeededLength() { + return wantedBytes; + } + /** * Returns the total number of uploaded bytes of all peers. */ @@ -330,10 +355,24 @@ public class PeerCoordinator implements PeerListener return infohash; } + /** + * Inbound. + * Not halted, peers < max. + * @since 0.9.1 + */ public boolean needPeers() { return !halted && peers.size() < getMaxConnections(); } + + /** + * Outbound. + * Not halted, peers < max, and need pieces. + * @since 0.9.1 + */ + public boolean needOutboundPeers() { + return wantedBytes != 0 && needPeers(); + } /** * Reduce max if huge pieces to keep from ooming when leeching @@ -472,7 +511,10 @@ public class PeerCoordinator implements PeerListener return null; } -// returns true if actual attempt to add peer occurs + /** + * Add peer (inbound or outbound) + * @return true if actual attempt to add peer occurs + */ public boolean addPeer(final Peer peer) { if (halted) @@ -755,6 +797,7 @@ public class PeerCoordinator implements PeerListener if (!want.get(i)) { Piece piece = new Piece(i); wantedPieces.add(piece); + wantedBytes += metainfo.getPieceLength(i); // As connections are already up, new Pieces will // not have their PeerID list populated, so do that. for (Peer p : peers) { @@ -777,6 +820,7 @@ public class PeerCoordinator implements PeerListener } else { iter.remove(); toCancel.add(p); + wantedBytes -= metainfo.getPieceLength(p.getId()); } } if (_log.shouldLog(Log.DEBUG)) @@ -910,11 +954,14 @@ public class PeerCoordinator implements PeerListener throw new RuntimeException(msg, ioe); } wantedPieces.remove(p); + wantedBytes -= metainfo.getPieceLength(p.getId()); } // just in case removePartialPiece(piece); + boolean done = wantedBytes <= 0; + // Announce to the world we have it! // Disconnect from other seeders when we get the last piece List<Peer> toDisconnect = new ArrayList(); @@ -924,7 +971,7 @@ public class PeerCoordinator implements PeerListener Peer p = it.next(); if (p.isConnected()) { - if (completed() && p.isCompleted()) + if (done && p.isCompleted()) toDisconnect.add(p); else p.have(piece); @@ -937,7 +984,11 @@ public class PeerCoordinator implements PeerListener p.disconnect(true); } - if (completed()) { + if (done) { + // put msg on the console if partial, since Storage won't do it + if (!completed()) + snark.storageCompleted(storage); + synchronized (partialPieces) { for (PartialPiece ppp : partialPieces) { ppp.release(); @@ -1262,11 +1313,12 @@ public class PeerCoordinator implements PeerListener } /** + * Get peers from PEX - * PeerListener callback * @since 0.8.4 */ public void gotPeers(Peer peer, List<PeerID> peers) { - if (completed() || !needPeers()) + if (!needOutboundPeers()) return; Destination myDest = _util.getMyDestination(); if (myDest == null) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 75b6672694..0d8d451a8f 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -782,6 +782,7 @@ public class Snark } /** + * Bytes not yet in storage. Does NOT account for skipped files. * @return exact value. or -1 if no storage yet. * getNeeded() * pieceLength(0) isn't accurate if last piece * is still needed. @@ -802,6 +803,20 @@ public class Snark } /** + * Bytes still wanted. DOES account for skipped files. + * FIXME -1 when not running. + * @return exact value. or -1 if no storage yet or when not running. + * @since 0.9.1 + */ + public long getNeededLength() { + PeerCoordinator coord = coordinator; + if (coord != null) + return coord.getNeededLength(); + return -1; + } + + /** + * Does not account for skipped files. * @return number of pieces still needed (magnet mode or not), or -1 if unknown * @since 0.8.4 */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 467094d7fc..3c8be63846 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -338,7 +338,8 @@ public class Storage } /** - * Must call setPiecePriorities() after calling this + * Must call Snark.updatePiecePriorities() + * (which calls getPiecePriorities()) after calling this. * @param file canonical path (non-directory) * @param pri default 0; <0 to disable * @since 0.8.1 diff --git a/apps/i2psnark/java/src/org/klomp/snark/StorageListener.java b/apps/i2psnark/java/src/org/klomp/snark/StorageListener.java index 573e18d7c5..279d958e4d 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/StorageListener.java +++ b/apps/i2psnark/java/src/org/klomp/snark/StorageListener.java @@ -23,7 +23,7 @@ package org.klomp.snark; /** * Callback used when Storage changes. */ -public interface StorageListener +interface StorageListener { /** * Called when the storage creates a new file of a given length. diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index 2ac9cac9aa..d0e4ca8d9b 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -72,7 +72,7 @@ public class TrackerClient extends I2PAppThread private boolean stop; private boolean started; - private List trackers; + private List<Tracker> trackers; /** * @param meta null if in magnet mode @@ -260,7 +260,7 @@ public class TrackerClient extends I2PAppThread for (Iterator iter = trackers.iterator(); iter.hasNext(); ) { Tracker tr = (Tracker)iter.next(); if ((!stop) && (!tr.stop) && - (completed || coordinator.needPeers()) && + (completed || coordinator.needOutboundPeers()) && (event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval)) { try @@ -292,7 +292,7 @@ public class TrackerClient extends I2PAppThread } } - if ( (left != 0) && (!completed) ) { + if (coordinator.needOutboundPeers()) { // we only want to talk to new people if we need things // from them (duh) List<Peer> ordered = new ArrayList(peers); @@ -341,7 +341,7 @@ public class TrackerClient extends I2PAppThread } // *** end of trackers loop here // Get peers from PEX - if (left > 0 && coordinator.needPeers() && (meta == null || !meta.isPrivate()) && !stop) { + if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) { Set<PeerID> pids = coordinator.getPEXPeers(); if (!pids.isEmpty()) { _util.debug("Got " + pids.size() + " from PEX", Snark.INFO); @@ -365,7 +365,7 @@ public class TrackerClient extends I2PAppThread // FIXME this needs to be in its own thread if (_util.getDHT() != null && (meta == null || !meta.isPrivate()) && !stop) { int numwant; - if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) + if (event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers()) numwant = 1; else numwant = _util.getMaxConnections(); @@ -459,7 +459,7 @@ public class TrackerClient extends I2PAppThread if (! event.equals(NO_EVENT)) buf.append("&event=").append(event); buf.append("&numwant="); - if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers()) + if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers()) buf.append('0'); else buf.append(_util.getMaxConnections()); 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 01dd3a4f01..a8a99887af 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -890,14 +890,19 @@ public class I2PSnarkServlet extends DefaultServlet { } } long total = snark.getTotalLength(); + // includes skipped files, -1 for magnet mode long remaining = snark.getRemainingLength(); if (remaining > total) remaining = total; + // does not include skipped files, -1 for magnet mode or when not running. + long needed = snark.getNeededLength(); + if (needed > total) + needed = total; long downBps = snark.getDownloadRate(); long upBps = snark.getUploadRate(); long remainingSeconds; - if (downBps > 0) - remainingSeconds = remaining / downBps; + if (downBps > 0 && needed > 0) + remainingSeconds = needed / downBps; else remainingSeconds = -1; boolean isRunning = !snark.isStopped(); @@ -938,18 +943,31 @@ public class I2PSnarkServlet extends DefaultServlet { statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") + "<br>" + err; } - } else if (remaining == 0) { // < 0 means no meta size yet - if (isRunning && curPeers > 0 && !showPeers) - statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") + + } else if (remaining == 0 || needed == 0) { // < 0 means no meta size yet + // partial complete or seeding + if (isRunning) { + String img; + String txt; + if (remaining == 0) { + img = "seeding"; + txt = _("Seeding"); + } else { + // partial + img = "complete"; + txt = _("Complete"); + } + if (curPeers > 0 && !showPeers) + statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + img + ".png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + txt + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers) + "</a>"; - else if (isRunning) - statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") + + else + statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + img + ".png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + txt + ": " + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers); - else + } else { statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "complete.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Complete"); + } } else { if (isRunning && curPeers > 0 && downBps > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") + @@ -1062,7 +1080,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write(formatSize(uploaded)); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">"); - if(isRunning && remaining != 0) + if(isRunning && needed > 0) out.write(formatSize(downBps) + "ps"); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentRateUp\">"); @@ -1108,7 +1126,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. // Single quotes in translate strings with parameters must be doubled. - // Then the remaining single quite must be escaped + // Then the remaining single quote must be escaped out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename)); out.write("')) { return false; }\""); out.write(" src=\"" + _imgPath + "remove.png\" alt=\""); @@ -1127,7 +1145,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. // Single quotes in translate strings with parameters must be doubled. - // Then the remaining single quite must be escaped + // Then the remaining single quote must be escaped out.write(_("Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?", fullFilename)); out.write("')) { return false; }\""); out.write(" src=\"" + _imgPath + "delete.png\" alt=\""); @@ -1194,7 +1212,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">"); - if (remaining > 0) { + if (needed > 0) { if (peer.isInteresting() && !peer.isChoked()) { out.write("<span class=\"unchoked\">"); out.write(formatSize(peer.getDownloadRate()) + "ps</span>"); @@ -1886,6 +1904,9 @@ public class I2PSnarkServlet extends DefaultServlet { else buf.append("<br>").append(_("Complete")); // else unknown + long needed = snark.getNeededLength(); + if (needed > 0) + buf.append("<br>").append(_("Remaining")).append(": ").append(formatSize(needed)); buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength())); MetaInfo meta = snark.getMetaInfo(); if (meta != null) { -- GitLab