forked from I2P_Developers/i2p.i2p
* i2psnark:
- Store received chunks in temp files - Don't allocate from heap for unneeded chunks - Remove peer count restriction for torrents with large pieces - Use priorities and rarest calculations to sort partials - Preserve p parameter in clear messages link
This commit is contained in:
@ -184,6 +184,9 @@ public class I2PSnarkUtil {
|
||||
/** @since 0.8.9 */
|
||||
public void setFilesPublic(boolean yes) { _areFilesPublic = yes; }
|
||||
|
||||
/** @since 0.9.1 */
|
||||
public File getTempDir() { return _tmpDir; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
|
@ -422,6 +422,20 @@ public class MetaInfo
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.1
|
||||
*/
|
||||
boolean checkPiece(PartialPiece pp) throws IOException {
|
||||
MessageDigest sha1 = SHA1.getInstance();
|
||||
int piece = pp.getPiece();
|
||||
|
||||
byte[] hash = pp.getHash();
|
||||
for (int i = 0; i < 20; i++)
|
||||
if (hash[i] != piece_hashes[20 * piece + i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length of the torrent in bytes.
|
||||
|
@ -1,6 +1,22 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
/**
|
||||
* Store the received data either on the heap or in a temp file.
|
||||
* The third option, to write chunks directly to the destination file,
|
||||
* is unimplemented.
|
||||
*
|
||||
* This is the class passed from PeerCoordinator to PeerState so
|
||||
* PeerState may start requests.
|
||||
*
|
||||
@ -8,45 +24,81 @@ package org.klomp.snark;
|
||||
* a piece is not completely downloaded, for example
|
||||
* when the Peer disconnects or chokes.
|
||||
*
|
||||
* New objects for the same piece are created during the end game -
|
||||
* this object should not be shared among multiple peers.
|
||||
*
|
||||
* @since 0.8.2
|
||||
*/
|
||||
class PartialPiece implements Comparable {
|
||||
|
||||
private final int piece;
|
||||
// we store the piece so we can use it in compareTo()
|
||||
private final Piece piece;
|
||||
// null if using temp file
|
||||
private final byte[] bs;
|
||||
private final int off;
|
||||
private final long createdTime;
|
||||
private int off;
|
||||
//private final long createdTime;
|
||||
private File tempfile;
|
||||
private RandomAccessFile raf;
|
||||
private final int pclen;
|
||||
private final File tempDir;
|
||||
|
||||
// Any bigger than this, use temp file instead of heap
|
||||
private static final int MAX_IN_MEM = 128 * 1024;
|
||||
// May be reduced on OOM
|
||||
private static int _max_in_mem = MAX_IN_MEM;
|
||||
|
||||
/**
|
||||
* Used by PeerCoordinator.
|
||||
* Creates a new PartialPiece, with no chunks yet downloaded.
|
||||
* Allocates the data.
|
||||
* Allocates the data storage area, either on the heap or in the
|
||||
* temp directory, depending on size.
|
||||
*
|
||||
* @param piece Piece number requested.
|
||||
* @param len must be equal to the piece length
|
||||
*/
|
||||
public PartialPiece (int piece, int len) throws OutOfMemoryError {
|
||||
public PartialPiece (Piece piece, int len, File tempDir) {
|
||||
this.piece = piece;
|
||||
this.bs = new byte[len];
|
||||
this.off = 0;
|
||||
this.createdTime = 0;
|
||||
this.pclen = len;
|
||||
//this.createdTime = 0;
|
||||
this.tempDir = tempDir;
|
||||
|
||||
// temps for finals
|
||||
byte[] tbs = null;
|
||||
try {
|
||||
if (len <= MAX_IN_MEM) {
|
||||
try {
|
||||
tbs = new byte[len];
|
||||
return;
|
||||
} catch (OutOfMemoryError oom) {
|
||||
if (_max_in_mem > PeerState.PARTSIZE)
|
||||
_max_in_mem /= 2;
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class);
|
||||
log.logAlways(Log.WARN, "OOM creating new partial piece");
|
||||
// fall through to use temp file
|
||||
}
|
||||
}
|
||||
// delay creating temp file until required in read()
|
||||
} finally {
|
||||
// finals
|
||||
this.bs = tbs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by PeerState.
|
||||
* Creates a new PartialPiece, with chunks up to but not including
|
||||
* firstOutstandingRequest already downloaded and stored in the Request byte array.
|
||||
* Caller must synchronize
|
||||
*
|
||||
* Note that this cannot handle gaps; chunks after a missing chunk cannot be saved.
|
||||
* That would be harder.
|
||||
*
|
||||
* @param firstOutstandingRequest the first request not fulfilled for the piece
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public PartialPiece (Request firstOutstandingRequest) {
|
||||
this.piece = firstOutstandingRequest.piece;
|
||||
this.bs = firstOutstandingRequest.bs;
|
||||
this.off = firstOutstandingRequest.off;
|
||||
this.createdTime = System.currentTimeMillis();
|
||||
private void createTemp() throws IOException {
|
||||
//tfile = SecureFile.createTempFile("piece", null, tempDir);
|
||||
// debug
|
||||
tempfile = SecureFile.createTempFile("piece_" + piece.getId() + '_', null, tempDir);
|
||||
//I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Created " + tempfile);
|
||||
// tfile.deleteOnExit() ???
|
||||
raf = new RandomAccessFile(tempfile, "rw");
|
||||
// Do not preallocate the file space.
|
||||
// Not necessary to call setLength(), file is extended when written
|
||||
//traf.setLength(len);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,33 +107,168 @@ class PartialPiece implements Comparable {
|
||||
*/
|
||||
|
||||
public Request getRequest() {
|
||||
return new Request(this.piece, this.bs, this.off, Math.min(this.bs.length - this.off, PeerState.PARTSIZE));
|
||||
return new Request(this, this.off, Math.min(this.pclen - this.off, PeerState.PARTSIZE));
|
||||
}
|
||||
|
||||
/** piece number */
|
||||
public int getPiece() {
|
||||
return this.piece;
|
||||
return this.piece.getId();
|
||||
}
|
||||
|
||||
/** how many bytes are good */
|
||||
/**
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.pclen;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes are good - only valid by setDownloaded()
|
||||
*/
|
||||
public int getDownloaded() {
|
||||
return this.off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this before returning a PartialPiece to the PeerCoordinator
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void setDownloaded(int offset) {
|
||||
this.off = offset;
|
||||
}
|
||||
|
||||
/****
|
||||
public long getCreated() {
|
||||
return this.createdTime;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Highest downloaded first
|
||||
* Piece must be complete.
|
||||
* The SHA1 hash of the completely read data.
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public byte[] getHash() throws IOException {
|
||||
MessageDigest sha1 = SHA1.getInstance();
|
||||
if (bs != null) {
|
||||
sha1.update(bs);
|
||||
} else {
|
||||
int read = 0;
|
||||
byte[] buf = new byte[Math.min(pclen, 16384)];
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
throw new IOException();
|
||||
raf.seek(0);
|
||||
while (read < pclen) {
|
||||
int rd = raf.read(buf, 0, Math.min(buf.length, pclen - read));
|
||||
if (rd < 0)
|
||||
break;
|
||||
read += rd;
|
||||
sha1.update(buf, 0, rd);
|
||||
}
|
||||
}
|
||||
if (read < pclen)
|
||||
throw new IOException();
|
||||
}
|
||||
return sha1.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void read(DataInputStream din, int off, int len) throws IOException {
|
||||
if (bs != null) {
|
||||
din.readFully(bs, off, len);
|
||||
} else {
|
||||
// read in fully before synching on raf
|
||||
byte[] tmp = new byte[len];
|
||||
din.readFully(tmp);
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
createTemp();
|
||||
raf.seek(off);
|
||||
raf.write(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Piece must be complete.
|
||||
* Caller must synchronize on out and seek to starting point.
|
||||
* Caller must call release() when done with the whole piece.
|
||||
*
|
||||
* @param start offset in the output file
|
||||
* @param offset offset in the piece
|
||||
* @param len length to write
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void write(DataOutput out, int offset, int len) throws IOException {
|
||||
if (bs != null) {
|
||||
out.write(bs, offset, len);
|
||||
} else {
|
||||
int read = 0;
|
||||
byte[] buf = new byte[Math.min(len, 16384)];
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
throw new IOException();
|
||||
raf.seek(offset);
|
||||
while (read < len) {
|
||||
int rd = Math.min(buf.length, len - read);
|
||||
raf.readFully(buf, 0, rd);
|
||||
read += rd;
|
||||
out.write(buf, 0, rd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all resources.
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void release() {
|
||||
if (bs == null) {
|
||||
synchronized (this) {
|
||||
if (raf != null)
|
||||
locked_release();
|
||||
}
|
||||
//if (raf != null)
|
||||
// I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Released " + tempfile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must synchronize
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
private void locked_release() {
|
||||
try {
|
||||
raf.close();
|
||||
} catch (IOException ioe) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Error closing " + raf, ioe);
|
||||
}
|
||||
tempfile.delete();
|
||||
}
|
||||
|
||||
/*
|
||||
* Highest priority first,
|
||||
* then rarest first,
|
||||
* then highest downloaded first
|
||||
*/
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
return ((PartialPiece)o).off - this.off; // reverse
|
||||
PartialPiece opp = (PartialPiece)o;
|
||||
int d = this.piece.compareTo(opp.piece);
|
||||
if (d != 0)
|
||||
return d;
|
||||
return opp.off - this.off; // reverse
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return piece * 7777;
|
||||
return piece.getId() * 7777;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,13 +279,13 @@ class PartialPiece implements Comparable {
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof PartialPiece) {
|
||||
PartialPiece pp = (PartialPiece)o;
|
||||
return pp.piece == this.piece;
|
||||
return pp.piece.getId() == this.piece.getId();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Partial(" + piece + ',' + off + ',' + bs.length + ')';
|
||||
return "Partial(" + piece.getId() + ',' + off + ',' + pclen + ')';
|
||||
}
|
||||
}
|
||||
|
@ -460,7 +460,7 @@ public class Peer implements Comparable
|
||||
if (this.deregister) {
|
||||
PeerListener p = s.listener;
|
||||
if (p != null) {
|
||||
List<PartialPiece> pcs = s.returnPartialPieces();
|
||||
List<Request> pcs = s.returnPartialPieces();
|
||||
if (!pcs.isEmpty())
|
||||
p.savePartialPieces(this, pcs);
|
||||
// now covered by savePartialPieces
|
||||
|
@ -148,11 +148,9 @@ class PeerConnectionIn implements Runnable
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
Request req = ps.getOutstandingRequest(piece, begin, len);
|
||||
byte[] piece_bytes;
|
||||
if (req != null)
|
||||
{
|
||||
piece_bytes = req.bs;
|
||||
din.readFully(piece_bytes, begin, len);
|
||||
req.read(din);
|
||||
ps.pieceMessage(req);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received data(" + piece + "," + begin + ") from " + peer);
|
||||
@ -160,8 +158,9 @@ class PeerConnectionIn implements Runnable
|
||||
else
|
||||
{
|
||||
// XXX - Consume but throw away afterwards.
|
||||
piece_bytes = new byte[len];
|
||||
din.readFully(piece_bytes);
|
||||
int rcvd = din.skipBytes(len);
|
||||
if (rcvd != len)
|
||||
throw new IOException("EOF reading unwanted data");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
|
||||
}
|
||||
|
@ -395,7 +395,7 @@ class PeerConnectionOut implements Runnable
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST && m.piece == req.piece &&
|
||||
if (m.type == Message.REQUEST && m.piece == req.getPiece() &&
|
||||
m.begin == req.off && m.length == req.len)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -406,7 +406,7 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.piece = req.getPiece();
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
@ -492,7 +492,7 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST
|
||||
&& m.piece == req.piece
|
||||
&& m.piece == req.getPiece()
|
||||
&& m.begin == req.off
|
||||
&& m.length == req.len)
|
||||
it.remove();
|
||||
@ -502,7 +502,7 @@ class PeerConnectionOut implements Runnable
|
||||
// Always send, just to be sure it it is really canceled.
|
||||
Message m = new Message();
|
||||
m.type = Message.CANCEL;
|
||||
m.piece = req.piece;
|
||||
m.piece = req.getPiece();
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
|
@ -116,7 +116,7 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
private final List<Piece> wantedPieces;
|
||||
|
||||
/** partial pieces - lock by synching on wantedPieces */
|
||||
/** partial pieces - lock by synching on wantedPieces - TODO store Requests, not PartialPieces */
|
||||
private final List<PartialPiece> partialPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
@ -349,11 +349,12 @@ public class PeerCoordinator implements PeerListener
|
||||
return 6;
|
||||
int size = metainfo.getPieceLength(0);
|
||||
int max = _util.getMaxConnections();
|
||||
if (size <= 512*1024 || completed())
|
||||
// Now that we use temp files, no memory concern
|
||||
//if (size <= 512*1024 || completed())
|
||||
return max;
|
||||
if (size <= 1024*1024)
|
||||
return (max + max + 2) / 3;
|
||||
return (max + 2) / 3;
|
||||
//if (size <= 1024*1024)
|
||||
// return (max + max + 2) / 3;
|
||||
//return (max + 2) / 3;
|
||||
}
|
||||
|
||||
public boolean halted() { return halted; }
|
||||
@ -380,6 +381,9 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
// delete any saved orphan partial piece
|
||||
synchronized (partialPieces) {
|
||||
for (PartialPiece pp : partialPieces) {
|
||||
pp.release();
|
||||
}
|
||||
partialPieces.clear();
|
||||
}
|
||||
}
|
||||
@ -630,22 +634,23 @@ public class PeerCoordinator implements PeerListener
|
||||
* -1 if none of the given pieces are wanted.
|
||||
*/
|
||||
public int wantPiece(Peer peer, BitField havePieces) {
|
||||
return wantPiece(peer, havePieces, true);
|
||||
Piece pc = wantPiece(peer, havePieces, true);
|
||||
return pc != null ? pc.getId() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
* null if none of the given pieces are wanted.
|
||||
*
|
||||
* @param record if true, actually record in our data structures that we gave the
|
||||
* request to this peer. If false, do not update the data structures.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private int wantPiece(Peer peer, BitField havePieces, boolean record) {
|
||||
private Piece wantPiece(Peer peer, BitField havePieces, boolean record) {
|
||||
if (halted) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
Piece piece = null;
|
||||
@ -680,7 +685,7 @@ public class PeerCoordinator implements PeerListener
|
||||
// If we do end game all the time, we generate lots of extra traffic
|
||||
// when the seeder is super-slow and all the peers are "caught up"
|
||||
if (wantedSize > END_GAME_THRESHOLD)
|
||||
return -1; // nothing to request and not in end game
|
||||
return null; // nothing to request and not in end game
|
||||
// let's not all get on the same piece
|
||||
// Even better would be to sort by number of requests
|
||||
if (record)
|
||||
@ -704,7 +709,7 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
|
||||
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
return null; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here -
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
@ -719,7 +724,7 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.info(peer + " is now requesting: piece " + piece + " priority " + piece.getPriority());
|
||||
piece.setRequested(peer, true);
|
||||
}
|
||||
return piece.getId();
|
||||
return piece;
|
||||
} // synch
|
||||
}
|
||||
|
||||
@ -846,10 +851,11 @@ public class PeerCoordinator implements PeerListener
|
||||
*
|
||||
* @throws RuntimeException on IOE saving the piece
|
||||
*/
|
||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||
public boolean gotPiece(Peer peer, PartialPiece pp)
|
||||
{
|
||||
if (metainfo == null || storage == null)
|
||||
return true;
|
||||
int piece = pp.getPiece();
|
||||
if (halted) {
|
||||
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
return true; // We don't actually care anymore.
|
||||
@ -872,7 +878,7 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
try
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
if (storage.putPiece(pp))
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
@ -922,6 +928,15 @@ public class PeerCoordinator implements PeerListener
|
||||
p.disconnect(true);
|
||||
}
|
||||
|
||||
if (completed()) {
|
||||
synchronized (partialPieces) {
|
||||
for (PartialPiece ppp : partialPieces) {
|
||||
ppp.release();
|
||||
}
|
||||
partialPieces.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -998,17 +1013,24 @@ public class PeerCoordinator implements PeerListener
|
||||
* Also mark the piece unrequested if this peer was the only one.
|
||||
*
|
||||
* @param peer partials, must include the zero-offset (empty) ones too
|
||||
* No dup pieces, piece.setDownloaded() must be set
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public void savePartialPieces(Peer peer, List<PartialPiece> partials)
|
||||
public void savePartialPieces(Peer peer, List<Request> partials)
|
||||
{
|
||||
if (halted)
|
||||
return;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Partials received from " + peer + ": " + partials);
|
||||
if (halted || completed()) {
|
||||
for (Request req : partials) {
|
||||
PartialPiece pp = req.getPartialPiece();
|
||||
pp.release();
|
||||
}
|
||||
return;
|
||||
}
|
||||
synchronized(wantedPieces) {
|
||||
for (PartialPiece pp : partials) {
|
||||
if (pp.getDownloaded() > 0) {
|
||||
for (Request req : partials) {
|
||||
PartialPiece pp = req.getPartialPiece();
|
||||
if (req.off > 0) {
|
||||
// PartialPiece.equals() only compares piece number, which is what we want
|
||||
int idx = partialPieces.indexOf(pp);
|
||||
if (idx < 0) {
|
||||
@ -1017,10 +1039,12 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.info("Saving orphaned partial piece (new) " + pp);
|
||||
} else if (idx >= 0 && pp.getDownloaded() > partialPieces.get(idx).getDownloaded()) {
|
||||
// replace what's there now
|
||||
partialPieces.get(idx).release();
|
||||
partialPieces.set(idx, pp);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Saving orphaned partial piece (bigger) " + pp);
|
||||
} else {
|
||||
pp.release();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Discarding partial piece (not bigger)" + pp);
|
||||
}
|
||||
@ -1029,10 +1053,14 @@ public class PeerCoordinator implements PeerListener
|
||||
// sorts by remaining bytes, least first
|
||||
Collections.sort(partialPieces);
|
||||
PartialPiece gone = partialPieces.remove(max);
|
||||
gone.release();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Discarding orphaned partial piece (list full)" + gone);
|
||||
}
|
||||
} // else drop the empty partial piece
|
||||
} else {
|
||||
// drop the empty partial piece
|
||||
pp.release();
|
||||
}
|
||||
// synchs on wantedPieces...
|
||||
markUnrequested(peer, pp.getPiece());
|
||||
}
|
||||
@ -1079,14 +1107,9 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
// ...and this section turns this into the general move-requests-around code!
|
||||
// Temporary? So PeerState never calls wantPiece() directly for now...
|
||||
int piece = wantPiece(peer, havePieces);
|
||||
if (piece >= 0) {
|
||||
try {
|
||||
return new PartialPiece(piece, metainfo.getPieceLength(piece));
|
||||
} catch (OutOfMemoryError oom) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("OOM creating new partial piece");
|
||||
}
|
||||
Piece piece = wantPiece(peer, havePieces, true);
|
||||
if (piece != null) {
|
||||
return new PartialPiece(piece, metainfo.getPieceLength(piece.getId()), _util.getTempDir());
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We have no partial piece to return");
|
||||
@ -1121,7 +1144,7 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
}
|
||||
return wantPiece(peer, havePieces, false) >= 0;
|
||||
return wantPiece(peer, havePieces, false) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,12 +95,12 @@ interface PeerListener
|
||||
* will be closed.
|
||||
*
|
||||
* @param peer the Peer that got the piece.
|
||||
* @param piece the piece number received.
|
||||
* @param piece the piece received.
|
||||
* @param bs the byte array containing the piece.
|
||||
*
|
||||
* @return true when the bytes represent the piece, false otherwise.
|
||||
*/
|
||||
boolean gotPiece(Peer peer, int piece, byte[] bs);
|
||||
boolean gotPiece(Peer peer, PartialPiece piece);
|
||||
|
||||
/**
|
||||
* Called when the peer wants (part of) a piece from us. Only called
|
||||
@ -167,7 +167,7 @@ interface PeerListener
|
||||
* @param peer the peer
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void savePartialPieces(Peer peer, List<PartialPiece> pcs);
|
||||
void savePartialPieces(Peer peer, List<Request> pcs);
|
||||
|
||||
/**
|
||||
* Called when a peer has connected and there may be a partially
|
||||
|
@ -107,7 +107,7 @@ class PeerState implements DataLoader
|
||||
// The only problem with returning the partials to the coordinator
|
||||
// is that chunks above a missing request are lost.
|
||||
// Future enhancements to PartialPiece could keep track of the holes.
|
||||
List<PartialPiece> pcs = returnPartialPieces();
|
||||
List<Request> pcs = returnPartialPieces();
|
||||
if (!pcs.isEmpty()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " got choked, returning partial pieces to the PeerCoordinator: " + pcs);
|
||||
@ -304,22 +304,22 @@ class PeerState implements DataLoader
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("got end of Chunk("
|
||||
+ req.piece + "," + req.off + "," + req.len + ") from "
|
||||
+ req.getPiece() + "," + req.off + "," + req.len + ") from "
|
||||
+ peer);
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
if (getFirstOutstandingRequest(req.getPiece()) == -1)
|
||||
{
|
||||
// warning - may block here for a while
|
||||
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||
if (listener.gotPiece(peer, req.getPartialPiece()))
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got " + req.piece + ": " + peer);
|
||||
_log.debug("Got " + req.getPiece() + ": " + peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.piece + " from " + peer);
|
||||
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +335,7 @@ class PeerState implements DataLoader
|
||||
synchronized private int getFirstOutstandingRequest(int piece)
|
||||
{
|
||||
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||
if (outstandingRequests.get(i).piece == piece)
|
||||
if (outstandingRequests.get(i).getPiece() == piece)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
@ -371,7 +371,7 @@ class PeerState implements DataLoader
|
||||
synchronized(this)
|
||||
{
|
||||
req = outstandingRequests.get(r);
|
||||
while (req.piece == piece && req.off != begin
|
||||
while (req.getPiece() == piece && req.off != begin
|
||||
&& r < outstandingRequests.size() - 1)
|
||||
{
|
||||
r++;
|
||||
@ -379,7 +379,7 @@ class PeerState implements DataLoader
|
||||
}
|
||||
|
||||
// Something wrong?
|
||||
if (req.piece != piece || req.off != begin || req.len != length)
|
||||
if (req.getPiece() != piece || req.off != begin || req.len != length)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unrequested or unneeded 'piece: "
|
||||
@ -427,13 +427,13 @@ class PeerState implements DataLoader
|
||||
Request rv = null;
|
||||
int lowest = Integer.MAX_VALUE;
|
||||
for (Request r : outstandingRequests) {
|
||||
if (r.piece == piece && r.off < lowest) {
|
||||
if (r.getPiece() == piece && r.off < lowest) {
|
||||
lowest = r.off;
|
||||
rv = r;
|
||||
}
|
||||
}
|
||||
if (pendingRequest != null &&
|
||||
pendingRequest.piece == piece && pendingRequest.off < lowest)
|
||||
pendingRequest.getPiece() == piece && pendingRequest.off < lowest)
|
||||
rv = pendingRequest;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -447,14 +447,16 @@ class PeerState implements DataLoader
|
||||
* @return List of PartialPieces, even those with an offset == 0, or empty list
|
||||
* @since 0.8.2
|
||||
*/
|
||||
synchronized List<PartialPiece> returnPartialPieces()
|
||||
synchronized List<Request> returnPartialPieces()
|
||||
{
|
||||
Set<Integer> pcs = getRequestedPieces();
|
||||
List<PartialPiece> rv = new ArrayList(pcs.size());
|
||||
List<Request> rv = new ArrayList(pcs.size());
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null)
|
||||
rv.add(new PartialPiece(req));
|
||||
if (req != null) {
|
||||
req.getPartialPiece().setDownloaded(req.off);
|
||||
rv.add(req);
|
||||
}
|
||||
}
|
||||
outstandingRequests.clear();
|
||||
pendingRequest = null;
|
||||
@ -468,9 +470,9 @@ class PeerState implements DataLoader
|
||||
synchronized private Set<Integer> getRequestedPieces() {
|
||||
Set<Integer> rv = new HashSet(outstandingRequests.size() + 1);
|
||||
for (Request req : outstandingRequests) {
|
||||
rv.add(Integer.valueOf(req.piece));
|
||||
rv.add(Integer.valueOf(req.getPiece()));
|
||||
if (pendingRequest != null)
|
||||
rv.add(Integer.valueOf(pendingRequest.piece));
|
||||
rv.add(Integer.valueOf(pendingRequest.getPiece()));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@ -571,14 +573,14 @@ class PeerState implements DataLoader
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized void cancelPiece(int piece) {
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
if (lastRequest != null && lastRequest.getPiece() == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator<Request> it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = it.next();
|
||||
if (req.piece == piece)
|
||||
if (req.getPiece() == piece)
|
||||
{
|
||||
it.remove();
|
||||
// Send cancel even when we are choked to make sure that it is
|
||||
@ -594,10 +596,10 @@ class PeerState implements DataLoader
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized boolean isRequesting(int piece) {
|
||||
if (pendingRequest != null && pendingRequest.piece == piece)
|
||||
if (pendingRequest != null && pendingRequest.getPiece() == piece)
|
||||
return true;
|
||||
for (Request req : outstandingRequests) {
|
||||
if (req.piece == piece)
|
||||
if (req.getPiece() == piece)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -679,7 +681,7 @@ class PeerState implements DataLoader
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.getPiece());
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
|
||||
// Last part of a piece?
|
||||
@ -687,14 +689,13 @@ class PeerState implements DataLoader
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
PartialPiece nextPiece = lastRequest.getPartialPiece();
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
int maxLength = pieceLength - nextBegin;
|
||||
int nextLength = maxLength > PARTSIZE ? PARTSIZE
|
||||
: maxLength;
|
||||
Request req
|
||||
= new Request(nextPiece, bs, nextBegin, nextLength);
|
||||
= new Request(nextPiece,nextBegin, nextLength);
|
||||
outstandingRequests.add(req);
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
@ -740,7 +741,7 @@ class PeerState implements DataLoader
|
||||
// what piece to give us next.
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece)) {
|
||||
&& (lastRequest == null || lastRequest.getPiece() != nextPiece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
// Fail safe to make sure we are interested
|
||||
|
@ -20,14 +20,16 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Holds all information needed for a partial piece request.
|
||||
* This class should be used only by PeerState, PeerConnectionIn, and PeerConnectionOut.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
final int piece;
|
||||
final byte[] bs;
|
||||
private final PartialPiece piece;
|
||||
final int off;
|
||||
final int len;
|
||||
long sendTime;
|
||||
@ -36,26 +38,49 @@ class Request
|
||||
* Creates a new Request.
|
||||
*
|
||||
* @param piece Piece number requested.
|
||||
* @param bs byte array where response should be stored.
|
||||
* @param off the offset in the array.
|
||||
* @param len the number of bytes requested.
|
||||
*/
|
||||
Request(int piece, byte[] bs, int off, int len)
|
||||
Request(PartialPiece piece, int off, int len)
|
||||
{
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
|
||||
this.piece = piece;
|
||||
this.bs = bs;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
/**
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void read(DataInputStream din) throws IOException {
|
||||
piece.read(din, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* The piece number this Request is for
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public int getPiece() {
|
||||
return piece.getPiece();
|
||||
}
|
||||
|
||||
/**
|
||||
* The PartialPiece this Request is for
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public PartialPiece getPartialPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return piece ^ off ^ len;
|
||||
return piece.getPiece() ^ off ^ len;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,7 +89,7 @@ class Request
|
||||
if (o instanceof Request)
|
||||
{
|
||||
Request req = (Request)o;
|
||||
return req.piece == piece && req.off == off && req.len == len;
|
||||
return req.piece.equals(piece) && req.off == off && req.len == len;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -73,6 +98,6 @@ class Request
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "(" + piece + "," + off + "," + len + ")";
|
||||
return "(" + piece.getPiece() + "," + off + "," + len + ")";
|
||||
}
|
||||
}
|
||||
|
@ -1214,7 +1214,7 @@ public class Snark
|
||||
total += c.getCurrentUploadRate();
|
||||
}
|
||||
long limit = 1024l * _util.getMaxUpBW();
|
||||
debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
|
||||
debug("Total up bw: " + total + " Limit: " + limit, Snark.NOTICE);
|
||||
return total > limit;
|
||||
}
|
||||
|
||||
|
@ -873,54 +873,55 @@ public class Storage
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] ba) throws IOException
|
||||
public boolean putPiece(PartialPiece pp) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// Copy the array first to be paranoid.
|
||||
byte[] bs = ba.clone();
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, piece, correctHash);
|
||||
if (!correctHash)
|
||||
return false;
|
||||
int piece = pp.getPiece();
|
||||
try {
|
||||
synchronized(bitfield) {
|
||||
if (bitfield.get(piece))
|
||||
return true; // No need to store twice.
|
||||
}
|
||||
|
||||
synchronized(bitfield)
|
||||
{
|
||||
if (bitfield.get(piece))
|
||||
return true; // No need to store twice.
|
||||
}
|
||||
// TODO alternative - check hash on the fly as we write to the file,
|
||||
// to save another I/O pass
|
||||
boolean correctHash = metainfo.checkPiece(pp);
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, piece, correctHash);
|
||||
if (!correctHash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) piece_size;
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) piece_size;
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen) {
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
|
||||
int written = 0;
|
||||
int off = 0;
|
||||
while (written < length)
|
||||
{
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(RAFlock[i])
|
||||
{
|
||||
checkRAF(i);
|
||||
rafs[i].seek(start);
|
||||
rafs[i].write(bs, off + written, len);
|
||||
}
|
||||
written += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
int written = 0;
|
||||
int off = 0;
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
while (written < length) {
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
rafs[i].seek(start);
|
||||
//rafs[i].write(bs, off + written, len);
|
||||
pp.write(rafs[i], off + written, len);
|
||||
}
|
||||
written += len;
|
||||
if (need - len > 0) {
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pp.release();
|
||||
}
|
||||
|
||||
changed = true;
|
||||
|
@ -150,6 +150,15 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
String path = req.getServletPath();
|
||||
resp.setHeader("X-Frame-Options", "SAMEORIGIN");
|
||||
|
||||
String peerParam = req.getParameter("p");
|
||||
String peerString;
|
||||
if (peerParam == null || (!_manager.util().connected()) ||
|
||||
peerParam.replaceAll("[a-zA-Z0-9~=-]", "").length() > 0) { // XSS
|
||||
peerString = "";
|
||||
} else {
|
||||
peerString = "?p=" + peerParam;
|
||||
}
|
||||
|
||||
// AJAX for mainsection
|
||||
if ("/.ajax/xhr1.html".equals(path)) {
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
@ -157,7 +166,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
PrintWriter out = resp.getWriter();
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _manager.addMessage((_context.clock().now() / 1000) + " xhr1 p=" + req.getParameter("p"));
|
||||
writeMessages(out, false);
|
||||
writeMessages(out, false, peerString);
|
||||
writeTorrents(out, req);
|
||||
return;
|
||||
}
|
||||
@ -201,15 +210,6 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
_manager.addMessage("Please retry form submission (bad nonce)");
|
||||
}
|
||||
|
||||
String peerParam = req.getParameter("p");
|
||||
String peerString;
|
||||
if (peerParam == null || (!_manager.util().connected()) ||
|
||||
peerParam.replaceAll("[a-zA-Z0-9~=-]", "").length() > 0) { // XSS
|
||||
peerString = "";
|
||||
} else {
|
||||
peerString = "?p=" + peerParam;
|
||||
}
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.write(DOCTYPE + "<html>\n" +
|
||||
"<head><link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">\n" +
|
||||
@ -274,7 +274,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
_manager.addMessage(_("Click \"Add torrent\" button to fetch torrent"));
|
||||
out.write("<div class=\"page\"><div id=\"mainsection\" class=\"mainsection\">");
|
||||
|
||||
writeMessages(out, isConfigure);
|
||||
writeMessages(out, isConfigure, peerString);
|
||||
|
||||
if (isConfigure) {
|
||||
// end of mainsection div
|
||||
@ -294,7 +294,7 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
out.write(FOOTER);
|
||||
}
|
||||
|
||||
private void writeMessages(PrintWriter out, boolean isConfigure) throws IOException {
|
||||
private void writeMessages(PrintWriter out, boolean isConfigure, String peerString) throws IOException {
|
||||
List<String> msgs = _manager.getMessages();
|
||||
if (!msgs.isEmpty()) {
|
||||
out.write("<div class=\"snarkMessages\"><ul>");
|
||||
@ -302,11 +302,14 @@ public class I2PSnarkServlet extends DefaultServlet {
|
||||
String msg = msgs.get(i);
|
||||
out.write("<li>" + msg + "</li>\n");
|
||||
}
|
||||
// lazy GET, lose p parameter
|
||||
out.write("</ul><p><a href=\"/i2psnark/");
|
||||
if (isConfigure)
|
||||
out.write("configure");
|
||||
out.write("?action=Clear&nonce=" + _nonce + "\">" + _("clear messages") + "</a></p></div>");
|
||||
if (peerString.length() > 0)
|
||||
out.write(peerString + "&");
|
||||
else
|
||||
out.write("?");
|
||||
out.write("action=Clear&nonce=" + _nonce + "\">" + _("clear messages") + "</a></p></div>");
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user