* i2psnark:

- Display torrent file downloads in torrent area
   - Sort magnets and downloads first
   - Fix sorting problem when torrent dir is a symlink
   - Reduce max file idle time
   - arrow_down icon copied from console css
This commit is contained in:
zzz
2012-06-11 12:04:40 +00:00
parent 7469e9c63d
commit 30e2f73d5f
10 changed files with 421 additions and 97 deletions

View File

@ -250,6 +250,15 @@ public class I2PSnarkUtil {
public boolean connected() { return _manager != null; }
/**
* For FetchAndAdd
* @return null if not connected
* @since 0.9.1
*/
public I2PSocketManager getSocketManager() {
return _manager;
}
/**
* Destroy the destination itself
*/
@ -310,7 +319,7 @@ public class I2PSnarkUtil {
// we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms...
out = SecureFile.createTempFile("i2psnark", null, _tmpDir);
} catch (IOException ioe) {
ioe.printStackTrace();
_log.error("temp file error", ioe);
if (out != null)
out.delete();
return null;

View File

@ -346,6 +346,8 @@ public class Snark
in = new FileInputStream(f);
else
{
/**** No, we don't ever fetch a torrent file this way
and we don't want to block in the constructor
activity = "Getting torrent";
File torrentFile = _util.get(torrent, 3);
if (torrentFile == null) {
@ -355,6 +357,8 @@ public class Snark
torrentFile.deleteOnExit();
in = new FileInputStream(torrentFile);
}
*****/
throw new IOException("not found");
}
meta = new MetaInfo(in);
infoHash = meta.getInfoHash();

View File

@ -598,7 +598,7 @@ public class SnarkManager implements Snark.CompleteListener {
}
/**
* @param ot null ok, default is none
* @param pt null ok, default is none
* @since 0.9.1
*/
public void savePrivateTrackers(List<String> pt) {
@ -881,6 +881,29 @@ public class SnarkManager implements Snark.CompleteListener {
_magnets.remove(snark.getName());
removeMagnetStatus(snark.getInfoHash());
}
/**
* Add and start a FetchAndAdd task.
* Remove it with deleteMagnet().
*
* @param torrent must be instanceof FetchAndAdd
* @throws RuntimeException via Snark.fatal()?
* @since 0.9.1
*/
public void addDownloader(Snark torrent) {
synchronized (_snarks) {
Snark snark = getTorrentByInfoHash(torrent.getInfoHash());
if (snark != null) {
addMessage(_("Download already running: {0}", snark.getBaseName()));
return;
}
String name = torrent.getName();
// Tell the dir monitor not to delete us
_magnets.add(name);
_snarks.put(name, torrent);
}
torrent.startTorrent();
}
/**
* Add a torrent from a MetaInfo. Save the MetaInfo data to filename.
@ -1399,7 +1422,7 @@ public class SnarkManager implements Snark.CompleteListener {
byte[] ih = Base64.decode(b64);
// ignore value - TODO put tracker URL in value
if (ih != null && ih.length == 20)
addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, null, false);
addMagnet("* " + _("Magnet") + ' ' + I2PSnarkUtil.toHex(ih), ih, null, false);
// else remove from config?
}
}

View File

@ -1071,7 +1071,7 @@ public class Storage
/**
* Close unused RAFs - call periodically
*/
private static final long RAFCloseDelay = 7*60*1000;
private static final long RAFCloseDelay = 4*60*1000;
public void cleanRAFs() {
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
for (int i = 0; i < RAFlock.length; i++) {

View File

@ -0,0 +1,346 @@
package org.klomp.snark.web;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocketEepGet;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.crypto.SHA1;
import net.i2p.util.EepGet;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
import org.klomp.snark.SnarkManager;
import org.klomp.snark.Storage;
/**
* A cancellable torrent file downloader.
* We extend Snark so its status may be easily listed in the
* web table without adding a lot of code there.
*
* Upon successful download, this Snark will be deleted and
* a "real" Snark created.
*
* The methods return values similar to a Snark in magnet mode.
* A fake info hash, which is the SHA1 of the URL, is returned
* to prevent duplicates.
*
* This Snark may be stopped and restarted, although a partially
* downloaded file is discarded.
*
* @since 0.9.1 Moved from I2PSnarkUtil
*/
public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnable {
private final I2PAppContext _ctx;
private final Log _log;
private final SnarkManager _mgr;
private final String _url;
private final byte[] _fakeHash;
private final String _name;
private volatile long _remaining = -1;
private volatile long _total = -1;
private volatile long _transferred;
private volatile boolean _isRunning;
private volatile boolean _active;
private volatile long _started;
private String _failCause;
private Thread _thread;
private EepGet _eepGet;
private static final int RETRIES = 3;
/**
* Caller should call _mgr.addDownloader(this), which
* will start things off.
*/
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url) {
// magnet constructor
super(mgr.util(), "Torrent download",
null, null, null, null, null, false, null);
_ctx = ctx;
_log = ctx.logManager().getLog(FetchAndAdd.class);
_mgr = mgr;
_url = url;
_name = "* " + _("Download torrent file from {0}", url);
byte[] fake = null;
try {
fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1"));
} catch (IOException ioe) {}
_fakeHash = fake;
}
/**
* Set off by startTorrent()
*/
public void run() {
_mgr.addMessage(_("Fetching {0}", urlify(_url)));
File file = get();
if (!_isRunning) // stopped?
return;
_isRunning = false;
if (file != null && file.exists() && file.length() > 0) {
// remove this in snarks
_mgr.deleteMagnet(this);
add(file);
} else {
_mgr.addMessage(_("Torrent was not retrieved from {0}", urlify(_url)) +
((_failCause != null) ? (": " + _failCause) : ""));
}
if (file != null)
file.delete();
}
/**
* Copied from I2PSnarkUtil so we may add ourselves as a status listener
* @return null on failure
*/
private File get() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching [" + _url + "]");
File out = null;
try {
out = SecureFile.createTempFile("torrentFile", null, _mgr.util().getTempDir());
} catch (IOException ioe) {
_log.error("temp file error", ioe);
_mgr.addMessage("Temp file error: " + ioe);
if (out != null)
out.delete();
return null;
}
out.deleteOnExit();
if (!_mgr.util().connected()) {
_mgr.addMessage(_("Opening the I2P tunnel"));
if (!_mgr.util().connect())
return null;
}
I2PSocketManager manager = _mgr.util().getSocketManager();
if (manager == null)
return null;
_eepGet = new I2PSocketEepGet(_ctx, manager, RETRIES, out.getAbsolutePath(), _url);
_eepGet.addStatusListener(this);
if (_eepGet.fetch()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch successful [" + _url + "]: size=" + out.length());
return out;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch failed [" + _url + ']');
out.delete();
return null;
}
}
/**
* Tell SnarkManager to copy the torrent file over and add it to the Snarks list.
* This Snark may then be deleted.
*/
private void add(File file) {
_mgr.addMessage(_("Torrent fetched from {0}", urlify(_url)));
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] fileInfoHash = new byte[20];
String name = MetaInfo.getNameAndInfoHash(in, fileInfoHash);
try { in.close(); } catch (IOException ioe) {}
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
if (snark != null) {
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
}
name = Storage.filterName(name);
name = name + ".torrent";
File torrentFile = new File(_mgr.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_mgr.getTorrent(canonical) != null)
_mgr.addMessage(_("Torrent already running: {0}", name));
else
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
} else {
// This may take a LONG time to create the storage.
_mgr.copyAndAddTorrent(file, canonical);
}
} catch (IOException ioe) {
_mgr.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
} catch (OutOfMemoryError oom) {
_mgr.addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + oom.getMessage());
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
}
// Snark overrides so all the buttons and stats on the web page work
@Override
public synchronized void startTorrent() {
if (_isRunning)
return;
// reset counters in case starting a second time
_remaining = -1;
// leave the total if we knew it before
//_total = -1;
_transferred = 0;
_failCause = null;
_started = _ctx.clock().now();
_isRunning = true;
_active = false;
_thread = new I2PAppThread(this, "Torrent File EepGet", true);
_thread.start();
}
@Override
public synchronized void stopTorrent() {
if (_thread != null && _isRunning) {
if (_eepGet != null)
_eepGet.stopFetching();
_thread.interrupt();
}
_isRunning = false;
_active = false;
}
@Override
public boolean isStopped() {
return !_isRunning;
}
@Override
public String getName() {
return _name;
}
@Override
public String getBaseName() {
return _name;
}
@Override
public byte[] getInfoHash() {
return _fakeHash;
}
/**
* @return torrent file size or -1
*/
@Override
public long getTotalLength() {
return _total;
}
/**
* @return torrent file bytes remaining or -1
*/
@Override
public long getRemainingLength() {
return _remaining;
}
/**
* @return torrent file bytes remaining or -1
*/
@Override
public long getNeededLength() {
return _remaining;
}
@Override
public long getDownloadRate() {
if (_isRunning && _active) {
long time = _ctx.clock().now() - _started;
if (time > 1000) {
long rv = (_transferred * 1000) / time;
if (rv >= 100)
return rv;
}
}
return 0;
}
@Override
public long getDownloaded() {
return _total - _remaining;
}
@Override
public int getPeerCount() {
return (_isRunning && _active && _transferred > 0) ? 1 : 0;
}
@Override
public int getTrackerSeenPeers() {
return (_transferred > 0) ? 1 : 0;
}
// End Snark overrides
// EepGet status listeners to maintain the state for the web page
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
}
_transferred = bytesTransferred;
if (cause != null)
_failCause = cause.toString();
_active = false;
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
_total = bytesRemaining + currentWrite + alreadyTransferred;
}
_transferred = bytesTransferred;
_active = true;
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
_total = bytesRemaining + alreadyTransferred;
}
_transferred = bytesTransferred;
_active = false;
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
}
_transferred = bytesTransferred;
_active = false;
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
// End of EepGet status listeners
private String _(String s) {
return _mgr.util().getString(s);
}
private String _(String s, String o) {
return _mgr.util().getString(s, o);
}
private static String urlify(String s) {
return I2PSnarkServlet.urlify(s);
}
}

