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.Translate;
|
||||
|
||||
import org.klomp.snark.dht.KRPC;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
* We use this class as a sort of context for i2psnark
|
||||
@ -56,6 +58,7 @@ public class I2PSnarkUtil {
|
||||
private int _maxConnections;
|
||||
private File _tmpDir;
|
||||
private int _startupDelay;
|
||||
private KRPC _krpc;
|
||||
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
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 int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||
private static final boolean ENABLE_DHT = true;
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
@ -185,10 +190,20 @@ public class I2PSnarkUtil {
|
||||
// opts.setProperty("i2p.streaming.readTimeout", "120000");
|
||||
_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 null if disabled or not started
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public KRPC getDHT() { return _krpc; }
|
||||
|
||||
public boolean connected() { return _manager != null; }
|
||||
|
||||
/**
|
||||
* Destroy the destination itself
|
||||
*/
|
||||
|
@ -39,6 +39,7 @@ public class Peer implements Comparable
|
||||
private final PeerID peerID;
|
||||
|
||||
private final byte[] my_id;
|
||||
private final byte[] infohash;
|
||||
final MetaInfo metainfo;
|
||||
|
||||
// The data in/output streams set during the handshake and used by
|
||||
@ -70,11 +71,13 @@ public class Peer implements Comparable
|
||||
* Outgoing connection.
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
* 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.my_id = my_id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
_id = ++__id;
|
||||
//_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
|
||||
* the connect() method.
|
||||
*
|
||||
* @param metainfo null if in magnet mode
|
||||
* @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
|
||||
{
|
||||
this.my_id = my_id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
this.sock = sock;
|
||||
|
||||
@ -312,8 +317,7 @@ public class Peer implements Comparable
|
||||
// FIXME not if DHT disabled
|
||||
dout.writeLong(OPTION_EXTENSION | OPTION_DHT);
|
||||
// Handshake write - metainfo hash
|
||||
byte[] shared_hash = metainfo.getInfoHash();
|
||||
dout.write(shared_hash);
|
||||
dout.write(infohash);
|
||||
// Handshake write - peer id
|
||||
dout.write(my_id);
|
||||
dout.flush();
|
||||
@ -341,7 +345,7 @@ public class Peer implements Comparable
|
||||
// Handshake read - metainfo hash
|
||||
bs = new byte[20];
|
||||
din.readFully(bs);
|
||||
if (!Arrays.equals(shared_hash, bs))
|
||||
if (!Arrays.equals(infohash, bs))
|
||||
throw new IOException("Unexpected MetaInfo hash");
|
||||
|
||||
// Handshake read - peer id
|
||||
|
@ -88,12 +88,11 @@ public class PeerAcceptor
|
||||
}
|
||||
if (coordinator != null) {
|
||||
// single torrent capability
|
||||
MetaInfo meta = coordinator.getMetaInfo();
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
if (DataHelper.eq(coordinator.getInfoHash(), peerInfoHash)) {
|
||||
if (coordinator.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
coordinator.getInfoHash(), coordinator.getMetaInfo());
|
||||
coordinator.addPeer(peer);
|
||||
}
|
||||
else
|
||||
@ -101,19 +100,18 @@ public class PeerAcceptor
|
||||
} else {
|
||||
// its for another infohash, but we are only single torrent capable. b0rk.
|
||||
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 {
|
||||
// multitorrent capable, so lets see what we can handle
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator cur = (PeerCoordinator)iter.next();
|
||||
MetaInfo meta = cur.getMetaInfo();
|
||||
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
|
||||
if (DataHelper.eq(cur.getInfoHash(), peerInfoHash)) {
|
||||
if (cur.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, cur.getID(),
|
||||
cur.getMetaInfo());
|
||||
cur.getInfoHash(), cur.getMetaInfo());
|
||||
cur.addPeer(peer);
|
||||
return;
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ public class PeerCoordinator implements PeerListener
|
||||
private final CheckEvent timer;
|
||||
|
||||
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
|
||||
* when priorities change.
|
||||
@ -108,11 +109,16 @@ public class PeerCoordinator implements PeerListener
|
||||
private final I2PSnarkUtil _util;
|
||||
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)
|
||||
{
|
||||
_util = util;
|
||||
this.id = id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
this.storage = storage;
|
||||
this.listener = listener;
|
||||
@ -149,6 +155,8 @@ public class PeerCoordinator implements PeerListener
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
if (metainfo == null || storage == null)
|
||||
return;
|
||||
// Make a list of pieces
|
||||
synchronized(wantedPieces) {
|
||||
wantedPieces.clear();
|
||||
@ -188,6 +196,9 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
public boolean completed()
|
||||
{
|
||||
// FIXME return metainfo complete status
|
||||
if (storage == null)
|
||||
return false;
|
||||
return storage.complete();
|
||||
}
|
||||
|
||||
@ -204,9 +215,12 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
/**
|
||||
* Returns how many bytes are still needed to get the complete file.
|
||||
* @return -1 if in magnet mode
|
||||
*/
|
||||
public long getLeft()
|
||||
{
|
||||
if (metainfo == null | storage == null)
|
||||
return -1;
|
||||
// XXX - Only an approximation.
|
||||
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
||||
}
|
||||
@ -291,6 +305,12 @@ public class PeerCoordinator implements PeerListener
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
public byte[] getInfoHash()
|
||||
{
|
||||
return infohash;
|
||||
}
|
||||
|
||||
public boolean needPeers()
|
||||
{
|
||||
return !halted && peers.size() < getMaxConnections();
|
||||
@ -301,6 +321,8 @@ public class PeerCoordinator implements PeerListener
|
||||
* @return 512K: 16; 1M: 11; 2M: 6
|
||||
*/
|
||||
private int getMaxConnections() {
|
||||
if (metainfo == null)
|
||||
return 6;
|
||||
int size = metainfo.getPieceLength(0);
|
||||
int max = _util.getMaxConnections();
|
||||
if (size <= 512*1024 || completed())
|
||||
@ -375,8 +397,15 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
// 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.
|
||||
// And try to optimistically make it a uploader.
|
||||
@ -435,12 +464,22 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
if (need_more)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run"));
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
// 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.
|
||||
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()
|
||||
{
|
||||
public void run()
|
||||
@ -506,11 +545,6 @@ public class PeerCoordinator implements PeerListener
|
||||
interestedAndChoking = count;
|
||||
}
|
||||
|
||||
public byte[] getBitMap()
|
||||
{
|
||||
return storage.getBitField().getFieldBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if we still want the given piece
|
||||
*/
|
||||
@ -679,6 +713,8 @@ public class PeerCoordinator implements PeerListener
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void updatePiecePriorities() {
|
||||
if (storage == null)
|
||||
return;
|
||||
int[] pri = storage.getPiecePriorities();
|
||||
if (pri == null) {
|
||||
_log.debug("Updated piece priorities called but no priorities to set?");
|
||||
@ -745,6 +781,8 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
if (metainfo == null || storage == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
@ -787,6 +825,8 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||
{
|
||||
if (metainfo == null || storage == null)
|
||||
return true;
|
||||
if (halted) {
|
||||
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
return true; // We don't actually care anymore.
|
||||
@ -983,6 +1023,8 @@ public class PeerCoordinator implements PeerListener
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
|
||||
if (metainfo == null)
|
||||
return null;
|
||||
synchronized(wantedPieces) {
|
||||
// sorts by remaining bytes, least first
|
||||
Collections.sort(partialPieces);
|
||||
|
@ -71,6 +71,9 @@ class PeerState implements DataLoader
|
||||
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
|
||||
|
||||
/**
|
||||
* @param metainfo null if in magnet mode
|
||||
*/
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
PeerConnectionIn in, PeerConnectionOut out)
|
||||
{
|
||||
@ -132,6 +135,9 @@ class PeerState implements DataLoader
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv have(" + piece + ")");
|
||||
// FIXME we will lose these until we get the metainfo
|
||||
if (metainfo == null)
|
||||
return;
|
||||
// Sanity check
|
||||
if (piece < 0 || piece >= metainfo.getPieces())
|
||||
{
|
||||
@ -169,8 +175,15 @@ class PeerState implements DataLoader
|
||||
}
|
||||
|
||||
// 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);
|
||||
setInteresting(interest);
|
||||
if (bitfield.complete() && !interest) {
|
||||
@ -188,6 +201,8 @@ class PeerState implements DataLoader
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv request("
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (metainfo == null)
|
||||
return;
|
||||
if (choking)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -606,6 +621,8 @@ class PeerState implements DataLoader
|
||||
// no bitfield yet? nothing to request then.
|
||||
if (bitfield == null)
|
||||
return;
|
||||
if (metainfo == null)
|
||||
return;
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
|
@ -317,31 +317,7 @@ public class Snark
|
||||
stopped = true;
|
||||
activity = "Network setup";
|
||||
|
||||
// "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;
|
||||
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);
|
||||
|
||||
id = generateID();
|
||||
debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||
|
||||
int port;
|
||||
@ -468,6 +444,64 @@ public class Snark
|
||||
if (start)
|
||||
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
|
||||
*/
|
||||
@ -484,7 +518,7 @@ public class Snark
|
||||
}
|
||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||
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) {
|
||||
// multitorrent
|
||||
_peerCoordinatorSet.add(coordinator);
|
||||
@ -507,7 +541,7 @@ public class Snark
|
||||
// restart safely, so lets build a new one to replace the old
|
||||
if (_peerCoordinatorSet != null)
|
||||
_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)
|
||||
_peerCoordinatorSet.add(newCoord);
|
||||
coordinator = newCoord;
|
||||
@ -516,18 +550,17 @@ public class Snark
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
try
|
||||
{
|
||||
storage.reopen(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(_util, coordinator.getMetaInfo(), coordinator, this);
|
||||
if (storage != null) {
|
||||
try {
|
||||
storage.reopen(rootDataDir);
|
||||
} catch (IOException ioe) {
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(_util, meta, coordinator, this);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
trackerclient = newClient;
|
||||
@ -601,6 +634,7 @@ public class Snark
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public byte[] getInfoHash() {
|
||||
// should always be the same
|
||||
if (meta != null)
|
||||
return meta.getInfoHash();
|
||||
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
|
||||
*/
|
||||
|
@ -38,6 +38,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.klomp.snark.dht.KRPC;
|
||||
|
||||
/**
|
||||
* Informs metainfo tracker of events and gets new peers for peer
|
||||
@ -73,6 +74,9 @@ public class TrackerClient extends I2PAppThread
|
||||
|
||||
private List trackers;
|
||||
|
||||
/**
|
||||
* @param meta null if in magnet mode
|
||||
*/
|
||||
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator, Snark snark)
|
||||
{
|
||||
super();
|
||||
@ -173,6 +177,7 @@ public class TrackerClient extends I2PAppThread
|
||||
// FIXME really need to get this message to the gui
|
||||
stop = true;
|
||||
_log.error("No valid trackers for infoHash: " + infoHash);
|
||||
// FIXME keep going if DHT enabled
|
||||
return;
|
||||
}
|
||||
|
||||
@ -192,6 +197,9 @@ public class TrackerClient extends I2PAppThread
|
||||
Random r = I2PAppContext.getGlobalContext().random();
|
||||
while(!stop)
|
||||
{
|
||||
// Local DHT tracker announce
|
||||
if (_util.getDHT() != null)
|
||||
_util.getDHT().announce(snark.getInfoHash());
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
@ -319,6 +327,45 @@ public class TrackerClient extends I2PAppThread
|
||||
maxSeenPeers = tr.seenPeers;
|
||||
} // *** 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
|
||||
snark.setTrackerSeenPeers(maxSeenPeers);
|
||||
if (!runStarted)
|
||||
@ -333,6 +380,9 @@ public class TrackerClient extends I2PAppThread
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Local DHT tracker unannounce
|
||||
if (_util.getDHT() != null)
|
||||
_util.getDHT().unannounce(snark.getInfoHash());
|
||||
try
|
||||
{
|
||||
// try to contact everybody we can
|
||||
|
@ -144,7 +144,7 @@ public class TrackerInfo
|
||||
continue;
|
||||
}
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
@ -172,7 +172,7 @@ public class TrackerInfo
|
||||
// won't happen
|
||||
continue;
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
|
@ -61,6 +61,10 @@ public class I2PSnarkServlet extends Default {
|
||||
private String _imgPath;
|
||||
|
||||
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
|
||||
public void init(ServletConfig cfg) throws ServletException {
|
||||
@ -455,8 +459,10 @@ public class I2PSnarkServlet extends Default {
|
||||
_manager.addMessage(_("Fetching {0}", urlify(newURL)));
|
||||
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add", true);
|
||||
fetch.start();
|
||||
} else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) {
|
||||
addMagnet(newURL);
|
||||
} else {
|
||||
_manager.addMessage(_("Invalid URL - must start with http://"));
|
||||
_manager.addMessage(_("Invalid URL - must start with http://, {0} or {1}", MAGNET, MAGGOT));
|
||||
}
|
||||
} else {
|
||||
// no file or URL specified
|
||||
@ -503,6 +509,8 @@ public class I2PSnarkServlet extends Default {
|
||||
_manager.stopTorrent(name, true);
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null) {
|
||||
// magnet
|
||||
_manager.deleteMagnet(snark.getInfoHash());
|
||||
return;
|
||||
}
|
||||
// should we delete the torrent file?
|
||||
@ -527,6 +535,8 @@ public class I2PSnarkServlet extends Default {
|
||||
_manager.stopTorrent(name, true);
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null) {
|
||||
// magnet
|
||||
_manager.deleteMagnet(snark.getInfoHash());
|
||||
return;
|
||||
}
|
||||
File f = new File(name);
|
||||
@ -1314,6 +1324,48 @@ public class I2PSnarkServlet extends Default {
|
||||
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 */
|
||||
private static final String HOP = "hop";
|
||||
private static final String TUNNEL = "tunnel";
|
||||
|
Reference in New Issue
Block a user