propagate from branch 'i2p.i2p' (head e606c473eb1e461a477e45419f6295b6430a7353)

to branch 'i2p.i2p.zzz.test2' (head 6212892778308db10a86e58f9f275c838f604973)
This commit is contained in:
zzz
2014-09-09 19:27:10 +00:00
83 changed files with 1081 additions and 236 deletions

View File

@ -54,8 +54,15 @@ public interface CompleteListener {
*/
public void gotPiece(Snark snark);
// not really listeners but the easiest way to get back to an optional SnarkManager
/** not really listeners but the easiest way to get back to an optional SnarkManager */
public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark);
/**
* @since 0.9.15
*/
public boolean getSavedPreserveNamesSetting(Snark snark);
/**
* @since 0.9.15
*/
public long getSavedUploaded(Snark snark);
}

View File

@ -281,6 +281,14 @@ class PeerCoordinator implements PeerListener
return uploaded;
}
/**
* Sets the initial total of uploaded bytes of all peers (from a saved status)
* @since 0.9.15
*/
public void setUploaded(long up) {
uploaded = up;
}
/**
* Returns the total number of downloaded bytes of all peers.
*/

View File

@ -237,6 +237,7 @@ public class Snark
private volatile boolean _autoStoppable;
// String indicating main activity
private volatile String activity = "Not started";
private final long savedUploaded;
/**
@ -463,6 +464,7 @@ public class Snark
trackerclient = new TrackerClient(meta, coordinator);
*/
savedUploaded = (completeListener != null) ? completeListener.getSavedUploaded(this) : 0;
if (start)
startTorrent();
}
@ -488,6 +490,7 @@ public class Snark
this.infoHash = ih;
this.additionalTrackerURL = trackerURL;
this.rootDataDir = rootDir != null ? new File(rootDir) : null; // null only for FetchAndAdd extension
savedUploaded = 0;
stopped = true;
id = generateID();
@ -556,6 +559,7 @@ public class Snark
_log.info("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient");
activity = "Collecting pieces";
coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
coordinator.setUploaded(savedUploaded);
if (_peerCoordinatorSet != null) {
// multitorrent
_peerCoordinatorSet.add(coordinator);
@ -619,7 +623,7 @@ public class Snark
pc.halt();
Storage st = storage;
if (st != null) {
boolean changed = storage.isChanged();
boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
try {
storage.close();
} catch (IOException ioe) {
@ -773,7 +777,7 @@ public class Snark
PeerCoordinator coord = coordinator;
if (coord != null)
return coord.getUploaded();
return 0;
return savedUploaded;
}
/**

View File

@ -92,6 +92,7 @@ public class SnarkManager implements CompleteListener {
private static final String PROP_META_BITFIELD = "bitfield";
private static final String PROP_META_PRIORITY = "priority";
private static final String PROP_META_PRESERVE_NAMES = "preserveFileNames";
private static final String PROP_META_UPLOADED = "uploaded";
//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.";
@ -128,6 +129,9 @@ public class SnarkManager implements CompleteListener {
/**
* "name", "announceURL=websiteURL" pairs
* '=' in announceURL must be escaped as ,
*
* Please use host name, not b32 or full dest, in announce URL. Ensure in default hosts.txt.
* Please use host name, not b32 or full dest, in website URL. Ensure in default hosts.txt.
*/
private static final String DEFAULT_TRACKERS[] = {
// "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/"
@ -1357,7 +1361,7 @@ public class SnarkManager implements CompleteListener {
return false;
}
// so addTorrent won't recheck
saveTorrentStatus(metainfo, bitfield, null, baseFile, true); // no file priorities
saveTorrentStatus(metainfo, bitfield, null, baseFile, true, 0); // no file priorities
try {
locked_writeMetaInfo(metainfo, filename, areFilesPublic());
// hold the lock for a long time
@ -1522,6 +1526,21 @@ public class SnarkManager implements CompleteListener {
Properties config = getConfig(snark);
return Boolean.parseBoolean(config.getProperty(PROP_META_PRESERVE_NAMES));
}
/**
* Get setting for a torrent from the config file.
* @return setting, 0 if not found
* @since 0.9.15
*/
public long getSavedUploaded(Snark snark) {
Properties config = getConfig(snark);
if (config != null) {
try {
return Long.parseLong(config.getProperty(PROP_META_UPLOADED));
} catch (NumberFormatException nfe) {}
}
return 0;
}
/**
* Save the completion status of a torrent and other data in the config file
@ -1535,7 +1554,8 @@ public class SnarkManager implements CompleteListener {
if (meta == null || storage == null)
return;
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(),
storage.getBase(), storage.getPreserveFileNames());
storage.getBase(), storage.getPreserveFileNames(),
snark.getUploaded());
}
/**
@ -1550,14 +1570,14 @@ public class SnarkManager implements CompleteListener {
* @param base may be null
*/
private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities,
File base, boolean preserveNames) {
File base, boolean preserveNames, long uploaded) {
synchronized (_configLock) {
locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames);
locked_saveTorrentStatus(metainfo, bitfield, priorities, base, preserveNames, uploaded);
}
}
private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities,
File base, boolean preserveNames) {
File base, boolean preserveNames, long uploaded) {
byte[] ih = metainfo.getInfoHash();
String bfs;
if (bitfield.complete()) {
@ -1570,6 +1590,7 @@ public class SnarkManager implements CompleteListener {
config.setProperty(PROP_META_STAMP, Long.toString(System.currentTimeMillis()));
config.setProperty(PROP_META_BITFIELD, bfs);
config.setProperty(PROP_META_PRESERVE_NAMES, Boolean.toString(preserveNames));
config.setProperty(PROP_META_UPLOADED, Long.toString(uploaded));
if (base != null)
config.setProperty(PROP_META_BASE, base.getAbsolutePath());
@ -1826,7 +1847,7 @@ public class SnarkManager implements CompleteListener {
Storage storage = snark.getStorage();
if (meta != null && storage != null)
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(),
storage.getBase(), storage.getPreserveFileNames());
storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded());
}
/**
@ -1849,7 +1870,7 @@ public class SnarkManager implements CompleteListener {
return null;
}
saveTorrentStatus(meta, storage.getBitField(), null,
storage.getBase(), storage.getPreserveFileNames()); // no file priorities
storage.getBase(), storage.getPreserveFileNames(), 0);
// temp for addMessage() in case canonical throws
String name = storage.getBaseName();
try {

View File

@ -316,15 +316,34 @@ public class Storage
}
/**
* Get index to pass to remaining(), getPriority(), setPriority()
*
* @param file non-canonical path (non-directory)
* @return internal index of file; -1 if unknown file
* @since 0.9.15
*/
public int indexOf(File file) {
for (int i = 0; i < _torrentFiles.size(); i++) {
File f = _torrentFiles.get(i).RAFfile;
if (f.equals(file))
return i;
}
return -1;
}
/**
* @param fileIndex as obtained from indexOf
* @return number of bytes remaining; -1 if unknown file
* @since 0.7.14
*/
public long remaining(File file) {
public long remaining(int fileIndex) {
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return -1;
long bytes = 0;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
if (f.equals(file)) {
for (int i = 0; i < _torrentFiles.size(); i++) {
TorrentFile tf = _torrentFiles.get(i);
if (i == fileIndex) {
File f = tf.RAFfile;
if (complete())
return 0;
int psz = piece_size;
@ -350,37 +369,30 @@ public class Storage
}
/**
* @param file non-canonical path (non-directory)
* @param fileIndex as obtained from indexOf
* @since 0.8.1
*/
public int getPriority(File file) {
public int getPriority(int fileIndex) {
if (complete() || metainfo.getFiles() == null)
return 0;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
if (f.equals(file))
return tf.priority;
}
return 0;
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return 0;
return _torrentFiles.get(fileIndex).priority;
}
/**
* Must call Snark.updatePiecePriorities()
* (which calls getPiecePriorities()) after calling this.
* @param file non-canonical path (non-directory)
* @param fileIndex as obtained from indexOf
* @param pri default 0; <0 to disable
* @since 0.8.1
*/
public void setPriority(File file, int pri) {
public void setPriority(int fileIndex, int pri) {
if (complete() || metainfo.getFiles() == null)
return;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
if (f.equals(file)) {
tf.priority = pri;
return;
}
}
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return;
_torrentFiles.get(fileIndex).priority = pri;
}
/**

View File

@ -72,6 +72,8 @@ public class TrackerClient implements Runnable {
private static final String STOPPED_EVENT = "stopped";
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
private static final String NOT_REGISTERED_2 = "torrent not found"; // diftracker
private static final String NOT_REGISTERED_3 = "torrent unauthorised"; // vuze
private static final String ERROR_GOT_HTML = "received html"; // fake return
/** this is our equivalent to router.utorrent.com for bootstrap */
private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
@ -577,14 +579,20 @@ public class TrackerClient implements Runnable {
if (tr.isPrimary)
snark.setTrackerProblems(tr.trackerProblems);
String tplc = tr.trackerProblems.toLowerCase(Locale.US);
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2)) {
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2) ||
tplc.startsWith(NOT_REGISTERED_3) || tplc.startsWith(ERROR_GOT_HTML)) {
// Give a guy some time to register it if using opentrackers too
//if (trckrs.size() == 1) {
// stop = true;
// snark.stopTorrent();
//} else { // hopefully each on the opentrackers list is really open
if (tr.registerFails++ > MAX_REGISTER_FAILS ||
!completed || // no use retrying if we aren't seeding
tplc.startsWith(ERROR_GOT_HTML) || // fake msg from doRequest()
(!tr.isPrimary && tr.registerFails > MAX_REGISTER_FAILS / 2))
if (_log.shouldLog(Log.WARN))
_log.warn("Not longer announcing to " + tr.announce + " : " +
tr.trackerProblems + " after " + tr.registerFails + " failures");
tr.stop = true;
//
}
@ -797,10 +805,15 @@ public class TrackerClient implements Runnable {
tr.lastRequestTime = System.currentTimeMillis();
// Don't wait for a response to stopped when shutting down
boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 32*1024);
if (fetched == null)
throw new IOException("Error fetching");
if (fetched.length == 0)
throw new IOException("No data");
// The HTML check only works if we didn't exceed the maxium fetch size specified in get(),
// otherwise we already threw an IOE.
if (fetched[0] == '<')
throw new IOException(ERROR_GOT_HTML);
InputStream in = new ByteArrayInputStream(fetched);

View File

@ -295,6 +295,10 @@ class UpdateRunner implements UpdateTask, CompleteListener {
return _smgr.getSavedPreserveNamesSetting(snark);
}
public long getSavedUploaded(Snark snark) {
return _smgr.getSavedUploaded(snark);
}
//////// end CompleteListener methods
private static String linkify(String url) {

View File

@ -25,8 +25,10 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.i2p.data.Base32;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.util.Log;
import org.klomp.snark.I2PSnarkUtil;
@ -498,7 +500,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write(_("RX"));
out.write("\">");
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (!snarks.isEmpty()) {
out.write("<img border=\"0\" src=\"" + _imgPath + "head_tx.png\" title=\"");
out.write(_("Uploaded"));
out.write("\" alt=\"");
@ -1430,8 +1432,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("</td><td class=\"snarkTorrentName\"");
if (isMultiFile) {
// link on the whole td
String jsec = encodedBaseName.replace("'", "\\'");
out.write(" onclick=\"document.location='" + jsec + "/';\">");
out.write(" onclick=\"document.location='" + encodedBaseName + "/';\">");
} else {
out.write('>');
}
@ -1465,15 +1466,15 @@ public class I2PSnarkServlet extends BasicServlet {
// out.write("??"); // no meta size yet
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentUploaded\">");
if(isRunning && isValid)
if (isValid && uploaded > 0)
out.write(formatSize(uploaded));
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">");
if(isRunning && needed > 0)
if (isRunning && needed > 0)
out.write(formatSize(downBps) + "ps");
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentRateUp\">");
if(isRunning && isValid)
if (isRunning && isValid)
out.write(formatSize(upBps) + "ps");
out.write("</td>\n\t");
out.write("<td align=\"center\" class=\"snarkTorrentAction\">");
@ -1717,8 +1718,10 @@ public class I2PSnarkServlet extends BasicServlet {
}
/**
* Start of anchor only, caller must add anchor text or img and close anchor
* @return string or null
* Generate link to details page if we know it supports it.
* Start of anchor only, caller must add anchor text or img and close anchor.
*
* @return string or null if unknown tracker
* @since 0.8.4
*/
private String getTrackerLinkUrl(String announce, byte[] infohash) {
@ -1745,8 +1748,8 @@ public class I2PSnarkServlet extends BasicServlet {
}
/**
* Full anchor with img
* @return string or null
* Full link to details page with img
* @return string or null if details page unsupported
* @since 0.8.4
*/
private String getTrackerLink(String announce, byte[] infohash) {
@ -1762,7 +1765,7 @@ public class I2PSnarkServlet extends BasicServlet {
}
/**
* Full anchor with shortened URL as anchor text
* Full anchor to home page or details page with shortened host name as anchor text
* @return string, non-null
* @since 0.9.5
*/
@ -1771,14 +1774,37 @@ public class I2PSnarkServlet extends BasicServlet {
String trackerLinkUrl = getTrackerLinkUrl(announce, infohash);
if (announce.startsWith("http://"))
announce = announce.substring(7);
// strip path
int slsh = announce.indexOf('/');
if (slsh > 0)
announce = announce.substring(0, slsh);
if (trackerLinkUrl != null)
if (trackerLinkUrl != null) {
buf.append(trackerLinkUrl);
else
// TODO encode
buf.append("<a href=\"http://").append(urlEncode(announce)).append("/\">");
} else {
// browsers don't like a full b64 dest, so convert it to b32
String host = announce;
if (host.length() >= 516) {
int colon = announce.indexOf(':');
String port = "";
if (colon > 0) {
port = host.substring(colon);
host = host.substring(0, colon);
}
if (host.endsWith(".i2p"))
host = host.substring(0, host.length() - 4);
byte[] b = Base64.decode(host);
if (b != null) {
Hash h = _context.sha().calculateHash(b);
// should we add the port back or strip it?
host = Base32.encode(h.getData()) + ".b32.i2p" + port;
}
}
buf.append("<a href=\"http://").append(urlEncode(host)).append("/\">");
}
// strip port
int colon = announce.indexOf(':');
if (colon > 0)
announce = announce.substring(0, colon);
if (announce.length() > 67)
announce = DataHelper.escapeHTML(announce.substring(0, 40)) + "&hellip;" +
DataHelper.escapeHTML(announce.substring(announce.length() - 8));
@ -2673,6 +2699,7 @@ public class I2PSnarkServlet extends BasicServlet {
boolean complete = false;
String status = "";
long length = item.length();
int fileIndex = -1;
int priority = 0;
if (item.isDirectory()) {
complete = true;
@ -2684,8 +2711,9 @@ public class I2PSnarkServlet extends BasicServlet {
status = toImg("cancel") + ' ' + _("Torrent not found?");
} else {
Storage storage = snark.getStorage();
fileIndex = storage.indexOf(item);
long remaining = storage.remaining(item);
long remaining = storage.remaining(fileIndex);
if (remaining < 0) {
complete = true;
status = toImg("cancel") + ' ' + _("File not found in torrent?");
@ -2693,7 +2721,7 @@ public class I2PSnarkServlet extends BasicServlet {
complete = true;
status = toImg("tick") + ' ' + _("Complete");
} else {
priority = storage.getPriority(item);
priority = storage.getPriority(fileIndex);
if (priority < 0)
status = toImg("cancel");
else if (priority == 0)
@ -2745,17 +2773,17 @@ public class I2PSnarkServlet extends BasicServlet {
if (showPriority) {
buf.append("<td class=\"priority\">");
if ((!complete) && (!item.isDirectory())) {
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prihigh\" value=\"5\" name=\"pri.").append(item).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prihigh\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
if (priority > 0)
buf.append("checked=\"checked\"");
buf.append('>').append(_("High"));
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prinorm\" value=\"0\" name=\"pri.").append(item).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prinorm\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
if (priority == 0)
buf.append("checked=\"checked\"");
buf.append('>').append(_("Normal"));
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"priskip\" value=\"-9\" name=\"pri.").append(item).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"priskip\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
if (priority < 0)
buf.append("checked=\"checked\"");
buf.append('>').append(_("Skip"));
@ -2857,10 +2885,10 @@ public class I2PSnarkServlet extends BasicServlet {
String key = entry.getKey();
if (key.startsWith("pri.")) {
try {
File file = new File(key.substring(4));
int fileIndex = Integer.parseInt(key.substring(4));
String val = entry.getValue()[0]; // jetty arrays
int pri = Integer.parseInt(val);
storage.setPriority(file, pri);
storage.setPriority(fileIndex, pri);
//System.err.println("Priority now " + pri + " for " + file);
} catch (Throwable t) { t.printStackTrace(); }
}

View File

@ -84,6 +84,7 @@ class URIUtil
case '<':
case '>':
case ' ':
case ':':
buf=new StringBuilder(path.length()*2);
break loop;
default:
@ -139,6 +140,9 @@ class URIUtil
case 0x7f:
buf.append("%7F");
continue;
case ':':
buf.append("%3A");
continue;
default:
if (c <= 0x1f) // includes negative
toHex(c,buf);
@ -183,6 +187,9 @@ class URIUtil
case ' ':
buf.append("%20");
continue;
case ':':
buf.append("%3A");
continue;
default:
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || Character.isSpaceChar(c))
toHex(c,buf);