diff --git a/apps/i2psnark/java/src/org/klomp/snark/Snark.java b/apps/i2psnark/java/src/org/klomp/snark/Snark.java index ba4dfc286..398d9bd6e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Snark.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Snark.java @@ -223,7 +223,7 @@ public class Snark private PeerCoordinator coordinator; private ConnectionAcceptor acceptor; private TrackerClient trackerclient; - private String rootDataDir = "."; + private final File rootDataDir; private final CompleteListener completeListener; private volatile boolean stopped; private volatile boolean starting; @@ -291,7 +291,7 @@ public class Snark acceptor = connectionAcceptor; this.torrent = torrent; - this.rootDataDir = rootDir; + this.rootDataDir = new File(rootDir); stopped = true; activity = "Network setup"; @@ -395,13 +395,12 @@ public class Snark try { activity = "Checking storage"; - storage = new Storage(_util, meta, slistener); + storage = new Storage(_util, rootDataDir, meta, slistener); if (completeListener != null) { - storage.check(rootDataDir, - completeListener.getSavedTorrentTime(this), + storage.check(completeListener.getSavedTorrentTime(this), completeListener.getSavedTorrentBitField(this)); } else { - storage.check(rootDataDir); + storage.check(); } // have to figure out when to reopen // if (!start) @@ -453,7 +452,7 @@ public class Snark this.torrent = torrent; this.infoHash = ih; this.additionalTrackerURL = trackerURL; - this.rootDataDir = rootDir; + this.rootDataDir = new File(rootDir); stopped = true; id = generateID(); @@ -548,7 +547,7 @@ public class Snark } else if (trackerclient.halted()) { if (storage != null) { try { - storage.reopen(rootDataDir); + storage.reopen(); } catch (IOException ioe) { try { storage.close(); } catch (IOException ioee) { ioee.printStackTrace(); @@ -1104,8 +1103,8 @@ public class Snark public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) { try { // The following two may throw IOE... - storage = new Storage(_util, metainfo, this); - storage.check(rootDataDir); + storage = new Storage(_util, rootDataDir, metainfo, this); + storage.check(); // ... so don't set meta until here meta = metainfo; if (completeListener != null) { diff --git a/apps/i2psnark/java/src/org/klomp/snark/Storage.java b/apps/i2psnark/java/src/org/klomp/snark/Storage.java index 8b2b5a39b..e9bf050b8 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/Storage.java +++ b/apps/i2psnark/java/src/org/klomp/snark/Storage.java @@ -32,7 +32,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.SortedSet; import java.util.StringTokenizer; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -52,6 +54,7 @@ public class Storage { private final MetaInfo metainfo; private final List _torrentFiles; + private final File _base; private final StorageListener listener; private final I2PSnarkUtil _util; private final Log _log; @@ -88,10 +91,15 @@ public class Storage * * Does not check storage. Caller MUST call check() */ - public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener) + public Storage(I2PSnarkUtil util, File rootDir, MetaInfo metainfo, StorageListener listener) { _util = util; _log = util.getContext().logManager().getLog(Storage.class); + boolean areFilesPublic = _util.getFilesPublic(); + if (areFilesPublic) + _base = new File(rootDir, filterName(metainfo.getName())); + else + _base = new SecureFile(rootDir, filterName(metainfo.getName())); this.metainfo = metainfo; this.listener = listener; needed = metainfo.getPieces(); @@ -121,6 +129,7 @@ public class Storage throws IOException { _util = util; + _base = baseFile; _log = util.getContext().logManager().getLog(Storage.class); this.listener = listener; // Create names, rafs and lengths arrays. @@ -305,24 +314,15 @@ public class Storage } /** - * @param file canonical path (non-directory) + * @param file non-canonical path (non-directory) * @return number of bytes remaining; -1 if unknown file * @since 0.7.14 */ - public long remaining(String file) { + public long remaining(File file) { long bytes = 0; for (TorrentFile tf : _torrentFiles) { File f = tf.RAFfile; - // use canonical in case snark dir or sub dirs are symlinked - String canonical = null; - if (f != null) { - try { - canonical = f.getCanonicalPath(); - } catch (IOException ioe) { - f = null; - } - } - if (f != null && canonical.equals(file)) { + if (f.equals(file)) { if (complete()) return 0; int psz = piece_size; @@ -348,22 +348,16 @@ public class Storage } /** - * @param file canonical path (non-directory) + * @param file non-canonical path (non-directory) * @since 0.8.1 */ - public int getPriority(String file) { + public int getPriority(File file) { if (complete() || metainfo.getFiles() == null) return 0; for (TorrentFile tf : _torrentFiles) { File f = tf.RAFfile; - // use canonical in case snark dir or sub dirs are symlinked - if (f != null) { - try { - String canonical = f.getCanonicalPath(); - if (canonical.equals(file)) - return tf.priority; - } catch (IOException ioe) {} - } + if (f.equals(file)) + return tf.priority; } return 0; } @@ -371,24 +365,18 @@ public class Storage /** * Must call Snark.updatePiecePriorities() * (which calls getPiecePriorities()) after calling this. - * @param file canonical path (non-directory) + * @param file non-canonical path (non-directory) * @param pri default 0; <0 to disable * @since 0.8.1 */ - public void setPriority(String file, int pri) { + public void setPriority(File file, int pri) { if (complete() || metainfo.getFiles() == null) return; for (TorrentFile tf : _torrentFiles) { File f = tf.RAFfile; - // use canonical in case snark dir or sub dirs are symlinked - if (f != null) { - try { - String canonical = f.getCanonicalPath(); - if (canonical.equals(file)) { - tf.priority = pri; - return; - } - } catch (IOException ioe) {} + if (f.equals(file)) { + tf.priority = pri; + return; } } } @@ -490,9 +478,9 @@ public class Storage * Creates (and/or checks) all files from the metainfo file list. * Only call this once, and only after the constructor with the metainfo. */ - public void check(String rootDir) throws IOException + public void check() throws IOException { - check(rootDir, 0, null); + check(0, null); } /** @@ -500,14 +488,9 @@ public class Storage * Use a saved bitfield and timestamp from a config file. * Only call this once, and only after the constructor with the metainfo. */ - public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException + public void check(long savedTime, BitField savedBitField) throws IOException { - File base; boolean areFilesPublic = _util.getFilesPublic(); - if (areFilesPublic) - base = new File(rootDir, filterName(metainfo.getName())); - else - base = new SecureFile(rootDir, filterName(metainfo.getName())); boolean useSavedBitField = savedTime > 0 && savedBitField != null; if (!_torrentFiles.isEmpty()) @@ -517,16 +500,16 @@ public class Storage { // Create base as file. if (_log.shouldLog(Log.INFO)) - _log.info("Creating/Checking file: " + base); - if (!base.createNewFile() && !base.exists()) - throw new IOException("Could not create file " + base); + _log.info("Creating/Checking file: " + _base); + if (!_base.createNewFile() && !_base.exists()) + throw new IOException("Could not create file " + _base); - _torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength())); + _torrentFiles.add(new TorrentFile(_base, _base, metainfo.getTotalLength())); if (useSavedBitField) { - long lm = base.lastModified(); + long lm = _base.lastModified(); if (lm <= 0 || lm > savedTime) useSavedBitField = false; - else if (base.length() != metainfo.getTotalLength()) + else if (_base.length() != metainfo.getTotalLength()) useSavedBitField = false; } } @@ -534,9 +517,9 @@ public class Storage { // Create base as dir. if (_log.shouldLog(Log.INFO)) - _log.info("Creating/Checking directory: " + base); - if (!base.mkdir() && !base.isDirectory()) - throw new IOException("Could not create directory " + base); + _log.info("Creating/Checking directory: " + _base); + if (!_base.mkdir() && !_base.isDirectory()) + throw new IOException("Could not create directory " + _base); List ls = metainfo.getLengths(); int size = files.size(); @@ -544,7 +527,7 @@ public class Storage for (int i = 0; i < size; i++) { List path = files.get(i); - File f = createFileFromNames(base, path, areFilesPublic); + File f = createFileFromNames(_base, path, areFilesPublic); // dup file name check after filtering for (int j = 0; j < i; j++) { if (f.equals(_torrentFiles.get(j).RAFfile)) { @@ -560,12 +543,12 @@ public class Storage else lastPath = '_' + lastPath; path.set(last, lastPath); - f = createFileFromNames(base, path, areFilesPublic); + f = createFileFromNames(_base, path, areFilesPublic); j = 0; } } long len = ls.get(i).longValue(); - _torrentFiles.add(new TorrentFile(base, f, len)); + _torrentFiles.add(new TorrentFile(_base, f, len)); total += len; if (useSavedBitField) { long lm = f.lastModified(); @@ -612,7 +595,7 @@ public class Storage * @param rootDir ignored * @throws IOE on fail */ - public void reopen(String rootDir) throws IOException + public void reopen() throws IOException { if (_torrentFiles.isEmpty()) throw new IOException("Storage not checked yet"); @@ -688,6 +671,8 @@ public class Storage * Note that filtering each path element individually may lead to * things going in the wrong place if there are duplicates * in intermediate path elements after filtering. + * + * @param names path elements */ private static File createFileFromNames(File base, List names, boolean areFilesPublic) throws IOException { @@ -721,15 +706,46 @@ public class Storage return f; } - public static File getFileFromNames(File base, List names) - { - Iterator it = names.iterator(); - while (it.hasNext()) - { - String name = filterName(it.next()); - base = new File(base, name); + /** + * The base file or directory. + * @return a new List + */ + public File getBase() { + return _base; + } + + /** + * Does not include directories. Unsorted. + * @since 0.9.10 + * @return a new List + */ + public List getFiles() { + List rv = new ArrayList(_torrentFiles.size()); + for (TorrentFile tf : _torrentFiles) { + rv.add(tf.RAFfile); } - return base; + return rv; + } + + /** + * Includes the base for a multi-file torrent. + * Sorted bottom-up for easy deletion. + * Slow. Use for deletion only. + * @since 0.9.10 + * @return a new Set or null for a single-file torrent + */ + public SortedSet getDirectories() { + if (!_base.isDirectory()) + return null; + SortedSet rv = new TreeSet(Collections.reverseOrder()); + rv.add(_base); + for (TorrentFile tf : _torrentFiles) { + File f = tf.RAFfile; + do { + f = f.getParentFile(); + } while (f != null && rv.add(f)); + } + return rv; } /** 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 a77fbf093..466c8d897 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -17,7 +17,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -828,42 +827,36 @@ public class I2PSnarkServlet extends BasicServlet { _manager.addMessage(_("Data file could not be deleted: {0}", f.getAbsolutePath())); break; } + Storage storage = snark.getStorage(); + if (storage == null) + break; // step 1 delete files - for (int i = 0; i < files.size(); i++) { - // multifile torrents have the getFiles() return lists of lists of filenames, but - // each of those lists just contain a single file afaict... - File df = Storage.getFileFromNames(f, files.get(i)); + for (File df : storage.getFiles()) { if (df.delete()) { //_manager.addMessage(_("Data file deleted: {0}", df.getAbsolutePath())); } else { _manager.addMessage(_("Data file could not be deleted: {0}", df.getAbsolutePath())); } } - // step 2 make Set of dirs with reverse sort - Set dirs = new TreeSet(Collections.reverseOrder()); - for (List list : files) { - for (int i = 1; i < list.size(); i++) { - dirs.add(Storage.getFileFromNames(f, list.subList(0, i))); - } - } - // step 3 delete dirs bottom-up + // step 2 delete dirs bottom-up + Set dirs = storage.getDirectories(); + if (_log.shouldLog(Log.INFO)) + _log.info("Dirs to delete: " + DataHelper.toString(dirs)); + boolean ok = false; for (File df : dirs) { if (df.delete()) { + ok = true; //_manager.addMessage(_("Data dir deleted: {0}", df.getAbsolutePath())); } else { + ok = false; _manager.addMessage(_("Directory could not be deleted: {0}", df.getAbsolutePath())); if (_log.shouldLog(Log.WARN)) _log.warn("Could not delete dir " + df); } } - // step 4 delete base - if (f.delete()) { - _manager.addMessage(_("Directory deleted: {0}", f.getAbsolutePath())); - } else { - _manager.addMessage(_("Directory could not be deleted: {0}", f.getAbsolutePath())); - if (_log.shouldLog(Log.WARN)) - _log.warn("Could not delete dir " + f); - } + // step 3 message for base (last one) + if (ok) + _manager.addMessage(_("Directory deleted: {0}", storage.getBase())); break; } } @@ -2462,6 +2455,7 @@ public class I2PSnarkServlet extends BasicServlet { boolean complete = false; String status = ""; long length = item.length(); + int priority = 0; if (item.isDirectory()) { complete = true; //status = toImg("tick") + ' ' + _("Directory"); @@ -2472,9 +2466,8 @@ public class I2PSnarkServlet extends BasicServlet { status = toImg("cancel") + ' ' + _("Torrent not found?"); } else { Storage storage = snark.getStorage(); - try { - File f = item; - long remaining = storage.remaining(f.getCanonicalPath()); + + long remaining = storage.remaining(item); if (remaining < 0) { complete = true; status = toImg("cancel") + ' ' + _("File not found in torrent?"); @@ -2482,7 +2475,7 @@ public class I2PSnarkServlet extends BasicServlet { complete = true; status = toImg("tick") + ' ' + _("Complete"); } else { - int priority = storage.getPriority(f.getCanonicalPath()); + priority = storage.getPriority(item); if (priority < 0) status = toImg("cancel"); else if (priority == 0) @@ -2493,9 +2486,7 @@ public class I2PSnarkServlet extends BasicServlet { (100 * (length - remaining) / length) + "% " + _("complete") + " (" + DataHelper.formatSize2(remaining) + "B " + _("remaining") + ")"; } - } catch (IOException ioe) { - status = "Not a file? " + ioe; - } + } } @@ -2534,21 +2525,19 @@ public class I2PSnarkServlet extends BasicServlet { buf.append(""); if (showPriority) { buf.append(""); - File f = item; if ((!complete) && (!item.isDirectory())) { - int pri = snark.getStorage().getPriority(f.getCanonicalPath()); - buf.append(" 0) + buf.append(" 0) buf.append("checked=\"true\""); buf.append('>').append(_("High")); - buf.append("').append(_("Normal")); - buf.append("').append(_("Skip")); showSaveButton = true; @@ -2643,7 +2632,7 @@ public class I2PSnarkServlet extends BasicServlet { String key = entry.getKey(); if (key.startsWith("pri.")) { try { - String file = key.substring(4); + File file = new File(key.substring(4)); String val = entry.getValue()[0]; // jetty arrays int pri = Integer.parseInt(val); storage.setPriority(file, pri);