forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.snarkconfig' (head ad48ab1a9e769c58ea2e286337927f5c0e1568be)
to branch 'i2p.i2p' (head 0cd9e265bd38c40839e68de8f51233489acad346)
This commit is contained in:
@ -66,7 +66,8 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Closing tunnels on idle");
|
||||
_util.disconnect();
|
||||
_mgr.addMessage(_util.getString("I2P tunnel closed."));
|
||||
_mgr.addMessage(_util.getString("No more torrents running.") + ' ' +
|
||||
_util.getString("I2P tunnel closed."));
|
||||
schedule(3 * CHECK_TIME);
|
||||
return;
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ class PeerState implements DataLoader
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you except?
|
||||
// XXX - Be liberal in what you accept?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got unexpected bitfield message from " + peer);
|
||||
return;
|
||||
|
@ -34,6 +34,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
@ -221,7 +222,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;
|
||||
@ -238,13 +239,21 @@ public class Snark
|
||||
private volatile String activity = "Not started";
|
||||
|
||||
|
||||
/** from main() via parseArguments() single torrent */
|
||||
/**
|
||||
* from main() via parseArguments() single torrent
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
|
||||
}
|
||||
|
||||
/** single torrent - via router */
|
||||
/**
|
||||
* single torrent - via router
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
public Snark(I2PAppContext ctx, Properties opts, String torrent,
|
||||
StorageListener slistener, boolean start, String rootDir) {
|
||||
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
|
||||
@ -275,11 +284,28 @@ public class Snark
|
||||
this.startTorrent();
|
||||
}
|
||||
|
||||
/** multitorrent */
|
||||
/**
|
||||
* multitorrent
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
|
||||
{
|
||||
this(util, torrent, ip, user_port, slistener, clistener, complistener,
|
||||
peerCoordinatorSet, connectionAcceptor, start, rootDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* multitorrent
|
||||
*
|
||||
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir, File baseFile)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
@ -291,7 +317,7 @@ public class Snark
|
||||
acceptor = connectionAcceptor;
|
||||
|
||||
this.torrent = torrent;
|
||||
this.rootDataDir = rootDir;
|
||||
this.rootDataDir = new File(rootDir);
|
||||
|
||||
stopped = true;
|
||||
activity = "Network setup";
|
||||
@ -394,13 +420,19 @@ public class Snark
|
||||
try
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(_util, meta, slistener);
|
||||
if (baseFile == null) {
|
||||
String base = Storage.filterName(meta.getName());
|
||||
if (_util.getFilesPublic())
|
||||
baseFile = new File(rootDataDir, base);
|
||||
else
|
||||
baseFile = new SecureFile(rootDataDir, base);
|
||||
}
|
||||
storage = new Storage(_util, baseFile, 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)
|
||||
@ -452,7 +484,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();
|
||||
|
||||
@ -547,7 +579,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();
|
||||
@ -1102,9 +1134,15 @@ public class Snark
|
||||
*/
|
||||
public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
|
||||
try {
|
||||
String base = Storage.filterName(metainfo.getName());
|
||||
File baseFile;
|
||||
if (_util.getFilesPublic())
|
||||
baseFile = new File(rootDataDir, base);
|
||||
else
|
||||
baseFile = new SecureFile(rootDataDir, base);
|
||||
// The following two may throw IOE...
|
||||
storage = new Storage(_util, metainfo, this);
|
||||
storage.check(rootDataDir);
|
||||
storage = new Storage(_util, baseFile, metainfo, this);
|
||||
storage.check();
|
||||
// ... so don't set meta until here
|
||||
meta = metainfo;
|
||||
if (completeListener != null) {
|
||||
|
@ -15,6 +15,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
@ -40,6 +41,7 @@ import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.dht.DHT;
|
||||
import org.klomp.snark.dht.KRPC;
|
||||
|
||||
/**
|
||||
* Manage multiple snarks
|
||||
@ -55,7 +57,10 @@ public class SnarkManager implements CompleteListener {
|
||||
/** used to prevent DirMonitor from deleting torrents that don't have a torrent file yet */
|
||||
private final Set<String> _magnets;
|
||||
private final Object _addSnarkLock;
|
||||
private /* FIXME final FIXME */ File _configFile;
|
||||
private File _configFile;
|
||||
private File _configDir;
|
||||
/** one lock for all config, files for simplicity */
|
||||
private final Object _configLock = new Object();
|
||||
private Properties _config;
|
||||
private final I2PAppContext _context;
|
||||
private final String _contextPath;
|
||||
@ -81,14 +86,20 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
|
||||
public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
|
||||
public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
|
||||
private static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
private static final String PROP_META_STAMP = "stamp";
|
||||
private static final String PROP_META_BASE = "base";
|
||||
private static final String PROP_META_BITFIELD = "bitfield";
|
||||
private static final String PROP_META_PRIORITY = "priority";
|
||||
private static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
private static final String PROP_META_PRIORITY_SUFFIX = ".priority";
|
||||
private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
|
||||
|
||||
private static final String CONFIG_FILE_SUFFIX = ".config";
|
||||
private static final String CONFIG_FILE = "i2psnark" + CONFIG_FILE_SUFFIX;
|
||||
public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String PROP_OLD_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String PROP_AUTO_START = "i2psnark.autoStart"; // convert in migration to new config file
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
//public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
|
||||
//public static final String DEFAULT_LINK_PREFIX = "file:///";
|
||||
@ -109,6 +120,9 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final int DEFAULT_REFRESH_DELAY_SECS = 60;
|
||||
private static final int DEFAULT_PAGE_SIZE = 50;
|
||||
public static final String CONFIG_DIR_SUFFIX = ".d";
|
||||
private static final String SUBDIR_PREFIX = "s";
|
||||
private static final String B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~";
|
||||
|
||||
/**
|
||||
* "name", "announceURL=websiteURL" pairs
|
||||
@ -169,9 +183,11 @@ public class SnarkManager implements CompleteListener {
|
||||
_messages = new LinkedBlockingQueue<String>();
|
||||
_util = new I2PSnarkUtil(_context, ctxName);
|
||||
String cfile = ctxName + CONFIG_FILE_SUFFIX;
|
||||
_configFile = new File(cfile);
|
||||
if (!_configFile.isAbsolute())
|
||||
_configFile = new File(_context.getConfigDir(), cfile);
|
||||
File configFile = new File(cfile);
|
||||
if (!configFile.isAbsolute())
|
||||
configFile = new File(_context.getConfigDir(), cfile);
|
||||
_configDir = migrateConfig(configFile);
|
||||
_configFile = new File(_configDir, CONFIG_FILE);
|
||||
_trackerMap = new ConcurrentHashMap<String, Tracker>(4);
|
||||
loadConfig(null);
|
||||
}
|
||||
@ -341,20 +357,179 @@ public class SnarkManager implements CompleteListener {
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the old flat config file to the new config dir
|
||||
* containing the config file minus the per-torrent entries,
|
||||
* the dht file, and 16 subdirs for per-torrent config files
|
||||
* Caller must synch.
|
||||
*
|
||||
* @return the new config directory, non-null
|
||||
* @throws RuntimeException on creation fail
|
||||
* @since 0.9.11
|
||||
*/
|
||||
private File migrateConfig(File oldFile) {
|
||||
File dir = new SecureDirectory(oldFile + CONFIG_DIR_SUFFIX);
|
||||
if ((!dir.exists()) && (!dir.mkdirs())) {
|
||||
_log.error("Error creating I2PSnark config dir " + dir);
|
||||
throw new RuntimeException("Error creating I2PSnark config dir " + dir);
|
||||
}
|
||||
// move the DHT file as-is
|
||||
String oldName = oldFile.toString();
|
||||
if (oldName.endsWith(CONFIG_FILE_SUFFIX)) {
|
||||
String oldDHT = oldName.replace(CONFIG_FILE_SUFFIX, KRPC.DHT_FILE_SUFFIX);
|
||||
File oldDHTFile = new File(oldDHT);
|
||||
if (oldDHTFile.exists()) {
|
||||
File newDHTFile = new File(dir, "i2psnark" + KRPC.DHT_FILE_SUFFIX);
|
||||
FileUtil.rename(oldDHTFile, newDHTFile);
|
||||
}
|
||||
}
|
||||
if (!oldFile.exists())
|
||||
return dir;
|
||||
Properties oldProps = new Properties();
|
||||
try {
|
||||
DataHelper.loadProps(oldProps, oldFile);
|
||||
// a good time to fix this ancient typo
|
||||
String auto = (String) oldProps.remove(PROP_OLD_AUTO_START);
|
||||
if (auto != null)
|
||||
oldProps.setProperty(PROP_AUTO_START, auto);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading I2PSnark config " + oldFile, ioe);
|
||||
return dir;
|
||||
}
|
||||
// Gather the props for each torrent, removing them from config
|
||||
// old b64 of hash as key
|
||||
Map<String, Properties> configs = new HashMap<String, Properties>(16);
|
||||
for (Iterator<Map.Entry<Object, Object>> iter = oldProps.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry<Object, Object> e = iter.next();
|
||||
String k = (String) e.getKey();
|
||||
if (k.startsWith(PROP_META_PREFIX)) {
|
||||
iter.remove();
|
||||
String v = (String) e.getValue();
|
||||
try {
|
||||
k = k.substring(PROP_META_PREFIX.length());
|
||||
String h = k.substring(0, 28); // length of b64 of 160 bit infohash
|
||||
k = k.substring(29); // skip '.'
|
||||
Properties tprops = configs.get(h);
|
||||
if (tprops == null) {
|
||||
tprops = new OrderedProperties();
|
||||
configs.put(h, tprops);
|
||||
}
|
||||
if (k.equals(PROP_META_BITFIELD)) {
|
||||
// old config was timestamp,bitfield; split them
|
||||
int comma = v.indexOf(',');
|
||||
if (comma > 0 && v.length() > comma + 1) {
|
||||
tprops.put(PROP_META_STAMP, v.substring(0, comma));
|
||||
tprops.put(PROP_META_BITFIELD, v.substring(comma + 1));
|
||||
} else {
|
||||
// timestamp only??
|
||||
tprops.put(PROP_META_STAMP, v);
|
||||
}
|
||||
} else {
|
||||
tprops.put(k, v);
|
||||
}
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now make a config file for each torrent
|
||||
for (Map.Entry<String, Properties> e : configs.entrySet()) {
|
||||
String b64 = e.getKey();
|
||||
Properties props = e.getValue();
|
||||
if (props.isEmpty())
|
||||
continue;
|
||||
b64 = b64.replace('$', '=');
|
||||
byte[] ih = Base64.decode(b64);
|
||||
if (ih == null || ih.length != 20)
|
||||
continue;
|
||||
File cfg = configFile(dir, ih);
|
||||
if (!cfg.exists()) {
|
||||
File subdir = cfg.getParentFile();
|
||||
if (!subdir.exists())
|
||||
subdir.mkdirs();
|
||||
try {
|
||||
DataHelper.storeProps(props, cfg);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error storing I2PSnark config " + cfg, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
// now store in new location, minus the zmeta entries
|
||||
File newFile = new File(dir, CONFIG_FILE);
|
||||
Properties newProps = new OrderedProperties();
|
||||
newProps.putAll(oldProps);
|
||||
try {
|
||||
DataHelper.storeProps(newProps, newFile);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error storing I2PSnark config " + newFile, ioe);
|
||||
return dir;
|
||||
}
|
||||
oldFile.delete();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Config migrated from " + oldFile + " to " + dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* The config for a torrent
|
||||
* @return non-null, possibly empty
|
||||
* @since 0.9.11
|
||||
*/
|
||||
private Properties getConfig(Snark snark) {
|
||||
return getConfig(snark.getInfoHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* The config for a torrent
|
||||
* @param ih 20-byte infohash
|
||||
* @return non-null, possibly empty
|
||||
* @since 0.9.11
|
||||
*/
|
||||
private Properties getConfig(byte[] ih) {
|
||||
Properties rv = new OrderedProperties();
|
||||
File conf = configFile(_configDir, ih);
|
||||
synchronized(_configLock) { // one lock for all
|
||||
try {
|
||||
DataHelper.loadProps(rv, conf);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The config file for a torrent
|
||||
* @param confDir the config directory
|
||||
* @param ih 20-byte infohash
|
||||
* @since 0.9.11
|
||||
*/
|
||||
private static File configFile(File confDir, byte[] ih) {
|
||||
String hex = I2PSnarkUtil.toHex(ih);
|
||||
File subdir = new SecureDirectory(confDir, SUBDIR_PREFIX + B64.charAt((ih[0] >> 2) & 0x3f));
|
||||
return new File(subdir, hex + CONFIG_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
/** null to set initial defaults */
|
||||
public void loadConfig(String filename) {
|
||||
synchronized(_configLock) {
|
||||
locked_loadConfig(filename);
|
||||
}
|
||||
}
|
||||
|
||||
/** null to set initial defaults */
|
||||
private void locked_loadConfig(String filename) {
|
||||
if (_config == null)
|
||||
_config = new OrderedProperties();
|
||||
if (filename != null) {
|
||||
File cfg = new File(filename);
|
||||
if (!cfg.isAbsolute())
|
||||
cfg = new File(_context.getConfigDir(), filename);
|
||||
_configFile = cfg;
|
||||
if (cfg.exists()) {
|
||||
_configDir = migrateConfig(cfg);
|
||||
_configFile = new File(_configDir, CONFIG_FILE);
|
||||
if (_configFile.exists()) {
|
||||
try {
|
||||
DataHelper.loadProps(_config, cfg);
|
||||
DataHelper.loadProps(_config, _configFile);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
|
||||
_log.error("Error loading I2PSnark config " + _configFile, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -388,6 +563,7 @@ public class SnarkManager implements CompleteListener {
|
||||
// _config.setProperty(PROP_USE_DHT, Boolean.toString(I2PSnarkUtil.DEFAULT_USE_DHT));
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current theme.
|
||||
* @return String -- the current theme
|
||||
@ -505,6 +681,18 @@ public class SnarkManager implements CompleteListener {
|
||||
String startDelay, String pageSize, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
|
||||
synchronized(_configLock) {
|
||||
locked_updateConfig(dataDir, filesPublic, autoStart, refreshDelay,
|
||||
startDelay, pageSize, seedPct, eepHost,
|
||||
eepPort, i2cpHost, i2cpPort, i2cpOpts,
|
||||
upLimit, upBW, useOpenTrackers, useDHT, theme);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
|
||||
String startDelay, String pageSize, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
|
||||
boolean changed = false;
|
||||
boolean interruptMonitor = false;
|
||||
//if (eepHost != null) {
|
||||
@ -836,7 +1024,7 @@ public class SnarkManager implements CompleteListener {
|
||||
|
||||
public void saveConfig() {
|
||||
try {
|
||||
synchronized (_configFile) {
|
||||
synchronized (_configLock) {
|
||||
DataHelper.storeProps(_config, _configFile);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
@ -844,13 +1032,6 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getConfig() { return _config; }
|
||||
|
||||
/** @since Jetty 7 */
|
||||
public String getConfigFilename() {
|
||||
return _configFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
|
||||
public static final int MAX_FILES_PER_TORRENT = 512;
|
||||
|
||||
@ -908,15 +1089,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
|
||||
* @throws RuntimeException via Snark.fatal()
|
||||
*/
|
||||
private void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
private void addTorrent(String filename) {
|
||||
addTorrent(filename, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws RuntimeException via Snark.fatal()
|
||||
*/
|
||||
private void addTorrent(String filename, boolean dontAutoStart) {
|
||||
private void addTorrent(String filename, File baseFile, boolean dontAutoStart) {
|
||||
if ((!dontAutoStart) && !_util.connected()) {
|
||||
addMessage(_("Connecting to I2P"));
|
||||
boolean ok = _util.connect();
|
||||
@ -997,9 +1186,13 @@ public class SnarkManager implements CompleteListener {
|
||||
} else {
|
||||
// TODO load saved closest DHT nodes and pass to the Snark ?
|
||||
// This may take a LONG time
|
||||
if (baseFile == null)
|
||||
baseFile = getSavedBaseFile(info.getInfoHash());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New Snark, torrent: " + filename + " base: " + baseFile);
|
||||
torrent = new Snark(_util, filename, null, -1, null, null, this,
|
||||
_peerCoordinatorSet, _connectionAcceptor,
|
||||
false, dataDir.getPath());
|
||||
false, dataDir.getPath(), baseFile);
|
||||
loadSavedFilePriorities(torrent);
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
@ -1142,14 +1335,17 @@ public class SnarkManager implements CompleteListener {
|
||||
* This verifies that a torrent with this infohash is not already added.
|
||||
* This may take a LONG time to create or check the storage.
|
||||
*
|
||||
* Called from servlet.
|
||||
*
|
||||
* @param metainfo the metainfo for the torrent
|
||||
* @param bitfield the current completion status of the torrent
|
||||
* @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 baseFile may be null, if so look in rootDataDir
|
||||
* @throws RuntimeException via Snark.fatal()
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, boolean dontAutoStart) throws IOException {
|
||||
public void addTorrent(MetaInfo metainfo, BitField bitfield, String filename, File baseFile, boolean dontAutoStart) throws IOException {
|
||||
// prevent interference by DirMonitor
|
||||
synchronized (_snarks) {
|
||||
Snark snark = getTorrentByInfoHash(metainfo.getInfoHash());
|
||||
@ -1158,11 +1354,11 @@ public class SnarkManager implements CompleteListener {
|
||||
return;
|
||||
}
|
||||
// so addTorrent won't recheck
|
||||
saveTorrentStatus(metainfo, bitfield, null); // no file priorities
|
||||
saveTorrentStatus(metainfo, bitfield, null, baseFile); // no file priorities
|
||||
try {
|
||||
locked_writeMetaInfo(metainfo, filename, areFilesPublic());
|
||||
// hold the lock for a long time
|
||||
addTorrent(filename, dontAutoStart);
|
||||
addTorrent(filename, baseFile, dontAutoStart);
|
||||
} catch (IOException ioe) {
|
||||
addMessage(_("Failed to copy torrent file to {0}", filename));
|
||||
_log.error("Failed to write torrent file", ioe);
|
||||
@ -1235,16 +1431,10 @@ public class SnarkManager implements CompleteListener {
|
||||
* A Snark.CompleteListener method.
|
||||
*/
|
||||
public long getSavedTorrentTime(Snark snark) {
|
||||
byte[] ih = snark.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
Properties config = getConfig(snark);
|
||||
String time = config.getProperty(PROP_META_STAMP);
|
||||
if (time == null)
|
||||
return 0;
|
||||
int comma = time.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return 0;
|
||||
time = time.substring(0, comma);
|
||||
try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
|
||||
return 0;
|
||||
}
|
||||
@ -1258,16 +1448,10 @@ public class SnarkManager implements CompleteListener {
|
||||
MetaInfo metainfo = snark.getMetaInfo();
|
||||
if (metainfo == null)
|
||||
return null;
|
||||
byte[] ih = snark.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
Properties config = getConfig(snark);
|
||||
String bf = config.getProperty(PROP_META_BITFIELD);
|
||||
if (bf == null)
|
||||
return null;
|
||||
int comma = bf.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return null;
|
||||
bf = bf.substring(comma + 1).trim();
|
||||
int len = metainfo.getPieces();
|
||||
if (bf.equals(".")) {
|
||||
BitField bitfield = new BitField(len);
|
||||
@ -1294,10 +1478,8 @@ public class SnarkManager implements CompleteListener {
|
||||
return;
|
||||
if (metainfo.getFiles() == null)
|
||||
return;
|
||||
byte[] ih = snark.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String pri = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
|
||||
Properties config = getConfig(snark);
|
||||
String pri = config.getProperty(PROP_META_PRIORITY);
|
||||
if (pri == null)
|
||||
return;
|
||||
int filecount = metainfo.getFiles().size();
|
||||
@ -1313,23 +1495,38 @@ public class SnarkManager implements CompleteListener {
|
||||
storage.setFilePriorities(rv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base location for a torrent from the config file.
|
||||
* @return File or null, doesn't necessarily exist
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public File getSavedBaseFile(byte[] ih) {
|
||||
Properties config = getConfig(ih);
|
||||
String base = config.getProperty(PROP_META_BASE);
|
||||
if (base == null)
|
||||
return null;
|
||||
return new File(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the completion status of a torrent and the current time in the config file
|
||||
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
|
||||
* The config file property key is appended with the Base64 of the infohash,
|
||||
* with the '=' changed to '$' since a key can't contain '='.
|
||||
* for that torrent.
|
||||
* The time is a standard long converted to string.
|
||||
* The status is either a bitfield converted to Base64 or "." for a completed
|
||||
* torrent to save space in the config file and in memory.
|
||||
*
|
||||
* @param bitfield non-null
|
||||
* @param priorities may be null
|
||||
* @param base may be null
|
||||
*/
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities) {
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
|
||||
synchronized (_configLock) {
|
||||
locked_saveTorrentStatus(metainfo, bitfield, priorities, base);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, File base) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String now = "" + System.currentTimeMillis();
|
||||
String bfs;
|
||||
if (bitfield.complete()) {
|
||||
bfs = ".";
|
||||
@ -1337,10 +1534,13 @@ public class SnarkManager implements CompleteListener {
|
||||
byte[] bf = bitfield.getFieldBytes();
|
||||
bfs = Base64.encode(bf);
|
||||
}
|
||||
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
|
||||
Properties config = getConfig(ih);
|
||||
config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
|
||||
config.setProperty(PROP_META_BITFIELD, bfs);
|
||||
if (base != null)
|
||||
config.setProperty(PROP_META_BASE, base.getAbsolutePath());
|
||||
|
||||
// now the file priorities
|
||||
String prop = PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX;
|
||||
if (priorities != null) {
|
||||
boolean nonzero = false;
|
||||
for (int i = 0; i < priorities.length; i++) {
|
||||
@ -1358,30 +1558,40 @@ public class SnarkManager implements CompleteListener {
|
||||
if (i != priorities.length - 1)
|
||||
buf.append(',');
|
||||
}
|
||||
_config.setProperty(prop, buf.toString());
|
||||
config.setProperty(PROP_META_PRIORITY, buf.toString());
|
||||
} else {
|
||||
_config.remove(prop);
|
||||
config.remove(PROP_META_PRIORITY);
|
||||
}
|
||||
} else {
|
||||
_config.remove(prop);
|
||||
config.remove(PROP_META_PRIORITY);
|
||||
}
|
||||
|
||||
// TODO save closest DHT nodes too
|
||||
|
||||
saveConfig();
|
||||
File conf = configFile(_configDir, ih);
|
||||
File subdir = conf.getParentFile();
|
||||
if (!subdir.exists())
|
||||
subdir.mkdirs();
|
||||
try {
|
||||
DataHelper.storeProps(config, conf);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to save the config to " + conf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the status of a torrent from the config file.
|
||||
* This may help the config file from growing too big.
|
||||
* Remove the status of a torrent by removing the config file.
|
||||
*/
|
||||
public void removeTorrentStatus(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_PRIORITY_SUFFIX);
|
||||
saveConfig();
|
||||
File conf = configFile(_configDir, ih);
|
||||
synchronized (_configLock) {
|
||||
conf.delete();
|
||||
File subdir = conf.getParentFile();
|
||||
String[] files = subdir.list();
|
||||
if (files != null && files.length == 0)
|
||||
subdir.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1581,7 +1791,7 @@ public class SnarkManager implements CompleteListener {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
Storage storage = snark.getStorage();
|
||||
if (meta != null && storage != null)
|
||||
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities());
|
||||
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getBase());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1603,7 +1813,7 @@ public class SnarkManager implements CompleteListener {
|
||||
snark.stopTorrent();
|
||||
return null;
|
||||
}
|
||||
saveTorrentStatus(meta, storage.getBitField(), null); // no file priorities
|
||||
saveTorrentStatus(meta, storage.getBitField(), null, storage.getBase()); // no file priorities
|
||||
// temp for addMessage() in case canonical throws
|
||||
String name = storage.getBaseName();
|
||||
try {
|
||||
@ -1704,7 +1914,7 @@ public class SnarkManager implements CompleteListener {
|
||||
try {
|
||||
// Snark.fatal() throws a RuntimeException
|
||||
// don't let one bad torrent kill the whole loop
|
||||
addTorrent(name, !shouldAutoStart());
|
||||
addTorrent(name, null, !shouldAutoStart());
|
||||
} catch (Exception e) {
|
||||
addMessage(_("Error: Could not add the torrent {0}", name) + ": " + e);
|
||||
_log.error("Unable to add the torrent " + name, e);
|
||||
|
@ -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;
|
||||
@ -83,15 +86,18 @@ public class Storage
|
||||
private static final ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
||||
|
||||
/**
|
||||
* Creates a new storage based on the supplied MetaInfo. This will
|
||||
* Creates a new storage based on the supplied MetaInfo.
|
||||
*
|
||||
* Does not check storage. Caller MUST call check(), which will
|
||||
* try to create and/or check all needed files in the MetaInfo.
|
||||
*
|
||||
* Does not check storage. Caller MUST call check()
|
||||
* @param baseFile the torrent data file or dir
|
||||
*/
|
||||
public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
|
||||
public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener)
|
||||
{
|
||||
_util = util;
|
||||
_log = util.getContext().logManager().getLog(Storage.class);
|
||||
_base = baseFile;
|
||||
this.metainfo = metainfo;
|
||||
this.listener = listener;
|
||||
needed = metainfo.getPieces();
|
||||
@ -121,6 +127,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 +312,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 +346,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))
|
||||
if (f.equals(file))
|
||||
return tf.priority;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -371,25 +363,19 @@ 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)) {
|
||||
if (f.equals(file)) {
|
||||
tf.priority = pri;
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,9 +476,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 +486,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,18 +498,18 @@ public class Storage
|
||||
{
|
||||
// Create base as file.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating/Checking file: " + base);
|
||||
_log.info("Creating/Checking file: " + _base);
|
||||
// createNewFile() can throw a "Permission denied" IOE even if the file exists???
|
||||
// so do it second
|
||||
if (!base.exists() && !base.createNewFile())
|
||||
throw new IOException("Could not create file " + base);
|
||||
if (!_base.exists() && !_base.createNewFile())
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -536,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();
|
||||
@ -546,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)) {
|
||||
@ -562,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();
|
||||
@ -614,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");
|
||||
@ -690,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
|
||||
{
|
||||
@ -725,15 +708,47 @@ 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 the File
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public File getBase() {
|
||||
return _base;
|
||||
}
|
||||
return base;
|
||||
|
||||
/**
|
||||
* Does not include directories. Unsorted.
|
||||
* @return a new List
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public List<File> getFiles() {
|
||||
List<File> rv = new ArrayList<File>(_torrentFiles.size());
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
rv.add(tf.RAFfile);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes the base for a multi-file torrent.
|
||||
* Sorted bottom-up for easy deletion.
|
||||
* Slow. Use for deletion only.
|
||||
* @since 0.9.11
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +40,7 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.SnarkManager;
|
||||
import org.klomp.snark.TrackerClient;
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEncoder;
|
||||
@ -152,7 +153,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private static final long CLEAN_TIME = 63*1000;
|
||||
private static final long EXPLORE_TIME = 877*1000;
|
||||
private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
|
||||
private static final String DHT_FILE_SUFFIX = ".dht.dat";
|
||||
public static final String DHT_FILE_SUFFIX = ".dht.dat";
|
||||
|
||||
private static final int SEND_CRYPTO_TAGS = 8;
|
||||
private static final int LOW_CRYPTO_TAGS = 4;
|
||||
@ -185,8 +186,14 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_myNID = new NID(_myID);
|
||||
}
|
||||
_myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
|
||||
_dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
|
||||
_backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
|
||||
File conf = new File(ctx.getConfigDir(), baseName + ".config" + SnarkManager.CONFIG_DIR_SUFFIX);
|
||||
_dhtFile = new File(conf, "i2psnark" + DHT_FILE_SUFFIX);
|
||||
if (baseName.equals("i2psnark")) {
|
||||
_backupDhtFile = null;
|
||||
} else {
|
||||
File bconf = new File(ctx.getConfigDir(), "i2psnark.config" + SnarkManager.CONFIG_DIR_SUFFIX);
|
||||
_backupDhtFile = new File(bconf, "i2psnark" + DHT_FILE_SUFFIX);
|
||||
}
|
||||
_knownNodes = new DHTNodes(ctx, _myNID);
|
||||
|
||||
start();
|
||||
|
@ -181,11 +181,10 @@ class BasicServlet extends HttpServlet
|
||||
HttpContent r = null;
|
||||
if (_warBase != null && pathInContext.startsWith(_warBase)) {
|
||||
r = new JarContent(pathInContext);
|
||||
} else if (!pathInContext.contains("..") &&
|
||||
!pathInContext.endsWith("/")) {
|
||||
File f = new File(_resourceBase, pathInContext);
|
||||
} else {
|
||||
File f = getResource(pathInContext);
|
||||
// exists && !directory
|
||||
if (f.isFile())
|
||||
if (f != null && f.isFile())
|
||||
r = new FileContent(f);
|
||||
}
|
||||
return r;
|
||||
|
@ -18,7 +18,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;
|
||||
@ -59,6 +58,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
private static final String DEFAULT_NAME = "i2psnark";
|
||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||
private static final String WARBASE = "/.icons/";
|
||||
|
||||
public I2PSnarkServlet() {
|
||||
super();
|
||||
@ -84,7 +84,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
_manager.start();
|
||||
loadMimeMap("org/klomp/snark/web/mime");
|
||||
setResourceBase(_manager.getDataDir());
|
||||
setWarBase("/.icons/");
|
||||
setWarBase(WARBASE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -95,17 +95,35 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this instead of passing a resource base to super(), because
|
||||
* if a resource base is set, super.getResource() always uses that base,
|
||||
* and we can't get any resources (like icons) out of the .war
|
||||
* We override this to set the file relative to the storage dirctory
|
||||
* for the torrent.
|
||||
*
|
||||
* @param pathInContext should always start with /
|
||||
*/
|
||||
@Override
|
||||
public File getResource(String pathInContext)
|
||||
{
|
||||
if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") ||
|
||||
pathInContext.equals("/index.html") || pathInContext.startsWith("/.icons/"))
|
||||
!pathInContext.startsWith("/") || pathInContext.length() == 0 ||
|
||||
pathInContext.equals("/index.html") || pathInContext.startsWith(WARBASE))
|
||||
return super.getResource(pathInContext);
|
||||
// files in the i2psnark/ directory
|
||||
// get top level
|
||||
pathInContext = pathInContext.substring(1);
|
||||
File top = new File(pathInContext);
|
||||
File parent;
|
||||
while ((parent = top.getParentFile()) != null) {
|
||||
top = parent;
|
||||
}
|
||||
Snark snark = _manager.getTorrentByBaseName(top.getPath());
|
||||
if (snark != null) {
|
||||
Storage storage = snark.getStorage();
|
||||
if (storage != null) {
|
||||
File sbase = storage.getBase();
|
||||
String child = pathInContext.substring(top.getPath().length());
|
||||
return new File(sbase, child);
|
||||
}
|
||||
}
|
||||
return new File(_resourceBase, pathInContext);
|
||||
}
|
||||
|
||||
@ -191,9 +209,18 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
return;
|
||||
}
|
||||
|
||||
// in-war icons etc.
|
||||
if (path != null && path.startsWith(WARBASE)) {
|
||||
if (method.equals("GET") || method.equals("HEAD"))
|
||||
super.doGet(req, resp);
|
||||
else // no POST either
|
||||
resp.sendError(405);
|
||||
}
|
||||
|
||||
boolean isConfigure = "/configure".equals(path);
|
||||
// index.jsp doesn't work, it is grabbed by the war handler before here
|
||||
if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
|
||||
if (!(path == null || path.equals("/") || path.equals("/index.jsp") ||
|
||||
path.equals("/index.html") || path.equals("/_post") || isConfigure)) {
|
||||
if (path.endsWith("/")) {
|
||||
// Listing of a torrent (torrent detail page)
|
||||
// bypass the horrid Resource.getListHTML()
|
||||
@ -848,42 +875,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;
|
||||
}
|
||||
}
|
||||
@ -922,7 +943,9 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
} else if ("Create".equals(action)) {
|
||||
String baseData = req.getParameter("baseFile");
|
||||
if (baseData != null && baseData.trim().length() > 0) {
|
||||
File baseFile = new File(_manager.getDataDir(), baseData);
|
||||
File baseFile = new File(baseData.trim());
|
||||
if (!baseFile.isAbsolute())
|
||||
baseFile = new File(_manager.getDataDir(), baseData);
|
||||
String announceURL = req.getParameter("announceURL");
|
||||
// make the user add a tracker on the config form now
|
||||
//String announceURLOther = req.getParameter("announceURLOther");
|
||||
@ -982,7 +1005,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent");
|
||||
// FIXME is the storage going to stay around thanks to the info reference?
|
||||
// now add it, but don't automatically start it
|
||||
_manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), true);
|
||||
_manager.addTorrent(info, s.getBitField(), torrentFile.getAbsolutePath(), baseFile, true);
|
||||
_manager.addMessage(_("Torrent created for \"{0}\"", baseFile.getName()) + ": " + torrentFile.getAbsolutePath());
|
||||
if (announceURL != null && !_manager.util().getOpenTrackers().contains(announceURL))
|
||||
_manager.addMessage(_("Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\"", baseFile.getName()));
|
||||
@ -1783,10 +1806,11 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("</span><hr>\n<table border=\"0\"><tr><td>");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
out.write(_("Data to seed"));
|
||||
out.write(":<td><code>" + _manager.getDataDir().getAbsolutePath() + File.separatorChar
|
||||
+ "</code><input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
|
||||
out.write(":<td>"
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"58\" value=\"" + baseFile
|
||||
+ "\" spellcheck=\"false\" title=\"");
|
||||
out.write(_("File or directory to seed (must be within the specified path)"));
|
||||
out.write(_("File or directory to seed (full path or within the directory {0} )",
|
||||
_manager.getDataDir().getAbsolutePath() + File.separatorChar));
|
||||
out.write("\" ><tr><td>\n");
|
||||
out.write(_("Trackers"));
|
||||
out.write(":<td><table style=\"width: 30%;\"><tr><td></td><td align=\"center\">");
|
||||
@ -2230,9 +2254,11 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
private static class ListingComparator implements Comparator<File>, Serializable {
|
||||
|
||||
public int compare(File l, File r) {
|
||||
if (l.isDirectory() && !r.isDirectory())
|
||||
boolean ld = l.isDirectory();
|
||||
boolean rd = r.isDirectory();
|
||||
if (ld && !rd)
|
||||
return -1;
|
||||
if (r.isDirectory() && !l.isDirectory())
|
||||
if (rd && !ld)
|
||||
return 1;
|
||||
return Collator.getInstance().compare(l.getName(), r.getName());
|
||||
}
|
||||
@ -2261,22 +2287,16 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
* </pre>
|
||||
*
|
||||
* Get the resource list as a HTML directory listing.
|
||||
* @param r The Resource
|
||||
* @param xxxr The Resource unused
|
||||
* @param base The base URL
|
||||
* @param parent True if the parent directory should be included
|
||||
* @param postParams map of POST parameters or null if not a POST
|
||||
* @return String of HTML or null if postParams != null
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String getListHTML(File r, String base, boolean parent, Map<String, String[]> postParams)
|
||||
private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams)
|
||||
throws IOException
|
||||
{
|
||||
File[] ls = null;
|
||||
if (r.isDirectory()) {
|
||||
ls = r.listFiles();
|
||||
Arrays.sort(ls, new ListingComparator());
|
||||
} // if r is not a directory, we are only showing torrent info section
|
||||
|
||||
String title = decodePath(base);
|
||||
String cpath = _contextPath + '/';
|
||||
if (title.startsWith(cpath))
|
||||
@ -2284,19 +2304,47 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
// Get the snark associated with this directory
|
||||
String torrentName;
|
||||
String pathInTorrent;
|
||||
int slash = title.indexOf('/');
|
||||
if (slash > 0)
|
||||
if (slash > 0) {
|
||||
torrentName = title.substring(0, slash);
|
||||
else
|
||||
pathInTorrent = title.substring(slash);
|
||||
} else {
|
||||
torrentName = title;
|
||||
pathInTorrent = "/";
|
||||
}
|
||||
Snark snark = _manager.getTorrentByBaseName(torrentName);
|
||||
|
||||
if (snark != null && postParams != null) {
|
||||
// caller must P-R-G
|
||||
String[] val = postParams.get("nonce");
|
||||
if (val != null) {
|
||||
String nonce = val[0];
|
||||
if (String.valueOf(_nonce).equals(nonce))
|
||||
savePriorities(snark, postParams);
|
||||
else
|
||||
_manager.addMessage("Please retry form submission (bad nonce)");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
File r;
|
||||
if (snark != null) {
|
||||
Storage storage = snark.getStorage();
|
||||
if (storage != null) {
|
||||
File sbase = storage.getBase();
|
||||
if (pathInTorrent.equals("/"))
|
||||
r = sbase;
|
||||
else
|
||||
r = new File(sbase, pathInTorrent);
|
||||
} else {
|
||||
// magnet, dummy
|
||||
r = new File("");
|
||||
}
|
||||
} else {
|
||||
// dummy
|
||||
r = new File("");
|
||||
}
|
||||
StringBuilder buf=new StringBuilder(4096);
|
||||
buf.append(DOCTYPE).append("<HTML><HEAD><TITLE>");
|
||||
if (title.endsWith("/"))
|
||||
@ -2306,7 +2354,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append(title);
|
||||
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
|
||||
"</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
|
||||
buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "arrow_refresh.png\"> ");
|
||||
buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\"> ");
|
||||
if (_contextName.equals(DEFAULT_NAME))
|
||||
buf.append(_("I2PSnark"));
|
||||
else
|
||||
@ -2315,9 +2363,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
if (parent) // always true
|
||||
buf.append("<div class=\"page\"><div class=\"mainsection\">");
|
||||
boolean showPriority = ls != null && snark != null && snark.getStorage() != null && !snark.getStorage().complete();
|
||||
if (showPriority)
|
||||
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
|
||||
r.isDirectory();
|
||||
if (showPriority) {
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
|
||||
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
|
||||
}
|
||||
if (snark != null) {
|
||||
// first table - torrent info
|
||||
buf.append("<table class=\"snarkTorrentInfo\">\n");
|
||||
@ -2330,11 +2381,17 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
String fullPath = snark.getName();
|
||||
String baseName = urlEncode((new File(fullPath)).getName());
|
||||
buf.append("<tr><td>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > <b>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" > <b>")
|
||||
.append(_("Torrent file"))
|
||||
.append(":</b> <a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
|
||||
.append(fullPath)
|
||||
.append("</a></td></tr>\n");
|
||||
buf.append("<tr><td>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" > <b>")
|
||||
.append(_("Data location"))
|
||||
.append(":</b> ")
|
||||
.append(urlEncode(snark.getStorage().getBase().getPath()))
|
||||
.append("</td></tr>\n");
|
||||
|
||||
String announce = null;
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
@ -2435,40 +2492,40 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// .append(MAGGOT).append(hex).append(':').append(hex).append("</a></td></tr>");
|
||||
|
||||
buf.append("<tr><td>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" > <b>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("size.png\" > <b>")
|
||||
.append(_("Size"))
|
||||
.append(":</b> ")
|
||||
.append(formatSize(snark.getTotalLength()));
|
||||
int pieces = snark.getPieces();
|
||||
double completion = (pieces - snark.getNeeded()) / (double) pieces;
|
||||
if (completion < 1.0)
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" > <b>")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > <b>")
|
||||
.append(_("Completion"))
|
||||
.append(":</b> ")
|
||||
.append((new DecimalFormat("0.00%")).format(completion));
|
||||
else
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" > ")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > ")
|
||||
.append(_("Complete"));
|
||||
// else unknown
|
||||
long needed = snark.getNeededLength();
|
||||
if (needed > 0)
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "head_rx.png\" > <b>")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > <b>")
|
||||
.append(_("Remaining"))
|
||||
.append(":</b> ")
|
||||
.append(formatSize(needed));
|
||||
if (meta != null) {
|
||||
List<List<String>> files = meta.getFiles();
|
||||
int fileCount = files != null ? files.size() : 1;
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > <b>")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" > <b>")
|
||||
.append(_("Files"))
|
||||
.append(":</b> ")
|
||||
.append(fileCount);
|
||||
}
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > <b>")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" > <b>")
|
||||
.append(_("Pieces"))
|
||||
.append(":</b> ")
|
||||
.append(pieces);
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > <b>")
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("file.png\" > <b>")
|
||||
.append(_("Piece size"))
|
||||
.append(":</b> ")
|
||||
.append(formatSize(snark.getPieceLength(0)))
|
||||
@ -2481,6 +2538,22 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append("\"</th></tr>\n");
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
|
||||
if (snark != null && !r.exists()) {
|
||||
// fixup TODO
|
||||
buf.append("<p>Does not exist<br>resource=\"").append(r.toString())
|
||||
.append("\"<br>base=\"").append(base)
|
||||
.append("\"<br>torrent=\"").append(torrentName)
|
||||
.append("\"</p></div></div></BODY></HTML>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
File[] ls = null;
|
||||
if (r.isDirectory()) {
|
||||
ls = r.listFiles();
|
||||
Arrays.sort(ls, new ListingComparator());
|
||||
} // if r is not a directory, we are only showing torrent info section
|
||||
|
||||
if (ls == null) {
|
||||
// We are only showing the torrent info section
|
||||
buf.append("</div></div></BODY></HTML>");
|
||||
@ -2491,7 +2564,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("<table class=\"snarkDirInfo\"><thead>\n");
|
||||
buf.append("<tr>\n")
|
||||
.append("<th colspan=2>")
|
||||
.append("<img border=\"0\" src=\"" + _imgPath + "file.png\" title=\"")
|
||||
.append("<img border=\"0\" src=\"").append(_imgPath).append("file.png\" title=\"")
|
||||
.append(_("Directory"))
|
||||
.append(": ")
|
||||
.append(directory)
|
||||
@ -2499,20 +2572,20 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append(_("Directory"))
|
||||
.append("\"></th>\n");
|
||||
buf.append("<th align=\"right\">")
|
||||
.append("<img border=\"0\" src=\"" + _imgPath + "size.png\" title=\"")
|
||||
.append("<img border=\"0\" src=\"").append(_imgPath).append("size.png\" title=\"")
|
||||
.append(_("Size"))
|
||||
.append("\" alt=\"")
|
||||
.append(_("Size"))
|
||||
.append("\"></th>\n");
|
||||
buf.append("<th class=\"headerstatus\">")
|
||||
.append("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"")
|
||||
.append("<img border=\"0\" src=\"").append(_imgPath).append("status.png\" title=\"")
|
||||
.append(_("Status"))
|
||||
.append("\" alt=\"")
|
||||
.append(_("Status"))
|
||||
.append("\"></th>\n");
|
||||
if (showPriority)
|
||||
buf.append("<th class=\"headerpriority\">")
|
||||
.append("<img border=\"0\" src=\"" + _imgPath + "priority.png\" title=\"")
|
||||
.append("<img border=\"0\" src=\"").append(_imgPath).append("priority.png\" title=\"")
|
||||
.append(_("Priority"))
|
||||
.append("\" alt=\"")
|
||||
.append(_("Priority"))
|
||||
@ -2520,7 +2593,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("</tr>\n</thead>\n");
|
||||
buf.append("<tr><td colspan=\"" + (showPriority ? '5' : '4') + "\" class=\"ParentDir\"><A HREF=\"");
|
||||
buf.append(addPaths(base,"../"));
|
||||
buf.append("\"><img alt=\"\" border=\"0\" src=\"" + _imgPath + "up.png\"> ")
|
||||
buf.append("\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("up.png\"> ")
|
||||
.append(_("Up to higher level directory"))
|
||||
.append("</A></td></tr>\n");
|
||||
|
||||
@ -2544,6 +2617,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");
|
||||
@ -2554,9 +2628,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?");
|
||||
@ -2564,7 +2637,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)
|
||||
@ -2575,9 +2648,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
(100 * (length - remaining) / length) + "% " + _("complete") +
|
||||
" (" + DataHelper.formatSize2(remaining) + "B " + _("remaining") + ")";
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
status = "Not a file? " + ioe;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2617,21 +2688,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;
|
||||
@ -2703,6 +2772,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
icon = "application";
|
||||
else if (plc.endsWith(".iso"))
|
||||
icon = "cd";
|
||||
else if (mime.equals("application/x-bittorrent"))
|
||||
icon = "magnet";
|
||||
else
|
||||
icon = "page_white";
|
||||
return icon;
|
||||
@ -2727,7 +2798,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);
|
||||
@ -2736,6 +2807,6 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
}
|
||||
snark.updatePiecePriorities();
|
||||
_manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities());
|
||||
_manager.saveTorrentStatus(snark.getMetaInfo(), storage.getBitField(), storage.getFilePriorities(), storage.getBase());
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ su2 = application/zip
|
||||
su3 = application/zip
|
||||
sud = application/zip
|
||||
tbz = application/x-bzip2
|
||||
torrent = application/x-bittorrent
|
||||
txt = text/plain
|
||||
war = application/java-archive
|
||||
webm = video/webm
|
||||
|
Reference in New Issue
Block a user