merge of '398a24f487b61ef778a2e849660e953ef7e43b39'
and '598d00efae4c9b675b64fd626bc2eab2b921e0c5'
This commit is contained in:
@ -152,6 +152,9 @@ public class I2PSnarkUtil {
|
||||
*/
|
||||
synchronized public boolean connect() {
|
||||
if (_manager == null) {
|
||||
// try to find why reconnecting after stop
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connecting to I2P", new Exception("I did it"));
|
||||
Properties opts = new Properties();
|
||||
if (_opts != null) {
|
||||
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
@ -163,6 +166,10 @@ public class I2PSnarkUtil {
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("outbound.nickname") == null)
|
||||
opts.setProperty("outbound.nickname", "I2PSnark");
|
||||
// Dont do this for now, it is set in I2PSocketEepGet for announces,
|
||||
// we don't need fast handshake for peer connections.
|
||||
//if (opts.getProperty("i2p.streaming.connectDelay") == null)
|
||||
// opts.setProperty("i2p.streaming.connectDelay", "500");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
@ -186,6 +193,7 @@ public class I2PSnarkUtil {
|
||||
*/
|
||||
public void disconnect() {
|
||||
I2PSocketManager mgr = _manager;
|
||||
// FIXME this can cause race NPEs elsewhere
|
||||
_manager = null;
|
||||
_shitlist.clear();
|
||||
mgr.destroySocketManager();
|
||||
@ -197,6 +205,9 @@ public class I2PSnarkUtil {
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
I2PSocketManager mgr = _manager;
|
||||
if (mgr == null)
|
||||
throw new IOException("No socket manager");
|
||||
Destination addr = peer.getAddress();
|
||||
if (addr == null)
|
||||
throw new IOException("Null address");
|
||||
|
@ -56,8 +56,8 @@ public class Peer implements Comparable
|
||||
private long _id;
|
||||
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
|
||||
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
@ -117,10 +117,15 @@ public class Peer implements Comparable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns socket (for debug printing)
|
||||
* @return socket debug string (for debug printing)
|
||||
*/
|
||||
public String getSocket()
|
||||
{
|
||||
if (state != null) {
|
||||
String r = state.getRequests();
|
||||
if (r != null)
|
||||
return sock.toString() + "<br>Requests: " + r;
|
||||
}
|
||||
return sock.toString();
|
||||
}
|
||||
|
||||
@ -387,6 +392,37 @@ public class Peer implements Comparable
|
||||
s.havePiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the other side that we are no longer interested in any of
|
||||
* the outstanding requests (if any) for this piece.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void cancel(int piece) {
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.cancelPiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently requesting the piece?
|
||||
* @since 0.8.1
|
||||
*/
|
||||
boolean isRequesting(int p) {
|
||||
PeerState s = state;
|
||||
return s != null && s.isRequesting(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the request queue.
|
||||
* Call after adding wanted pieces.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void request() {
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.addRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer is interested in pieces we have. Returns
|
||||
* false if not connected.
|
||||
@ -545,17 +581,8 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
PeerCoordinator.setRate(up, uploaded_old);
|
||||
PeerCoordinator.setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -563,28 +590,11 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
return PeerCoordinator.getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
return PeerCoordinator.getRate(downloaded_old);
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++){
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,9 +57,9 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List<Peer> peers = new ArrayList();
|
||||
@ -78,6 +78,7 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
public I2PSnarkUtil _util;
|
||||
private static final Random _random = I2PAppContext.getGlobalContext().random();
|
||||
|
||||
public String trackerProblems = null;
|
||||
public int trackerSeenPeers = 0;
|
||||
@ -97,20 +98,29 @@ public class PeerCoordinator implements PeerListener
|
||||
// Install a timer to check the uploaders.
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
// this will help the behavior with global limits
|
||||
Random r = I2PAppContext.getGlobalContext().random();
|
||||
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + _random.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
// Make a list of pieces
|
||||
// FIXME synchronize, clear and re-add instead?
|
||||
// Don't replace something we are synchronizing on.
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
int[] pri = storage.getPiecePriorities();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++) {
|
||||
// only add if we don't have and the priority is >= 0
|
||||
if ((!bitfield.get(i)) &&
|
||||
(pri == null || pri[i] >= 0)) {
|
||||
Piece p = new Piece(i);
|
||||
if (pri != null)
|
||||
p.setPriority(pri[i]);
|
||||
wantedPieces.add(p);
|
||||
}
|
||||
}
|
||||
Collections.shuffle(wantedPieces, _random);
|
||||
}
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
@ -183,7 +193,7 @@ public class PeerCoordinator implements PeerListener
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private static void setRate(long val, long array[])
|
||||
static void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
@ -214,20 +224,23 @@ public class PeerCoordinator implements PeerListener
|
||||
return (r * 1000) / CHECK_PERIOD;
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
static long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
int factor = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++) {
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
int f = RATE_DEPTH - i;
|
||||
rate += array[i] * f;
|
||||
factor += f;
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
return rate / (factor * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
@ -454,7 +467,7 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we don't have the given piece yet.
|
||||
* @return true if we still want the given piece
|
||||
*/
|
||||
public boolean gotHave(Peer peer, int piece)
|
||||
{
|
||||
@ -499,6 +512,12 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
private static final int END_GAME_THRESHOLD = 8;
|
||||
|
||||
/**
|
||||
* Max number of peers to get a piece from when in end game
|
||||
* @since 0.8.1
|
||||
*/
|
||||
private static final int MAX_PARALLEL_REQUESTS = 4;
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
@ -520,6 +539,9 @@ public class PeerCoordinator implements PeerListener
|
||||
while (piece == null && it.hasNext())
|
||||
{
|
||||
Piece p = it.next();
|
||||
// sorted by priority, so when we hit a disabled piece we are done
|
||||
if (p.isDisabled())
|
||||
break;
|
||||
if (havePieces.get(p.getId()) && !p.isRequested())
|
||||
{
|
||||
piece = p;
|
||||
@ -538,15 +560,32 @@ public class PeerCoordinator implements PeerListener
|
||||
if (wantedPieces.size() > END_GAME_THRESHOLD)
|
||||
return -1; // nothing to request and not in end game
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
// Even better would be to sort by number of requests
|
||||
Collections.shuffle(requested, _random);
|
||||
Iterator<Piece> it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
Piece p = it2.next();
|
||||
if (havePieces.get(p.getId()))
|
||||
{
|
||||
if (havePieces.get(p.getId())) {
|
||||
// limit number of parallel requests
|
||||
int requestedCount = 0;
|
||||
synchronized(peers) {
|
||||
for (Peer pr : peers) {
|
||||
if (pr.isRequesting(p.getId())) {
|
||||
if (pr.equals(peer)) {
|
||||
// don't give it to him again
|
||||
requestedCount = MAX_PARALLEL_REQUESTS;
|
||||
break;
|
||||
}
|
||||
if (++requestedCount >= MAX_PARALLEL_REQUESTS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requestedCount >= MAX_PARALLEL_REQUESTS)
|
||||
continue;
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -555,7 +594,7 @@ public class PeerCoordinator implements PeerListener
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here - limit # of parallel attempts and
|
||||
// Should be a lot smarter here -
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
// This is where the flaws of the snark data model are really exposed.
|
||||
// Could also randomize within the duplicate set rather than strict rarest-first
|
||||
@ -563,11 +602,83 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Now requesting: piece " + piece + " priority " + piece.getPriority());
|
||||
piece.setRequested(true);
|
||||
return piece.getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps file priorities to piece priorities.
|
||||
* Call after updating file priorities Storage.setPriority()
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void updatePiecePriorities() {
|
||||
int[] pri = storage.getPiecePriorities();
|
||||
if (pri == null) {
|
||||
_log.debug("Updated piece priorities called but no priorities to set?");
|
||||
return;
|
||||
}
|
||||
synchronized(wantedPieces) {
|
||||
// Add incomplete and previously unwanted pieces to the list
|
||||
// Temp to avoid O(n**2)
|
||||
BitField want = new BitField(pri.length);
|
||||
for (Piece p : wantedPieces) {
|
||||
want.set(p.getId());
|
||||
}
|
||||
BitField bitfield = storage.getBitField();
|
||||
for (int i = 0; i < pri.length; i++) {
|
||||
if (pri[i] >= 0 && !bitfield.get(i)) {
|
||||
if (!want.get(i)) {
|
||||
Piece piece = new Piece(i);
|
||||
wantedPieces.add(piece);
|
||||
// As connections are already up, new Pieces will
|
||||
// not have their PeerID list populated, so do that.
|
||||
synchronized(peers) {
|
||||
for (Peer p : peers) {
|
||||
PeerState s = p.state;
|
||||
if (s != null) {
|
||||
BitField bf = s.bitfield;
|
||||
if (bf != null && bf.get(i))
|
||||
piece.addPeer(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// now set the new priorities and remove newly unwanted pieces
|
||||
for (Iterator<Piece> iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece p = iter.next();
|
||||
int priority = pri[p.getId()];
|
||||
if (priority >= 0) {
|
||||
p.setPriority(priority);
|
||||
} else {
|
||||
iter.remove();
|
||||
// cancel all peers
|
||||
synchronized(peers) {
|
||||
for (Peer peer : peers) {
|
||||
peer.cancel(p.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Updated piece priorities, now wanted: " + wantedPieces);
|
||||
// if we added pieces, they will be in-order unless we shuffle
|
||||
Collections.shuffle(wantedPieces, _random);
|
||||
|
||||
// update request queues, in case we added wanted pieces
|
||||
// and we were previously uninterested
|
||||
synchronized(peers) {
|
||||
for (Peer peer : peers) {
|
||||
peer.request();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
@ -632,14 +743,18 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
// No need to announce have piece to peers.
|
||||
// Assume we got a good piece, we don't really care anymore.
|
||||
return true;
|
||||
// Well, this could be caused by a change in priorities, so
|
||||
// only return true if we already have it, otherwise might as well keep it.
|
||||
if (storage.getBitField().get(piece))
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
{
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -55,12 +55,10 @@ class PeerState
|
||||
final PeerConnectionOut out;
|
||||
|
||||
// Outstanding request
|
||||
private final List outstandingRequests = new ArrayList();
|
||||
private final List<Request> outstandingRequests = new ArrayList();
|
||||
/** the tail (NOT the head) of the request queue */
|
||||
private Request lastRequest = null;
|
||||
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 5; // this is for outbound requests
|
||||
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
|
||||
public final static int PARTSIZE = 16*1024; // outbound request
|
||||
@ -91,14 +89,13 @@ class PeerState
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
|
||||
|
||||
boolean resend = choked && !choke;
|
||||
choked = choke;
|
||||
if (choked)
|
||||
resend = true;
|
||||
|
||||
listener.gotChoke(peer, choke);
|
||||
|
||||
if (!choked && interesting)
|
||||
request();
|
||||
if (interesting && !choked)
|
||||
request(resend);
|
||||
}
|
||||
|
||||
void interestedMessage(boolean interest)
|
||||
@ -278,7 +275,7 @@ class PeerState
|
||||
synchronized private int getFirstOutstandingRequest(int piece)
|
||||
{
|
||||
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||
if (((Request)outstandingRequests.get(i)).piece == piece)
|
||||
if (outstandingRequests.get(i).piece == piece)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
@ -313,12 +310,12 @@ class PeerState
|
||||
Request req;
|
||||
synchronized(this)
|
||||
{
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
req = outstandingRequests.get(r);
|
||||
while (req.piece == piece && req.off != begin
|
||||
&& r < outstandingRequests.size() - 1)
|
||||
{
|
||||
r++;
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
req = outstandingRequests.get(r);
|
||||
}
|
||||
|
||||
// Something wrong?
|
||||
@ -342,7 +339,7 @@ class PeerState
|
||||
+ ", wanted for peer: " + peer);
|
||||
for (int i = 0; i < r; i++)
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
Request dropReq = outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
@ -366,11 +363,11 @@ class PeerState
|
||||
{
|
||||
Request req = null;
|
||||
for (int i = 0; i < outstandingRequests.size(); i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
Request r1 = outstandingRequests.get(i);
|
||||
int j = getFirstOutstandingRequest(r1.piece);
|
||||
if (j == -1)
|
||||
continue;
|
||||
Request r2 = (Request)outstandingRequests.get(j);
|
||||
Request r2 = outstandingRequests.get(j);
|
||||
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
|
||||
req = r2;
|
||||
}
|
||||
@ -398,7 +395,7 @@ class PeerState
|
||||
}
|
||||
Request req = null;
|
||||
for (int i = 0; i < size; i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
Request r1 = outstandingRequests.get(i);
|
||||
if (pc != r1.piece) {
|
||||
pc = r1.piece;
|
||||
arr[pos++] = pc;
|
||||
@ -423,32 +420,19 @@ class PeerState
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* We now have this piece.
|
||||
* Tell the peer and cancel any requests for the piece.
|
||||
*/
|
||||
void havePiece(int piece)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Tell the other side that we are no longer interested in any of
|
||||
// the outstanding requests for this piece.
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if (req.piece == piece)
|
||||
{
|
||||
it.remove();
|
||||
// Send cancel even when we are choked to make sure that it is
|
||||
// really never ever send.
|
||||
out.sendCancel(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancelPiece(piece);
|
||||
|
||||
// Tell the other side that we really have this piece.
|
||||
out.sendHave(piece);
|
||||
|
||||
@ -463,8 +447,46 @@ class PeerState
|
||||
}
|
||||
}
|
||||
|
||||
// Starts or resumes requesting pieces.
|
||||
private void request()
|
||||
/**
|
||||
* Tell the other side that we are no longer interested in any of
|
||||
* the outstanding requests (if any) for this piece.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized void cancelPiece(int piece) {
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator<Request> it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = it.next();
|
||||
if (req.piece == piece)
|
||||
{
|
||||
it.remove();
|
||||
// Send cancel even when we are choked to make sure that it is
|
||||
// really never ever send.
|
||||
out.sendCancel(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently requesting the piece?
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized boolean isRequesting(int piece) {
|
||||
for (Request req : outstandingRequests) {
|
||||
if (req.piece == piece)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or resumes requesting pieces.
|
||||
* @param resend should we resend outstanding requests?
|
||||
*/
|
||||
private void request(boolean resend)
|
||||
{
|
||||
// Are there outstanding requests that have to be resend?
|
||||
if (resend)
|
||||
@ -472,7 +494,6 @@ class PeerState
|
||||
synchronized (this) {
|
||||
out.sendRequests(outstandingRequests);
|
||||
}
|
||||
resend = false;
|
||||
}
|
||||
|
||||
// Add/Send some more requests if necessary.
|
||||
@ -481,8 +502,11 @@ class PeerState
|
||||
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
* Then send interested if we weren't.
|
||||
* Then send new requests if not choked.
|
||||
* If nothing to request, send not interested if we were.
|
||||
*/
|
||||
synchronized private void addRequest()
|
||||
synchronized void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
@ -526,6 +550,7 @@ class PeerState
|
||||
/**
|
||||
* Starts requesting first chunk of next piece. Returns true if
|
||||
* something has been added to the requests, false otherwise.
|
||||
* Caller should synchronize.
|
||||
*/
|
||||
private boolean requestNextPiece()
|
||||
{
|
||||
@ -553,11 +578,10 @@ class PeerState
|
||||
}
|
||||
}
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
// Fail safe to make sure we are interested
|
||||
// When we transition into the end game we may not be interested...
|
||||
if (!interesting) {
|
||||
@ -584,9 +608,25 @@ class PeerState
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " no more pieces to request");
|
||||
}
|
||||
}
|
||||
|
||||
// failsafe
|
||||
if (outstandingRequests.isEmpty())
|
||||
lastRequest = null;
|
||||
|
||||
// If we are not in the end game, we may run out of things to request
|
||||
// because we are asking other peers. Set not-interesting now rather than
|
||||
// wait for those other requests to be satisfied via havePiece()
|
||||
if (interesting && lastRequest == null) {
|
||||
interesting = false;
|
||||
out.sendInterest(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " nothing more to request, now uninteresting");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -601,7 +641,7 @@ class PeerState
|
||||
out.sendInterest(interest);
|
||||
|
||||
if (interesting && !choked)
|
||||
request();
|
||||
request(true); // we shouldnt have any pending requests, but if we do, resend them
|
||||
}
|
||||
}
|
||||
|
||||
@ -627,4 +667,16 @@ class PeerState
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* debug
|
||||
* @return string or null
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized String getRequests() {
|
||||
if (outstandingRequests.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return outstandingRequests.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,31 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
|
||||
private int id;
|
||||
private Set peers;
|
||||
private Set<PeerID> peers;
|
||||
private boolean requested;
|
||||
/** @since 0.8.1 */
|
||||
private int priority;
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = Collections.synchronizedSet(new HashSet());
|
||||
this.requested = false;
|
||||
this.peers = new ConcurrentHashSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest priority first,
|
||||
* then rarest first
|
||||
*/
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
int pdiff = ((Piece)o).priority - this.priority; // reverse
|
||||
if (pdiff != 0)
|
||||
return pdiff;
|
||||
return this.peers.size() - ((Piece)o).peers.size();
|
||||
}
|
||||
|
||||
@ -37,12 +46,25 @@ public class Piece implements Comparable {
|
||||
}
|
||||
|
||||
public int getId() { return this.id; }
|
||||
public Set getPeers() { return this.peers; }
|
||||
/** @deprecated unused */
|
||||
public Set<PeerID> getPeers() { return this.peers; }
|
||||
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
|
||||
/** @return default 0 @since 0.8.1 */
|
||||
public int getPriority() { return this.priority; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void setPriority(int p) { this.priority = p; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public boolean isDisabled() { return this.priority < 0; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void setDisabled() { this.priority = -1; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(id);
|
||||
|
@ -54,6 +54,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
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";
|
||||
|
||||
private static final String CONFIG_FILE = "i2psnark.config";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
@ -510,6 +511,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
torrent = new Snark(_util, filename, null, -1, null, null, this,
|
||||
_peerCoordinatorSet, _connectionAcceptor,
|
||||
false, dataDir.getPath());
|
||||
loadSavedFilePriorities(torrent);
|
||||
torrent.completeListener = this;
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
@ -587,6 +589,33 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
return new BitField(bitfield, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved priorities for a torrent from the config file.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void loadSavedFilePriorities(Snark snark) {
|
||||
MetaInfo metainfo = snark.meta;
|
||||
if (metainfo.getFiles() == null)
|
||||
return;
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
|
||||
if (pri == null)
|
||||
return;
|
||||
int filecount = metainfo.getFiles().size();
|
||||
int[] rv = new int[filecount];
|
||||
String[] arr = pri.split(",");
|
||||
for (int i = 0; i < filecount && i < arr.length; i++) {
|
||||
if (arr[i].length() > 0) {
|
||||
try {
|
||||
rv[i] = Integer.parseInt(arr[i]);
|
||||
} catch (Throwable t) {}
|
||||
}
|
||||
}
|
||||
snark.storage.setFilePriorities(rv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the completion status of a torrent and the current time in the config file
|
||||
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
|
||||
@ -595,8 +624,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* The time is a standard long converted to string.
|
||||
* The status is either a bitfield converted to Base64 or "." for a completed
|
||||
* torrent to save space in the config file and in memory.
|
||||
* @param priorities may be null
|
||||
*/
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
@ -609,6 +639,34 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
bfs = Base64.encode(bf);
|
||||
}
|
||||
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
|
||||
|
||||
// now the file priorities
|
||||
String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX;
|
||||
if (priorities != null) {
|
||||
boolean nonzero = false;
|
||||
for (int i = 0; i < priorities.length; i++) {
|
||||
if (priorities[i] != 0) {
|
||||
nonzero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nonzero) {
|
||||
// generate string like -5,,4,3,,,,,,-2 where no number is zero.
|
||||
StringBuilder buf = new StringBuilder(2 * priorities.length);
|
||||
for (int i = 0; i < priorities.length; i++) {
|
||||
if (priorities[i] != 0)
|
||||
buf.append(Integer.toString(priorities[i]));
|
||||
if (i != priorities.length - 1)
|
||||
buf.append(',');
|
||||
}
|
||||
_config.setProperty(prop, buf.toString());
|
||||
} else {
|
||||
_config.remove(prop);
|
||||
}
|
||||
} else {
|
||||
_config.remove(prop);
|
||||
}
|
||||
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
@ -621,6 +679,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
@ -742,7 +801,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
|
||||
public void updateStatus(Snark snark) {
|
||||
saveTorrentStatus(snark.meta, snark.storage.getBitField());
|
||||
saveTorrentStatus(snark.meta, snark.storage.getBitField(), snark.storage.getFilePriorities());
|
||||
}
|
||||
|
||||
private void monitorTorrents(File dir) {
|
||||
|
@ -42,6 +42,8 @@ public class Storage
|
||||
private Object[] RAFlock; // lock on RAF access
|
||||
private long[] RAFtime; // when was RAF last accessed, or 0 if closed
|
||||
private File[] RAFfile; // File to make it easier to reopen
|
||||
/** priorities by file; default 0; may be null. @since 0.8.1 */
|
||||
private int[] priorities;
|
||||
|
||||
private final StorageListener listener;
|
||||
private I2PSnarkUtil _util;
|
||||
@ -228,6 +230,8 @@ public class Storage
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
priorities = new int[size];
|
||||
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
@ -330,6 +334,102 @@ public class Storage
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int getPriority(String file) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file))
|
||||
return priorities[i];
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must call setPiecePriorities() after calling this
|
||||
* @param file canonical path (non-directory)
|
||||
* @param priority default 0; <0 to disable
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void setPriority(String file, int pri) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file)) {
|
||||
priorities[i] = pri;
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file priorities array.
|
||||
* @return null on error, if complete, or if only one file
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getFilePriorities() {
|
||||
return priorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file priorities array.
|
||||
* Only call this when stopped, but after check()
|
||||
* @param p may be null
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void setFilePriorities(int[] p) {
|
||||
priorities = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setPriority() for all changed files first,
|
||||
* then call this.
|
||||
* Set the piece priority to the highest priority
|
||||
* of all files spanning the piece.
|
||||
* Caller must pass array to the PeerCoordinator.
|
||||
* @return null on error, if complete, or if only one file
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getPiecePriorities() {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return null;
|
||||
int[] rv = new int[metainfo.getPieces()];
|
||||
int file = 0;
|
||||
long pcEnd = -1;
|
||||
long fileEnd = lengths[0] - 1;
|
||||
int psz = metainfo.getPieceLength(0);
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
pcEnd += psz;
|
||||
int pri = priorities[file];
|
||||
while (fileEnd <= pcEnd && file < lengths.length - 1) {
|
||||
file++;
|
||||
long oldFileEnd = fileEnd;
|
||||
fileEnd += lengths[file];
|
||||
if (priorities[file] > pri && oldFileEnd < pcEnd)
|
||||
pri = priorities[file];
|
||||
}
|
||||
rv[i] = pri;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
@ -436,10 +536,14 @@ public class Storage
|
||||
changed = true;
|
||||
checkCreateFiles();
|
||||
}
|
||||
if (complete())
|
||||
if (complete()) {
|
||||
_util.debug("Torrent is complete", Snark.NOTICE);
|
||||
else
|
||||
} else {
|
||||
// fixme saved priorities
|
||||
if (files != null)
|
||||
priorities = new int[files.size()];
|
||||
_util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -565,6 +669,10 @@ public class Storage
|
||||
changed = true;
|
||||
synchronized(RAFlock[i]) {
|
||||
allocateFile(i);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_util.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
|
||||
@ -573,8 +681,10 @@ public class Storage
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
// will be closed below
|
||||
}
|
||||
}
|
||||
|
||||
@ -583,10 +693,25 @@ public class Storage
|
||||
{
|
||||
pieces = metainfo.getPieces();
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
int file = 0;
|
||||
long fileEnd = lengths[0];
|
||||
long pieceEnd = 0;
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
pieceEnd += length;
|
||||
while (fileEnd <= pieceEnd) {
|
||||
synchronized(RAFlock[file]) {
|
||||
try {
|
||||
closeRAF(file);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (++file >= rafs.length)
|
||||
break;
|
||||
fileEnd += lengths[file];
|
||||
}
|
||||
if (correctHash)
|
||||
{
|
||||
bitfield.set(i);
|
||||
@ -601,13 +726,14 @@ public class Storage
|
||||
_probablyComplete = complete();
|
||||
// close all the files so we don't end up with a zillion open ones;
|
||||
// we will reopen as needed
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
synchronized(RAFlock[i]) {
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
// Now closed above to avoid running out of file descriptors
|
||||
//for (int i = 0; i < rafs.length; i++) {
|
||||
// synchronized(RAFlock[i]) {
|
||||
// try {
|
||||
// closeRAF(i);
|
||||
// } catch (IOException ioe) {}
|
||||
// }
|
||||
//}
|
||||
|
||||
if (listener != null) {
|
||||
listener.storageAllChecked(this);
|
||||
@ -616,6 +742,7 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
/** this calls openRAF(); caller must synnchronize and call closeRAF() */
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// caller synchronized
|
||||
@ -624,7 +751,12 @@ public class Storage
|
||||
// the whole file?
|
||||
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
byte[] zeros;
|
||||
try {
|
||||
zeros = new byte[ZEROBLOCKSIZE];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
throw new IOException(oom.toString());
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
|
||||
{
|
||||
|
@ -266,9 +266,9 @@ public class TrackerClient extends I2PAppThread
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List ordered = new ArrayList(peers);
|
||||
Collections.shuffle(ordered);
|
||||
Collections.shuffle(ordered, r);
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
while ((!stop) && it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
// FIXME if id == us || dest == us continue;
|
||||
// only delay if we actually make an attempt to add peer
|
||||
@ -357,7 +357,7 @@ public class TrackerClient extends I2PAppThread
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ "&compact"
|
||||
+ "&compact=1" // NOTE: opentracker will return 400 for &compact alone
|
||||
+ ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
|
||||
if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
s += "&numwant=0";
|
||||
|
@ -133,6 +133,7 @@ public class I2PSnarkServlet extends Default {
|
||||
// bypass the horrid Resource.getListHTML()
|
||||
String pathInfo = req.getPathInfo();
|
||||
String pathInContext = URI.addPaths(path, pathInfo);
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
Resource resource = getResource(pathInContext);
|
||||
@ -140,7 +141,7 @@ public class I2PSnarkServlet extends Default {
|
||||
resp.sendError(HttpResponse.__404_Not_Found);
|
||||
} else {
|
||||
String base = URI.addPaths(req.getRequestURI(), "/");
|
||||
String listing = getListHTML(resource, base, true);
|
||||
String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null);
|
||||
if (listing != null)
|
||||
resp.getWriter().write(listing);
|
||||
else // shouldn't happen
|
||||
@ -241,7 +242,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(TABLE_HEADER);
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/status.png\"");
|
||||
out.write(" title=\"");
|
||||
out.write(_("Torrent Status"));
|
||||
out.write(_("Status"));
|
||||
out.write("\">");
|
||||
out.write(_("Status"));
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
@ -249,24 +250,24 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(req.getRequestURI());
|
||||
if (peerParam != null) {
|
||||
out.write("\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/showpeers.png\" title=\"");
|
||||
out.write(_("Toggle Peer Visibility"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Hide Peers"));
|
||||
out.write("\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/showpeers.png\" title=\"");
|
||||
out.write(_("Hide Peers"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Hide Peers"));
|
||||
out.write("\">");
|
||||
} else {
|
||||
out.write("?p=1\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/hidepeers.png\" title=\"");
|
||||
out.write(_("Toggle Peer Visibility"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Show Peers"));
|
||||
out.write("\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/hidepeers.png\" title=\"");
|
||||
out.write(_("Show Peers"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Show Peers"));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("</a><br>\n");
|
||||
}
|
||||
out.write("</th>\n<th align=\"left\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/torrent.png\" title=\"");
|
||||
out.write(_("Loaded Torrents"));
|
||||
out.write(_("Torrent"));
|
||||
out.write("\">");
|
||||
out.write(_("Torrent"));
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
@ -276,21 +277,21 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("ETA"));
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/console/images/inbound.png\" title=\"");
|
||||
out.write(_("Data Downloaded"));
|
||||
out.write(_("Downloaded"));
|
||||
out.write("\">");
|
||||
out.write(_("RX"));
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/console/images/outbound.png\" title=\"");
|
||||
out.write(_("Data Uploaded"));
|
||||
out.write(_("Uploaded"));
|
||||
out.write("\">");
|
||||
out.write(_("TX"));
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/console/images/inbound.png\" title=\"");
|
||||
out.write(_("Download Speed"));
|
||||
out.write(_("Down Rate"));
|
||||
out.write("\">Rate");
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/console/images/outbound.png\" title=\"");
|
||||
out.write(_("Upload Speed"));
|
||||
out.write(_("Up Rate"));
|
||||
out.write("\">");
|
||||
out.write(_("Rate"));
|
||||
out.write("</th>\n");
|
||||
@ -301,7 +302,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Stop all torrents and the I2P tunnel"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/stop_all.png\" title=\"");
|
||||
out.write(_("Stop All Torrents"));
|
||||
out.write(_("Stop all torrents and the I2P tunnel"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Stop All"));
|
||||
out.write("\">");
|
||||
@ -312,7 +313,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Start all torrents and the I2P tunnel"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/start_all.png\" title=\"");
|
||||
out.write(_("Start All Torrents"));
|
||||
out.write(_("Start all torrents and the I2P tunnel"));
|
||||
out.write("\" alt=\"Start All\">");
|
||||
out.write("</a>");
|
||||
} else {
|
||||
@ -537,7 +538,7 @@ public class I2PSnarkServlet extends Default {
|
||||
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
|
||||
if (torrentFile.exists())
|
||||
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
|
||||
_manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck
|
||||
_manager.saveTorrentStatus(info, s.getBitField(), null); // so addTorrent won't recheck
|
||||
// DirMonitor could grab this first, maybe hold _snarks lock?
|
||||
FileOutputStream out = new FileOutputStream(torrentFile);
|
||||
out.write(info.getTorrentData());
|
||||
@ -564,6 +565,8 @@ public class I2PSnarkServlet extends Default {
|
||||
_manager.stopTorrent(snark.torrent, false);
|
||||
}
|
||||
if (_manager.util().connected()) {
|
||||
// Give the stopped announces time to get out
|
||||
try { Thread.sleep(2000); } catch (InterruptedException ie) {}
|
||||
_manager.util().disconnect();
|
||||
_manager.addMessage(_("I2P tunnel closed."));
|
||||
}
|
||||
@ -718,7 +721,7 @@ public class I2PSnarkServlet extends Default {
|
||||
curPeers + "/" +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
|
||||
else
|
||||
statusString = "<img border=\"0\" src=\"/themes/snark/ubergine/images/complete.png\" title=\"" + _("Complete") + "\">" + _("Not Seeding");
|
||||
statusString = "<img border=\"0\" src=\"/themes/snark/ubergine/images/complete.png\" title=\"" + _("Complete") + "\">" + _("Complete");
|
||||
} else {
|
||||
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
|
||||
statusString = "<img border=\"0\" src=\"/themes/snark/ubergine/images/downloading.png\" title=\"" + _("Downloading") + "\">" +
|
||||
@ -727,7 +730,7 @@ public class I2PSnarkServlet extends Default {
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
|
||||
else if (isRunning && curPeers > 0 && downBps > 0)
|
||||
statusString = "<img border=\"0\" src=\"/themes/snark/ubergine/images/downloading.png\" title=\"" + _("Downloading") + "\">" +
|
||||
" (" + curPeers + "/" +
|
||||
curPeers + "/" +
|
||||
ngettext("1 peer", "{0} peers", knownPeers);
|
||||
else if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "<img border=\"0\" src=\"/themes/snark/ubergine/images/stalled.png\" title=\"" + _("Stalled") + "\">" +
|
||||
@ -790,7 +793,7 @@ public class I2PSnarkServlet extends Default {
|
||||
baseURL = baseURL.substring(e + 1);
|
||||
out.write(" <a href=\"" + baseURL + "details.php?dllist=1&filelist=1&info_hash=");
|
||||
out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
|
||||
out.write("\" title=\"" + name + _("Tracker") + "\" target=\"_blank\">");
|
||||
out.write("\" title=\"" + name + ' ' + _("Tracker") + "\" target=\"_blank\">");
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/details.png\">");
|
||||
out.write("</a>");
|
||||
break;
|
||||
@ -804,7 +807,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0)
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
out.write(formatSize(total-remaining) + " / " + formatSize(total)); // 18MB/3GB; thin space so it will line break well
|
||||
else
|
||||
out.write(formatSize(total)); // 3GB
|
||||
out.write("</td>\n\t");
|
||||
@ -828,7 +831,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Stop the torrent"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/stop.png\" title=\"");
|
||||
out.write(_("Stop Torrent"));
|
||||
out.write(_("Stop"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Stop"));
|
||||
out.write("\">");
|
||||
@ -840,7 +843,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Start the torrent"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/start.png\" title=\"");
|
||||
out.write(_("Start Torrent"));
|
||||
out.write(_("Start the torrent"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Start"));
|
||||
out.write("\">");
|
||||
@ -856,7 +859,7 @@ public class I2PSnarkServlet extends Default {
|
||||
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("<img src=\"/themes/snark/ubergine/images/remove.png\" title=\"");
|
||||
out.write(_("Remove Torrent"));
|
||||
out.write(_("Remove"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Remove"));
|
||||
out.write("\">");
|
||||
@ -871,7 +874,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?", fullFilename));
|
||||
out.write("')) { return false; }\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/delete.png\" title=\"");
|
||||
out.write(_("Delete Torrent + Data"));
|
||||
out.write(_("Delete"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Delete"));
|
||||
out.write("\">");
|
||||
@ -1007,7 +1010,7 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(_("Add torrent"));
|
||||
out.write("\" name=\"foo\" ><br>\n");
|
||||
out.write("<tr><td> <td><span class=\"snarkAddInfo\">");
|
||||
out.write(_("You can also copy .torrent files to: {0}.", "<code>" + _manager.getDataDir().getAbsolutePath ())) + "</code>";
|
||||
out.write(_("You can also copy .torrent files to: {0}.", "<code>" + _manager.getDataDir().getAbsolutePath () + "</code>"));
|
||||
out.write("\n");
|
||||
out.write(_("Removing a .torrent will cause it to stop."));
|
||||
out.write("<br></span></table>\n");
|
||||
@ -1262,19 +1265,21 @@ public class I2PSnarkServlet extends Default {
|
||||
// rounding makes us look faster :)
|
||||
private static String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + " B";
|
||||
return bytes + " B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return ((bytes + 512)/1024) + " KB";
|
||||
return ((bytes + 512)/1024) + " KB";
|
||||
else if (bytes < 10*1024*1024*1024l)
|
||||
return ((bytes + 512*1024)/(1024*1024)) + " MB";
|
||||
return ((bytes + 512*1024)/(1024*1024)) + " MB";
|
||||
else
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB";
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB";
|
||||
}
|
||||
|
||||
/** @since 0.7.14 */
|
||||
private static String urlify(String s) {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append("<a href=\"").append(s).append("\">").append(s).append("</a>");
|
||||
// browsers seem to work without doing this but let's be strict
|
||||
String link = s.replace("&", "&");
|
||||
buf.append("<a href=\"").append(link).append("\">").append(link).append("</a>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@ -1313,10 +1318,11 @@ public class I2PSnarkServlet extends Default {
|
||||
* @param r The Resource
|
||||
* @param base The base URL
|
||||
* @param parent True if the parent directory should be included
|
||||
* @param postParams map of POST parameters or null if not a POST
|
||||
* @return String of HTML
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String getListHTML(Resource r, String base, boolean parent)
|
||||
private String getListHTML(Resource r, String base, boolean parent, Map postParams)
|
||||
throws IOException
|
||||
{
|
||||
if (!r.isDirectory())
|
||||
@ -1341,19 +1347,40 @@ public class I2PSnarkServlet extends Default {
|
||||
else
|
||||
torrentName = title;
|
||||
Snark snark = _manager.getTorrentByBaseName(torrentName);
|
||||
|
||||
if (snark != null && postParams != null)
|
||||
savePriorities(snark, postParams);
|
||||
|
||||
if (title.endsWith("/"))
|
||||
title = title.substring(0, title.length() - 1);
|
||||
title = _("Torrent") + ": " + title;
|
||||
buf.append(title);
|
||||
buf.append("</TITLE>").append(HEADER).append("<link rel=\"shortcut icon\" href=\"/themes/snark/ubergine/favicon.ico\"></HEAD><BODY>\n<center><div class=\"snarknavbar\"> <a href=\"/i2psnark/\" title=\"Torrents\"");
|
||||
buf.append(" class=\"snarkRefresh\">I2PSnark</a>").append("</div>");
|
||||
|
||||
buf.append("<div class=\"page\"><div class=\"mainsection\">" +
|
||||
"<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
|
||||
|
||||
if (parent)
|
||||
{
|
||||
buf.append("\n<br><A HREF=\"");
|
||||
// corrupts utf-8
|
||||
//buf.append(URI.encodePath(URI.addPaths(base,"../")));
|
||||
buf.append(URI.addPaths(base,"../"));
|
||||
buf.append("\"><img border=\"0\" src=\"/themes/console/images/outbound.png\"> ")
|
||||
.append(_("Up to higher level directory")).append("</A>\n");
|
||||
}
|
||||
|
||||
buf.append("</div><div class=\"page\"><div class=\"mainsection\">");
|
||||
boolean showPriority = snark != null && !snark.storage.complete();
|
||||
if (showPriority)
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
|
||||
buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
|
||||
"<thead><tr><th>").append("<img border=\"0\" src=\"/themes/snark/ubergine/images/file.png\" title=\"").append(_("File")).append("\" alt=\"").append(_("File")).append("\"> ").append(title).append("</th><th align=\"right\">").append("<img border=\"0\" src=\"/themes/snark/ubergine/images/size.png\" title=\"").append(_("FileSize")).append("\" alt=\"").append(_("FileSize")).append("\">").append(_("Size"));
|
||||
buf.append("</th><th>").append("<img border=\"0\" src=\"/themes/snark/ubergine/images/status.png\" title=\"").append(_("Download Status")).append("\">").append(_("Status")).append("</th></tr></thead>");
|
||||
buf.append("</th><th>").append("<img border=\"0\" src=\"/themes/snark/ubergine/images/status.png\" title=\"").append(_("Download Status")).append("\">").append(_("Status")).append("</th>");
|
||||
if (showPriority)
|
||||
buf.append("<th>").append(_("Priority")).append("</th>");
|
||||
buf.append("</tr></thead>\n");
|
||||
//DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
|
||||
// DateFormat.MEDIUM);
|
||||
boolean showSaveButton = false;
|
||||
for (int i=0 ; i< ls.length ; i++)
|
||||
{
|
||||
String encoded=URI.encodePath(ls[i]);
|
||||
@ -1391,7 +1418,8 @@ public class I2PSnarkServlet extends Default {
|
||||
complete = true;
|
||||
status = toImg("tick") + _("Complete");
|
||||
} else {
|
||||
status = toImg("clock") +
|
||||
status =
|
||||
(snark.storage.getPriority(f.getCanonicalPath()) < 0 ? toImg("cancel") : toImg("clock")) +
|
||||
(100 * (length - remaining) / length) + "% " + _("complete") +
|
||||
" (" + DataHelper.formatSize2(remaining) + _("bytes remaining") + ")";
|
||||
}
|
||||
@ -1435,22 +1463,41 @@ public class I2PSnarkServlet extends Default {
|
||||
buf.append("</TD><TD class=\"").append(rowClass).append(" snarkFileStatus\">");
|
||||
//buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append(status);
|
||||
buf.append("</TD></TR>\n");
|
||||
buf.append("</TD>");
|
||||
if (showPriority) {
|
||||
buf.append("<td>");
|
||||
File f = item.getFile();
|
||||
if ((!complete) && (!item.isDirectory()) && f != null) {
|
||||
int pri = snark.storage.getPriority(f.getCanonicalPath());
|
||||
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
|
||||
if (pri > 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append('>').append(_("High"));
|
||||
|
||||
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
|
||||
if (pri == 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append('>').append(_("Normal"));
|
||||
|
||||
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
|
||||
if (pri < 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append('>').append(_("Do not download"));
|
||||
showSaveButton = true;
|
||||
}
|
||||
buf.append("</td>");
|
||||
}
|
||||
buf.append("</TR>\n");
|
||||
}
|
||||
|
||||
if (parent)
|
||||
{
|
||||
buf.append("<tfoot align=\"left\"><tr><td colspan=\"3\"><A HREF=\"");
|
||||
// corrupts utf-8
|
||||
//buf.append(URI.encodePath(URI.addPaths(base,"../")));
|
||||
buf.append(URI.addPaths(base,"../"));
|
||||
buf.append("\"><img border=\"0\" src=\"/themes/snark/ubergine/images/up.png\"> ")
|
||||
.append(_("Up to higher level directory")).append("</A></td></tr></thead>\n");
|
||||
if (showSaveButton) {
|
||||
buf.append("<thead><tr><th colspan=\"3\"> </th><th align=\"center\"><input type=\"submit\" value=\"");
|
||||
buf.append(_("Save priorities"));
|
||||
buf.append("\" name=\"foo\" ></th></tr></thead>\n");
|
||||
}
|
||||
|
||||
|
||||
buf.append("</TABLE>\n");
|
||||
buf.append("</div></div></center></BODY></HTML>\n");
|
||||
if (showPriority)
|
||||
buf.append("</form>");
|
||||
buf.append("</div></div></BODY></HTML>\n");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
@ -1515,6 +1562,26 @@ buf.append("</div></div></center></BODY></HTML>\n");
|
||||
return "<img alt=\"\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\"> ";
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
private void savePriorities(Snark snark, Map postParams) {
|
||||
Set<Map.Entry> entries = postParams.entrySet();
|
||||
for (Map.Entry entry : entries) {
|
||||
String key = (String)entry.getKey();
|
||||
if (key.startsWith("pri.")) {
|
||||
try {
|
||||
String file = key.substring(4);
|
||||
String val = ((String[])entry.getValue())[0]; // jetty arrays
|
||||
int pri = Integer.parseInt(val);
|
||||
snark.storage.setPriority(file, pri);
|
||||
//System.err.println("Priority now " + pri + " for " + file);
|
||||
} catch (Throwable t) { t.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
if (snark.coordinator != null)
|
||||
snark.coordinator.updatePiecePriorities();
|
||||
_manager.saveTorrentStatus(snark.storage.getMetaInfo(), snark.storage.getBitField(), snark.storage.getFilePriorities());
|
||||
}
|
||||
|
||||
|
||||
/** inner class, don't bother reindenting */
|
||||
private static class FetchAndAdd implements Runnable {
|
||||
|
Reference in New Issue
Block a user