(zzz) i2psnark: Save torrent completion status in i2psnark.config

This commit is contained in:
zzz
2007-03-18 21:43:41 +00:00
committed by zzz
parent cf4a9ffc27
commit 6a7dbc8e3a
3 changed files with 124 additions and 4 deletions

View File

@ -468,6 +468,8 @@ public class Snark
pc.halt();
Storage st = storage;
if (st != null) {
if (storage.changed)
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
try {
storage.close();
} catch (IOException ioe) {

View File

@ -3,6 +3,7 @@ package org.klomp.snark;
import java.io.*;
import java.util.*;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@ -30,6 +31,8 @@ public class SnarkManager implements Snark.CompleteListener {
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
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_AUTO_START = "i2snark.autoStart";
public static final String DEFAULT_AUTO_START = "false";
@ -271,7 +274,9 @@ public class SnarkManager implements Snark.CompleteListener {
public void saveConfig() {
try {
synchronized (_configFile) {
DataHelper.storeProps(_config, new File(_configFile));
}
} catch (IOException ioe) {
addMessage("Unable to save the config to '" + _configFile + "'");
}
@ -361,6 +366,91 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
/**
* Get the timestamp for a torrent from the config file
*/
public long getSavedTorrentTime(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
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;
}
/**
* Get the saved bitfield for a torrent from the config file.
* Convert "." to a full bitfield.
*/
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
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);
for (int i = 0; i < len; i++)
bitfield.set(i);
return bitfield;
}
byte[] bitfield = Base64.decode(bf);
if (bitfield == null)
return null;
if (bitfield.length * 8 < len)
return null;
return new BitField(bitfield, len);
}
/**
* 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 '='.
* 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.
*/
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
String now = "" + System.currentTimeMillis();
String bfs;
if (bitfield.complete()) {
bfs = ".";
} else {
byte[] bf = bitfield.getFieldBytes();
bfs = Base64.encode(bf);
}
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
saveConfig();
}
/**
* Remove the status of a torrent from the config file.
* This may help the config file from growing too big.
*/
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);
saveConfig();
}
private String locked_validateTorrent(MetaInfo info) throws IOException {
List files = info.getFiles();
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
@ -426,6 +516,8 @@ public class SnarkManager implements Snark.CompleteListener {
if (torrent != null) {
File torrentFile = new File(filename);
torrentFile.delete();
if (torrent.storage != null)
removeTorrentStatus(torrent.storage.getMetaInfo());
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
}
}

View File

@ -45,6 +45,7 @@ public class Storage
// XXX - Not always set correctly
int piece_size;
int pieces;
boolean changed;
/** The default piece size. */
private static int MIN_PIECE_SIZE = 256*1024;
@ -285,6 +286,10 @@ public class Storage
public void check(String rootDir) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
// look for saved bitfield and timestamp in the config file
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
List files = metainfo.getFiles();
if (files == null)
@ -298,6 +303,11 @@ public class Storage
rafs = new RandomAccessFile[1];
names = new String[1];
lengths[0] = metainfo.getTotalLength();
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
rafs[0] = new RandomAccessFile(base, "r");
else
@ -322,6 +332,11 @@ public class Storage
File f = createFileFromNames(base, (List)files.get(i));
lengths[i] = ((Long)ls.get(i)).longValue();
total += lengths[i];
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (f.exists() && !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
@ -335,7 +350,14 @@ public class Storage
throw new IOException("File lengths do not add up "
+ total + " != " + metalength);
}
if (useSavedBitField) {
bitfield = savedBitField;
needed = metainfo.getPieces() - bitfield.count();
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
} else {
checkCreateFiles();
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
/**
@ -354,7 +376,7 @@ public class Storage
if (!base.exists())
throw new IOException("Could not reopen file " + base);
if (!base.canWrite()) // hope we can get away with this, if we are only seeding...
if (complete() || !base.canWrite()) // hope we can get away with this, if we are only seeding...
rafs[0] = new RandomAccessFile(base, "r");
else
rafs[0] = new RandomAccessFile(base, "rw");
@ -372,7 +394,7 @@ public class Storage
File f = getFileFromNames(base, (List)files.get(i));
if (!f.exists())
throw new IOException("Could not reopen file " + f);
if (!f.canWrite()) // see above re: only seeding
if (complete() || !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
rafs[i] = new RandomAccessFile(f, "rw");
@ -517,6 +539,7 @@ public class Storage
// gobble gobble
}
}
changed = false;
}
/**
@ -604,6 +627,7 @@ public class Storage
}
}
changed = true;
if (complete) {
// listener.storageCompleted(this);
// do we also need to close all of the files and reopen
@ -623,6 +647,8 @@ public class Storage
listener.setWantedPieces(this);
Snark.debug("WARNING: Not really done, missing " + needed
+ " pieces", Snark.WARNING);
} else {
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}