propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)

to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
This commit is contained in:
zzz
2014-09-13 14:50:11 +00:00
158 changed files with 2982 additions and 947 deletions

View File

@ -100,15 +100,15 @@
<target name="war" depends="jar, bundle, warUpToDate, listChangedFiles" unless="war.uptodate" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<copy todir="build/icons/.icons" >
<fileset dir="../icons/" />
<copy todir="build/resources/.resources" >
<fileset dir="../resources/" />
</copy>
<!-- mime.properties must be in with the classes -->
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
<war destfile="../i2psnark.war" webxml="../web.xml" >
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
<classes dir="./build/obj" includes="**/web/*" />
<fileset dir="build/icons/" />
<fileset dir="build/resources/" />
<manifest>
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
@ -121,7 +121,7 @@
<target name="warUpToDate">
<uptodate property="war.uptodate" targetfile="../i2psnark.war" >
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../icons/* ../web.xml" />
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../resources/**/* ../web.xml" />
</uptodate>
</target>

View File

@ -57,8 +57,8 @@ public class Peer implements Comparable<Peer>
private DataOutputStream dout;
/** running counters */
private long downloaded;
private long uploaded;
private final AtomicLong downloaded = new AtomicLong();
private final AtomicLong uploaded = new AtomicLong();
// Keeps state for in/out connections. Non-null when the handshake
// was successful, the connection setup and runs
@ -618,7 +618,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void downloaded(int size) {
downloaded += size;
downloaded.addAndGet(size);
}
/**
@ -626,7 +626,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void uploaded(int size) {
uploaded += size;
uploaded.addAndGet(size);
}
/**
@ -635,7 +635,7 @@ public class Peer implements Comparable<Peer>
*/
public long getDownloaded()
{
return downloaded;
return downloaded.get();
}
/**
@ -644,7 +644,7 @@ public class Peer implements Comparable<Peer>
*/
public long getUploaded()
{
return uploaded;
return uploaded.get();
}
/**
@ -652,8 +652,8 @@ public class Peer implements Comparable<Peer>
*/
public void resetCounters()
{
downloaded = 0;
uploaded = 0;
downloaded.set(0);
uploaded.set(0);
}
public long getInactiveTime() {

View File

@ -27,7 +27,6 @@ import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
@ -245,16 +244,19 @@ public class Snark
*
* @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
*
* @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);
@ -284,6 +286,7 @@ public class Snark
if (start)
this.startTorrent();
}
****/
/**
* multitorrent
@ -515,18 +518,13 @@ public class Snark
// Create a new ID and fill it with something random. First nine
// zeros bytes, then three bytes filled with snark and then
// sixteen random bytes.
// eight random bytes.
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
byte[] rv = new byte[20];
Random random = I2PAppContext.getGlobalContext().random();
int i;
for (i = 0; i < 9; i++)
rv[i] = 0;
rv[i++] = snark;
rv[i++] = snark;
rv[i++] = snark;
while (i < 20)
rv[i++] = (byte)random.nextInt(256);
rv[9] = snark;
rv[10] = snark;
rv[11] = snark;
I2PAppContext.getGlobalContext().random().nextBytes(rv, 12, 8);
return rv;
}
@ -958,6 +956,7 @@ public class Snark
* non-valid argument list. The given listeners will be
* passed to all components that take one.
*/
/****
private static Snark parseArguments(String[] args,
StorageListener slistener,
CoordinatorListener clistener)
@ -972,6 +971,7 @@ public class Snark
int i = 0;
while (i < args.length)
{
****/
/*
if (args[i].equals("--debug"))
{
@ -993,7 +993,9 @@ public class Snark
catch (NumberFormatException nfe) { }
}
}
else */ if (args[i].equals("--port"))
else */
/****
if (args[i].equals("--port"))
{
if (args.length - 1 < i + 1)
usage("--port needs port number to listen on");
@ -1099,6 +1101,7 @@ public class Snark
System.out.println
(" \tor (with --share) a file to share.");
}
****/
/**
* Aborts program abnormally.

View File

@ -61,7 +61,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/";
private static final String WARBASE = "/.resources/";
private static final char HELLIP = '\u2026';
public I2PSnarkServlet() {
@ -194,22 +194,8 @@ public class I2PSnarkServlet extends BasicServlet {
resp.setHeader("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'");
resp.setHeader("X-XSS-Protection", "1; mode=block");
String peerParam = req.getParameter("p");
String stParam = req.getParameter("st");
String peerString;
if (peerParam == null || (!_manager.util().connected()) ||
peerParam.replaceAll("[a-zA-Z0-9~=-]", "").length() > 0) { // XSS
peerString = "";
} else {
peerString = "?p=" + DataHelper.stripHTML(peerParam);
}
if (stParam != null && !stParam.equals("0")) {
stParam = DataHelper.stripHTML(stParam);
if (peerString.length() > 0)
peerString += "&amp;st=" + stParam;
else
peerString = "?st="+ stParam;
}
String pOverride = _manager.util().connected() ? null : "";
String peerString = getQueryString(req, pOverride, null, null);
// AJAX for mainsection
if ("/.ajax/xhr1.html".equals(path)) {
@ -292,6 +278,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write(_("Configuration"));
else
out.write(_("Anonymous BitTorrent Client"));
String peerParam = req.getParameter("p");
if ("2".equals(peerParam))
out.write(" | Debug Mode");
out.write("</title>\n");
@ -413,13 +400,7 @@ public class I2PSnarkServlet extends BasicServlet {
boolean isForm = _manager.util().connected() || !snarks.isEmpty();
if (isForm) {
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
// don't lose peer setting
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + peerParam + "\" >\n");
// ...or st setting
if (stParam != null)
out.write("<input type=\"hidden\" name=\"st\" value=\"" + stParam + "\" >\n");
writeHiddenInputs(out, req, null);
}
out.write(TABLE_HEADER);
@ -441,18 +422,29 @@ public class I2PSnarkServlet extends BasicServlet {
}
int pageSize = Math.max(_manager.getPageSize(), 5);
out.write("<tr><th><img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
out.write(_("Status"));
String currentSort = req.getParameter("sort");
boolean showSort = total > 1;
out.write("<tr><th>");
String sort = ("2".equals(currentSort)) ? "-2" : "2";
if (showSort) {
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
if (showSort)
out.write(_("Sort by {0}", _("Status")));
else
out.write(_("Status"));
out.write("\" alt=\"");
out.write(_("Status"));
out.write("\"></th>\n<th>");
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th>");
if (_manager.util().connected() && !snarks.isEmpty()) {
out.write(" <a href=\"" + _contextPath + '/');
if (peerParam != null) {
if (stParam != null) {
out.write("?st=");
out.write(stParam);
}
// disable peer view
out.write("\">");
out.write("<img border=\"0\" src=\"" + _imgPath + "hidepeers.png\" title=\"");
out.write(_("Hide Peers"));
@ -460,11 +452,8 @@ public class I2PSnarkServlet extends BasicServlet {
out.write(_("Hide Peers"));
out.write("\">");
} else {
out.write("?p=1");
if (stParam != null) {
out.write("&amp;st=");
out.write(stParam);
}
// enable peer view
out.write(getQueryString(req, "1", null, null));
out.write("\">");
out.write("<img border=\"0\" src=\"" + _imgPath + "showpeers.png\" title=\"");
out.write(_("Show Peers"));
@ -475,56 +464,158 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("</a><br>\n");
}
out.write("</th>\n<th colspan=\"2\" align=\"left\">");
// cycle through sort by name or type
boolean isTypeSort = false;
if (showSort) {
if (currentSort == null || "0".equals(currentSort) || "1".equals(currentSort)) {
sort = "-1";
} else if ("-1".equals(currentSort)) {
sort = "12";
isTypeSort = true;
} else if ("12".equals(currentSort)) {
sort = "-12";
isTypeSort = true;
} else {
sort = "";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "torrent.png\" title=\"");
out.write(_("Torrent"));
if (showSort)
out.write(_("Sort by {0}", (isTypeSort ? _("File type") : _("Torrent"))));
else
out.write(_("Torrent"));
out.write("\" alt=\"");
out.write(_("Torrent"));
out.write("\"></th>\n<th align=\"center\">");
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th align=\"center\">");
if (total > 0 && (start > 0 || total > pageSize)) {
writePageNav(out, start, pageSize, total, peerParam, noThinsp);
writePageNav(out, req, start, pageSize, total, noThinsp);
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("4".equals(currentSort)) ? "-4" : "4";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "eta.png\" title=\"");
out.write(_("Estimated time remaining"));
if (showSort)
out.write(_("Sort by {0}", _("Estimated time remaining")));
else
out.write(_("Estimated time remaining"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("ETA"));
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
// cycle through sort by size or downloaded
boolean isDlSort = false;
if (showSort) {
if ("5".equals(currentSort)) {
sort = "-5";
} else if ("-5".equals(currentSort)) {
sort = "6";
isDlSort = true;
} else if ("6".equals(currentSort)) {
sort = "-6";
isDlSort = true;
} else {
sort = "5";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rx.png\" title=\"");
out.write(_("Downloaded"));
if (showSort)
out.write(_("Sort by {0}", (isDlSort ? _("Downloaded") : _("Size"))));
else
out.write(_("Downloaded"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("RX"));
out.write("\">");
if (showSort)
out.write("</a>");
out.write("</th>\n<th align=\"right\">");
boolean isRatSort = false;
if (!snarks.isEmpty()) {
// cycle through sort by uploaded or ratio
boolean nextRatSort = false;
if (showSort) {
if ("7".equals(currentSort)) {
sort = "-7";
} else if ("-7".equals(currentSort)) {
sort = "11";
nextRatSort = true;
} else if ("11".equals(currentSort)) {
sort = "-11";
nextRatSort = true;
isRatSort = true;
} else if ("-11".equals(currentSort)) {
sort = "7";
isRatSort = true;
} else {
sort = "7";
}
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_tx.png\" title=\"");
out.write(_("Uploaded"));
if (showSort)
out.write(_("Sort by {0}", (nextRatSort ? _("Upload ratio") : _("Uploaded"))));
else
out.write(_("Uploaded"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("TX"));
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("8".equals(currentSort)) ? "-8" : "8";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rxspeed.png\" title=\"");
out.write(_("Down Rate"));
if (showSort)
out.write(_("Sort by {0}", _("Down Rate")));
else
out.write(_("Down Rate"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("RX Rate"));
out.write(" \">");
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"right\">");
if (_manager.util().connected() && !snarks.isEmpty()) {
if (showSort) {
sort = ("9".equals(currentSort)) ? "-9" : "9";
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
out.write("\">");
}
out.write("<img border=\"0\" src=\"" + _imgPath + "head_txspeed.png\" title=\"");
out.write(_("Up Rate"));
if (showSort)
out.write(_("Sort by {0}", _("Up Rate")));
else
out.write(_("Up Rate"));
out.write("\" alt=\"");
// Translators: Please keep short or translate as " "
out.write(_("TX Rate"));
out.write(" \">");
out.write("\">");
if (showSort)
out.write("</a>");
}
out.write("</th>\n<th align=\"center\">");
@ -580,12 +671,11 @@ public class I2PSnarkServlet extends BasicServlet {
String uri = _contextPath + '/';
boolean showDebug = "2".equals(peerParam);
String stParamStr = stParam == null ? "" : "&amp;st=" + stParam;
for (int i = 0; i < total; i++) {
Snark snark = snarks.get(i);
boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.getInfoHash()).equals(peerParam);
boolean hide = i < start || i >= start + pageSize;
displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, stParamStr);
displaySnark(out, req, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, isRatSort);
}
if (total == 0) {
@ -636,17 +726,105 @@ public class I2PSnarkServlet extends BasicServlet {
return start == 0;
}
/**
* hidden inputs for nonce and paramters p, st, and sort
*
* @param out writes to it
* @param action if non-null, add it as the action
* @since 0.9.16
*/
private void writeHiddenInputs(PrintWriter out, HttpServletRequest req, String action) {
StringBuilder buf = new StringBuilder(256);
writeHiddenInputs(buf, req, action);
out.write(buf.toString());
}
/**
* hidden inputs for nonce and paramters p, st, and sort
*
* @param out appends to it
* @param action if non-null, add it as the action
* @since 0.9.16
*/
private void writeHiddenInputs(StringBuilder buf, HttpServletRequest req, String action) {
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"")
.append(_nonce).append("\" >\n");
String peerParam = req.getParameter("p");
if (peerParam != null) {
buf.append("<input type=\"hidden\" name=\"p\" value=\"")
.append(DataHelper.stripHTML(peerParam)).append("\" >\n");
}
String stParam = req.getParameter("st");
if (stParam != null) {
buf.append("<input type=\"hidden\" name=\"st\" value=\"")
.append(DataHelper.stripHTML(stParam)).append("\" >\n");
}
String soParam = req.getParameter("sort");
if (soParam != null) {
buf.append("<input type=\"hidden\" name=\"sort\" value=\"")
.append(DataHelper.stripHTML(soParam)).append("\" >\n");
}
if (action != null) {
buf.append("<input type=\"hidden\" name=\"action\" value=\"")
.append(action).append("\" >\n");
}
}
/**
* Build HTML-escaped and stripped query string
*
* @param p override or "" for default or null to keep the same as in req
* @param st override or "" for default or null to keep the same as in req
* @param so override or "" for default or null to keep the same as in req
* @return non-null, possibly empty
* @since 0.9.16
*/
private static String getQueryString(HttpServletRequest req, String p, String st, String so) {
StringBuilder buf = new StringBuilder(64);
if (p == null) {
p = req.getParameter("p");
if (p != null)
p = DataHelper.stripHTML(p);
}
if (p != null && !p.equals(""))
buf.append("?p=").append(p);
if (so == null) {
so = req.getParameter("sort");
if (so != null)
so = DataHelper.stripHTML(so);
}
if (so != null && !so.equals("")) {
if (buf.length() <= 0)
buf.append("?sort=");
else
buf.append("&amp;sort=");
buf.append(so);
}
if (st == null) {
st = req.getParameter("st");
if (st != null)
st = DataHelper.stripHTML(st);
}
if (st != null && !st.equals("")) {
if (buf.length() <= 0)
buf.append("?st=");
else
buf.append("&amp;st=");
buf.append(st);
}
return buf.toString();
}
/**
* @since 0.9.6
*/
private void writePageNav(PrintWriter out, int start, int pageSize, int total,
String peerParam, boolean noThinsp) {
private void writePageNav(PrintWriter out, HttpServletRequest req, int start, int pageSize, int total,
boolean noThinsp) {
// Page nav
if (start > 0) {
// First
out.write("<a href=\"" + _contextPath);
if (peerParam != null)
out.write("?p=" + peerParam);
out.write(getQueryString(req, null, "", null));
out.write("\">" +
"<img alt=\"" + _("First") + "\" title=\"" + _("First page") + "\" border=\"0\" src=\"" +
_imgPath + "control_rewind_blue.png\">" +
@ -655,9 +833,9 @@ public class I2PSnarkServlet extends BasicServlet {
//if (prev > 0) {
if (true) {
// Back
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + prev);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
String sprev = (prev > 0) ? Integer.toString(prev) : "";
out.write(getQueryString(req, null, sprev, null));
out.write("\">" +
"<img alt=\"" + _("Prev") + "\" title=\"" + _("Previous page") + "\" border=\"0\" src=\"" +
_imgPath + "control_back_blue.png\">" +
@ -690,9 +868,8 @@ public class I2PSnarkServlet extends BasicServlet {
//if (next + pageSize < total) {
if (true) {
// Next
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + next);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
out.write(getQueryString(req, null, Integer.toString(next), null));
out.write("\">" +
"<img alt=\"" + _("Next") + "\" title=\"" + _("Next page") + "\" border=\"0\" src=\"" +
_imgPath + "control_play_blue.png\">" +
@ -700,9 +877,8 @@ public class I2PSnarkServlet extends BasicServlet {
}
// Last
int last = ((total - 1) / pageSize) * pageSize;
out.write("&nbsp;<a href=\"" + _contextPath + "?st=" + last);
if (peerParam != null)
out.write("&amp;p=" + peerParam);
out.write("&nbsp;<a href=\"" + _contextPath);
out.write(getQueryString(req, null, Integer.toString(last), null));
out.write("\">" +
"<img alt=\"" + _("Last") + "\" title=\"" + _("Last page") + "\" border=\"0\" src=\"" +
_imgPath + "control_fastforward_blue.png\">" +
@ -1190,34 +1366,22 @@ public class I2PSnarkServlet extends BasicServlet {
return buf.toString();
}
/**
* Sort alphabetically in current locale, ignore case, ignore leading "the "
* (I guess this is worth it, a lot of torrents start with "The "
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
public int compare(Snark l, Snark r) {
// put downloads and magnets first
if (l.getStorage() == null && r.getStorage() != null)
return -1;
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String llc = ls.toLowerCase(Locale.US);
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
ls = ls.substring(4);
String rs = r.getBaseName();
String rlc = rs.toLowerCase(Locale.US);
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
rs = rs.substring(4);
return Collator.getInstance().compare(ls, rs);
}
}
private List<Snark> getSortedSnarks(HttpServletRequest req) {
ArrayList<Snark> rv = new ArrayList<Snark>(_manager.getTorrents());
Collections.sort(rv, new TorrentNameComparator());
if (rv.size() > 1) {
int sort = 0;
String ssort = req.getParameter("sort");
if (ssort != null) {
try {
sort = Integer.parseInt(ssort);
} catch (NumberFormatException nfe) {}
}
try {
Collections.sort(rv, Sorters.getComparator(sort, this));
} catch (IllegalArgumentException iae) {
// Java 7 TimSort - may be unstable
}
}
return rv;
}
@ -1229,11 +1393,11 @@ public class I2PSnarkServlet extends BasicServlet {
*
* @param stats in/out param (totals)
* @param statsOnly if true, output nothing, update stats only
* @param stParam non null; empty or e.g. &amp;st=10
*/
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
private void displaySnark(PrintWriter out, HttpServletRequest req,
Snark snark, String uri, int row, long stats[], boolean showPeers,
boolean isDegraded, boolean noThinsp, boolean showDebug, boolean statsOnly,
String stParam) throws IOException {
boolean showRatios) throws IOException {
// stats
long uploaded = snark.getUploaded();
stats[0] += snark.getDownloaded();
@ -1335,7 +1499,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (curPeers > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + img + ".png\" title=\"" + txt + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + txt +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else
@ -1351,7 +1515,7 @@ public class I2PSnarkServlet extends BasicServlet {
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" title=\"" + _("OK") + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + _("OK") +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else if (isRunning && curPeers > 0 && downBps > 0)
@ -1362,7 +1526,7 @@ public class I2PSnarkServlet extends BasicServlet {
else if (isRunning && curPeers > 0 && !showPeers)
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" title=\"" + _("Stalled") + "\"></td>" +
"<td class=\"snarkTorrentStatus\">" + _("Stalled") +
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
curPeers + thinsp(noThinsp) +
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
else if (isRunning && curPeers > 0)
@ -1466,8 +1630,17 @@ public class I2PSnarkServlet extends BasicServlet {
// out.write("??"); // no meta size yet
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentUploaded\">");
if (isValid && uploaded > 0)
out.write(formatSize(uploaded));
if (isValid) {
if (showRatios) {
if (total > 0) {
double ratio = uploaded / ((double) total);
out.write((new DecimalFormat("0.000")).format(ratio));
out.write("&nbsp;x");
}
} else if (uploaded > 0) {
out.write(formatSize(uploaded));
}
}
out.write("</td>\n\t");
out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">");
if (isRunning && needed > 0)
@ -1484,7 +1657,8 @@ public class I2PSnarkServlet extends BasicServlet {
} else if (isRunning) {
// Stop Button
if (isDegraded)
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Stop_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Stop the torrent"));
@ -1498,7 +1672,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Start Button
// This works in Opera but it's displayed a little differently, so use noThinsp here too so all 3 icons are consistent
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Start_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Start the torrent"));
@ -1512,7 +1687,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Remove Button
// Doesnt work with Opera so use noThinsp instead of isDegraded
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Remove_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
@ -1533,7 +1709,8 @@ public class I2PSnarkServlet extends BasicServlet {
// Delete Button
// Doesnt work with Opera so use noThinsp instead of isDegraded
if (noThinsp)
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&amp;nonce=" + _nonce + stParam + "\"><img title=\"");
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&amp;nonce=" + _nonce +
getQueryString(req, "", null, null).replace("?", "&amp;") + "\"><img title=\"");
else
out.write("<input type=\"image\" name=\"action_Delete_" + b64 + "\" value=\"foo\" title=\"");
out.write(_("Delete the .torrent file and the associated data file(s)"));
@ -1842,12 +2019,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("<div class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
out.write("<input type=\"hidden\" name=\"action\" value=\"Add\" >\n");
// don't lose peer setting
String peerParam = req.getParameter("p");
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
writeHiddenInputs(out, req, "Add");
out.write("<div class=\"addtorrentsection\"><span class=\"snarkConfigTitle\">");
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "add.png\"> ");
out.write(_("Add Torrent"));
@ -1874,12 +2046,7 @@ public class I2PSnarkServlet extends BasicServlet {
out.write("<a name=\"add\"></a><div class=\"newtorrentsection\"><div class=\"snarkNewTorrent\">\n");
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
out.write("<form action=\"_post\" method=\"POST\">\n");
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
out.write("<input type=\"hidden\" name=\"action\" value=\"Create\" >\n");
// don't lose peer setting
String peerParam = req.getParameter("p");
if (peerParam != null)
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
writeHiddenInputs(out, req, "Create");
out.write("<span class=\"snarkConfigTitle\">");
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "create.png\"> ");
out.write(_("Create Torrent"));
@ -1945,10 +2112,9 @@ public class I2PSnarkServlet extends BasicServlet {
//int seedPct = 0;
out.write("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
"<input type=\"hidden\" name=\"action\" value=\"Save\" >\n" +
"<span class=\"snarkConfigTitle\">" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
writeHiddenInputs(out, req, "Save");
out.write("<span class=\"snarkConfigTitle\">" +
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
out.write(_("Configuration"));
out.write("</span><hr>\n" +
@ -2131,10 +2297,9 @@ public class I2PSnarkServlet extends BasicServlet {
private void writeTrackerForm(PrintWriter out, HttpServletRequest req) throws IOException {
StringBuilder buf = new StringBuilder(1024);
buf.append("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
"<input type=\"hidden\" name=\"action\" value=\"Save2\" >\n" +
"<span class=\"snarkConfigTitle\">" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
writeHiddenInputs(buf, req, "Save2");
buf.append("<span class=\"snarkConfigTitle\">" +
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
buf.append(_("Trackers"));
buf.append("</span><hr>\n" +
@ -2435,6 +2600,10 @@ public class I2PSnarkServlet extends BasicServlet {
// dummy
r = new File("");
}
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
r.isDirectory();
StringBuilder buf=new StringBuilder(4096);
buf.append(DOCTYPE).append("<HTML><HEAD><TITLE>");
if (title.endsWith("/"))
@ -2442,8 +2611,14 @@ public class I2PSnarkServlet extends BasicServlet {
String directory = title;
title = _("Torrent") + ": " + DataHelper.escapeHTML(title);
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("</TITLE>\n").append(HEADER_A).append(_themePath).append(HEADER_B)
.append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">\n");
if (showPriority)
buf.append("<script src=\"").append(_contextPath).append(WARBASE + "js/folder.js\" type=\"text/javascript\"></script>\n");
buf.append("</HEAD><BODY");
if (showPriority)
buf.append(" onload=\"setupbuttons()\"");
buf.append(">\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\">&nbsp;&nbsp;");
if (_contextName.equals(DEFAULT_NAME))
buf.append(_("I2PSnark"));
@ -2453,8 +2628,6 @@ public class I2PSnarkServlet extends BasicServlet {
if (parent) // always true
buf.append("<div class=\"page\"><div class=\"mainsection\">");
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");
@ -2594,9 +2767,20 @@ public class I2PSnarkServlet extends BasicServlet {
.append(":</b> ")
.append((new DecimalFormat("0.00%")).format(completion));
else
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;")
.append(_("Complete"));
// else unknown
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
.append(_("Complete")).append("</b>");
// up ratio
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_tx.png\" >&nbsp;<b>")
.append(_("Upload ratio"))
.append(":</b> ");
long uploaded = snark.getUploaded();
if (uploaded > 0) {
double ratio = uploaded / ((double) snark.getTotalLength());
buf.append((new DecimalFormat("0.000")).format(ratio));
buf.append("&nbsp;x");
} else {
buf.append('0');
}
long needed = snark.getNeededLength();
if (needed > 0)
buf.append("&nbsp;<img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" >&nbsp;<b>")
@ -2781,19 +2965,19 @@ public class I2PSnarkServlet extends BasicServlet {
if (showPriority) {
buf.append("<td class=\"priority\">");
if ((!complete) && (!item.isDirectory())) {
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prihigh\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
if (priority > 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("High"));
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prinorm\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
if (priority == 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("Normal"));
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"priskip\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
if (priority < 0)
buf.append("checked=\"true\"");
buf.append("checked=\"checked\"");
buf.append('>').append(_("Skip"));
showSaveButton = true;
}
@ -2802,9 +2986,16 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("</TR>\n");
}
if (showSaveButton) {
buf.append("<thead><tr><th colspan=\"4\">&nbsp;</th><th class=\"headerpriority\"><input type=\"submit\" value=\"");
buf.append(_("Save priorities"));
buf.append("\" name=\"foo\" ></th></tr></thead>\n");
buf.append("<thead><tr><th colspan=\"4\">&nbsp;</th><th class=\"headerpriority\">" +
"<a class=\"control\" id=\"setallhigh\" href=\"javascript:void(null);\" onclick=\"setallhigh();\">")
.append(toImg("clock_red")).append(_("Set all high")).append("</a>\n" +
"<a class=\"control\" id=\"setallnorm\" href=\"javascript:void(null);\" onclick=\"setallnorm();\">")
.append(toImg("clock")).append(_("Set all normal")).append("</a>\n" +
"<a class=\"control\" id=\"setallskip\" href=\"javascript:void(null);\" onclick=\"setallskip();\">")
.append(toImg("cancel")).append(_("Skip all")).append("</a>\n" +
"<br><br><input type=\"submit\" class=\"accept\" value=\"").append(_("Save priorities"))
.append("\" name=\"savepri\" >\n" +
"</th></tr></thead>\n");
}
buf.append("</table>\n");
if (showPriority)
@ -2814,7 +3005,12 @@ public class I2PSnarkServlet extends BasicServlet {
return buf.toString();
}
/** @since 0.7.14 */
/**
* Pick an icon; try to catch the common types in an i2p environment.
*
* @return file name not including ".png"
* @since 0.7.14
*/
private String toIcon(File item) {
if (item.isDirectory())
return "folder";
@ -2823,10 +3019,12 @@ public class I2PSnarkServlet extends BasicServlet {
/**
* Pick an icon; try to catch the common types in an i2p environment
* Pkg private for FileTypeSorter.
*
* @return file name not including ".png"
* @since 0.7.14
*/
private String toIcon(String path) {
String toIcon(String path) {
String icon;
// Note that for this to work well, our custom mime.properties file must be loaded.
String plc = path.toLowerCase(Locale.US);
@ -2873,12 +3071,12 @@ public class I2PSnarkServlet extends BasicServlet {
/** @since 0.7.14 */
private String toImg(String icon) {
return "<img alt=\"\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
return toImg(icon, "");
}
/** @since 0.8.2 */
private String toImg(String icon, String altText) {
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + WARBASE + "icons/" + icon + ".png\">";
}
/** @since 0.8.1 */

View File

@ -0,0 +1,341 @@
package org.klomp.snark.web;
import java.io.Serializable;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
/**
* Comparators for various columns
*
* @since 0.9.16 from TorrentNameComparator, moved from I2PSnarkservlet
*/
class Sorters {
/**
* Negative is reverse
*
*<ul>
*<li>0, 1: Name
*<li>2: Status
*<li>3: Peers
*<li>4: ETA
*<li>5: Size
*<li>6: Downloaded
*<li>7: Uploaded
*<li>8: Down rate
*<li>9: Up rate
*<li>10: Remaining (needed)
*<li>11: Upload ratio
*<li>11: File type
*</ul>
*
* @param servlet for file type callback only
*/
public static Comparator<Snark> getComparator(int type, I2PSnarkServlet servlet) {
boolean rev = type < 0;
Comparator<Snark> rv;
switch (type) {
case -1:
case 0:
case 1:
default:
rv = new TorrentNameComparator();
if (rev)
rv = Collections.reverseOrder(rv);
break;
case -2:
case 2:
rv = new StatusComparator(rev);
break;
case -3:
case 3:
rv = new PeersComparator(rev);
break;
case -4:
case 4:
rv = new ETAComparator(rev);
break;
case -5:
case 5:
rv = new SizeComparator(rev);
break;
case -6:
case 6:
rv = new DownloadedComparator(rev);
break;
case -7:
case 7:
rv = new UploadedComparator(rev);
break;
case -8:
case 8:
rv = new DownRateComparator(rev);
break;
case -9:
case 9:
rv = new UpRateComparator(rev);
break;
case -10:
case 10:
rv = new RemainingComparator(rev);
break;
case -11:
case 11:
rv = new RatioComparator(rev);
break;
case -12:
case 12:
rv = new FileTypeComparator(rev, servlet);
break;
}
return rv;
}
/**
* Sort alphabetically in current locale, ignore case, ignore leading "the "
* (I guess this is worth it, a lot of torrents start with "The "
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
public int compare(Snark l, Snark r) {
return comp(l, r);
}
public static int comp(Snark l, Snark r) {
// put downloads and magnets first
if (l.getStorage() == null && r.getStorage() != null)
return -1;
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String llc = ls.toLowerCase(Locale.US);
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
ls = ls.substring(4);
String rs = r.getBaseName();
String rlc = rs.toLowerCase(Locale.US);
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
rs = rs.substring(4);
return Collator.getInstance().compare(ls, rs);
}
}
/**
* Forward or reverse sort, but the fallback is always forward
*/
private static abstract class Sort implements Comparator<Snark>, Serializable {
private final boolean _rev;
public Sort(boolean rev) {
_rev = rev;
}
public int compare(Snark l, Snark r) {
int rv = compareIt(l, r);
if (rv != 0)
return _rev ? 0 - rv : rv;
return TorrentNameComparator.comp(l, r);
}
protected abstract int compareIt(Snark l, Snark r);
protected static int compLong(long l, long r) {
if (l < r)
return -1;
if (l > r)
return 1;
return 0;
}
}
private static class StatusComparator extends Sort {
private StatusComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
int rv = getStatus(l) - getStatus(r);
if (rv != 0)
return rv;
// use reverse remaining as first tie break
return compLong(r.getNeededLength(), l.getNeededLength());
}
private static int getStatus(Snark snark) {
long remaining = snark.getRemainingLength();
long needed = snark.getNeededLength();
if (snark.isStopped()) {
if (remaining < 0)
return 0;
if (remaining > 0)
return 5;
return 10;
}
if (snark.isStarting())
return 15;
if (snark.isAllocating())
return 20;
if (remaining < 0)
return 15; // magnet
if (remaining == 0)
return 100;
if (snark.isChecking())
return 95;
if (snark.getNeededLength() <= 0)
return 90;
if (snark.getPeerCount() <= 0)
return 40;
if (snark.getDownloadRate() <= 0)
return 50;
return 60;
}
}
private static class PeersComparator extends Sort {
public PeersComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return l.getPeerCount() - r.getPeerCount();
}
}
private static class RemainingComparator extends Sort {
public RemainingComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getNeededLength(), r.getNeededLength());
}
}
private static class ETAComparator extends Sort {
public ETAComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(eta(l), eta(r));
}
private static long eta(Snark snark) {
long needed = snark.getNeededLength();
if (needed <= 0)
return 0;
long total = snark.getTotalLength();
if (needed > total)
needed = total;
long downBps = snark.getDownloadRate();
if (downBps > 0)
return needed / downBps;
return Long.MAX_VALUE;
}
}
private static class SizeComparator extends Sort {
public SizeComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getTotalLength(), r.getTotalLength());
}
}
private static class DownloadedComparator extends Sort {
public DownloadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long ld = l.getTotalLength() - l.getRemainingLength();
long rd = r.getTotalLength() - r.getRemainingLength();
return compLong(ld, rd);
}
}
private static class UploadedComparator extends Sort {
public UploadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploaded(), r.getUploaded());
}
}
private static class DownRateComparator extends Sort {
public DownRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getDownloadRate(), r.getDownloadRate());
}
}
private static class UpRateComparator extends Sort {
public UpRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploadRate(), r.getUploadRate());
}
}
private static class RatioComparator extends Sort {
private static final long M = 128 * 1024 * 1024;
public RatioComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long lt = l.getTotalLength();
long ld = lt > 0 ? ((M * l.getUploaded()) / lt) : 0;
long rt = r.getTotalLength();
long rd = rt > 0 ? ((M * r.getUploaded()) / rt) : 0;
return compLong(ld, rd);
}
}
private static class FileTypeComparator extends Sort {
private final I2PSnarkServlet servlet;
public FileTypeComparator(boolean rev, I2PSnarkServlet servlet) {
super(rev);
this.servlet = servlet;
}
public int compareIt(Snark l, Snark r) {
String ls = toName(l);
String rs = toName(r);
return ls.compareTo(rs);
}
private String toName(Snark snark) {
MetaInfo meta = snark.getMetaInfo();
if (meta == null)
return "0";
if (meta.getFiles() != null)
return "1";
// arbitrary sort based on icon name
return servlet.toIcon(meta.getName());
}
}
}