forked from I2P_Developers/i2p.i2p
merge of '3b6c4d6af6fae83cc9b7d42e8515804ae48ec675'
and '799a00a4929a59478c534a56cce350cdb9a042e0'
This commit is contained in:
14
apps/i2psnark/java/src/org/klomp/snark/DataLoader.java
Normal file
14
apps/i2psnark/java/src/org/klomp/snark/DataLoader.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used to fetch data
|
||||
* @since 0.8.2
|
||||
*/
|
||||
interface DataLoader
|
||||
{
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls to get the data from disk
|
||||
* @return bytes or null for errors
|
||||
*/
|
||||
public byte[] loadData(int piece, int begin, int length);
|
||||
}
|
@@ -39,23 +39,28 @@ class Message
|
||||
final static byte REQUEST = 6;
|
||||
final static byte PIECE = 7;
|
||||
final static byte CANCEL = 8;
|
||||
final static byte EXTENSION = 20;
|
||||
|
||||
// Not all fields are used for every message.
|
||||
// KEEP_ALIVE doesn't have a real wire representation
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
// low byte used for EXTENSION message
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
// Used for PIECE and BITFIELD messages
|
||||
// Used for PIECE and BITFIELD and EXTENSION messages
|
||||
byte[] data;
|
||||
int off;
|
||||
int len;
|
||||
|
||||
// Used to do deferred fetch of data
|
||||
DataLoader dataLoader;
|
||||
|
||||
SimpleTimer.TimedEvent expireEvent;
|
||||
|
||||
/** Utility method for sending a message through a DataStream. */
|
||||
@@ -68,6 +73,13 @@ class Message
|
||||
return;
|
||||
}
|
||||
|
||||
// Get deferred data
|
||||
if (data == null && dataLoader != null) {
|
||||
data = dataLoader.loadData(piece, begin, length);
|
||||
if (data == null)
|
||||
return; // hmm will get retried, but shouldn't happen
|
||||
}
|
||||
|
||||
// Calculate the total length in bytes
|
||||
|
||||
// Type is one byte.
|
||||
@@ -85,8 +97,12 @@ class Message
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// length is 1 byte
|
||||
if (type == EXTENSION)
|
||||
datalen += 1;
|
||||
|
||||
// add length of data for piece or bitfield array.
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
||||
datalen += len;
|
||||
|
||||
// Send length
|
||||
@@ -105,8 +121,11 @@ class Message
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
dos.writeInt(length);
|
||||
|
||||
if (type == EXTENSION)
|
||||
dos.writeByte((byte) piece & 0xff);
|
||||
|
||||
// Send actual data
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
||||
dos.write(data, off, len);
|
||||
}
|
||||
|
||||
@@ -135,6 +154,8 @@ class Message
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
}
|
||||
|
@@ -59,6 +59,11 @@ public class Peer implements Comparable
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// bytes per bt spec: 0011223344556677
|
||||
static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
static final long OPTION_FAST = 0x0000000000000004l;
|
||||
private long options;
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
* relevant MetaInfo.
|
||||
@@ -285,9 +290,8 @@ public class Peer implements Comparable
|
||||
// Handshake write - header
|
||||
dout.write(19);
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - zeros
|
||||
byte[] zeros = new byte[8];
|
||||
dout.write(zeros);
|
||||
// Handshake write - options
|
||||
dout.writeLong(OPTION_EXTENSION);
|
||||
// Handshake write - metainfo hash
|
||||
byte[] shared_hash = metainfo.getInfoHash();
|
||||
dout.write(shared_hash);
|
||||
@@ -312,8 +316,8 @@ public class Peer implements Comparable
|
||||
+ "'Bittorrent protocol', got '"
|
||||
+ bittorrentProtocol + "'");
|
||||
|
||||
// Handshake read - zeros
|
||||
din.readFully(zeros);
|
||||
// Handshake read - options
|
||||
options = din.readLong();
|
||||
|
||||
// Handshake read - metainfo hash
|
||||
bs = new byte[20];
|
||||
@@ -325,6 +329,15 @@ public class Peer implements Comparable
|
||||
din.readFully(bs);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the remote side's hash and peerID fully from " + toString());
|
||||
|
||||
// if ((options & OPTION_EXTENSION) != 0) {
|
||||
if (options != 0) {
|
||||
// send them something
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
//_log.debug("Peer supports extension message, what should we say? " + toString());
|
||||
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ", what should we say? " + toString());
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
|
@@ -171,6 +171,13 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 20: // Extension message
|
||||
int id = din.readUnsignedByte();
|
||||
byte[] payload = new byte[i-2];
|
||||
din.readFully(payload);
|
||||
ps.extensionMessage(id, payload);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received extension message from " + peer + " on " + peer.metainfo.getName());
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
|
@@ -430,6 +430,33 @@ class PeerConnectionOut implements Runnable
|
||||
return total;
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
void sendPiece(int piece, int begin, int length, DataLoader loader)
|
||||
{
|
||||
boolean sendNow = false;
|
||||
// are there any cases where we should?
|
||||
|
||||
if (sendNow) {
|
||||
// queue the real thing
|
||||
byte[] bytes = loader.loadData(piece, begin, length);
|
||||
if (bytes != null)
|
||||
sendPiece(piece, begin, length, bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// queue a fake message... set everything up,
|
||||
// except save the PeerState instead of the bytes.
|
||||
Message m = new Message();
|
||||
m.type = Message.PIECE;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.dataLoader = loader;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
{
|
||||
Message m = new Message();
|
||||
@@ -488,4 +515,16 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
void sendExtension(int id, byte[] bytes) {
|
||||
Message m = new Message();
|
||||
m.type = Message.EXTENSION;
|
||||
m.piece = id;
|
||||
m.data = bytes;
|
||||
m.begin = 0;
|
||||
m.length = bytes.length;
|
||||
addMessage(m);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -20,14 +20,20 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerState
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
|
||||
class PeerState implements DataLoader
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class);
|
||||
final Peer peer;
|
||||
@@ -201,13 +207,28 @@ class PeerState
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Queueing (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
|
||||
// don't load the data into mem now, let PeerConnectionOut do it
|
||||
out.sendPiece(piece, begin, length, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls
|
||||
*
|
||||
* @return bytes or null for errors
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public byte[] loadData(int piece, int begin, int length) {
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
@@ -219,13 +240,13 @@ class PeerState
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
out.sendPiece(piece, begin, length, pieceBytes);
|
||||
return pieceBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,6 +434,24 @@ class PeerState
|
||||
out.cancelRequest(piece, begin, length);
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
void extensionMessage(int id, byte[] bs)
|
||||
{
|
||||
if (id == 0) {
|
||||
InputStream is = new ByteArrayInputStream(bs);
|
||||
try {
|
||||
BDecoder dec = new BDecoder(is);
|
||||
BEValue bev = dec.bdecodeMap();
|
||||
Map map = bev.getMap();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got extension handshake message " + bev.toString());
|
||||
} catch (Exception e) {}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got extended message type: " + id + " length: " + bs.length);
|
||||
}
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@@ -242,6 +242,13 @@ public class I2PSnarkServlet extends Default {
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
boolean isForm = _manager.util().connected() || !snarks.isEmpty();
|
||||
if (isForm) {
|
||||
out.write("<form action=\"");
|
||||
out.write(uri);
|
||||
out.write("\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
|
||||
}
|
||||
out.write(TABLE_HEADER);
|
||||
out.write("<img border=\"0\" src=\"/themes/snark/ubergine/images/status.png\"");
|
||||
out.write(" title=\"");
|
||||
@@ -301,25 +308,17 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write("</th>\n");
|
||||
out.write("<th align=\"center\">");
|
||||
if (_manager.util().connected()) {
|
||||
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
|
||||
"\" title=\"");
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\"");
|
||||
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 and the I2P tunnel"));
|
||||
out.write("\" alt=\"");
|
||||
out.write("\" src=\"/themes/snark/ubergine/images/stop_all.png\" alt=\"");
|
||||
out.write(_("Stop All"));
|
||||
out.write("\">");
|
||||
out.write("</a>");
|
||||
} else if (!snarks.isEmpty()) {
|
||||
out.write("<a href=\"" + uri + "?action=StartAll&nonce=" + _nonce +
|
||||
"\" title=\"");
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"StartAll\" title=\"");
|
||||
out.write(_("Start all torrents and the I2P tunnel"));
|
||||
out.write("\" src=\"/themes/snark/ubergine/images/start_all.png\" alt=\"");
|
||||
out.write(_("Start All"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/start_all.png\" title=\"");
|
||||
out.write(_("Start all torrents and the I2P tunnel"));
|
||||
out.write("\" alt=\"Start All\">");
|
||||
out.write("</a>");
|
||||
} else {
|
||||
out.write(" ");
|
||||
}
|
||||
@@ -357,6 +356,8 @@ public class I2PSnarkServlet extends Default {
|
||||
}
|
||||
|
||||
out.write("</table>");
|
||||
if (isForm)
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -366,7 +367,11 @@ public class I2PSnarkServlet extends Default {
|
||||
String action = req.getParameter("action");
|
||||
if (action == null) {
|
||||
// noop
|
||||
} else if ("Add".equals(action)) {
|
||||
return;
|
||||
}
|
||||
if (!"POST".equals(req.getMethod()))
|
||||
return;
|
||||
if ("Add".equals(action)) {
|
||||
String newFile = req.getParameter("newFile");
|
||||
String newURL = req.getParameter("newURL");
|
||||
// NOTE - newFile currently disabled in HTML form - see below
|
||||
@@ -410,8 +415,8 @@ public class I2PSnarkServlet extends Default {
|
||||
} else {
|
||||
// no file or URL specified
|
||||
}
|
||||
} else if ("Stop".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
} else if (action.startsWith("Stop_")) {
|
||||
String torrent = action.substring(5);
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
@@ -425,8 +430,8 @@ public class I2PSnarkServlet extends Default {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Start".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
} else if (action.startsWith("Start_")) {
|
||||
String torrent = action.substring(6);
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
@@ -441,8 +446,8 @@ public class I2PSnarkServlet extends Default {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Remove".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
} else if (action.startsWith("Remove_")) {
|
||||
String torrent = action.substring(7);
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
@@ -461,8 +466,8 @@ public class I2PSnarkServlet extends Default {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Delete".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
} else if (action.startsWith("Delete_")) {
|
||||
String torrent = action.substring(7);
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
@@ -618,10 +623,10 @@ public class I2PSnarkServlet extends Default {
|
||||
if (r.startsWith(skip))
|
||||
r = r.substring(skip.length());
|
||||
String llc = l.toLowerCase();
|
||||
if (llc.startsWith("the ") || llc.startsWith("the."))
|
||||
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
|
||||
l = l.substring(4);
|
||||
String rlc = r.toLowerCase();
|
||||
if (rlc.startsWith("the ") || rlc.startsWith("the."))
|
||||
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
|
||||
r = r.substring(4);
|
||||
return collator.compare(l, r);
|
||||
}
|
||||
@@ -828,62 +833,55 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td align=\"center\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
|
||||
String b64 = Base64.encode(snark.meta.getInfoHash());
|
||||
if (showPeers)
|
||||
parameters = parameters + "&p=1";
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop" + parameters
|
||||
+ "\" title=\"");
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"Stop_");
|
||||
out.write(b64);
|
||||
out.write("\" title=\"");
|
||||
out.write(_("Stop the torrent"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/stop.png\" title=\"");
|
||||
out.write(_("Stop the torrent"));
|
||||
out.write("\" alt=\"");
|
||||
out.write("\" src=\"/themes/snark/ubergine/images/stop.png\" alt=\"");
|
||||
out.write(_("Stop"));
|
||||
out.write("\">");
|
||||
out.write("</a>");
|
||||
} else {
|
||||
if (isValid) {
|
||||
out.write("<a href=\"" + uri + "?action=Start" + parameters
|
||||
+ "\" title=\"");
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"Start_");
|
||||
out.write(b64);
|
||||
out.write("\" title=\"");
|
||||
out.write(_("Start the torrent"));
|
||||
out.write("\">");
|
||||
out.write("<img src=\"/themes/snark/ubergine/images/start.png\" title=\"");
|
||||
out.write(_("Start the torrent"));
|
||||
out.write("\" alt=\"");
|
||||
out.write("\" src=\"/themes/snark/ubergine/images/start.png\" alt=\"");
|
||||
out.write(_("Start"));
|
||||
out.write("\">");
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("<a href=\"" + uri + "?action=Remove" + parameters
|
||||
+ "\" title=\"");
|
||||
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"Remove_");
|
||||
out.write(b64);
|
||||
out.write("\" title=\"");
|
||||
out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
|
||||
out.write("\" onclick=\"if (!confirm('");
|
||||
// Can't figure out how to escape double quotes inside the onclick string.
|
||||
// Single quotes in translate strings with parameters must be doubled.
|
||||
// Then the remaining single quite must be escaped
|
||||
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 the torrent from the active list, deleting the .torrent file"));
|
||||
out.write("\" alt=\"");
|
||||
out.write("')) { return false; }\"");
|
||||
out.write(" src=\"/themes/snark/ubergine/images/remove.png\" alt=\"");
|
||||
out.write(_("Remove"));
|
||||
out.write("\">");
|
||||
out.write("</a>");
|
||||
out.write("<a href=\"" + uri + "?action=Delete" + parameters
|
||||
+ "\" title=\"");
|
||||
|
||||
out.write("<input type=\"image\" name=\"action\" value=\"Delete_");
|
||||
out.write(b64);
|
||||
out.write("\" title=\"");
|
||||
out.write(_("Delete the .torrent file and the associated data file(s)"));
|
||||
out.write("\" onclick=\"if (!confirm('");
|
||||
// Can't figure out how to escape double quotes inside the onclick string.
|
||||
// Single quotes in translate strings with parameters must be doubled.
|
||||
// Then the remaining single quite must be escaped
|
||||
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 the .torrent file and the associated data file(s)"));
|
||||
out.write("\" alt=\"");
|
||||
out.write("')) { return false; }\"");
|
||||
out.write(" src=\"/themes/snark/ubergine/images/delete.png\" alt=\"");
|
||||
out.write(_("Delete"));
|
||||
out.write("\">");
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("</td>\n</tr>\n");
|
||||
|
||||
|
Reference in New Issue
Block a user