View File

@ -540,11 +540,8 @@ public class I2PSnarkServlet extends DefaultServlet {
*****/
if (newURL != null) {
if (newURL.startsWith("http://")) {
if (!_manager.util().connected())
_manager.addMessage(_("Opening the I2P tunnel"));
_manager.addMessage(_("Fetching {0}", urlify(newURL)));
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add", true);
fetch.start();
FetchAndAdd fetch = new FetchAndAdd(_context, _manager, newURL);
_manager.addDownloader(fetch);
} else if (newURL.startsWith(MAGNET) || newURL.startsWith(MAGGOT)) {
addMagnet(newURL);
} else if (newURL.length() == 40 && newURL.replaceAll("[a-fA-F0-9]", "").length() == 0) {
@ -880,7 +877,17 @@ public class I2PSnarkServlet extends DefaultServlet {
*/
private class TorrentNameComparator implements Comparator<String> {
private final Comparator collator = Collator.getInstance();
private final String skip = _manager.getDataDir().getAbsolutePath() + File.separator;
private final String skip;
public TorrentNameComparator() {
String s;
try {
s = _manager.getDataDir().getCanonicalPath();
} catch (IOException ioe) {
s = _manager.getDataDir().getAbsolutePath();
}
skip = s + File.separator;
}
public int compare(String l, String r) {
if (l.startsWith(skip))
@ -916,8 +923,12 @@ public class I2PSnarkServlet extends DefaultServlet {
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
boolean isDegraded, boolean noThinsp, boolean showDebug) throws IOException {
String filename = snark.getName();
File f = new File(filename);
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
if (snark.getMetaInfo() != null) {
// Only do this if not a magnet or torrent download
// Strip full path down to the local name
File f = new File(filename);
filename = f.getName();
}
int i = filename.lastIndexOf(".torrent");
if (i > 0)
filename = filename.substring(0, i);
@ -1076,6 +1087,8 @@ public class I2PSnarkServlet extends DefaultServlet {
icon = "folder";
else if (isValid)
icon = toIcon(meta.getName());
else if (snark instanceof FetchAndAdd)
icon = "arrow_down";
else
icon = "magnet";
if (isValid) {
@ -1724,7 +1737,7 @@ public class I2PSnarkServlet extends DefaultServlet {
}
ihash = xt.substring("urn:btih:".length());
trackerURL = getTrackerParam(url);
name = "Magnet " + ihash;
name = "* " + _("Magnet") + ' ' + ihash;
String dn = getParam("dn", url);
if (dn != null)
name += " (" + Storage.filterName(dn) + ')';
@ -1734,7 +1747,7 @@ public class I2PSnarkServlet extends DefaultServlet {
int col = ihash.indexOf(':');
if (col >= 0)
ihash = ihash.substring(0, col);
name = "Maggot " + ihash;
name = "* " + _("Magnet") + ' ' + ihash;
} else {
return;
}
@ -1941,7 +1954,7 @@ public class I2PSnarkServlet extends DefaultServlet {
}
/** @since 0.7.14 */
private static String urlify(String s) {
static String urlify(String s) {
return urlify(s, 100);
}
@ -2357,83 +2370,4 @@ public class I2PSnarkServlet extends DefaultServlet {
snark.updatePiecePriorities();
_manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities());
}
/** inner class, don't bother reindenting */
private static class FetchAndAdd implements Runnable {
private SnarkManager _manager;
private String _url;
public FetchAndAdd(SnarkManager mgr, String url) {
_manager = mgr;
_url = url;
}
public void run() {
_url = _url.trim();
// 3 retries
File file = _manager.util().get(_url, false, 3);
try {
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
_manager.addMessage(_("Torrent fetched from {0}", urlify(_url)));
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] fileInfoHash = new byte[20];
String name = MetaInfo.getNameAndInfoHash(in, fileInfoHash);
try { in.close(); } catch (IOException ioe) {}
Snark snark = _manager.getTorrentByInfoHash(fileInfoHash);
if (snark != null) {
_manager.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
}
name = Storage.filterName(name);
name = name + ".torrent";
File torrentFile = new File(_manager.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_manager.getTorrent(canonical) != null)
_manager.addMessage(_("Torrent already running: {0}", name));
else
_manager.addMessage(_("Torrent already in the queue: {0}", name));
} else {
// This may take a LONG time to create the storage.
_manager.copyAndAddTorrent(file, canonical);
}
} catch (IOException ioe) {
_manager.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
} catch (OutOfMemoryError oom) {
_manager.addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + oom.getMessage());
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
} else {
// Generate a retry link, but sadly can't have a form inside a table
// So make this an ugly GET
StringBuilder buf = new StringBuilder(1024);
// FIXME don't lose peer setting
//String peerParam = req.getParameter("p");
//if (peerParam != null)
// buf.append("<input type=\"hidden\" name=\"p\" value=\"").append(peerParam).append("\" >\n");
buf.append(_("Torrent was not retrieved from {0}", urlify(_url)));
/**** FIXME ticket #575
String link = urlEncode(_url).replace(":", "%3A").replace("/", "%2F");
buf.append(" - [<a href=\"/i2psnark/?newURL=").append(link).append("#add\" >");
buf.append(_("Retry"));
buf.append("</a>]");
****/
_manager.addMessage(buf.toString());
}
} finally {
if (file != null) file.delete();
}
}
private String _(String s, String o) {
return _manager.util().getString(s, o);
}
}
}