forked from I2P_Developers/i2p.i2p
* 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
This commit is contained in:
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user