i2psnark:

- Refactor file deletion in prep for better file name handling (ticket #571)
 - Don't use canonical files in directory listings,
   for speed and to avoid file comparison problems (tickets #1079, #1148)
 - Set base file/dir in Storage constructor, make final,
   in prep for arbitrary locations (ticket #1028)
This commit is contained in:
zzz
2013-12-22 13:52:35 +00:00
parent 01b153488a
commit 30ccf1b334
3 changed files with 114 additions and 110 deletions

View File

@@ -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) {

View File

@@ -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<TorrentFile> _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<Long> ls = metainfo.getLengths();
int size = files.size();
@@ -544,7 +527,7 @@ public class Storage
for (int i = 0; i < size; i++)
{
List<String> 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<String> names, boolean areFilesPublic) throws IOException
{
@@ -721,15 +706,46 @@ public class Storage
return f;
}
public static File getFileFromNames(File base, List<String> names)
{
Iterator<String> 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<File> getFiles() {
List<File> rv = new ArrayList<File>(_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<File> getDirectories() {
if (!_base.isDirectory())
return null;
SortedSet<File> rv = new TreeSet<File>(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;
}
/**

View File

@@ -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<File> dirs = new TreeSet<File>(Collections.reverseOrder());
for (List<String> 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<File> 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("</TD>");
if (showPriority) {
buf.append("<td class=\"priority\">");
File f = item;
if ((!complete) && (!item.isDirectory())) {
int pri = snark.getStorage().getPriority(f.getCanonicalPath());
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
if (pri > 0)
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(item).append("\" ");
if (priority > 0)
buf.append("checked=\"true\"");
buf.append('>').append(_("High"));
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
if (pri == 0)
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(item).append("\" ");
if (priority == 0)
buf.append("checked=\"true\"");
buf.append('>').append(_("Normal"));
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(f.getCanonicalPath()).append("\" ");
if (pri < 0)
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(item).append("\" ");
if (priority < 0)
buf.append("checked=\"true\"");
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);