From ddc750469c85110344a3a31bd9a315990875acd9 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 6 Oct 2012 13:41:50 +0000 Subject: [PATCH] * i2psnark: - Add allocating and checking indications - Add bandwidth message at startup - More checks at torrent creation --- .../src/org/klomp/snark/PeerCoordinator.java | 3 +- .../java/src/org/klomp/snark/Snark.java | 16 ++++ .../src/org/klomp/snark/SnarkManager.java | 4 +- .../java/src/org/klomp/snark/Storage.java | 88 +++++++++++++++---- .../org/klomp/snark/web/I2PSnarkServlet.java | 13 ++- 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java index 1eb93e1116..2869193a0a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java +++ b/apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java @@ -787,7 +787,8 @@ class PeerCoordinator implements PeerListener } if (record) { if (_log.shouldLog(Log.INFO)) - _log.info(peer + " is now requesting: piece " + piece + " priority " + piece.getPriority()); + _log.info("Now requesting from " + peer + ": piece " + piece + " priority " + piece.getPriority() + + " peers " + piece.getPeerCount() + '/' + peers.size()); piece.setRequested(peer, true); } return piece; diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index c0781d7d8a..f553f54d29 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -688,6 +688,22 @@ public class Snark starting = true; } + /** + * File checking in progress. + * @since 0.9.3 + */ + public boolean isChecking() { + return storage != null && storage.isChecking(); + } + + /** + * Disk allocation (ballooning) in progress. + * @since 0.9.3 + */ + public boolean isAllocating() { + return storage != null && storage.isAllocating(); + } + /** * @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 739b8bc063..e0b56b1409 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -707,7 +707,7 @@ public class SnarkManager implements Snark.CompleteListener { public Properties getConfig() { return _config; } /** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */ - private static final int MAX_FILES_PER_TORRENT = 512; + public static final int MAX_FILES_PER_TORRENT = 512; /** * Set of canonical .torrent filenames that we are dealing with. @@ -1370,6 +1370,8 @@ public class SnarkManager implements Snark.CompleteListener { } catch (Exception e) { _log.error("Error in the DirectoryMonitor", e); } + if (!_snarks.isEmpty()) + addMessage(_("Up bandwidth limit is {0} KBps", _util.getMaxUpBW())); } try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} } diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 5ae23203a0..21e006f5e4 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import net.i2p.crypto.SHA1; import net.i2p.data.ByteArray; @@ -45,7 +46,7 @@ import net.i2p.util.SystemVersion; */ public class Storage { - private MetaInfo metainfo; + private final MetaInfo metainfo; private long[] lengths; private RandomAccessFile[] rafs; private String[] names; @@ -69,6 +70,8 @@ public class Storage private final int pieces; private final long total_length; private boolean changed; + private volatile boolean _isChecking; + private final AtomicInteger _allocateCount = new AtomicInteger(); /** The default piece size. */ private static final int MIN_PIECE_SIZE = 256*1024; @@ -89,17 +92,15 @@ public class Storage * Creates a new storage based on the supplied MetaInfo. This will * try to create and/or check all needed files in the MetaInfo. * - * @exception IOException when creating and/or checking files fails. + * Does not check storage. Caller MUST call check() */ public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener) - throws IOException { _util = util; _log = util.getContext().logManager().getLog(Storage.class); this.metainfo = metainfo; this.listener = listener; needed = metainfo.getPieces(); - _probablyComplete = false; bitfield = new BitField(needed); piece_size = metainfo.getPieceLength(0); pieces = needed; @@ -107,12 +108,15 @@ public class Storage } /** - * Creates a storage from the existing file or directory together - * with an appropriate MetaInfo file as can be announced on the - * given announce String location. + * Creates a storage from the existing file or directory. + * Creates an in-memory metainfo but does not save it to + * a file, caller must do that. + * + * Creates the metainfo, this may take a LONG time. BLOCKING. * * @param announce may be null * @param listener may be null + * @throws IOException when creating and/or checking files fails. */ public Storage(I2PSnarkUtil util, File baseFile, String announce, boolean privateTorrent, StorageListener listener) @@ -135,6 +139,8 @@ public class Storage if (total <= 0) throw new IOException("Torrent contains no data"); + if (total > MAX_TOTAL_SIZE) + throw new IOException("Torrent too big (" + total + " bytes), max is " + MAX_TOTAL_SIZE); int pc_size = MIN_PIECE_SIZE; int pcs = (int) ((total - 1)/pc_size) + 1; @@ -170,6 +176,7 @@ public class Storage lengthsList = null; } + // TODO thread this so we can return and show something on the UI byte[] piece_hashes = fast_digestCreate(); metainfo = new MetaInfo(announce, baseFile.getName(), null, files, lengthsList, piece_size, piece_hashes, total, privateTorrent); @@ -205,6 +212,8 @@ public class Storage private void getFiles(File base) throws IOException { + if (base.getAbsolutePath().equals("/")) + throw new IOException("Don't seed root"); ArrayList files = new ArrayList(); addFiles(files, base); @@ -233,12 +242,15 @@ public class Storage } } - private void addFiles(List l, File f) - { - if (!f.isDirectory()) - l.add(f); - else - { + /** + * @throws IOException if too many total files + */ + private void addFiles(List l, File f) throws IOException { + if (!f.isDirectory()) { + if (l.size() >= SnarkManager.MAX_FILES_PER_TORRENT) + throw new IOException("Too many files, limit is " + SnarkManager.MAX_FILES_PER_TORRENT + ", zip them?"); + l.add(f); + } else { File[] files = f.listFiles(); if (files == null) { @@ -284,6 +296,23 @@ public class Storage return changed; } + /** + * File checking in progress. + * @since 0.9.3 + */ + public boolean isChecking() { + return _isChecking; + } + + /** + * Disk allocation (ballooning) in progress. + * Always false on Windows. + * @since 0.9.3 + */ + public boolean isAllocating() { + return _allocateCount.get() > 0; + } + /** * @param file canonical path (non-directory) * @return number of bytes remaining; -1 if unknown file @@ -703,11 +732,25 @@ public class Storage * This is called at the beginning, and at presumed completion, * so we have to be careful about locking. * + * TODO thread the checking so we can return and display + * something on the UI + * * @param recheck if true, this is a check after we downloaded the * last piece, and we don't modify the global bitfield unless * the check fails. */ - private void checkCreateFiles(boolean recheck) throws IOException + private void checkCreateFiles(boolean recheck) throws IOException { + synchronized(this) { + _isChecking = true; + try { + locked_checkCreateFiles(recheck); + } finally { + _isChecking = false; + } + } + } + + private void locked_checkCreateFiles(boolean recheck) throws IOException { // Whether we are resuming or not, // if any of the files already exists we assume we are resuming. @@ -867,10 +910,19 @@ public class Storage final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024); byte[] zeros = new byte[ZEROBLOCKSIZE]; rafs[nr].seek(0); - while (remaining > 0) { - int size = (int) Math.min(remaining, ZEROBLOCKSIZE); - rafs[nr].write(zeros, 0, size); - remaining -= size; + // don't bother setting flag for small files + if (remaining > 20*1024*1024) + _allocateCount.incrementAndGet(); + try { + while (remaining > 0) { + int size = (int) Math.min(remaining, ZEROBLOCKSIZE); + rafs[nr].write(zeros, 0, size); + remaining -= size; + } + } finally { + remaining = lengths[nr]; + if (remaining > 20*1024*1024) + _allocateCount.decrementAndGet(); } isSparse[nr] = false; } 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 7a0407ab37..7283b14be1 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -743,6 +743,7 @@ public class I2PSnarkServlet extends DefaultServlet { try { // This may take a long time to check the storage, but since it already exists, // it shouldn't be THAT bad, so keep it in this thread. + // TODO thread it for big torrents, perhaps a la FetchAndAdd boolean isPrivate = _manager.getPrivateTrackers().contains(announceURL); Storage s = new Storage(_manager.util(), baseFile, announceURL, isPrivate, null); s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over @@ -992,7 +993,13 @@ public class I2PSnarkServlet extends DefaultServlet { String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd"); String statusString; - if (err != null) { + if (snark.isChecking()) { + statusString = "\"\"" + + "" + _("Checking"); + } else if (snark.isAllocating()) { + statusString = "\"\"" + + "" + _("Allocating"); + } else if (err != null) { if (isRunning && curPeers > 0 && !showPeers) statusString = "\"\"" + "" + _("Tracker Error") + @@ -1174,7 +1181,9 @@ public class I2PSnarkServlet extends DefaultServlet { String b64 = Base64.encode(snark.getInfoHash()); if (showPeers) parameters = parameters + "&p=1"; - if (isRunning) { + if (snark.isChecking()) { + // show no buttons + } else if (isRunning) { // Stop Button if (isDegraded) out.write("