i2psnark:

- Fix announce hosts of the form b64dest[.i2p]
- Add last activity stat
- Disallow illegal filenames on Windows
- cleanups and log tweaks
This commit is contained in:
zzz
2019-08-01 20:20:44 +00:00
parent e3481f6730
commit 8158753dac
6 changed files with 150 additions and 54 deletions

View File

@ -656,10 +656,10 @@ public class Snark
ioe.printStackTrace();
}
savedUploaded = nowUploaded;
if (changed && completeListener != null)
completeListener.updateStatus(this);
// TODO should save comments at shutdown even if never started...
// SnarkManager.stopAllTorrents() will save comments at shutdown even if never started...
if (completeListener != null) {
if (changed)
completeListener.updateStatus(this);
synchronized(_commentLock) {
if (_comments != null) {
synchronized(_comments) {

View File

@ -116,6 +116,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
private static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
/** @since 0.9.31 */
private static final String PROP_META_COMMENTS = "comments";
/** @since 0.9.42 */
private static final String PROP_META_ACTIVITY = "activity";
private static final String CONFIG_FILE_SUFFIX = ".config";
private static final String CONFIG_FILE = "i2psnark" + CONFIG_FILE_SUFFIX;
@ -1609,13 +1611,16 @@ public class SnarkManager implements CompleteListener, ClientApp {
}
// ok, snark created, now lets start it up or configure it further
Properties config = getConfig(torrent);
boolean running;
String prop = config.getProperty(PROP_META_RUNNING);
if(prop == null || Boolean.parseBoolean(prop)) {
running = true;
} else {
running = false;
boolean running = prop == null || Boolean.parseBoolean(prop);
prop = config.getProperty(PROP_META_ACTIVITY);
if (prop != null && torrent.getStorage() != null) {
try {
long activity = Long.parseLong(prop);
torrent.getStorage().setActivity(activity);
} catch (NumberFormatException nfe) {}
}
// Were we running last time?
String link = linkify(torrent);
if (!dontAutoStart && shouldAutoStart() && running) {
@ -1779,7 +1784,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return false;
} else if (bitfield != null) {
saveTorrentStatus(metainfo, bitfield, null, false, baseFile, true, 0, true); // no file priorities
saveTorrentStatus(metainfo, bitfield, null, false, baseFile, true, 0, 0, true); // no file priorities
}
// so addTorrent won't recheck
if (filename == null) {
@ -2041,7 +2046,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
return;
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getInOrder(),
storage.getBase(), storage.getPreserveFileNames(),
snark.getUploaded(), snark.isStopped(), comments);
snark.getUploaded(), storage.getActivity(), snark.isStopped(), comments);
}
/**
@ -2057,8 +2062,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
* @param base may be null
*/
private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, boolean inOrder,
File base, boolean preserveNames, long uploaded, boolean stopped) {
saveTorrentStatus(metainfo, bitfield, priorities, inOrder, base, preserveNames, uploaded, stopped, null);
File base, boolean preserveNames, long uploaded, long activity, boolean stopped) {
saveTorrentStatus(metainfo, bitfield, priorities, inOrder, base, preserveNames, uploaded, activity, stopped, null);
}
/*
@ -2066,15 +2071,15 @@ public class SnarkManager implements CompleteListener, ClientApp {
* @since 0.9.31
*/
private void saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, boolean inOrder,
File base, boolean preserveNames, long uploaded, boolean stopped,
File base, boolean preserveNames, long uploaded, long activity, boolean stopped,
Boolean comments) {
synchronized (_configLock) {
locked_saveTorrentStatus(metainfo, bitfield, priorities, inOrder, base, preserveNames, uploaded, stopped, comments);
locked_saveTorrentStatus(metainfo, bitfield, priorities, inOrder, base, preserveNames, uploaded, activity, stopped, comments);
}
}
private void locked_saveTorrentStatus(MetaInfo metainfo, BitField bitfield, int[] priorities, boolean inOrder,
File base, boolean preserveNames, long uploaded, boolean stopped,
File base, boolean preserveNames, long uploaded, long activity, boolean stopped,
Boolean comments) {
byte[] ih = metainfo.getInfoHash();
Properties config = getConfig(ih);
@ -2104,6 +2109,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
config.setProperty(PROP_META_BASE, base.getAbsolutePath());
if (comments != null)
config.setProperty(PROP_META_COMMENTS, comments.toString());
if (activity > 0)
config.setProperty(PROP_META_ACTIVITY, Long.toString(activity));
// now the file priorities
if (priorities != null) {
@ -2469,7 +2476,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
Storage storage = snark.getStorage();
if (meta != null && storage != null)
saveTorrentStatus(meta, storage.getBitField(), storage.getFilePriorities(), storage.getInOrder(),
storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded(),
storage.getBase(), storage.getPreserveFileNames(), snark.getUploaded(), storage.getActivity(),
snark.isStopped());
}
@ -2493,7 +2500,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
return null;
}
saveTorrentStatus(meta, storage.getBitField(), null, false,
storage.getBase(), storage.getPreserveFileNames(), 0,
storage.getBase(), storage.getPreserveFileNames(), 0, 0,
snark.isStopped());
// temp for addMessage() in case canonical throws
String name = storage.getBaseName();

View File

@ -35,12 +35,14 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
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;
import java.util.concurrent.atomic.AtomicLong;
import gnu.getopt.Getopt;
@ -78,6 +80,7 @@ public class Storage implements Closeable
private boolean _inOrder;
private final AtomicInteger _allocateCount = new AtomicInteger();
private final AtomicInteger _checkProgress = new AtomicInteger();
private final AtomicLong _activity = new AtomicLong();
/** The default piece size. */
private static final int DEFAULT_PIECE_SIZE = 256*1024;
@ -319,6 +322,28 @@ public class Storage implements Closeable
changed = false;
}
/**
* @since 0.9.42
*/
public long getActivity() {
return _activity.get();
}
/**
* @since 0.9.42
*/
private void setActivity() {
setActivity(I2PAppContext.getGlobalContext().clock().now());
}
/**
* @since 0.9.42
*/
public void setActivity(long time) {
_activity.set(time);
changed = true;
}
/**
* File checking in progress.
* @since 0.9.3
@ -827,6 +852,13 @@ public class Storage implements Closeable
0x2028, 0x2029
};
// https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
private static final String[] WIN_ILLEGAL = new String[] {
"con", "prn", "aux", "nul",
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
};
/**
* Filter the name, but only if configured to do so.
* We will do so on torrents received from others, but not
@ -859,8 +891,18 @@ public class Storage implements Closeable
rv = "_";
} else {
rv = name;
if (rv.startsWith("."))
if (rv.startsWith(".")) {
rv = '_' + rv.substring(1);
} else if (SystemVersion.isWindows()) {
// https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
String iname = name.toLowerCase(Locale.US);
for (int i = 0; i < WIN_ILLEGAL.length; i++) {
String w = WIN_ILLEGAL[i];
if (iname.equals(w) ||
(iname.startsWith(w + '.') && w.indexOf('.', w.length() + 1) < 0))
rv = '_' + rv;
}
}
if (rv.endsWith(".") || rv.endsWith(" "))
rv = rv.substring(0, rv.length() - 1) + '_';
for (int i = 0; i < ILLEGAL.length; i++) {
@ -1227,6 +1269,7 @@ public class Storage implements Closeable
}
bs = rv.getData();
getUncheckedPiece(piece, bs, off, len);
setActivity();
return rv;
}
@ -1311,7 +1354,7 @@ public class Storage implements Closeable
pp.release();
}
changed = true;
setActivity();
// do this after the write, so we know it succeeded, and we don't set the
// needed count to zero, which would cause checkRAF() to open the file readonly.
@ -1567,8 +1610,7 @@ public class Storage implements Closeable
* Caller must synchronize and call checkRAF() or openRAF().
* @since 0.9.1
*/
public synchronized void balloonFile() throws IOException
{
private synchronized void balloonFile() throws IOException {
long remaining = length;
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
byte[] zeros = new byte[ZEROBLOCKSIZE];

View File

@ -292,7 +292,7 @@ public class TrackerClient implements Runnable {
_log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping invalid or non-i2p announce: " + primary);
_log.warn("Skipping invalid or non-i2p announce: " + primary + " for torrent " + snark.getBaseName());
}
} else {
_log.warn("No primary announce");
@ -366,7 +366,7 @@ public class TrackerClient implements Runnable {
Hash h = getHostHash(ann);
if (h == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad announce URL: [" + ann + ']');
_log.warn("Bad announce URL: [" + ann + "] for torrent " + snark.getBaseName());
return false;
}
// comment this out if tracker.welterde.i2p upgrades
@ -374,19 +374,19 @@ public class TrackerClient implements Runnable {
Destination dest = _util.getMyDestination();
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping incompatible tracker: " + ann);
_log.warn("Skipping incompatible tracker: " + ann + " for torrent " + snark.getBaseName());
return false;
}
}
if (existing.size() >= MAX_TRACKERS) {
if (_log.shouldLog(Log.INFO))
_log.info("Not using announce URL, we have enough: [" + ann + ']');
_log.info("Not using announce URL, we have enough: [" + ann + "] for torrent " + snark.getBaseName());
return false;
}
boolean rv = existing.add(h);
if (!rv) {
if (_log.shouldLog(Log.INFO))
_log.info("Dup announce URL: [" + ann + ']');
_log.info("Dup announce URL: [" + ann + "] for torrent " + snark.getBaseName());
}
return rv;
}
@ -605,7 +605,7 @@ public class TrackerClient implements Runnable {
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 + " : " +
_log.warn("No longer announcing to " + tr.announce + " : " +
tr.trackerProblems + " after " + tr.registerFails + " failures");
tr.stop = true;
//
@ -917,8 +917,21 @@ public class TrackerClient implements Runnable {
if (!"http".equals(url.getScheme()))
return null;
String host = url.getHost();
if (host == null)
if (host == null) {
// URI can't handle b64dest or b64dest.i2p if it contains '~'
// but it doesn't throw an exception, just returns a null host
if (ann.startsWith("http://") && ann.length() >= 7 + 516 && ann.contains("~")) {
ann = ann.substring(7);
int slash = ann.indexOf('/');
if (slash >= 516) {
ann = ann.substring(0, slash);
if (ann.endsWith(".i2p"))
ann = ann.substring(0, ann.length() - 4);
return ConvertToHash.getHash(ann);
}
}
return null;
}
if (host.endsWith(".i2p")) {
String path = url.getPath();
if (path == null || !path.startsWith("/"))

View File

@ -3106,7 +3106,7 @@ public class I2PSnarkServlet extends BasicServlet {
}
long dat = meta.getCreationDate();
// needs locale configured for automatic translation
SimpleDateFormat fmt = new SimpleDateFormat("HH:mm, EEEE dd MMMM yyyy");
SimpleDateFormat fmt = new SimpleDateFormat("EEEE dd MMMM yyyy HH:mm");
fmt.setTimeZone(SystemVersion.getSystemTimeZone(_context));
if (dat > 0) {
String date = fmt.format(new Date(dat));
@ -3147,6 +3147,18 @@ public class I2PSnarkServlet extends BasicServlet {
.append(date)
.append("</td></tr>\n");
}
if (storage != null) {
dat = storage.getActivity();
if (dat > 0) {
String date = fmt.format(new Date(dat));
buf.append("<tr><td>");
toThemeImg(buf, "details");
buf.append("</td><td><b>")
.append(_t("Last activity")).append(":</b> ")
.append(date)
.append("</td></tr>\n");
}
}
}
if (meta == null || !meta.isPrivate()) {

View File

@ -15,6 +15,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
@ -39,7 +40,8 @@ import net.i2p.util.SocketTimeout;
* Supports http://example.i2p/blah
* Supports http://B32KEY.b32.i2p/blah
* Supports http://i2p/B64KEY/blah for compatibility with the eepproxy
* Supports http://B64KEY/blah for compatibility with the eepproxy
* Supports http://B64KEY/blah for as of 0.9.42
* Supports http://B64KEY.i2p/blah as of 0.9.42
* Warning - does not support /eepproxy/blah, address helpers, http://B64KEY.i2p/blah,
* or other odd things that may be found in the HTTP proxy.
*
@ -115,9 +117,28 @@ public class I2PSocketEepGet extends EepGet {
try {
URI url = new URI(_actualURL);
if ("http".equals(url.getScheme())) {
Destination dest = null;
String host = url.getHost();
if (host == null)
if (host == null) {
String ann = _actualURL;
// URI can't handle b64dest.i2p if it contains '~'
// but it doesn't throw an exception, just returns a null host
if (ann.startsWith("http://") && ann.length() >= 7 + 516 && ann.contains("~")) {
ann = ann.substring(7);
int slash = ann.indexOf('/');
if (slash >= 516) {
ann = ann.substring(0, slash);
if (ann.endsWith(".i2p"))
ann = ann.substring(0, ann.length() - 4);
try {
dest = new Destination(ann);
} catch (DataFormatException dfe) {}
}
}
if (dest == null)
throw new MalformedURLException("no hostname: " + _actualURL);
// won't pick up the port either, but the path will be OK
}
int port = url.getPort();
if (port <= 0 || port > 65535)
port = 80;
@ -139,9 +160,9 @@ public class I2PSocketEepGet extends EepGet {
}
}
if (dest == null) {
// Use existing I2PSession for lookups.
// This is much more efficient than using the naming service
Destination dest;
I2PSession sess = _socketManager.getSession();
if (sess != null && !sess.isClosed()) {
try {
@ -164,6 +185,7 @@ public class I2PSocketEepGet extends EepGet {
}
if (dest == null)
throw new UnknownHostException("Unknown or non-i2p host: " + host);
}
// Set the timeouts, using the other existing options in the socket manager
// This currently duplicates what SocketTimeout is doing in EepGet,
@ -257,7 +279,7 @@ public class I2PSocketEepGet extends EepGet {
* Uses I2CP at localhost:7654 with a single 1-hop tunnel each direction.
* Tunnel build time not included in the timeout.
*
* This is just for testing, it will be commented out someday.
* This is just for testing.
* Real command line apps should use EepGet.main(),
* which has more options, and you don't have to wait for tunnels to be built.
*/