forked from I2P_Developers/i2p.i2p
- Hide I2CP settings when in router context
- Better BEValue.toString() (most of the following got missed in the last checkin) - Fix about 9 NPEs - Fix numwant in magnet mode - Send metadata size in extension handshake - Open trackers are primary if we don't have primary trackers - Add missing break in port message handling - Increase max msg size to account for metadata msg - Remember magnets across restarts - Drop peers w/o extensions if we need metainfo - Fix DATA messages - Fix tracker transition to non-magnet - Fix infohash for non-magnet - Fix up peer transition to non-magnet - More logging
This commit is contained in:
@ -21,7 +21,6 @@ import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
*/
|
||||
abstract class ExtensionHandler {
|
||||
|
||||
private static final byte[] _handshake = buildHandshake();
|
||||
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ExtensionHandler.class);
|
||||
|
||||
public static final int ID_METADATA = 3;
|
||||
@ -32,17 +31,15 @@ abstract class ExtensionHandler {
|
||||
|
||||
|
||||
/**
|
||||
* @param metasize -1 if unknown
|
||||
* @return bencoded outgoing handshake message
|
||||
*/
|
||||
public static byte[] getHandshake() {
|
||||
return _handshake;
|
||||
}
|
||||
|
||||
/** outgoing handshake message */
|
||||
private static byte[] buildHandshake() {
|
||||
public static byte[] getHandshake(int metasize) {
|
||||
Map<String, Object> handshake = new HashMap();
|
||||
Map<String, Integer> m = new HashMap();
|
||||
m.put(TYPE_METADATA, Integer.valueOf(ID_METADATA));
|
||||
if (metasize >= 0)
|
||||
handshake.put("metadata_size", Integer.valueOf(metasize));
|
||||
handshake.put("m", m);
|
||||
handshake.put("p", Integer.valueOf(6881));
|
||||
handshake.put("v", "I2PSnark");
|
||||
@ -51,6 +48,8 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
|
||||
public static void handleMessage(Peer peer, PeerListener listener, int id, byte[] bs) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got extension msg " + id + " length " + bs.length + " from " + peer);
|
||||
if (id == 0)
|
||||
handleHandshake(peer, listener, bs);
|
||||
else if (id == ID_METADATA)
|
||||
@ -71,10 +70,24 @@ abstract class ExtensionHandler {
|
||||
peer.setHandshakeMap(map);
|
||||
Map<String, BEValue> msgmap = map.get("m").getMap();
|
||||
|
||||
// rv not used, just to throw an NPE to get out of here
|
||||
msgmap.get(TYPE_METADATA).getInt();
|
||||
if (msgmap.get(TYPE_METADATA) == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Peer does not support metadata extension: " + peer);
|
||||
// drop if we need metainfo ?
|
||||
return;
|
||||
}
|
||||
|
||||
BEValue msize = map.get("metadata_size");
|
||||
if (msize == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Peer does not have the metainfo size yet: " + peer);
|
||||
// drop if we need metainfo ?
|
||||
return;
|
||||
}
|
||||
int metaSize = msize.getInt();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Got the metainfo size: " + metaSize);
|
||||
|
||||
int metaSize = map.get("metadata_size").getInt();
|
||||
MagnetState state = peer.getMagnetState();
|
||||
int remaining;
|
||||
synchronized(state) {
|
||||
@ -83,12 +96,16 @@ abstract class ExtensionHandler {
|
||||
|
||||
if (state.isInitialized()) {
|
||||
if (state.getSize() != metaSize) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Wrong metainfo size " + metaSize + " from: " + peer);
|
||||
peer.disconnect();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// initialize it
|
||||
if (metaSize > MAX_METADATA_SIZE) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.debug("Huge metainfo size " + metaSize + " from: " + peer);
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
@ -112,8 +129,7 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.info("Handshake exception from " + peer, e);
|
||||
//peer.disconnect(false);
|
||||
_log.warn("Handshake exception from " + peer, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,6 +156,8 @@ abstract class ExtensionHandler {
|
||||
|
||||
MagnetState state = peer.getMagnetState();
|
||||
if (type == TYPE_REQUEST) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got request for " + piece + " from: " + peer);
|
||||
byte[] pc;
|
||||
synchronized(state) {
|
||||
pc = state.getChunk(piece);
|
||||
@ -150,12 +168,19 @@ abstract class ExtensionHandler {
|
||||
listener.uploaded(peer, pc.length);
|
||||
} else if (type == TYPE_DATA) {
|
||||
int size = map.get("total_size").getInt();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
boolean done;
|
||||
int chk = -1;
|
||||
synchronized(state) {
|
||||
if (state.isComplete())
|
||||
return;
|
||||
int len = is.available();
|
||||
if (len != size) {
|
||||
// probably fatal
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("total_size " + size + " but avail data " + len);
|
||||
}
|
||||
peer.downloaded(len);
|
||||
listener.downloaded(peer, len);
|
||||
done = state.saveChunk(piece, bs, bs.length - len, len);
|
||||
@ -219,15 +244,15 @@ abstract class ExtensionHandler {
|
||||
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data) {
|
||||
Map<String, Object> map = new HashMap();
|
||||
map.put("msg_type", Integer.valueOf(TYPE_REQUEST));
|
||||
map.put("msg_type", Integer.valueOf(TYPE_DATA));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
map.put("total_size", Integer.valueOf(data.length));
|
||||
byte[] dict = BEncoder.bencode(map);
|
||||
byte[] payload = new byte[dict.length + data.length];
|
||||
System.arraycopy(dict, 0, payload, 0, dict.length);
|
||||
System.arraycopy(data, 0, payload, dict.length, payload.length);
|
||||
System.arraycopy(data, 0, payload, dict.length, data.length);
|
||||
try {
|
||||
int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get("METADATA").getInt();
|
||||
int hisMsgCode = peer.getHandshakeMap().get("m").getMap().get(TYPE_METADATA).getInt();
|
||||
peer.sendExtension(hisMsgCode, payload);
|
||||
} catch (Exception e) {
|
||||
// NPE, no metadata caps
|
||||
|
@ -446,6 +446,21 @@ public class I2PSnarkUtil {
|
||||
return Boolean.valueOf(rv).booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Like DataHelper.toHexString but ensures no loss of leading zero bytes
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public static String toHex(byte[] b) {
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
int bi = b[i] & 0xff;
|
||||
if (bi < 16)
|
||||
buf.append('0');
|
||||
buf.append(Integer.toHexString(bi));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** hook between snark's logger and an i2p log */
|
||||
void debug(String msg, int snarkDebugLevel) {
|
||||
debug(msg, snarkDebugLevel, null);
|
||||
|
@ -53,8 +53,6 @@ class MagnetState {
|
||||
metainfo = meta;
|
||||
initialize(meta.getInfoBytes().length);
|
||||
complete = true;
|
||||
} else {
|
||||
metainfoBytes = new byte[metaSize];
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,10 +66,13 @@ class MagnetState {
|
||||
isInitialized = true;
|
||||
metaSize = size;
|
||||
totalChunks = (size + (CHUNK_SIZE - 1)) / CHUNK_SIZE;
|
||||
if (metainfo == null) {
|
||||
if (metainfo != null) {
|
||||
metainfoBytes = metainfo.getInfoBytes();
|
||||
} else {
|
||||
// we don't need these if complete
|
||||
have = new BitField(totalChunks);
|
||||
requested = new BitField(totalChunks);
|
||||
metainfoBytes = new byte[metaSize];
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,8 +195,7 @@ class MagnetState {
|
||||
InputStream is = new ByteArrayInputStream(metainfoBytes);
|
||||
BDecoder dec = new BDecoder(is);
|
||||
BEValue bev = dec.bdecodeMap();
|
||||
Map<String, BEValue> info = bev.getMap();
|
||||
map.put("info", info);
|
||||
map.put("info", bev);
|
||||
MetaInfo newmeta = new MetaInfo(map);
|
||||
if (!DataHelper.eq(newmeta.getInfoHash(), infohash))
|
||||
throw new IOException("info hash mismatch");
|
||||
|
@ -104,7 +104,7 @@ class Message
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// length is 1 byte
|
||||
// msg type is 1 byte
|
||||
if (type == EXTENSION)
|
||||
datalen += 1;
|
||||
|
||||
@ -167,6 +167,8 @@ class Message
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
case PORT:
|
||||
return "PORT(" + piece + ")";
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
default:
|
||||
|
@ -120,7 +120,7 @@ public class Peer implements Comparable
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
_id = ++__id;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating " + _id));
|
||||
_log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,14 +261,22 @@ public class Peer implements Comparable
|
||||
_log.debug("Already have din [" + sock + "] with " + toString());
|
||||
}
|
||||
|
||||
// bad idea?
|
||||
if (metainfo == null && (options & OPTION_EXTENSION) == 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Peer does not support extensions and we need metainfo, dropping");
|
||||
throw new IOException("Peer does not support extensions and we need metainfo, dropping");
|
||||
}
|
||||
|
||||
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||
PeerConnectionOut out = new PeerConnectionOut(this, dout);
|
||||
PeerState s = new PeerState(this, listener, metainfo, in, out);
|
||||
|
||||
if ((options & OPTION_EXTENSION) != 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports extensions, sending test message");
|
||||
out.sendExtension(0, ExtensionHandler.getHandshake());
|
||||
_log.debug("Peer supports extensions, sending reply message");
|
||||
int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
|
||||
out.sendExtension(0, ExtensionHandler.getHandshake(metasize));
|
||||
}
|
||||
|
||||
if ((options & OPTION_DHT) != 0 && util.getDHT() != null) {
|
||||
@ -423,6 +431,7 @@ public class Peer implements Comparable
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void setMetaInfo(MetaInfo meta) {
|
||||
metainfo = meta;
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.setMetaInfo(meta);
|
||||
|
@ -32,6 +32,13 @@ class PeerConnectionIn implements Runnable
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
// The max length of a complete message in bytes.
|
||||
// The biggest is the piece message, for which the length is the
|
||||
// request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
|
||||
// in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
|
||||
private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
|
||||
MagnetState.CHUNK_SIZE + 100); // 100 for the ext msg dictionary
|
||||
|
||||
private Thread thread;
|
||||
private volatile boolean quit;
|
||||
|
||||
@ -77,13 +84,9 @@ class PeerConnectionIn implements Runnable
|
||||
int len;
|
||||
|
||||
// Wait till we hear something...
|
||||
// The length of a complete message in bytes.
|
||||
// The biggest is the piece message, for which the length is the
|
||||
// request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
|
||||
// in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
|
||||
int i = din.readInt();
|
||||
lastRcvd = System.currentTimeMillis();
|
||||
if (i < 0 || i > PeerState.PARTSIZE + 9)
|
||||
if (i < 0 || i > MAX_MSG_SIZE)
|
||||
throw new IOException("Unexpected length prefix: " + i);
|
||||
|
||||
if (i == 0)
|
||||
@ -176,13 +179,14 @@ class PeerConnectionIn implements Runnable
|
||||
ps.portMessage(port);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received port message from " + peer);
|
||||
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);
|
||||
ps.extensionMessage(id, payload);
|
||||
break;
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
|
@ -410,7 +410,7 @@ public class PeerCoordinator implements PeerListener
|
||||
name = "Magnet";
|
||||
else
|
||||
name = metainfo.getName();
|
||||
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
||||
_log.info("New connection to peer: " + peer + " for " + name);
|
||||
}
|
||||
|
||||
// Add it to the beginning of the list.
|
||||
@ -1169,9 +1169,6 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.warn("Got completed metainfo via extension");
|
||||
metainfo = magnetState.getMetaInfo();
|
||||
listener.gotMetaInfo(this, metainfo);
|
||||
for (Peer p : peers) {
|
||||
p.setMetaInfo(metainfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1184,6 +1181,11 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void setStorage(Storage stg) {
|
||||
storage = stg;
|
||||
setWantedPieces();
|
||||
// ok we should be in business
|
||||
for (Peer p : peers) {
|
||||
p.setMetaInfo(metainfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -511,6 +511,8 @@ class PeerState implements DataLoader
|
||||
//bitfield = new BitField(meta.getPieces());
|
||||
}
|
||||
metainfo = meta;
|
||||
if (bitfield.count() > 0)
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
|
@ -243,7 +243,7 @@ public class Snark
|
||||
public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections";
|
||||
|
||||
/** most of these used to be public, use accessors below instead */
|
||||
private final String torrent;
|
||||
private String torrent;
|
||||
private MetaInfo meta;
|
||||
private Storage storage;
|
||||
private PeerCoordinator coordinator;
|
||||
@ -360,6 +360,7 @@ public class Snark
|
||||
}
|
||||
}
|
||||
meta = new MetaInfo(new BDecoder(in));
|
||||
infoHash = meta.getInfoHash();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
@ -1028,8 +1029,13 @@ public class Snark
|
||||
meta = metainfo;
|
||||
try {
|
||||
storage = new Storage(_util, meta, this);
|
||||
if (completeListener != null)
|
||||
completeListener.gotMetaInfo(this);
|
||||
storage.check(rootDataDir);
|
||||
if (completeListener != null) {
|
||||
String newName = completeListener.gotMetaInfo(this);
|
||||
if (newName != null)
|
||||
torrent = newName;
|
||||
// else some horrible problem
|
||||
}
|
||||
coordinator.setStorage(storage);
|
||||
} catch (IOException ioe) {
|
||||
if (storage != null) {
|
||||
@ -1125,9 +1131,10 @@ public class Snark
|
||||
* metainfo and storage. The listener should now call getMetaInfo()
|
||||
* and save the data to disk.
|
||||
*
|
||||
* @return the new name for the torrent or null on error
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void gotMetaInfo(Snark snark);
|
||||
public String gotMetaInfo(Snark snark);
|
||||
|
||||
// not really listeners but the easiest way to get back to an optional SnarkManager
|
||||
public long getSavedTorrentTime(Snark snark);
|
||||
|
@ -68,7 +68,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
|
||||
public static final String PROP_META_MAGNET_SUFFIX = ".magnet";
|
||||
public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
|
||||
|
||||
private static final String CONFIG_FILE = "i2psnark.config";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
@ -340,7 +340,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
int oldI2CPPort = _util.getI2CPPort();
|
||||
String oldI2CPHost = _util.getI2CPHost();
|
||||
int port = oldI2CPPort;
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
if (i2cpPort != null) {
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
String host = oldI2CPHost;
|
||||
Map opts = new HashMap();
|
||||
if (i2cpOpts == null) i2cpOpts = "";
|
||||
@ -627,7 +629,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* @throws RuntimeException via Snark.fatal()
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void addMagnet(String name, byte[] ih) {
|
||||
public void addMagnet(String name, byte[] ih, boolean updateStatus) {
|
||||
Snark torrent = new Snark(_util, name, ih, this,
|
||||
_peerCoordinatorSet, _connectionAcceptor,
|
||||
false, getDataDir().getPath());
|
||||
@ -640,6 +642,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
// Tell the dir monitor not to delete us
|
||||
_magnets.add(name);
|
||||
if (updateStatus)
|
||||
saveMagnetStatus(ih);
|
||||
_snarks.put(name, torrent);
|
||||
}
|
||||
if (shouldAutoStart()) {
|
||||
@ -667,6 +671,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
snark.stopTorrent();
|
||||
_magnets.remove(snark.getName());
|
||||
removeMagnetStatus(snark.getInfoHash());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -914,6 +919,28 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just remember we have it
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void saveMagnetStatus(byte[] ih) {
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.setProperty(PROP_META_MAGNET_PREFIX + infohash, ".");
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the magnet marker from the config file.
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void removeMagnetStatus(byte[] ih) {
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.remove(PROP_META_MAGNET_PREFIX + infohash);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not really delete on failure, that's the caller's responsibility.
|
||||
* Warning - does not validate announce URL - use TrackerClient.isValidAnnounce()
|
||||
@ -1032,12 +1059,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
//start magnets
|
||||
|
||||
|
||||
// here because we need to delay until I2CP is up
|
||||
// although the user will see the default until then
|
||||
getBWLimit();
|
||||
boolean doMagnets = true;
|
||||
while (true) {
|
||||
File dir = getDataDir();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -1050,6 +1075,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} catch (Exception e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
if (doMagnets) {
|
||||
addMagnets();
|
||||
doMagnets = false;
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@ -1090,9 +1119,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* and save the data to disk.
|
||||
* A Snark.CompleteListener method.
|
||||
*
|
||||
* @return the new name for the torrent or null on error
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void gotMetaInfo(Snark snark) {
|
||||
public String gotMetaInfo(Snark snark) {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
Storage storage = snark.getStorage();
|
||||
if (meta != null && storage != null) {
|
||||
@ -1100,23 +1130,51 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (rejectMessage != null) {
|
||||
addMessage(rejectMessage);
|
||||
snark.stopTorrent();
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
|
||||
String name = (new File(getDataDir(), storage.getBaseName() + ".torrent")).getAbsolutePath();
|
||||
try {
|
||||
synchronized (_snarks) {
|
||||
locked_writeMetaInfo(meta, name);
|
||||
// put it in the list under the new name
|
||||
_snarks.remove(snark.getName());
|
||||
_snarks.put(name, snark);
|
||||
}
|
||||
_magnets.remove(snark.getName());
|
||||
removeMagnetStatus(snark.getInfoHash());
|
||||
addMessage(_("Metainfo received for {0}", snark.getName()));
|
||||
addMessage(_("Starting up torrent {0}", storage.getBaseName()));
|
||||
return name;
|
||||
} catch (IOException ioe) {
|
||||
addMessage(_("Failed to copy torrent file to {0}", name));
|
||||
_log.error("Failed to write torrent file", ioe);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// End Snark.CompleteListeners
|
||||
|
||||
/**
|
||||
* Add all magnets from the config file
|
||||
* @since 0.8.4
|
||||
*/
|
||||
private void addMagnets() {
|
||||
for (Object o : _config.keySet()) {
|
||||
String k = (String) o;
|
||||
if (k.startsWith(PROP_META_MAGNET_PREFIX)) {
|
||||
String b64 = k.substring(PROP_META_MAGNET_PREFIX.length());
|
||||
b64 = b64.replace('$', '=');
|
||||
byte[] ih = Base64.decode(b64);
|
||||
// ignore value
|
||||
if (ih != null && ih.length == 20)
|
||||
addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, false);
|
||||
// else remove from config?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void monitorTorrents(File dir) {
|
||||
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
|
||||
List<String> foundNames = new ArrayList(0);
|
||||
|
@ -173,7 +173,8 @@ public class TrackerClient extends I2PAppThread
|
||||
continue;
|
||||
if (primary.startsWith("http://i2p/" + dest))
|
||||
continue;
|
||||
trackers.add(new Tracker(url, false));
|
||||
// opentrackers are primary if we don't have primary
|
||||
trackers.add(new Tracker(url, primary.equals("")));
|
||||
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
}
|
||||
@ -238,7 +239,7 @@ public class TrackerClient extends I2PAppThread
|
||||
|
||||
uploaded = coordinator.getUploaded();
|
||||
downloaded = coordinator.getDownloaded();
|
||||
left = coordinator.getLeft();
|
||||
left = coordinator.getLeft(); // -1 in magnet mode
|
||||
|
||||
// First time we got a complete download?
|
||||
String event;
|
||||
@ -289,7 +290,7 @@ public class TrackerClient extends I2PAppThread
|
||||
}
|
||||
}
|
||||
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
if ( (left != 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List<Peer> ordered = new ArrayList(peers);
|
||||
@ -344,7 +345,7 @@ public class TrackerClient extends I2PAppThread
|
||||
// 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())
|
||||
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
numwant = 1;
|
||||
else
|
||||
numwant = _util.getMaxConnections();
|
||||
@ -362,7 +363,7 @@ public class TrackerClient extends I2PAppThread
|
||||
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));
|
||||
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
|
||||
}
|
||||
Collections.shuffle(peers, r);
|
||||
Iterator<Peer> it = peers.iterator();
|
||||
@ -370,7 +371,7 @@ public class TrackerClient extends I2PAppThread
|
||||
Peer cur = it.next();
|
||||
if (coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay *= r.nextInt(10);
|
||||
delay += DELAY_MIN;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
@ -418,6 +419,8 @@ public class TrackerClient extends I2PAppThread
|
||||
long downloaded, long left, String event)
|
||||
throws IOException
|
||||
{
|
||||
// What do we send for left in magnet mode? Can we omit it?
|
||||
long tleft = left >= 0 ? left : 1;
|
||||
String s = tr.announce
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
@ -425,10 +428,10 @@ public class TrackerClient extends I2PAppThread
|
||||
+ "&ip=" + _util.getOurIPString() + ".i2p"
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ "&left=" + tleft
|
||||
+ "&compact=1" // NOTE: opentracker will return 400 for &compact alone
|
||||
+ ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
|
||||
if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
s += "&numwant=0";
|
||||
else
|
||||
s += "&numwant=" + _util.getMaxConnections();
|
||||
@ -445,7 +448,7 @@ public class TrackerClient extends I2PAppThread
|
||||
in = new FileInputStream(fetched);
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, snark.getID(),
|
||||
snark.getMetaInfo());
|
||||
snark.getInfoHash(), snark.getMetaInfo());
|
||||
_util.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
|
@ -46,19 +46,20 @@ public class TrackerInfo
|
||||
private int complete;
|
||||
private int incomplete;
|
||||
|
||||
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
/** @param metainfo may be null */
|
||||
public TrackerInfo(InputStream in, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(new BDecoder(in), my_id, metainfo);
|
||||
this(new BDecoder(in), my_id, infohash, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
private TrackerInfo(BDecoder be, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(be.bdecodeMap().getMap(), my_id, metainfo);
|
||||
this(be.bdecodeMap().getMap(), my_id, infohash, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
|
||||
private TrackerInfo(Map m, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
BEValue reason = (BEValue)m.get("failure reason");
|
||||
@ -84,10 +85,10 @@ public class TrackerInfo
|
||||
Set<Peer> p;
|
||||
try {
|
||||
// One big string (the official compact format)
|
||||
p = getPeers(bePeers.getBytes(), my_id, metainfo);
|
||||
p = getPeers(bePeers.getBytes(), my_id, infohash, metainfo);
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// List of Dictionaries or List of Strings
|
||||
p = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
p = getPeers(bePeers.getList(), my_id, infohash, metainfo);
|
||||
}
|
||||
peers = p;
|
||||
}
|
||||
@ -123,7 +124,7 @@ public class TrackerInfo
|
||||
******/
|
||||
|
||||
/** List of Dictionaries or List of Strings */
|
||||
private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo)
|
||||
private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
Set<Peer> peers = new HashSet(l.size());
|
||||
@ -144,7 +145,7 @@ public class TrackerInfo
|
||||
continue;
|
||||
}
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||
peers.add(new Peer(peerID, my_id, infohash, metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
@ -156,7 +157,7 @@ public class TrackerInfo
|
||||
* One big string of concatenated 32-byte hashes
|
||||
* @since 0.8.1
|
||||
*/
|
||||
private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo)
|
||||
private static Set<Peer> getPeers(byte[] l, byte[] my_id, byte[] infohash, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
int count = l.length / HASH_LENGTH;
|
||||
@ -172,7 +173,7 @@ public class TrackerInfo
|
||||
// won't happen
|
||||
continue;
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo.getInfoHash(), metainfo));
|
||||
peers.add(new Peer(peerID, my_id, infohash, metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
|
@ -24,6 +24,8 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
* You need to call the correct get method to get the correct java
|
||||
@ -180,10 +182,14 @@ public class BEValue
|
||||
{
|
||||
byte[] bs = (byte[])value;
|
||||
// XXX - Stupid heuristic... and not UTF-8
|
||||
if (bs.length <= 12)
|
||||
valueString = new String(bs);
|
||||
//if (bs.length <= 12)
|
||||
// valueString = new String(bs);
|
||||
//else
|
||||
// valueString = "bytes:" + bs.length;
|
||||
if (bs.length <= 32)
|
||||
valueString = bs.length + " bytes: " + Base64.encode(bs);
|
||||
else
|
||||
valueString = "bytes:" + bs.length;
|
||||
valueString = bs.length + " bytes";
|
||||
}
|
||||
else
|
||||
valueString = value.toString();
|
||||
|
@ -1314,15 +1314,17 @@ public class I2PSnarkServlet extends Default {
|
||||
out.write(" ");
|
||||
out.write(renderOptions(0, 4, options.remove("outbound.length"), "outbound.length", HOP));
|
||||
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP host"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ _manager.util().getI2CPHost() + "\" size=\"15\" > ");
|
||||
if (!_context.isRouterContext()) {
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP host"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ _manager.util().getI2CPHost() + "\" size=\"15\" > ");
|
||||
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP port"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
|
||||
+ _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP port"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
|
||||
+ _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
|
||||
}
|
||||
|
||||
StringBuilder opts = new StringBuilder(64);
|
||||
for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {
|
||||
|
Reference in New Issue
Block a user