forked from I2P_Developers/i2p.i2p
more prep and stubs for no metainfo
This commit is contained in:
@@ -32,6 +32,8 @@ import net.i2p.util.SimpleScheduler;
|
|||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer;
|
||||||
import net.i2p.util.Translate;
|
import net.i2p.util.Translate;
|
||||||
|
|
||||||
|
import org.klomp.snark.dht.KRPC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I2P specific helpers for I2PSnark
|
* I2P specific helpers for I2PSnark
|
||||||
* We use this class as a sort of context for i2psnark
|
* We use this class as a sort of context for i2psnark
|
||||||
@@ -56,6 +58,7 @@ public class I2PSnarkUtil {
|
|||||||
private int _maxConnections;
|
private int _maxConnections;
|
||||||
private File _tmpDir;
|
private File _tmpDir;
|
||||||
private int _startupDelay;
|
private int _startupDelay;
|
||||||
|
private KRPC _krpc;
|
||||||
|
|
||||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||||
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
||||||
@@ -64,6 +67,8 @@ public class I2PSnarkUtil {
|
|||||||
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
||||||
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||||
|
private static final boolean ENABLE_DHT = true;
|
||||||
|
|
||||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||||
_context = ctx;
|
_context = ctx;
|
||||||
_log = _context.logManager().getLog(Snark.class);
|
_log = _context.logManager().getLog(Snark.class);
|
||||||
@@ -185,10 +190,20 @@ public class I2PSnarkUtil {
|
|||||||
// opts.setProperty("i2p.streaming.readTimeout", "120000");
|
// opts.setProperty("i2p.streaming.readTimeout", "120000");
|
||||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||||
}
|
}
|
||||||
|
// FIXME this only instantiates krpc once, left stuck with old manager
|
||||||
|
if (ENABLE_DHT && _manager != null && _krpc == null)
|
||||||
|
_krpc = new KRPC(_context, _manager.getSession());
|
||||||
return (_manager != null);
|
return (_manager != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null if disabled or not started
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
public KRPC getDHT() { return _krpc; }
|
||||||
|
|
||||||
public boolean connected() { return _manager != null; }
|
public boolean connected() { return _manager != null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy the destination itself
|
* Destroy the destination itself
|
||||||
*/
|
*/
|
||||||
|
@@ -39,6 +39,7 @@ public class Peer implements Comparable
|
|||||||
private final PeerID peerID;
|
private final PeerID peerID;
|
||||||
|
|
||||||
private final byte[] my_id;
|
private final byte[] my_id;
|
||||||
|
private final byte[] infohash;
|
||||||
final MetaInfo metainfo;
|
final MetaInfo metainfo;
|
||||||
|
|
||||||
// The data in/output streams set during the handshake and used by
|
// The data in/output streams set during the handshake and used by
|
||||||
@@ -70,11 +71,13 @@ public class Peer implements Comparable
|
|||||||
* Outgoing connection.
|
* Outgoing connection.
|
||||||
* Creates a disconnected peer given a PeerID, your own id and the
|
* Creates a disconnected peer given a PeerID, your own id and the
|
||||||
* relevant MetaInfo.
|
* relevant MetaInfo.
|
||||||
|
* @param metainfo null if in magnet mode
|
||||||
*/
|
*/
|
||||||
public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
|
public Peer(PeerID peerID, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||||
{
|
{
|
||||||
this.peerID = peerID;
|
this.peerID = peerID;
|
||||||
this.my_id = my_id;
|
this.my_id = my_id;
|
||||||
|
this.infohash = infohash;
|
||||||
this.metainfo = metainfo;
|
this.metainfo = metainfo;
|
||||||
_id = ++__id;
|
_id = ++__id;
|
||||||
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
|
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
|
||||||
@@ -88,12 +91,14 @@ public class Peer implements Comparable
|
|||||||
* get the remote peer id. To completely start the connection call
|
* get the remote peer id. To completely start the connection call
|
||||||
* the connect() method.
|
* the connect() method.
|
||||||
*
|
*
|
||||||
|
* @param metainfo null if in magnet mode
|
||||||
* @exception IOException when an error occurred during the handshake.
|
* @exception IOException when an error occurred during the handshake.
|
||||||
*/
|
*/
|
||||||
public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
|
public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
this.my_id = my_id;
|
this.my_id = my_id;
|
||||||
|
this.infohash = infohash;
|
||||||
this.metainfo = metainfo;
|
this.metainfo = metainfo;
|
||||||
this.sock = sock;
|
this.sock = sock;
|
||||||
|
|
||||||
@@ -312,8 +317,7 @@ public class Peer implements Comparable
|
|||||||
// FIXME not if DHT disabled
|
// FIXME not if DHT disabled
|
||||||
dout.writeLong(OPTION_EXTENSION | OPTION_DHT);
|
dout.writeLong(OPTION_EXTENSION | OPTION_DHT);
|
||||||
// Handshake write - metainfo hash
|
// Handshake write - metainfo hash
|
||||||
byte[] shared_hash = metainfo.getInfoHash();
|
dout.write(infohash);
|
||||||
dout.write(shared_hash);
|
|
||||||
// Handshake write - peer id
|
// Handshake write - peer id
|
||||||
dout.write(my_id);
|
dout.write(my_id);
|
||||||
dout.flush();
|
dout.flush();
|
||||||
@@ -341,7 +345,7 @@ public class Peer implements Comparable
|
|||||||
// Handshake read - metainfo hash
|
// Handshake read - metainfo hash
|
||||||
bs = new byte[20];
|
bs = new byte[20];
|
||||||
din.readFully(bs);
|
din.readFully(bs);
|
||||||
if (!Arrays.equals(shared_hash, bs))
|
if (!Arrays.equals(infohash, bs))
|
||||||
throw new IOException("Unexpected MetaInfo hash");
|
throw new IOException("Unexpected MetaInfo hash");
|
||||||
|
|
||||||
// Handshake read - peer id
|
// Handshake read - peer id
|
||||||
|
@@ -88,12 +88,11 @@ public class PeerAcceptor
|
|||||||
}
|
}
|
||||||
if (coordinator != null) {
|
if (coordinator != null) {
|
||||||
// single torrent capability
|
// single torrent capability
|
||||||
MetaInfo meta = coordinator.getMetaInfo();
|
if (DataHelper.eq(coordinator.getInfoHash(), peerInfoHash)) {
|
||||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
|
||||||
if (coordinator.needPeers())
|
if (coordinator.needPeers())
|
||||||
{
|
{
|
||||||
Peer peer = new Peer(socket, in, out, coordinator.getID(),
|
Peer peer = new Peer(socket, in, out, coordinator.getID(),
|
||||||
coordinator.getMetaInfo());
|
coordinator.getInfoHash(), coordinator.getMetaInfo());
|
||||||
coordinator.addPeer(peer);
|
coordinator.addPeer(peer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -101,19 +100,18 @@ public class PeerAcceptor
|
|||||||
} else {
|
} else {
|
||||||
// its for another infohash, but we are only single torrent capable. b0rk.
|
// its for another infohash, but we are only single torrent capable. b0rk.
|
||||||
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
||||||
+ ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
|
+ ") while we only support (" + Base64.encode(coordinator.getInfoHash()) + ")");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// multitorrent capable, so lets see what we can handle
|
// multitorrent capable, so lets see what we can handle
|
||||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||||
PeerCoordinator cur = (PeerCoordinator)iter.next();
|
PeerCoordinator cur = (PeerCoordinator)iter.next();
|
||||||
MetaInfo meta = cur.getMetaInfo();
|
|
||||||
|
|
||||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
if (DataHelper.eq(cur.getInfoHash(), peerInfoHash)) {
|
||||||
if (cur.needPeers())
|
if (cur.needPeers())
|
||||||
{
|
{
|
||||||
Peer peer = new Peer(socket, in, out, cur.getID(),
|
Peer peer = new Peer(socket, in, out, cur.getID(),
|
||||||
cur.getMetaInfo());
|
cur.getInfoHash(), cur.getMetaInfo());
|
||||||
cur.addPeer(peer);
|
cur.addPeer(peer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -93,6 +93,7 @@ public class PeerCoordinator implements PeerListener
|
|||||||
private final CheckEvent timer;
|
private final CheckEvent timer;
|
||||||
|
|
||||||
private final byte[] id;
|
private final byte[] id;
|
||||||
|
private final byte[] infohash;
|
||||||
|
|
||||||
/** The wanted pieces. We could use a TreeSet but we'd have to clear and re-add everything
|
/** The wanted pieces. We could use a TreeSet but we'd have to clear and re-add everything
|
||||||
* when priorities change.
|
* when priorities change.
|
||||||
@@ -108,11 +109,16 @@ public class PeerCoordinator implements PeerListener
|
|||||||
private final I2PSnarkUtil _util;
|
private final I2PSnarkUtil _util;
|
||||||
private static final Random _random = I2PAppContext.getGlobalContext().random();
|
private static final Random _random = I2PAppContext.getGlobalContext().random();
|
||||||
|
|
||||||
public PeerCoordinator(I2PSnarkUtil util, byte[] id, MetaInfo metainfo, Storage storage,
|
/**
|
||||||
|
* @param metainfo null if in magnet mode
|
||||||
|
* @param storage null if in magnet mode
|
||||||
|
*/
|
||||||
|
public PeerCoordinator(I2PSnarkUtil util, byte[] id, byte[] infohash, MetaInfo metainfo, Storage storage,
|
||||||
CoordinatorListener listener, Snark torrent)
|
CoordinatorListener listener, Snark torrent)
|
||||||
{
|
{
|
||||||
_util = util;
|
_util = util;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.infohash = infohash;
|
||||||
this.metainfo = metainfo;
|
this.metainfo = metainfo;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
@@ -149,6 +155,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
// only called externally from Storage after the double-check fails
|
// only called externally from Storage after the double-check fails
|
||||||
public void setWantedPieces()
|
public void setWantedPieces()
|
||||||
{
|
{
|
||||||
|
if (metainfo == null || storage == null)
|
||||||
|
return;
|
||||||
// Make a list of pieces
|
// Make a list of pieces
|
||||||
synchronized(wantedPieces) {
|
synchronized(wantedPieces) {
|
||||||
wantedPieces.clear();
|
wantedPieces.clear();
|
||||||
@@ -188,6 +196,9 @@ public class PeerCoordinator implements PeerListener
|
|||||||
|
|
||||||
public boolean completed()
|
public boolean completed()
|
||||||
{
|
{
|
||||||
|
// FIXME return metainfo complete status
|
||||||
|
if (storage == null)
|
||||||
|
return false;
|
||||||
return storage.complete();
|
return storage.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +215,12 @@ public class PeerCoordinator implements PeerListener
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns how many bytes are still needed to get the complete file.
|
* Returns how many bytes are still needed to get the complete file.
|
||||||
|
* @return -1 if in magnet mode
|
||||||
*/
|
*/
|
||||||
public long getLeft()
|
public long getLeft()
|
||||||
{
|
{
|
||||||
|
if (metainfo == null | storage == null)
|
||||||
|
return -1;
|
||||||
// XXX - Only an approximation.
|
// XXX - Only an approximation.
|
||||||
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
||||||
}
|
}
|
||||||
@@ -291,6 +305,12 @@ public class PeerCoordinator implements PeerListener
|
|||||||
return metainfo;
|
return metainfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @since 0.8.4 */
|
||||||
|
public byte[] getInfoHash()
|
||||||
|
{
|
||||||
|
return infohash;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean needPeers()
|
public boolean needPeers()
|
||||||
{
|
{
|
||||||
return !halted && peers.size() < getMaxConnections();
|
return !halted && peers.size() < getMaxConnections();
|
||||||
@@ -301,6 +321,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
* @return 512K: 16; 1M: 11; 2M: 6
|
* @return 512K: 16; 1M: 11; 2M: 6
|
||||||
*/
|
*/
|
||||||
private int getMaxConnections() {
|
private int getMaxConnections() {
|
||||||
|
if (metainfo == null)
|
||||||
|
return 6;
|
||||||
int size = metainfo.getPieceLength(0);
|
int size = metainfo.getPieceLength(0);
|
||||||
int max = _util.getMaxConnections();
|
int max = _util.getMaxConnections();
|
||||||
if (size <= 512*1024 || completed())
|
if (size <= 512*1024 || completed())
|
||||||
@@ -375,8 +397,15 @@ public class PeerCoordinator implements PeerListener
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO)) {
|
||||||
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
// just for logging
|
||||||
|
String name;
|
||||||
|
if (metainfo == null)
|
||||||
|
name = "Magnet";
|
||||||
|
else
|
||||||
|
name = metainfo.getName();
|
||||||
|
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
||||||
|
}
|
||||||
|
|
||||||
// Add it to the beginning of the list.
|
// Add it to the beginning of the list.
|
||||||
// And try to optimistically make it a uploader.
|
// And try to optimistically make it a uploader.
|
||||||
@@ -435,12 +464,22 @@ public class PeerCoordinator implements PeerListener
|
|||||||
|
|
||||||
if (need_more)
|
if (need_more)
|
||||||
{
|
{
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG)) {
|
||||||
_log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run"));
|
// just for logging
|
||||||
|
String name;
|
||||||
|
if (metainfo == null)
|
||||||
|
name = "Magnet";
|
||||||
|
else
|
||||||
|
name = metainfo.getName();
|
||||||
|
_log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + name, new Exception("add/run"));
|
||||||
|
}
|
||||||
// Run the peer with us as listener and the current bitfield.
|
// Run the peer with us as listener and the current bitfield.
|
||||||
final PeerListener listener = this;
|
final PeerListener listener = this;
|
||||||
final BitField bitfield = storage.getBitField();
|
final BitField bitfield;
|
||||||
|
if (storage != null)
|
||||||
|
bitfield = storage.getBitField();
|
||||||
|
else
|
||||||
|
bitfield = null;
|
||||||
Runnable r = new Runnable()
|
Runnable r = new Runnable()
|
||||||
{
|
{
|
||||||
public void run()
|
public void run()
|
||||||
@@ -506,11 +545,6 @@ public class PeerCoordinator implements PeerListener
|
|||||||
interestedAndChoking = count;
|
interestedAndChoking = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBitMap()
|
|
||||||
{
|
|
||||||
return storage.getBitField().getFieldBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if we still want the given piece
|
* @return true if we still want the given piece
|
||||||
*/
|
*/
|
||||||
@@ -679,6 +713,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
* @since 0.8.1
|
* @since 0.8.1
|
||||||
*/
|
*/
|
||||||
public void updatePiecePriorities() {
|
public void updatePiecePriorities() {
|
||||||
|
if (storage == null)
|
||||||
|
return;
|
||||||
int[] pri = storage.getPiecePriorities();
|
int[] pri = storage.getPiecePriorities();
|
||||||
if (pri == null) {
|
if (pri == null) {
|
||||||
_log.debug("Updated piece priorities called but no priorities to set?");
|
_log.debug("Updated piece priorities called but no priorities to set?");
|
||||||
@@ -745,6 +781,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
{
|
{
|
||||||
if (halted)
|
if (halted)
|
||||||
return null;
|
return null;
|
||||||
|
if (metainfo == null || storage == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -787,6 +825,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
*/
|
*/
|
||||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||||
{
|
{
|
||||||
|
if (metainfo == null || storage == null)
|
||||||
|
return true;
|
||||||
if (halted) {
|
if (halted) {
|
||||||
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||||
return true; // We don't actually care anymore.
|
return true; // We don't actually care anymore.
|
||||||
@@ -983,6 +1023,8 @@ public class PeerCoordinator implements PeerListener
|
|||||||
* @since 0.8.2
|
* @since 0.8.2
|
||||||
*/
|
*/
|
||||||
public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
|
public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
|
||||||
|
if (metainfo == null)
|
||||||
|
return null;
|
||||||
synchronized(wantedPieces) {
|
synchronized(wantedPieces) {
|
||||||
// sorts by remaining bytes, least first
|
// sorts by remaining bytes, least first
|
||||||
Collections.sort(partialPieces);
|
Collections.sort(partialPieces);
|
||||||
|
@@ -71,6 +71,9 @@ class PeerState implements DataLoader
|
|||||||
public final static int PARTSIZE = 16*1024; // outbound request
|
public final static int PARTSIZE = 16*1024; // outbound request
|
||||||
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param metainfo null if in magnet mode
|
||||||
|
*/
|
||||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||||
PeerConnectionIn in, PeerConnectionOut out)
|
PeerConnectionIn in, PeerConnectionOut out)
|
||||||
{
|
{
|
||||||
@@ -132,6 +135,9 @@ class PeerState implements DataLoader
|
|||||||
{
|
{
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug(peer + " rcv have(" + piece + ")");
|
_log.debug(peer + " rcv have(" + piece + ")");
|
||||||
|
// FIXME we will lose these until we get the metainfo
|
||||||
|
if (metainfo == null)
|
||||||
|
return;
|
||||||
// Sanity check
|
// Sanity check
|
||||||
if (piece < 0 || piece >= metainfo.getPieces())
|
if (piece < 0 || piece >= metainfo.getPieces())
|
||||||
{
|
{
|
||||||
@@ -169,8 +175,15 @@ class PeerState implements DataLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// XXX - Check for weird bitfield and disconnect?
|
// XXX - Check for weird bitfield and disconnect?
|
||||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
// FIXME will have to regenerate the bitfield after we know exactly
|
||||||
|
// how many pieces there are, as we don't know how many spare bits there are.
|
||||||
|
if (metainfo == null)
|
||||||
|
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||||
|
else
|
||||||
|
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||||
}
|
}
|
||||||
|
if (metainfo == null)
|
||||||
|
return;
|
||||||
boolean interest = listener.gotBitField(peer, bitfield);
|
boolean interest = listener.gotBitField(peer, bitfield);
|
||||||
setInteresting(interest);
|
setInteresting(interest);
|
||||||
if (bitfield.complete() && !interest) {
|
if (bitfield.complete() && !interest) {
|
||||||
@@ -188,6 +201,8 @@ class PeerState implements DataLoader
|
|||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug(peer + " rcv request("
|
_log.debug(peer + " rcv request("
|
||||||
+ piece + ", " + begin + ", " + length + ") ");
|
+ piece + ", " + begin + ", " + length + ") ");
|
||||||
|
if (metainfo == null)
|
||||||
|
return;
|
||||||
if (choking)
|
if (choking)
|
||||||
{
|
{
|
||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
@@ -606,6 +621,8 @@ class PeerState implements DataLoader
|
|||||||
// no bitfield yet? nothing to request then.
|
// no bitfield yet? nothing to request then.
|
||||||
if (bitfield == null)
|
if (bitfield == null)
|
||||||
return;
|
return;
|
||||||
|
if (metainfo == null)
|
||||||
|
return;
|
||||||
boolean more_pieces = true;
|
boolean more_pieces = true;
|
||||||
while (more_pieces)
|
while (more_pieces)
|
||||||
{
|
{
|
||||||
|
@@ -317,31 +317,7 @@ public class Snark
|
|||||||
stopped = true;
|
stopped = true;
|
||||||
activity = "Network setup";
|
activity = "Network setup";
|
||||||
|
|
||||||
// "Taking Three as the subject to reason about--
|
id = generateID();
|
||||||
// A convenient number to state--
|
|
||||||
// We add Seven, and Ten, and then multiply out
|
|
||||||
// By One Thousand diminished by Eight.
|
|
||||||
//
|
|
||||||
// "The result we proceed to divide, as you see,
|
|
||||||
// By Nine Hundred and Ninety Two:
|
|
||||||
// Then subtract Seventeen, and the answer must be
|
|
||||||
// Exactly and perfectly true.
|
|
||||||
|
|
||||||
// Create a new ID and fill it with something random. First nine
|
|
||||||
// zeros bytes, then three bytes filled with snark and then
|
|
||||||
// sixteen random bytes.
|
|
||||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
|
||||||
id = new byte[20];
|
|
||||||
Random random = I2PAppContext.getGlobalContext().random();
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < 9; i++)
|
|
||||||
id[i] = 0;
|
|
||||||
id[i++] = snark;
|
|
||||||
id[i++] = snark;
|
|
||||||
id[i++] = snark;
|
|
||||||
while (i < 20)
|
|
||||||
id[i++] = (byte)random.nextInt(256);
|
|
||||||
|
|
||||||
debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||||
|
|
||||||
int port;
|
int port;
|
||||||
@@ -468,6 +444,64 @@ public class Snark
|
|||||||
if (start)
|
if (start)
|
||||||
startTorrent();
|
startTorrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* multitorrent, magnet
|
||||||
|
*
|
||||||
|
* @param torrent a fake name for now (not a file name)
|
||||||
|
* @param ih 20-byte info hash
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
public Snark(I2PSnarkUtil util, String torrent, byte[] ih,
|
||||||
|
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||||
|
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
|
||||||
|
{
|
||||||
|
completeListener = complistener;
|
||||||
|
_util = util;
|
||||||
|
_peerCoordinatorSet = peerCoordinatorSet;
|
||||||
|
acceptor = connectionAcceptor;
|
||||||
|
this.torrent = torrent;
|
||||||
|
this.infoHash = ih;
|
||||||
|
this.rootDataDir = rootDir;
|
||||||
|
stopped = true;
|
||||||
|
id = generateID();
|
||||||
|
|
||||||
|
// All we have is an infoHash
|
||||||
|
// meta remains null
|
||||||
|
// storage remains null
|
||||||
|
|
||||||
|
if (start)
|
||||||
|
startTorrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] generateID() {
|
||||||
|
// "Taking Three as the subject to reason about--
|
||||||
|
// A convenient number to state--
|
||||||
|
// We add Seven, and Ten, and then multiply out
|
||||||
|
// By One Thousand diminished by Eight.
|
||||||
|
//
|
||||||
|
// "The result we proceed to divide, as you see,
|
||||||
|
// By Nine Hundred and Ninety Two:
|
||||||
|
// Then subtract Seventeen, and the answer must be
|
||||||
|
// Exactly and perfectly true.
|
||||||
|
|
||||||
|
// Create a new ID and fill it with something random. First nine
|
||||||
|
// zeros bytes, then three bytes filled with snark and then
|
||||||
|
// sixteen random bytes.
|
||||||
|
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||||
|
byte[] rv = new byte[20];
|
||||||
|
Random random = I2PAppContext.getGlobalContext().random();
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 9; i++)
|
||||||
|
rv[i] = 0;
|
||||||
|
rv[i++] = snark;
|
||||||
|
rv[i++] = snark;
|
||||||
|
rv[i++] = snark;
|
||||||
|
while (i < 20)
|
||||||
|
rv[i++] = (byte)random.nextInt(256);
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start up contacting peers and querying the tracker
|
* Start up contacting peers and querying the tracker
|
||||||
*/
|
*/
|
||||||
@@ -484,7 +518,7 @@ public class Snark
|
|||||||
}
|
}
|
||||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||||
activity = "Collecting pieces";
|
activity = "Collecting pieces";
|
||||||
coordinator = new PeerCoordinator(_util, id, meta, storage, this, this);
|
coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
|
||||||
if (_peerCoordinatorSet != null) {
|
if (_peerCoordinatorSet != null) {
|
||||||
// multitorrent
|
// multitorrent
|
||||||
_peerCoordinatorSet.add(coordinator);
|
_peerCoordinatorSet.add(coordinator);
|
||||||
@@ -507,7 +541,7 @@ public class Snark
|
|||||||
// restart safely, so lets build a new one to replace the old
|
// restart safely, so lets build a new one to replace the old
|
||||||
if (_peerCoordinatorSet != null)
|
if (_peerCoordinatorSet != null)
|
||||||
_peerCoordinatorSet.remove(coordinator);
|
_peerCoordinatorSet.remove(coordinator);
|
||||||
PeerCoordinator newCoord = new PeerCoordinator(_util, id, meta, storage, this, this);
|
PeerCoordinator newCoord = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
|
||||||
if (_peerCoordinatorSet != null)
|
if (_peerCoordinatorSet != null)
|
||||||
_peerCoordinatorSet.add(newCoord);
|
_peerCoordinatorSet.add(newCoord);
|
||||||
coordinator = newCoord;
|
coordinator = newCoord;
|
||||||
@@ -516,18 +550,17 @@ public class Snark
|
|||||||
if (!trackerclient.started() && !coordinatorChanged) {
|
if (!trackerclient.started() && !coordinatorChanged) {
|
||||||
trackerclient.start();
|
trackerclient.start();
|
||||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||||
try
|
if (storage != null) {
|
||||||
{
|
try {
|
||||||
storage.reopen(rootDataDir);
|
storage.reopen(rootDataDir);
|
||||||
}
|
} catch (IOException ioe) {
|
||||||
catch (IOException ioe)
|
try { storage.close(); } catch (IOException ioee) {
|
||||||
{
|
ioee.printStackTrace();
|
||||||
try { storage.close(); } catch (IOException ioee) {
|
}
|
||||||
ioee.printStackTrace();
|
fatal("Could not reopen storage", ioe);
|
||||||
}
|
}
|
||||||
fatal("Could not reopen storage", ioe);
|
}
|
||||||
}
|
TrackerClient newClient = new TrackerClient(_util, meta, coordinator, this);
|
||||||
TrackerClient newClient = new TrackerClient(_util, coordinator.getMetaInfo(), coordinator, this);
|
|
||||||
if (!trackerclient.halted())
|
if (!trackerclient.halted())
|
||||||
trackerclient.halt();
|
trackerclient.halt();
|
||||||
trackerclient = newClient;
|
trackerclient = newClient;
|
||||||
@@ -601,6 +634,7 @@ public class Snark
|
|||||||
* @since 0.8.4
|
* @since 0.8.4
|
||||||
*/
|
*/
|
||||||
public byte[] getInfoHash() {
|
public byte[] getInfoHash() {
|
||||||
|
// should always be the same
|
||||||
if (meta != null)
|
if (meta != null)
|
||||||
return meta.getInfoHash();
|
return meta.getInfoHash();
|
||||||
return infoHash;
|
return infoHash;
|
||||||
|
@@ -578,6 +578,44 @@ public class SnarkManager implements Snark.CompleteListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a torrent with the info hash alone (magnet / maggot)
|
||||||
|
*
|
||||||
|
* @param name hex or b32 name from the magnet link
|
||||||
|
* @param ih 20 byte info hash
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
public void addMagnet(String name, byte[] ih) {
|
||||||
|
Snark torrent = new Snark(_util, name, ih, this,
|
||||||
|
_peerCoordinatorSet, _connectionAcceptor,
|
||||||
|
false, getDataDir().getPath());
|
||||||
|
|
||||||
|
// TODO tell the dir monitor not to delete us
|
||||||
|
synchronized (_snarks) {
|
||||||
|
_snarks.put(name, torrent);
|
||||||
|
}
|
||||||
|
if (shouldAutoStart()) {
|
||||||
|
torrent.startTorrent();
|
||||||
|
addMessage(_("Fetching {0}", name));
|
||||||
|
boolean haveSavedPeers = false;
|
||||||
|
if ((!util().connected()) && !haveSavedPeers) {
|
||||||
|
addMessage(_("We have no saved peers and no other torrents are running. " +
|
||||||
|
"Fetch of {0} will not succeed until you start another torrent.", name));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addMessage(_("Adding {0}", name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a torrent with the info hash alone (magnet / maggot)
|
||||||
|
*
|
||||||
|
* @param ih 20 byte info hash
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
public void deleteMagnet(byte[] ih) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the timestamp for a torrent from the config file
|
* Get the timestamp for a torrent from the config file
|
||||||
*/
|
*/
|
||||||
|
@@ -38,6 +38,7 @@ import net.i2p.data.Hash;
|
|||||||
import net.i2p.util.I2PAppThread;
|
import net.i2p.util.I2PAppThread;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
|
import org.klomp.snark.dht.KRPC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs metainfo tracker of events and gets new peers for peer
|
* Informs metainfo tracker of events and gets new peers for peer
|
||||||
@@ -73,6 +74,9 @@ public class TrackerClient extends I2PAppThread
|
|||||||
|
|
||||||
private List trackers;
|
private List trackers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param meta null if in magnet mode
|
||||||
|
*/
|
||||||
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator, Snark snark)
|
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator, Snark snark)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
@@ -173,6 +177,7 @@ public class TrackerClient extends I2PAppThread
|
|||||||
// FIXME really need to get this message to the gui
|
// FIXME really need to get this message to the gui
|
||||||
stop = true;
|
stop = true;
|
||||||
_log.error("No valid trackers for infoHash: " + infoHash);
|
_log.error("No valid trackers for infoHash: " + infoHash);
|
||||||
|
// FIXME keep going if DHT enabled
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +197,9 @@ public class TrackerClient extends I2PAppThread
|
|||||||
Random r = I2PAppContext.getGlobalContext().random();
|
Random r = I2PAppContext.getGlobalContext().random();
|
||||||
while(!stop)
|
while(!stop)
|
||||||
{
|
{
|
||||||
|
// Local DHT tracker announce
|
||||||
|
if (_util.getDHT() != null)
|
||||||
|
_util.getDHT().announce(snark.getInfoHash());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Sleep some minutes...
|
// Sleep some minutes...
|
||||||
@@ -319,6 +327,45 @@ public class TrackerClient extends I2PAppThread
|
|||||||
maxSeenPeers = tr.seenPeers;
|
maxSeenPeers = tr.seenPeers;
|
||||||
} // *** end of trackers loop here
|
} // *** end of trackers loop here
|
||||||
|
|
||||||
|
// Get peers from DHT
|
||||||
|
// FIXME this needs to be in its own thread
|
||||||
|
if (_util.getDHT() != null && !stop) {
|
||||||
|
int numwant;
|
||||||
|
if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||||
|
numwant = 1;
|
||||||
|
else
|
||||||
|
numwant = _util.getMaxConnections();
|
||||||
|
List<Hash> hashes = _util.getDHT().getPeers(snark.getInfoHash(), numwant, 2*60*1000);
|
||||||
|
_util.debug("Got " + hashes + " from DHT", Snark.INFO);
|
||||||
|
// announce ourselves while the token is still good
|
||||||
|
// FIXME this needs to be in its own thread
|
||||||
|
if (!stop) {
|
||||||
|
int good = _util.getDHT().announce(snark.getInfoHash(), 8, 5*60*1000);
|
||||||
|
_util.debug("Sent " + good + " good announces to DHT", Snark.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now try these peers
|
||||||
|
if ((!stop) && !hashes.isEmpty()) {
|
||||||
|
List<Peer> peers = new ArrayList(hashes.size());
|
||||||
|
for (Hash h : hashes) {
|
||||||
|
PeerID pID = new PeerID(h.getData());
|
||||||
|
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), meta));
|
||||||
|
}
|
||||||
|
Collections.shuffle(peers, r);
|
||||||
|
Iterator<Peer> it = peers.iterator();
|
||||||
|
while ((!stop) && it.hasNext()) {
|
||||||
|
Peer cur = it.next();
|
||||||
|
if (coordinator.addPeer(cur)) {
|
||||||
|
int delay = DELAY_MUL;
|
||||||
|
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||||
|
delay += DELAY_MIN;
|
||||||
|
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// we could try and total the unique peers but that's too hard for now
|
// we could try and total the unique peers but that's too hard for now
|
||||||
snark.setTrackerSeenPeers(maxSeenPeers);
|
snark.setTrackerSeenPeers(maxSeenPeers);
|
||||||
if (!runStarted)
|
if (!runStarted)
|
||||||
@@ -333,6 +380,9 @@ public class TrackerClient extends I2PAppThread
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
// Local DHT tracker unannounce
|
||||||
|
if (_util.getDHT() != null)
|
||||||
|
_util.getDHT().unannounce(snark.getInfoHash());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// try to contact everybody we can
|
// try to contact everybody we can
|
||||||
|
@@ -144,7 +144,7 @@ public class TrackerInfo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
peers.add(new Peer(peerID, my_id, metainfo));
|
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return peers;
|
return peers;
|
||||||
@@ -172,7 +172,7 @@ public class TrackerInfo
|
|||||||
// won't happen
|
// won't happen
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
peers.add(new Peer(peerID, my_id, metainfo));
|
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
return peers;
|
return peers;
|
||||||
|
@@ -61,6 +61,10 @@ public class I2PSnarkServlet extends Default {
|
|||||||
private String _imgPath;
|
private String _imgPath;
|
||||||
|
|
||||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||||
|
/** BEP 9 */
|
||||||
|
private static final String MAGNET = "magnet:?xt=urn:btih:";
|
||||||
|
/** http://sponge.i2p/files/maggotspec.txt */
|
||||||
|
private static final String MAGGOT = "maggot://";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ServletConfig cfg) throws ServletException {
|
public void init(ServletConfig cfg) throws ServletException {
|
||||||
@@ -455,8 +459,10 @@ public class I2PSnarkServlet extends Default {
|
|||||||
_manager.addMessage(_("Fetching {0}", urlify(newURL)));
|
_manager.addMessage(_("Fetching {0}", urlify(newURL)));
|
||||||
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add", true);
|
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add", true);
|
||||||
fetch.start();
|
fetch.start();
|
||||||
|
} else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) {
|
||||||
|
addMagnet(newURL);
|
||||||
} else {
|
} else {
|
||||||
_manager.addMessage(_("Invalid URL - must start with http://"));
|
_manager.addMessage(_("Invalid URL - must start with http://, {0} or {1}", MAGNET, MAGGOT));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no file or URL specified
|
// no file or URL specified
|
||||||
@@ -503,6 +509,8 @@ public class I2PSnarkServlet extends Default {
|
|||||||
_manager.stopTorrent(name, true);
|
_manager.stopTorrent(name, true);
|
||||||
MetaInfo meta = snark.getMetaInfo();
|
MetaInfo meta = snark.getMetaInfo();
|
||||||
if (meta == null) {
|
if (meta == null) {
|
||||||
|
// magnet
|
||||||
|
_manager.deleteMagnet(snark.getInfoHash());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// should we delete the torrent file?
|
// should we delete the torrent file?
|
||||||
@@ -527,6 +535,8 @@ public class I2PSnarkServlet extends Default {
|
|||||||
_manager.stopTorrent(name, true);
|
_manager.stopTorrent(name, true);
|
||||||
MetaInfo meta = snark.getMetaInfo();
|
MetaInfo meta = snark.getMetaInfo();
|
||||||
if (meta == null) {
|
if (meta == null) {
|
||||||
|
// magnet
|
||||||
|
_manager.deleteMagnet(snark.getInfoHash());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File f = new File(name);
|
File f = new File(name);
|
||||||
@@ -1314,6 +1324,48 @@ public class I2PSnarkServlet extends Default {
|
|||||||
out.write("</a></span></span></div>\n");
|
out.write("</a></span></span></div>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url in base32 or hex, xt must be first magnet param
|
||||||
|
* @since 0.8.4
|
||||||
|
*/
|
||||||
|
private void addMagnet(String url) {
|
||||||
|
String ihash;
|
||||||
|
String name;
|
||||||
|
if (url.startsWith(MAGNET)) {
|
||||||
|
ihash = url.substring(MAGNET.length()).trim();
|
||||||
|
int amp = ihash.indexOf('&');
|
||||||
|
if (amp >= 0)
|
||||||
|
ihash = url.substring(0, amp);
|
||||||
|
name = "Magnet " + ihash;
|
||||||
|
} else if (url.startsWith(MAGGOT)) {
|
||||||
|
ihash = url.substring(MAGGOT.length()).trim();
|
||||||
|
int col = ihash.indexOf(':');
|
||||||
|
if (col >= 0)
|
||||||
|
ihash = url.substring(0, col);
|
||||||
|
name = "Maggot " + ihash;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] ih = null;
|
||||||
|
if (ihash.length() == 32) {
|
||||||
|
ih = Base32.decode(ihash);
|
||||||
|
} else if (ihash.length() == 40) {
|
||||||
|
ih = new byte[20];
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
ih[i] = (byte) (Integer.parseInt(ihash.substring(i*2, (i*2) + 2), 16) & 0xff);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
ih = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ih == null || ih.length != 20) {
|
||||||
|
_manager.addMessage(_("Invalid info hash in magnet URL {0}", url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_manager.addMagnet(ihash, ih);
|
||||||
|
}
|
||||||
|
|
||||||
/** copied from ConfigTunnelsHelper */
|
/** copied from ConfigTunnelsHelper */
|
||||||
private static final String HOP = "hop";
|
private static final String HOP = "hop";
|
||||||
private static final String TUNNEL = "tunnel";
|
private static final String TUNNEL = "tunnel";
|
||||||
|
Reference in New Issue
Block a user