diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 58f27742b..9d0b3de32 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -70,6 +70,7 @@ public class I2PSnarkUtil { private boolean _areFilesPublic; private List _openTrackers; private DHT _dht; + private long _startedTime; private static final int EEPGET_CONNECT_TIMEOUT = 45*1000; private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000; @@ -260,6 +261,8 @@ public class I2PSnarkUtil { if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null) opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519"); _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts); + if (_manager != null) + _startedTime = _context.clock().now(); _connecting = false; } if (_shouldUseDHT && _manager != null && _dht == null) @@ -295,6 +298,7 @@ public class I2PSnarkUtil { _dht.stop(); _dht = null; } + _startedTime = 0; I2PSocketManager mgr = _manager; // FIXME this can cause race NPEs elsewhere _manager = null; @@ -310,6 +314,16 @@ public class I2PSnarkUtil { _tmpDir.mkdirs(); } + /** + * When did we connect to the network? + * For RPC + * @return 0 if not connected + * @since 0.9.30 + */ + public long getStartedTime() { + return _startedTime; + } + /** connect to the given destination */ I2PSocket connect(PeerID peer) throws IOException { I2PSocketManager mgr = _manager; diff --git a/apps/i2psnark/java/src/org/klomp/snark/Peer.java b/apps/i2psnark/java/src/org/klomp/snark/Peer.java index 0b38953b7..d89aa6612 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Peer.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Peer.java @@ -89,6 +89,7 @@ public class Peer implements Comparable */ //private static final long OPTION_AZMP = 0x1000000000000000l; private long options; + private final boolean _isIncoming; /** * Outgoing connection. @@ -103,6 +104,7 @@ public class Peer implements Comparable this.infohash = infohash; this.metainfo = metainfo; _id = __id.incrementAndGet(); + _isIncoming = false; //_log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating")); } @@ -130,6 +132,16 @@ public class Peer implements Comparable _id = __id.incrementAndGet(); if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id)); + _isIncoming = true; + } + + /** + * Is this an incoming connection? + * For RPC + * @since 0.9.30 + */ + public boolean isIncoming() { + return _isIncoming; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 74f64a34c..0c79af398 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PServerSocket; @@ -238,7 +239,8 @@ public class Snark // String indicating main activity private volatile String activity = "Not started"; private final long savedUploaded; - + private static final AtomicInteger __RPCID = new AtomicInteger(); + private final int _rpcID = __RPCID.incrementAndGet(); /** * from main() via parseArguments() single torrent @@ -1364,4 +1366,13 @@ public class Snark long limit = 1024l * _util.getMaxUpBW(); return total > limit; } + + /** + * A unique ID for this torrent, useful for RPC + * @return positive value unless you wrap around + * @since 0.9.30 + */ + public int getRPCID() { + return _rpcID; + } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 42a558f5e..039db6af7 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -27,7 +27,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; +import net.i2p.app.ClientApp; import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientAppState; import net.i2p.crypto.SHA1Hash; import net.i2p.crypto.SigType; import net.i2p.data.Base64; @@ -51,7 +53,7 @@ import org.klomp.snark.dht.KRPC; /** * Manage multiple snarks */ -public class SnarkManager implements CompleteListener { +public class SnarkManager implements CompleteListener, ClientApp { /** * Map of (canonical) filename of the .torrent file to Snark instance. @@ -246,6 +248,13 @@ public class SnarkManager implements CompleteListener { */ public void start() { _running = true; + if ("i2psnark".equals(_contextName)) { + // Register with the ClientAppManager so the rpc plugin can find us + // only if default instance + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) + cmgr.register(this); + } _peerCoordinatorSet = new PeerCoordinatorSet(); _connectionAcceptor = new ConnectionAcceptor(_util, _peerCoordinatorSet); _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); @@ -315,6 +324,12 @@ public class SnarkManager implements CompleteListener { _connectionAcceptor.halt(); _idleChecker.cancel(); stopAllTorrents(true); + if ("i2psnark".equals(_contextName)) { + // only if default instance + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) + cmgr.unregister(this); + } if (_log.shouldWarn()) _log.warn("Snark stop() end"); } @@ -322,6 +337,46 @@ public class SnarkManager implements CompleteListener { /** @since 0.9.1 */ public boolean isStopping() { return _stopping; } + /** + * ClientApp method. Does nothing. + * Doesn't matter, we are only registering. + * @since 0.9.30 + */ + public void startup() {} + + /** + * ClientApp method. Does nothing. + * Doesn't matter, we are only registering. + * @since 0.9.30 + */ + public void shutdown(String[] args) {} + + /** + * ClientApp method. + * Doesn't matter, we are only registering. + * @return INITIALIZED always. + * @since 0.9.30 + */ + public ClientAppState getState() { + return ClientAppState.INITIALIZED; + } + + /** + * ClientApp method. + * @since 0.9.30 + */ + public String getName() { + return "i2psnark"; + } + + /** + * ClientApp method. + * @since 0.9.30 + */ + public String getDisplayName() { + return "i2psnark: " + _contextPath; + } + /** hook to I2PSnarkUtil for the servlet */ public I2PSnarkUtil util() { return _util; } @@ -440,6 +495,14 @@ public class SnarkManager implements CompleteListener { return f; } + /** + * For RPC + * @since 0.9.30 + */ + public File getConfigDir() { + return _configDir; + } + /** * Migrate the old flat config file to the new config dir * containing the config file minus the per-torrent entries, @@ -1526,9 +1589,9 @@ public class SnarkManager implements CompleteListener { * Called from servlet. This is only for the 'create torrent' form. * * @param metainfo the metainfo for the torrent - * @param bitfield the current completion status of the torrent + * @param bitfield the current completion status of the torrent, or null * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent - * Must be a filesystem-safe name. + * Must be a filesystem-safe name. If null, will generate a name from the metainfo. * @param baseFile may be null, if so look in rootDataDir * @throws RuntimeException via Snark.fatal() * @return success @@ -1542,10 +1605,18 @@ public class SnarkManager implements CompleteListener { if (snark != null) { addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName())); return false; - } else { + } else if (bitfield != null) { saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0, true); // no file priorities } // so addTorrent won't recheck + if (filename == null) { + File f = new File(getDataDir(), Storage.filterName(metainfo.getName()) + ".torrent"); + if (f.exists()) { + addMessage(_t("Failed to copy torrent file to {0}", f.getAbsolutePath())); + _log.error("Torrent file already exists: " + f); + } + filename = f.getAbsolutePath(); + } try { locked_writeMetaInfo(metainfo, filename, areFilesPublic()); // hold the lock for a long time diff --git a/history.txt b/history.txt index 108934a31..63bb87d16 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,6 @@ +2017-03-20 zzz + * i2psnark: Enhancements to support RPC plugin + 2017-03-18 zzz * Addressbook (ticket #1966): - Build as jar, not war diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index fecba78d6..a6204817e 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 7; + public final static long BUILD = 8; /** for example "-test" */ public final static String EXTRA = "";