diff --git a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java index 5ebad97889e328d2b6141b5d2eb6e288aa8d0af9..2bf4f2d41de6af92d143e9015104dc8ca01e9353 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java +++ b/apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java @@ -55,8 +55,9 @@ public class I2PSnarkUtil { private String _i2cpHost; private int _i2cpPort; private final Map<String, String> _opts; - private I2PSocketManager _manager; + private volatile I2PSocketManager _manager; private boolean _configured; + private volatile boolean _connecting; private final Set<Hash> _shitlist; private int _maxUploaders; private int _maxUpBW; @@ -198,6 +199,7 @@ public class I2PSnarkUtil { */ synchronized public boolean connect() { if (_manager == null) { + _connecting = true; // try to find why reconnecting after stop if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting to I2P", new Exception("I did it")); @@ -237,6 +239,7 @@ public class I2PSnarkUtil { if (opts.getProperty("i2p.streaming.maxConnsPerHour") == null) opts.setProperty("i2p.streaming.maxConnsPerHour", "20"); _manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts); + _connecting = false; } // FIXME this only instantiates krpc once, left stuck with old manager //if (ENABLE_DHT && _manager != null && _dht == null) @@ -252,6 +255,9 @@ public class I2PSnarkUtil { public boolean connected() { return _manager != null; } + /** @since 0.9.1 */ + public boolean isConnecting() { return _manager == null && _connecting; } + /** * For FetchAndAdd * @return null if not connected diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 682fcf35e7a113aba3c1d8a1585aa27836021433..acf5f15ed5796b9ef2ab4bf9e33cd1a3fc5bccca 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -250,6 +250,7 @@ public class Snark private String rootDataDir = "."; private final CompleteListener completeListener; private boolean stopped; + private boolean starting; private byte[] id; private byte[] infoHash; private String additionalTrackerURL; @@ -509,9 +510,19 @@ public class Snark } /** - * Start up contacting peers and querying the tracker + * Start up contacting peers and querying the tracker. + * Blocks if tunnel is not yet open. */ - public void startTorrent() { + public synchronized void startTorrent() { + starting = true; + try { + x_startTorrent(); + } finally { + starting = false; + } + } + + private void x_startTorrent() { boolean ok = _util.connect(); if (!ok) fatal("Unable to connect to I2P"); if (coordinator == null) { @@ -585,7 +596,7 @@ public class Snark * @param fast if true, limit the life of the unannounce threads * @since 0.9.1 */ - public void stopTorrent(boolean fast) { + public synchronized void stopTorrent(boolean fast) { stopped = true; TrackerClient tc = trackerclient; if (tc != null) @@ -680,6 +691,22 @@ public class Snark return stopped; } + /** + * Startup in progress. + * @since 0.9.1 + */ + public boolean isStarting() { + return starting && stopped; + } + + /** + * Set startup in progress. + * @since 0.9.1 + */ + public void setStarting() { + starting = true; + } + /** * @since 0.8.4 */ diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 5c70cd0ea78b84e658d051b1f392ca9f25105f48..f79c13b815c12a016acb55d5ce657d690c48fa17 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -867,7 +867,7 @@ public class SnarkManager implements Snark.CompleteListener { torrent.startTorrent(); addMessage(_("Fetching {0}", name)); boolean haveSavedPeers = false; - if ((!util().connected()) && !haveSavedPeers) { + 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)); } @@ -1599,6 +1599,81 @@ public class SnarkManager implements Snark.CompleteListener { } } + /** + * If not connected, thread it, otherwise inline + * @since 0.9.1 + */ + public void startTorrent(byte[] infoHash) { + for (Snark snark : _snarks.values()) { + if (DataHelper.eq(infoHash, snark.getInfoHash())) { + if (snark.isStarting() || !snark.isStopped()) { + addMessage("Torrent already started"); + return; + } + boolean connected = _util.connected(); + if ((!connected) && !_util.isConnecting()) + addMessage(_("Opening the I2P tunnel")); + addMessage(_("Starting up torrent {0}", snark.getBaseName())); + if (connected) { + snark.startTorrent(); + } else { + // mark it for the UI + snark.setStarting(); + (new I2PAppThread(new ThreadedStarter(snark), "TorrentStarter", true)).start(); + try { Thread.sleep(200); } catch (InterruptedException ie) {} + } + return; + } + } + addMessage("Torrent not found?"); + } + + /** + * If not connected, thread it, otherwise inline + * @since 0.9.1 + */ + public void startAllTorrents() { + if (_util.connected()) { + startAll(); + } else { + addMessage(_("Opening the I2P tunnel and starting all torrents.")); + for (Snark snark : _snarks.values()) { + // mark it for the UI + snark.setStarting(); + } + (new I2PAppThread(new ThreadedStarter(null), "TorrentStarterAll", true)).start(); + try { Thread.sleep(200); } catch (InterruptedException ie) {} + } + } + + /** + * Use null constructor param for all + * @since 0.9.1 + */ + private class ThreadedStarter implements Runnable { + private final Snark snark; + public ThreadedStarter(Snark s) { snark = s; } + public void run() { + if (snark != null) { + if (snark.isStopped()) + snark.startTorrent(); + } else { + startAll(); + } + } + } + + /** + * Inline + * @since 0.9.1 + */ + private void startAll() { + for (Snark snark : _snarks.values()) { + if (snark.isStopped()) + snark.startTorrent(); + } + } + /** * Stop all running torrents, and close the tunnel after a delay * to allow for announces. @@ -1631,7 +1706,7 @@ public class SnarkManager implements Snark.CompleteListener { // Schedule this even for final shutdown, as there's a chance // that it's just this webapp that is stopping. SimpleScheduler.getInstance().addEvent(new Disconnector(), 60*1000); - addMessage(_("Closing I2P tunnel after announces to trackers.")); + addMessage(_("Closing I2P tunnel after notifying trackers.")); if (finalShutdown) { try { Thread.sleep(5*1000); } catch (InterruptedException ie) {} } diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index fbc6cb18d88067d72734b0439778a23d8cc90e23..8a018e59f8f7d53798ea38edf9d2df84d85e8ebe 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -422,7 +422,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("\">"); if (isDegraded) out.write("</a>"); - } else if (!snarks.isEmpty()) { + } else if ((!_manager.util().isConnecting()) && !snarks.isEmpty()) { if (isDegraded) out.write("<a href=\"/i2psnark/?action=StartAll&nonce=" + _nonce + "\"><img title=\""); else @@ -573,14 +573,7 @@ public class I2PSnarkServlet extends DefaultServlet { if (torrent != null) { byte infoHash[] = Base64.decode(torrent); if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1 - for (String name : _manager.listTorrentFiles()) { - Snark snark = _manager.getTorrent(name); - if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) { - snark.startTorrent(); - _manager.addMessage(_("Starting up torrent {0}", snark.getBaseName())); - break; - } - } + _manager.startTorrent(infoHash); } } } else if (action.startsWith("Remove_")) { @@ -747,13 +740,7 @@ public class I2PSnarkServlet extends DefaultServlet { } else if ("StopAll".equals(action)) { _manager.stopAllTorrents(false); } else if ("StartAll".equals(action)) { - _manager.addMessage(_("Opening the I2P tunnel and starting all torrents.")); - List<Snark> snarks = getSortedSnarks(req); - for (int i = 0; i < snarks.size(); i++) { - Snark snark = snarks.get(i); - if (snark.isStopped()) - snark.startTorrent(); - } + _manager.startAllTorrents(); } else if ("Clear".equals(action)) { _manager.clearMessages(); } else { @@ -989,6 +976,8 @@ public class I2PSnarkServlet extends DefaultServlet { statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") + "<br>" + err; } + } else if (snark.isStarting()) { + statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Starting"); } else if (remaining == 0 || needed == 0) { // < 0 means no meta size yet // partial complete or seeding if (isRunning) { @@ -1141,6 +1130,7 @@ public class I2PSnarkServlet extends DefaultServlet { if (showPeers) parameters = parameters + "&p=1"; if (isRunning) { + // Stop Button if (isDegraded) out.write("<a href=\"/i2psnark/?action=Stop_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else @@ -1151,7 +1141,8 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("\">"); if (isDegraded) out.write("</a>"); - } else { + } else if (!snark.isStarting()) { + // Start Button // This works in Opera but it's displayed a little differently, so use noThinsp here too so all 3 icons are consistent if (noThinsp) out.write("<a href=\"/i2psnark/?action=Start_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); @@ -1165,6 +1156,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("</a>"); if (isValid) { + // Remove Button // Doesnt work with Opera so use noThinsp instead of isDegraded if (noThinsp) out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); @@ -1184,6 +1176,7 @@ public class I2PSnarkServlet extends DefaultServlet { out.write("</a>"); } + // Delete Button // Doesnt work with Opera so use noThinsp instead of isDegraded if (noThinsp) out.write("<a href=\"/i2psnark/?action=Delete_" + b64 + "&nonce=" + _nonce + "\"><img title=\"");