diff --git a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java index 8344469bc8b76e4e7b214bb9fbb0f7cdd8c2b881..51952d39d7102b22402a39008008d62dbc03694e 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java +++ b/apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java @@ -1119,21 +1119,23 @@ public class SnarkManager implements CompleteListener { * Caller must verify this torrent is not already added. * * @param filename the absolute path to save the metainfo to, generally ending in ".torrent" - * @param baseFile may be null, if so look in rootDataDir + * @param baseFile may be null, if so look in dataDir * @throws RuntimeException via Snark.fatal() */ - private void addTorrent(String filename) { - addTorrent(filename, null, false); + private void addTorrent(String filename, File baseFile, boolean dontAutoStart) { + addTorrent(filename, baseFile, dontAutoStart, null); } /** * Caller must verify this torrent is not already added. * * @param filename the absolute path to save the metainfo to, generally ending in ".torrent" - * @param baseFile may be null, if so look in rootDataDir + * @param baseFile may be null, if so look in dataDir + * @param dataDir must exist, or null to default to snark data directory * @throws RuntimeException via Snark.fatal() + * @since 0.9.17 */ - private void addTorrent(String filename, File baseFile, boolean dontAutoStart) { + private void addTorrent(String filename, File baseFile, boolean dontAutoStart, File dataDir) { if ((!dontAutoStart) && !_util.connected()) { addMessage(_("Connecting to I2P")); boolean ok = _util.connect(); @@ -1150,7 +1152,8 @@ public class SnarkManager implements CompleteListener { addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe); return; } - File dataDir = getDataDir(); + if (dataDir == null) + dataDir = getDataDir(); Snark torrent = null; synchronized (_snarks) { torrent = _snarks.get(filename); @@ -1266,7 +1269,25 @@ public class SnarkManager implements CompleteListener { */ public void addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus) { // updateStatus is true from UI, false from config file bulk add - addMagnet(name, ih, trackerURL, updateStatus, updateStatus, this); + addMagnet(name, ih, trackerURL, updateStatus, updateStatus, null, this); + } + + /** + * Add a torrent with the info hash alone (magnet / maggot) + * + * @param name hex or b32 name from the magnet link + * @param ih 20 byte info hash + * @param trackerURL may be null + * @param updateStatus should we add this magnet to the config file, + * to save it across restarts, in case we don't get + * the metadata before shutdown? + * @param dataDir must exist, or null to default to snark data directory + * @throws RuntimeException via Snark.fatal() + * @since 0.9.17 + */ + public void addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus, File dataDir) { + // updateStatus is true from UI, false from config file bulk add + addMagnet(name, ih, trackerURL, updateStatus, updateStatus, dataDir, this); } /** @@ -1279,16 +1300,18 @@ public class SnarkManager implements CompleteListener { * @param updateStatus should we add this magnet to the config file, * to save it across restarts, in case we don't get * the metadata before shutdown? + * @param dataDir must exist, or null to default to snark data directory * @param listener to intercept callbacks, should pass through to this * @return the new Snark or null on failure * @throws RuntimeException via Snark.fatal() * @since 0.9.4 */ public Snark addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus, - boolean autoStart, CompleteListener listener) { + boolean autoStart, File dataDir, CompleteListener listener) { + String dirPath = dataDir != null ? dataDir.getAbsolutePath() : getDataDir().getPath(); Snark torrent = new Snark(_util, name, ih, trackerURL, listener, _peerCoordinatorSet, _connectionAcceptor, - false, getDataDir().getPath()); + false, dirPath); synchronized (_snarks) { Snark snark = getTorrentByInfoHash(ih); @@ -1407,10 +1430,11 @@ public class SnarkManager implements CompleteListener { * @param fromfile where the file is now, presumably in a temp directory somewhere * @param filename the absolute path to save the metainfo to, generally ending in ".torrent", which is also the name of the torrent * Must be a filesystem-safe name. + * @param dataDir must exist, or null to default to snark data directory * @throws RuntimeException via Snark.fatal() * @since 0.8.4 */ - public void copyAndAddTorrent(File fromfile, String filename) throws IOException { + public void copyAndAddTorrent(File fromfile, String filename, File dataDir) throws IOException { // prevent interference by DirMonitor synchronized (_snarks) { boolean success = FileUtil.copy(fromfile.getAbsolutePath(), filename, false); @@ -1422,7 +1446,7 @@ public class SnarkManager implements CompleteListener { if (!areFilesPublic()) SecureFileOutputStream.setPerms(new File(filename)); // hold the lock for a long time - addTorrent(filename); + addTorrent(filename, null, false, dataDir); } } diff --git a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java index d7f3b6690842119bf00358a18f6b3f73e91d1d13..4f9dff55aad18cb39ebbe444529c78cb49b75495 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java +++ b/apps/i2psnark/java/src/org/klomp/snark/UpdateRunner.java @@ -110,7 +110,7 @@ class UpdateRunner implements UpdateTask, CompleteListener { _umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null); continue; } - _snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this); + _snark = _smgr.addMagnet(name, ih, trackerURL, true, true, null, this); if (_snark != null) { updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>"); new Timeout(); diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/FetchAndAdd.java b/apps/i2psnark/java/src/org/klomp/snark/web/FetchAndAdd.java index d24d4f87c88b9322c2cec02c7318281109537e9d..99e2f62a694c293eb7bfc88d04ff1f56c92e41ee 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/FetchAndAdd.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/FetchAndAdd.java @@ -50,6 +50,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl private final String _url; private final byte[] _fakeHash; private final String _name; + private final File _dataDir; private volatile long _remaining = -1; private volatile long _total = -1; private volatile long _transferred; @@ -65,8 +66,10 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl /** * Caller should call _mgr.addDownloader(this), which * will start things off. + * + * @param dataDir null to default to snark data directory */ - public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url) { + public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url, File dataDir) { // magnet constructor super(mgr.util(), "Torrent download", null, null, null, null, null, false, null); @@ -75,6 +78,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl _mgr = mgr; _url = url; _name = _("Download torrent file from {0}", url); + _dataDir = dataDir; byte[] fake = null; try { fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1")); @@ -176,7 +180,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl _mgr.addMessage(_("Torrent already in the queue: {0}", name)); } else { // This may take a LONG time to create the storage. - _mgr.copyAndAddTorrent(file, canonical); + _mgr.copyAndAddTorrent(file, canonical, _dataDir); snark = _mgr.getTorrentByBaseName(originalName); if (snark != null) snark.startTorrent(); 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 4276679e7dd51df1aa941209d16465e84dd0db4e..a085864c17ef1ccb1d12ff1f2a86cf68e05fc36c 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -30,6 +30,7 @@ import net.i2p.data.Base64; import net.i2p.data.DataHelper; import net.i2p.data.Hash; import net.i2p.util.Log; +import net.i2p.util.SecureFile; import org.klomp.snark.I2PSnarkUtil; import org.klomp.snark.MagnetURI; @@ -933,13 +934,41 @@ public class I2PSnarkServlet extends BasicServlet { } else *****/ if (newURL != null) { + String newDir = req.getParameter("nofilter_newDir"); + File dir = null; + if (newDir != null) { + newDir = newDir.trim(); + if (newDir.length() > 0) { + dir = new SecureFile(newDir); + if (!dir.isAbsolute()) { + _manager.addMessage(_("Data directory must be an absolute path") + ": " + dir); + return; + } + if (!dir.isDirectory() && !dir.mkdirs()) { + _manager.addMessage(_("Data directory cannot be created") + ": " + dir); + return; + } + Collection<Snark> snarks = _manager.getTorrents(); + for (Snark s : snarks) { + Storage storage = s.getStorage(); + if (storage == null) + continue; + File sbase = storage.getBase(); + if (isParentOf(sbase, dir)) { + _manager.addMessage(_("Cannot add torrent {0} inside another torrent: {1}", + dir.getAbsolutePath(), sbase)); + return; + } + } + } + } if (newURL.startsWith("http://")) { - FetchAndAdd fetch = new FetchAndAdd(_context, _manager, newURL); + FetchAndAdd fetch = new FetchAndAdd(_context, _manager, newURL, dir); _manager.addDownloader(fetch); } else if (newURL.startsWith(MagnetURI.MAGNET) || newURL.startsWith(MagnetURI.MAGGOT)) { - addMagnet(newURL); + addMagnet(newURL, dir); } else if (newURL.length() == 40 && newURL.replaceAll("[a-fA-F0-9]", "").length() == 0) { - addMagnet(MagnetURI.MAGNET_FULL + newURL); + addMagnet(MagnetURI.MAGNET_FULL + newURL, dir); } else { _manager.addMessage(_("Invalid URL: Must start with \"http://\", \"{0}\", or \"{1}\"", MagnetURI.MAGNET, MagnetURI.MAGGOT)); @@ -2000,6 +2029,7 @@ public class I2PSnarkServlet extends BasicServlet { out.write(toThemeImg("add")); out.write(' '); out.write(_("Add Torrent")); + out.write("</span><hr>\n<table border=\"0\"><tr><td>"); out.write(_("From URL")); out.write(":<td><input type=\"text\" name=\"nofilter_newURL\" size=\"85\" value=\"" + newURL + "\" spellcheck=\"false\""); @@ -2010,9 +2040,17 @@ public class I2PSnarkServlet extends BasicServlet { //out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>"); out.write("<input type=\"submit\" class=\"add\" value=\""); out.write(_("Add torrent")); - out.write("\" name=\"foo\" ><br>\n"); + out.write("\" name=\"foo\" ><br>\n" + + + "<tr><td>"); + out.write(_("Data dir")); + out.write(":<td><input type=\"text\" name=\"nofilter_newDir\" size=\"85\" value=\"\" spellcheck=\"false\""); + out.write(" title=\""); + out.write(_("Enter the directory to save the data in (default {0})", _manager.getDataDir().getAbsolutePath())); + out.write("\"></td></tr>\n"); + out.write("<tr><td> <td><span class=\"snarkAddInfo\">"); - out.write(_("You can also copy .torrent files to: {0}.", "<code>" + _manager.getDataDir().getAbsolutePath () + "</code>")); + out.write(_("You can also copy .torrent files to: {0}.", "<code>" + _manager.getDataDir().getAbsolutePath() + "</code>")); out.write("\n"); out.write(_("Removing a .torrent will cause it to stop.")); out.write("<br></span></table>\n"); @@ -2372,15 +2410,16 @@ public class I2PSnarkServlet extends BasicServlet { /** * @param url in base32 or hex + * @param dataDir null to default to snark data directory * @since 0.8.4 */ - private void addMagnet(String url) { + private void addMagnet(String url, File dataDir) { try { MagnetURI magnet = new MagnetURI(_manager.util(), url); String name = magnet.getName(); byte[] ih = magnet.getInfoHash(); String trackerURL = magnet.getTrackerURL(); - _manager.addMagnet(name, ih, trackerURL, true); + _manager.addMagnet(name, ih, trackerURL, true, dataDir); } catch (IllegalArgumentException iae) { _manager.addMessage(_("Invalid magnet URL {0}", url)); }