more prep and stubs for no metainfo

This commit is contained in:
zzz
2010-12-20 00:05:03 +00:00
parent 4899a6d306
commit 7602999274
10 changed files with 318 additions and 68 deletions

View File

@ -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
*/

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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
*/

View 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

View File

@ -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;

View File

@ -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";