From ebe7f3b127f81049c7e71351d06fdc770c91e8e6 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 20 Dec 2010 18:55:10 +0000 Subject: [PATCH] UI adjustments when no metainfo yet --- .../java/src/org/klomp/snark/Snark.java | 8 +- .../src/org/klomp/snark/SnarkManager.java | 66 ++++++--- .../src/org/klomp/snark/TrackerClient.java | 17 ++- .../org/klomp/snark/web/I2PSnarkServlet.java | 126 ++++++++++-------- 4 files changed, 140 insertions(+), 77 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index 2460e9f530..27e38b5ca0 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -531,6 +531,7 @@ public class Snark // single torrent acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator)); } + // TODO pass saved closest DHT nodes to the tracker? or direct to the coordinator? trackerclient = new TrackerClient(_util, meta, coordinator, this); } @@ -781,8 +782,11 @@ public class Snark public long getNeeded() { if (storage != null) return storage.needed(); - // FIXME else return metainfo length if available - return -1; + if (meta != null) + // FIXME subtract chunks we have + return meta.getTotalLength(); + // FIXME fake + return 16 * 16 * 1024; } /** diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 270937688e..24e8ebc7f6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -20,6 +20,7 @@ import java.util.Collection; import net.i2p.I2PAppContext; import net.i2p.data.Base64; import net.i2p.data.DataHelper; +import net.i2p.util.ConcurrentHashSet; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; import net.i2p.util.OrderedProperties; @@ -34,6 +35,8 @@ public class SnarkManager implements Snark.CompleteListener { /** map of (canonical) filename of the .torrent file to Snark instance (unsynchronized) */ private final Map<String, Snark> _snarks; + /** used to prevent DirMonitor from deleting torrents that don't have a torrent file yet */ + private final Set<String> _magnets; private final Object _addSnarkLock; private /* FIXME final FIXME */ File _configFile; private Properties _config; @@ -72,6 +75,7 @@ public class SnarkManager implements Snark.CompleteListener { public static final int DEFAULT_STARTUP_DELAY = 3; private SnarkManager() { _snarks = new HashMap(); + _magnets = new ConcurrentHashSet(); _addSnarkLock = new Object(); _context = I2PAppContext.getGlobalContext(); _log = _context.logManager().getLog(SnarkManager.class); @@ -90,8 +94,6 @@ public class SnarkManager implements Snark.CompleteListener { _running = true; _peerCoordinatorSet = new PeerCoordinatorSet(); _connectionAcceptor = new ConnectionAcceptor(_util); - int minutes = getStartupDelayMinutes(); - _messages.add(_("Adding torrents in {0} minutes", minutes)); _monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true); _monitor.start(); _context.addShutdownTask(new SnarkManagerShutdown()); @@ -321,7 +323,7 @@ public class SnarkManager implements Snark.CompleteListener { _util.setStartupDelay(minutes); changed = true; _config.setProperty(PROP_STARTUP_DELAY, "" + minutes); - addMessage(_("Startup delay limit changed to {0} minutes", minutes)); + addMessage(_("Startup delay changed to {0}", DataHelper.formatDuration2(minutes * 60 * 1000))); } } @@ -549,6 +551,7 @@ public class SnarkManager implements Snark.CompleteListener { addMessage(rejectMessage); return; } else { + // TODO load saved closest DHT nodes and pass to the Snark ? torrent = new Snark(_util, filename, null, -1, null, null, this, _peerCoordinatorSet, _connectionAcceptor, false, dataDir.getPath()); @@ -583,6 +586,7 @@ public class SnarkManager implements Snark.CompleteListener { * * @param name hex or b32 name from the magnet link * @param ih 20 byte info hash + * @throws RuntimeException via Snark.fatal() * @since 0.8.4 */ public void addMagnet(String name, byte[] ih) { @@ -590,7 +594,8 @@ public class SnarkManager implements Snark.CompleteListener { _peerCoordinatorSet, _connectionAcceptor, false, getDataDir().getPath()); - // TODO tell the dir monitor not to delete us + // Tell the dir monitor not to delete us + _magnets.add(name); synchronized (_snarks) { _snarks.put(name, torrent); } @@ -608,12 +613,17 @@ public class SnarkManager implements Snark.CompleteListener { } /** - * Delete a torrent with the info hash alone (magnet / maggot) + * Stop and delete a torrent running in magnet mode * - * @param ih 20 byte info hash + * @param snark a torrent with a fake file name ("Magnet xxxx") * @since 0.8.4 */ - public void deleteMagnet(byte[] ih) { + public void deleteMagnet(Snark snark) { + synchronized (_snarks) { + _snarks.remove(snark.getName()); + } + snark.stopTorrent(); + _magnets.remove(snark.getName()); } /** @@ -748,6 +758,8 @@ public class SnarkManager implements Snark.CompleteListener { _config.remove(prop); } + // TODO save closest DHT nodes too + saveConfig(); } @@ -828,6 +840,23 @@ public class SnarkManager implements Snark.CompleteListener { } return torrent; } + + /** + * Stop the torrent, leaving it on the list of torrents unless told to remove it + * @since 0.8.4 + */ + public void stopTorrent(Snark torrent, boolean shouldRemove) { + if (shouldRemove) { + synchronized (_snarks) { + _snarks.remove(torrent.getName()); + } + } + boolean wasStopped = torrent.isStopped(); + torrent.stopTorrent(); + if (!wasStopped) + addMessage(_("Torrent stopped: \"{0}\"", torrent.getBaseName())); + } + /** * Stop the torrent and delete the torrent file itself, but leaving the data * behind. @@ -846,11 +875,16 @@ public class SnarkManager implements Snark.CompleteListener { private class DirMonitor implements Runnable { public void run() { - try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {} - // the first message was a "We are starting up in 1m" - synchronized (_messages) { - if (_messages.size() == 1) - _messages.remove(0); + // don't bother delaying if auto start is false + long delay = 60 * 1000 * getStartupDelayMinutes(); + if (delay > 0 && shouldAutoStart()) { + _messages.add(_("Adding torrents in {0}", DataHelper.formatDuration2(delay))); + try { Thread.sleep(delay); } catch (InterruptedException ie) {} + // the first message was a "We are starting up in 1m" + synchronized (_messages) { + if (_messages.size() == 1) + _messages.remove(0); + } } // here because we need to delay until I2CP is up @@ -922,6 +956,8 @@ public class SnarkManager implements Snark.CompleteListener { } } } + // Don't remove magnet torrents that don't have a torrent file yet + existingNames.removeAll(_magnets); // now lets see which ones have been removed... for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) { String name = (String)iter.next(); @@ -975,12 +1011,12 @@ public class SnarkManager implements Snark.CompleteListener { /** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */ public static final String PROP_TRACKERS = "i2psnark.trackers"; - private static Map trackerMap = null; + private static Map<String, String> trackerMap = null; /** sorted map of name to announceURL=baseURL */ - public Map getTrackers() { + public Map<String, String> getTrackers() { if (trackerMap != null) // only do this once, can't be updated while running return trackerMap; - Map rv = new TreeMap(); + Map<String, String> rv = new TreeMap(); String trackers = _config.getProperty(PROP_TRACKERS); if ( (trackers == null) || (trackers.trim().length() <= 0) ) trackers = _context.getProperty(PROP_TRACKERS); diff --git a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java index d14abaa0c9..f437beeeb6 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java +++ b/apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java @@ -126,10 +126,9 @@ public class TrackerClient extends I2PAppThread @Override public void run() { - String infoHash = urlencode(meta.getInfoHash()); + String infoHash = urlencode(snark.getInfoHash()); String peerID = urlencode(snark.getID()); - _log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash); // Construct the list of trackers for this torrent, // starting with the primary one listed in the metainfo, @@ -138,12 +137,18 @@ public class TrackerClient extends I2PAppThread // the primary tracker, that we don't add it twice. // todo: check for b32 matches as well trackers = new ArrayList(2); - String primary = meta.getAnnounce(); - if (isValidAnnounce(primary)) { - trackers.add(new Tracker(meta.getAnnounce(), true)); + String primary; + if (meta != null) { + primary = meta.getAnnounce(); + if (isValidAnnounce(primary)) { + trackers.add(new Tracker(meta.getAnnounce(), true)); + } else { + _log.warn("Skipping invalid or non-i2p announce: " + primary); + } } else { - _log.warn("Skipping invalid or non-i2p announce: " + primary); + primary = ""; } + _log.debug("Announce: [" + primary + "] infoHash: " + infoHash); List tlist = _util.getOpenTrackers(); if (tlist != null) { for (int i = 0; i < tlist.size(); i++) { 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 917652bc04..c6ba8b9fd8 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -462,7 +462,7 @@ public class I2PSnarkServlet extends Default { } else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) { addMagnet(newURL); } else { - _manager.addMessage(_("Invalid URL - must start with http://, {0} or {1}", MAGNET, MAGGOT)); + _manager.addMessage(_("Invalid URL: Must start with \"http://\", \"{0}\", or \"{1}\"", MAGNET, MAGGOT)); } } else { // no file or URL specified @@ -476,7 +476,7 @@ public class I2PSnarkServlet extends Default { String name = (String)iter.next(); Snark snark = _manager.getTorrent(name); if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) { - _manager.stopTorrent(name, false); + _manager.stopTorrent(snark, false); break; } } @@ -506,13 +506,14 @@ public class I2PSnarkServlet extends Default { String name = (String)iter.next(); Snark snark = _manager.getTorrent(name); if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) { - _manager.stopTorrent(name, true); MetaInfo meta = snark.getMetaInfo(); if (meta == null) { - // magnet - _manager.deleteMagnet(snark.getInfoHash()); + // magnet - remove and delete are the same thing + _manager.deleteMagnet(snark); + _manager.addMessage(_("Magnet deleted: {0}", name)); return; } + _manager.stopTorrent(snark, true); // should we delete the torrent file? // yeah, need to, otherwise it'll get autoadded again (at the moment File f = new File(name); @@ -532,13 +533,14 @@ public class I2PSnarkServlet extends Default { String name = (String)iter.next(); Snark snark = _manager.getTorrent(name); if ( (snark != null) && (DataHelper.eq(infoHash, snark.getInfoHash())) ) { - _manager.stopTorrent(name, true); MetaInfo meta = snark.getMetaInfo(); if (meta == null) { - // magnet - _manager.deleteMagnet(snark.getInfoHash()); + // magnet - remove and delete are the same thing + _manager.deleteMagnet(snark); + _manager.addMessage(_("Magnet deleted: {0}", name)); return; } + _manager.stopTorrent(snark, true); File f = new File(name); f.delete(); _manager.addMessage(_("Torrent file deleted: {0}", f.getAbsolutePath())); @@ -635,7 +637,7 @@ public class I2PSnarkServlet extends Default { for (int i = 0; i < snarks.size(); i++) { Snark snark = (Snark)snarks.get(i); if (!snark.isStopped()) - _manager.stopTorrent(snark.getName(), false); + _manager.stopTorrent(snark, false); } if (_manager.util().connected()) { // Give the stopped announces time to get out @@ -750,8 +752,9 @@ public class I2PSnarkServlet extends Default { stats[5] += total; MetaInfo meta = snark.getMetaInfo(); + // isValid means isNotMagnet boolean isValid = meta != null; - boolean singleFile = (!isValid) || meta.getFiles() == null; + boolean isMultiFile = isValid && meta.getFiles() != null; String err = snark.getTrackerProblems(); int curPeers = snark.getPeerCount(); @@ -776,7 +779,7 @@ public class I2PSnarkServlet extends Default { statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") + "<br>" + err; } - } else if (remaining <= 0) { + } else if (remaining == 0) { // < 0 means no meta size yet if (isRunning && curPeers > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + "\">" + @@ -822,9 +825,12 @@ public class I2PSnarkServlet extends Default { out.write("<td class=\"" + rowClass + "\">"); // temporarily hardcoded for postman* and anonymity, requires bytemonsoon patch for lookup by info_hash - String announce = meta.getAnnounce(); - if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr") || - announce.startsWith("http://lnQ6yoBT") || announce.startsWith("http://tracker2.postman.i2p/") || announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/")) { + String announce = null; + if (isValid) + announce = meta.getAnnounce(); + if (announce != null && (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr") || + announce.startsWith("http://lnQ6yoBT") || announce.startsWith("http://tracker2.postman.i2p/") || + announce.startsWith("http://ahsplxkbhemefwvvml7qovzl5a2b5xo5i7lyai7ntdunvcyfdtna.b32.i2p/"))) { Map trackers = _manager.getTrackers(); for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry)iter.next(); @@ -849,13 +855,13 @@ public class I2PSnarkServlet extends Default { out.write("</td>\n<td class=\"" + rowClass + "\">"); StringBuilder buf = null; - if (remaining == 0 || meta.getFiles() != null) { + if (remaining == 0 || isMultiFile) { buf = new StringBuilder(128); buf.append("<a href=\"").append(snark.getBaseName()); - if (meta.getFiles() != null) + if (isMultiFile) buf.append('/'); buf.append("\" title=\""); - if (meta.getFiles() != null) + if (isMultiFile) buf.append(_("View files")); else buf.append(_("Open file")); @@ -863,21 +869,24 @@ public class I2PSnarkServlet extends Default { out.write(buf.toString()); } String icon; - if (meta.getFiles() != null) + if (isMultiFile) icon = "folder"; - else + else if (isValid) icon = toIcon(meta.getName()); - if (remaining == 0 || meta.getFiles() != null) { + else + // todo get a nice magnet icon? + icon = "page_white"; + if (remaining == 0 || isMultiFile) { out.write(toImg(icon, _("Open"))); out.write("</a>"); } else { out.write(toImg(icon)); } out.write("</td><td class=\"snarkTorrentName " + rowClass + "\">"); - if (remaining == 0 || meta.getFiles() != null) + if (remaining == 0 || isMultiFile) out.write(buf.toString()); out.write(filename); - if (remaining == 0 || meta.getFiles() != null) + if (remaining == 0 || isMultiFile) out.write("</a>"); out.write("<td align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">"); @@ -887,19 +896,21 @@ public class I2PSnarkServlet extends Default { out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">"); if (remaining > 0) out.write(formatSize(total-remaining) + thinsp(isDegraded) + formatSize(total)); - else + else if (remaining == 0) out.write(formatSize(total)); // 3GB + else + out.write("??"); // no meta size yet out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentUploaded " + rowClass + "\">"); - if(isRunning) + if(isRunning && isValid) out.write(formatSize(uploaded)); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">"); - if(isRunning && remaining > 0) + if(isRunning && remaining != 0) out.write(formatSize(downBps) + "ps"); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentRateUp\">"); - if(isRunning) + if(isRunning && isValid) out.write(formatSize(upBps) + "ps"); out.write("</td>\n\t"); out.write("<td align=\"center\" class=\"snarkTorrentAction " + rowClass + "\">"); @@ -919,7 +930,6 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("</a>"); } else { - if (isValid) { if (isDegraded) out.write("<a href=\"/i2psnark/?action=Start_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else @@ -930,24 +940,25 @@ public class I2PSnarkServlet extends Default { out.write("\">"); if (isDegraded) out.write("</a>"); - } - if (isDegraded) - out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); - else - out.write("<input type=\"image\" name=\"action\" value=\"Remove_" + b64 + "\" title=\""); - out.write(_("Remove the torrent from the active list, deleting the .torrent file")); - out.write("\" onclick=\"if (!confirm('"); - // Can't figure out how to escape double quotes inside the onclick string. - // Single quotes in translate strings with parameters must be doubled. - // Then the remaining single quite must be escaped - out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename)); - out.write("')) { return false; }\""); - out.write(" src=\"" + _imgPath + "remove.png\" alt=\""); - out.write(_("Remove")); - out.write("\">"); - if (isDegraded) - out.write("</a>"); + if (isValid) { + if (isDegraded) + out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); + else + out.write("<input type=\"image\" name=\"action\" value=\"Remove_" + b64 + "\" title=\""); + out.write(_("Remove the torrent from the active list, deleting the .torrent file")); + out.write("\" onclick=\"if (!confirm('"); + // Can't figure out how to escape double quotes inside the onclick string. + // Single quotes in translate strings with parameters must be doubled. + // Then the remaining single quite must be escaped + out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename)); + out.write("')) { return false; }\""); + out.write(" src=\"" + _imgPath + "remove.png\" alt=\""); + out.write(_("Remove")); + out.write("\">"); + if (isDegraded) + out.write("</a>"); + } if (isDegraded) out.write("<a href=\"/i2psnark/?action=Delete_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); @@ -1002,14 +1013,21 @@ public class I2PSnarkServlet extends Default { out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">"); out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">"); - float pct = (float) (100.0 * (float) peer.completed() / meta.getPieces()); - if (pct == 100.0) - out.write(_("Seed")); - else { - String ps = String.valueOf(pct); - if (ps.length() > 5) - ps = ps.substring(0, 5); - out.write(ps + "%"); + float pct; + if (isValid) { + pct = (float) (100.0 * (float) peer.completed() / meta.getPieces()); + if (pct == 100.0) + out.write(_("Seed")); + else { + String ps = String.valueOf(pct); + if (ps.length() > 5) + ps = ps.substring(0, 5); + out.write(ps + "%"); + } + } else { + pct = (float) 101.0; + // until we get the metainfo we don't know how many pieces there are + out.write("??"); } out.write("</td>\n\t"); out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">"); @@ -1031,7 +1049,7 @@ public class I2PSnarkServlet extends Default { } out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">"); - if (pct != 100.0) { + if (isValid && pct < 100.0) { if (peer.isInterested() && !peer.isChoking()) { out.write("<span class=\"unchoked\">"); out.write(formatSize(peer.getUploadRate()) + "ps</span>"); @@ -1363,7 +1381,7 @@ public class I2PSnarkServlet extends Default { _manager.addMessage(_("Invalid info hash in magnet URL {0}", url)); return; } - _manager.addMagnet(ihash, ih); + _manager.addMagnet(name, ih); } /** copied from ConfigTunnelsHelper */ -- GitLab