forked from I2P_Developers/i2p.i2p
WIP: Torrent editor
This commit is contained in:
@@ -82,8 +82,9 @@ public class MetaInfo
|
||||
* @param created_by may be null
|
||||
* @param url_list may be null
|
||||
* @param comment may be null
|
||||
* @since public since 0.9.53, was package private
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
public MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
|
||||
List<List<String>> announce_list, String created_by, List<String> url_list, String comment)
|
||||
{
|
||||
@@ -442,9 +443,12 @@ public class MetaInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the piece hashes. Only used by storage so package local.
|
||||
* Returns the piece hashes.
|
||||
*
|
||||
* @return not a copy, do not modify
|
||||
* @since public since 0.9.53, was package private
|
||||
*/
|
||||
byte[] getPieceHashes()
|
||||
public byte[] getPieceHashes()
|
||||
{
|
||||
return piece_hashes;
|
||||
}
|
||||
|
||||
@@ -1321,6 +1321,15 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after editing torrent.
|
||||
* Caller must ensure infohash, files, etc. did not change.
|
||||
*
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void replaceMetaInfo(MetaInfo metainfo) {
|
||||
meta = metainfo;
|
||||
}
|
||||
|
||||
///////////// Begin StorageListener methods
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.klomp.snark.web;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -22,6 +23,7 @@ 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;
|
||||
@@ -35,8 +37,10 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.servlet.util.ServletUtil;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
import net.i2p.util.Translate;
|
||||
import net.i2p.util.UIMessages;
|
||||
@@ -251,11 +255,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
} else {
|
||||
String base = addPaths(req.getRequestURI(), "/");
|
||||
boolean showEdit = req.getParameter("showEdit") != null;
|
||||
String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null,
|
||||
req.getParameter("sort"));
|
||||
req.getParameter("sort"), showEdit);
|
||||
if (method.equals("POST")) {
|
||||
// P-R-G
|
||||
sendRedirect(req, resp, "");
|
||||
sendRedirect(req, resp, showEdit ? "?showEdit" : "");
|
||||
} else if (listing != null) {
|
||||
setHTMLHeaders(resp, cspNonce, true);
|
||||
resp.getWriter().write(listing);
|
||||
@@ -2984,8 +2989,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
* @return String of HTML or null if postParams != null
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams, String sortParam)
|
||||
throws IOException
|
||||
private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams,
|
||||
String sortParam, boolean showEdit) throws IOException
|
||||
{
|
||||
String decodedBase = decodePath(base);
|
||||
String title = decodedBase;
|
||||
@@ -3027,6 +3032,10 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
_manager.startTorrent(snark);
|
||||
} else if (postParams.get("recheck") != null) {
|
||||
_manager.recheckTorrent(snark);
|
||||
} else if (postParams.get("editTorrent") != null) {
|
||||
saveTorrentEdit(snark, postParams);
|
||||
} else if (postParams.get("showEdit") != null) {
|
||||
// P-R-G only
|
||||
} else {
|
||||
_manager.addMessage("Unknown command");
|
||||
}
|
||||
@@ -3093,7 +3102,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
final boolean includeForm = showStopStart || showPriority || er || ec;
|
||||
if (includeForm) {
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n" +
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\">\n");
|
||||
if (sortParam != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"sort\" value=\"")
|
||||
.append(DataHelper.stripHTML(sortParam)).append("\" >\n");
|
||||
@@ -3137,7 +3146,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
String announce = null;
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta != null) {
|
||||
if (meta != null && !showEdit) {
|
||||
announce = meta.getAnnounce();
|
||||
if (announce == null)
|
||||
announce = snark.getTrackerURL();
|
||||
@@ -3218,7 +3227,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Comment")).append("</b></td><td>")
|
||||
.append(DataHelper.stripHTML(com))
|
||||
.append(DataHelper.escapeHTML(com))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
long dat = meta.getCreationDate();
|
||||
@@ -3239,7 +3248,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Created By")).append("</b></td><td>")
|
||||
.append(DataHelper.stripHTML(cby))
|
||||
.append(DataHelper.escapeHTML(cby))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
long[] dates = _manager.getSavedAddedAndCompleted(snark);
|
||||
@@ -3390,7 +3399,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("<b>").append(_t("Starting")).append("…</b>");
|
||||
} else if (snark.isAllocating()) {
|
||||
buf.append("<b>").append(_t("Allocating")).append("…</b>");
|
||||
} else {
|
||||
} else if (isTopLevel && !showEdit) {
|
||||
boolean isRunning = !snark.isStopped();
|
||||
buf.append("<input type=\"submit\" value=\"");
|
||||
if (isRunning)
|
||||
@@ -3398,14 +3407,23 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
else
|
||||
buf.append(_t("Start")).append("\" name=\"start\" class=\"starttorrent\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"recheck\" value=\"").append(_t("Force Recheck"));
|
||||
if (isRunning)
|
||||
if (isRunning) {
|
||||
buf.append("\" class=\"disabled\" disabled=\"disabled\" title=\"")
|
||||
.append(_t("Stop the torrent in order to check file integrity"))
|
||||
.append("\">\n");
|
||||
else
|
||||
.append(_t("Torrent must be stopped"));
|
||||
} else {
|
||||
buf.append("\" class=\"reload\" title=\"")
|
||||
.append(_t("Check integrity of the downloaded files"))
|
||||
.append("\">\n");
|
||||
.append(_t("Check integrity of the downloaded files"));
|
||||
}
|
||||
buf.append("\">\n" +
|
||||
"<input type=\"submit\" name=\"showEdit\" value=\"").append(_t("Edit Torrent"));
|
||||
if (isRunning) {
|
||||
buf.append("\" class=\"disabled\" disabled=\"disabled\" title=\"")
|
||||
.append(_t("Torrent must be stopped"));
|
||||
} else {
|
||||
buf.append("\" class=\"reload\" title=\"")
|
||||
.append(_t("Add or remove trackers"));
|
||||
}
|
||||
buf.append("\">\n");
|
||||
}
|
||||
boolean showInOrder = storage != null && !storage.complete() &&
|
||||
meta != null;
|
||||
@@ -3439,6 +3457,13 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
|
||||
if (snark != null && isTopLevel && showEdit) {
|
||||
// Edit torrent. Show edit section only.
|
||||
displayTorrentEdit(snark, base, buf);
|
||||
buf.append("</form></div></div></center></body></html>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
if (snark != null && !r.exists()) {
|
||||
// fixup TODO
|
||||
buf.append("<table class=\"resourceError\" id=\"DoesNotExist\"><tr><th colspan=\"2\">")
|
||||
@@ -3481,7 +3506,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
displayComments(snark, er, ec, esc, buf);
|
||||
if (includeForm)
|
||||
buf.append("</form>");
|
||||
buf.append("</div></div></body></html>");
|
||||
buf.append("</div></div></center></body></html>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -3778,7 +3803,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// for stop/start/check
|
||||
if (includeForm)
|
||||
buf.append("</form>");
|
||||
buf.append("</div></div></body></html>");
|
||||
buf.append("</div></div></center></body></html>");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
@@ -4150,6 +4175,128 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("</div>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param snark non-null
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private void displayTorrentEdit(Snark snark, String base, StringBuilder buf) {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null)
|
||||
return;
|
||||
buf.append("<div id=\"snarkCommentSection\"><table class=\"snarkTorrentInfo\">\n<tr><th colspan=\"5\">")
|
||||
.append(_t("Edit Torrent"))
|
||||
.append("</th></tr><tr><td colspan=\"3\"></td><td>").append("Primary").append("</td><td>")
|
||||
.append("Delete").append(" / ").append("Add").append("</td></tr>");
|
||||
boolean isRunning = !snark.isStopped();
|
||||
if (isRunning) {
|
||||
// shouldn't happen
|
||||
buf.append(_t("Torrent must be stopped"));
|
||||
buf.append("</th></table></div></form>");
|
||||
return;
|
||||
}
|
||||
String announce = meta.getAnnounce();
|
||||
if (announce == null)
|
||||
announce = snark.getTrackerURL();
|
||||
if (announce != null) {
|
||||
// strip non-i2p trackers
|
||||
if (!isI2PTracker(announce))
|
||||
announce = null;
|
||||
}
|
||||
List<List<String>> alist = meta.getAnnounceList();
|
||||
Set<String> annlist = new TreeSet<String>();
|
||||
if (alist != null && !alist.isEmpty()) {
|
||||
// strip non-i2p trackers
|
||||
for (List<String> alist2 : alist) {
|
||||
for (String s : alist2) {
|
||||
if (isI2PTracker(s))
|
||||
annlist.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (announce != null)
|
||||
annlist.add(announce);
|
||||
if (!annlist.isEmpty()) {
|
||||
for (String s : annlist) {
|
||||
int hc = s.hashCode();
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Tracker")).append("</b></td><td>");
|
||||
s = DataHelper.stripHTML(s);
|
||||
buf.append("<span class=\"info_tracker\">");
|
||||
buf.append(getShortTrackerLink(s, snark.getInfoHash()));
|
||||
buf.append("</span> ");
|
||||
//buf.append(s);
|
||||
buf.append("</td><td>");
|
||||
buf.append("<input type=\"radio\" class=\"optbox\" name=\"primary\" ");
|
||||
if (s.equals(announce))
|
||||
buf.append("checked=\"checked\" ");
|
||||
buf.append("value=\"").append(hc);
|
||||
buf.append("\"></td><td>");
|
||||
buf.append("<input type=\"checkbox\" class=\"optbox\" name=\"trdelete.")
|
||||
.append(hc).append("\" title=\"").append(_t("Mark for deletion")).append("\">");
|
||||
buf.append("</td></tr>\n");
|
||||
}
|
||||
}
|
||||
|
||||
List<Tracker> newTrackers = _manager.getSortedTrackers();
|
||||
for (Iterator<Tracker> iter = newTrackers.iterator(); iter.hasNext(); ) {
|
||||
Tracker t = iter.next();
|
||||
String announceURL = t.announceURL.replace("=", "=");
|
||||
if (announceURL.equals(announce) || annlist.contains(announceURL))
|
||||
iter.remove();
|
||||
}
|
||||
for (Tracker t : newTrackers) {
|
||||
String name = t.name;
|
||||
int hc = t.announceURL.hashCode();
|
||||
String announceURL = t.announceURL.replace("=", "=");
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Add Tracker")).append("</b></td><td>");
|
||||
buf.append(name);
|
||||
buf.append("</td><td><input type=\"radio\" class=\"optbox\" name=\"primary\" value=\"");
|
||||
buf.append(hc);
|
||||
buf.append("\"></td><td>");
|
||||
buf.append("<input type=\"checkbox\" class=\"optbox\" id=\"").append(name).append("\" name=\"tradd.")
|
||||
.append(hc).append("\" title=\"").append(_t("Add tracker")).append("\"> ")
|
||||
.append("<td></td></tr>\n");
|
||||
}
|
||||
|
||||
String com = meta.getComment();
|
||||
if (com == null) {
|
||||
com = "";
|
||||
} else if (com.length() > 0) {
|
||||
com = DataHelper.escapeHTML(com);
|
||||
}
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Comment")).append("</b></td>");
|
||||
buf.append("<td colspan=\"2\" id=\"addCommentText\"><textarea name=\"nofilter_newTorrentComment\" cols=\"88\" rows=\"4\">")
|
||||
.append(com).append("</textarea></td><td></td>");
|
||||
buf.append("</tr>\n");
|
||||
|
||||
String cb = meta.getCreatedBy();
|
||||
if (cb == null) {
|
||||
cb = "";
|
||||
} else if (cb.length() > 0) {
|
||||
cb = DataHelper.escapeHTML(cb);
|
||||
}
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Created By")).append("</b></td>");
|
||||
buf.append("<td id=\"editTorrentCreatedBy\"><input type=\"text\" name=\"nofilter_newTorrentCreatedBy\" cols=\"44\" rows=\"1\" value=\"")
|
||||
.append(cb).append("\"></td>");
|
||||
//// new id
|
||||
buf.append("<tr id=\"torrentInfoControl\"><td colspan=\"5\">");
|
||||
buf.append("<input type=\"submit\" name=\"editTorrent\" value=\"");
|
||||
buf.append(_t("Save Changes"));
|
||||
buf.append("\" class=\"accept\"></td></tr>\n");
|
||||
buf.append("</table></div>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param so null ok
|
||||
* @return query string or ""
|
||||
@@ -4370,6 +4517,147 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
_manager.setSavedCommentsEnabled(snark, yes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private void saveTorrentEdit(Snark snark, Map<String, String[]> postParams) {
|
||||
if (!snark.isStopped()) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage(_t("Torrent must be stopped"));
|
||||
return;
|
||||
}
|
||||
List<Integer> toAdd = new ArrayList<Integer>();
|
||||
List<Integer> toDel = new ArrayList<Integer>();
|
||||
Integer primary = null;
|
||||
String newComment = "";
|
||||
String newCreatedBy = "";
|
||||
for (Map.Entry<String, String[]> entry : postParams.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String val = entry.getValue()[0]; // jetty arrays
|
||||
if (key.startsWith("tradd.")) {
|
||||
try {
|
||||
toAdd.add(Integer.parseInt(key.substring(6)));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.startsWith("trdelete.")) {
|
||||
try {
|
||||
toDel.add(Integer.parseInt(key.substring(9)));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.equals("primary")) {
|
||||
try {
|
||||
primary = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.equals("nofilter_newTorrentComment")) {
|
||||
newComment = val.trim();
|
||||
} else if (key.equals("nofilter_newTorrentCreatedBy")) {
|
||||
newCreatedBy = val.trim();
|
||||
}
|
||||
}
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage("Can't edit magnet");
|
||||
return;
|
||||
}
|
||||
String oldPrimary = meta.getAnnounce();
|
||||
String oldComment = meta.getComment();
|
||||
if (oldComment == null)
|
||||
oldComment = "";
|
||||
String oldCreatedBy = meta.getCreatedBy();
|
||||
if (oldCreatedBy == null)
|
||||
oldCreatedBy = "";
|
||||
if (toAdd.isEmpty() && toDel.isEmpty() &&
|
||||
(primary == null || primary.equals(oldPrimary)) &&
|
||||
oldComment.equals(newComment) &&
|
||||
oldCreatedBy.equals(newCreatedBy)) {
|
||||
_manager.addMessage("No changes to torrent, not saved");
|
||||
return;
|
||||
}
|
||||
List<List<String>> alist = meta.getAnnounceList();
|
||||
Set<String> annlist = new TreeSet<String>();
|
||||
if (alist != null && !alist.isEmpty()) {
|
||||
// strip non-i2p trackers
|
||||
for (List<String> alist2 : alist) {
|
||||
for (String s : alist2) {
|
||||
if (isI2PTracker(s))
|
||||
annlist.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldPrimary != null)
|
||||
annlist.add(oldPrimary);
|
||||
List<Tracker> newTrackers = _manager.getSortedTrackers();
|
||||
for (Integer i : toDel) {
|
||||
int hc = i.intValue();
|
||||
for (Iterator<String> iter = annlist.iterator(); iter.hasNext(); ) {
|
||||
String s = iter.next();
|
||||
if (s.hashCode() == hc)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
for (Integer i : toAdd) {
|
||||
int hc = i.intValue();
|
||||
for (Tracker t : newTrackers) {
|
||||
if (t.announceURL.hashCode() == hc) {
|
||||
annlist.add(t.announceURL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
String thePrimary = oldPrimary;
|
||||
if (primary != null) {
|
||||
int hc = primary.intValue();
|
||||
for (String s : annlist) {
|
||||
if (s.hashCode() == hc) {
|
||||
thePrimary = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
List<List<String>> newAnnList;
|
||||
if (annlist.isEmpty()) {
|
||||
newAnnList = null;
|
||||
thePrimary = null;
|
||||
} else {
|
||||
List<String> aalist = new ArrayList<String>(annlist);
|
||||
newAnnList = Collections.singletonList(aalist);
|
||||
if (!aalist.contains(thePrimary))
|
||||
thePrimary = aalist.get(0);
|
||||
}
|
||||
if (newComment.equals(""))
|
||||
newComment = null;
|
||||
if (newCreatedBy.equals(""))
|
||||
newCreatedBy = null;
|
||||
MetaInfo newMeta = new MetaInfo(thePrimary, meta.getName(), null, meta.getFiles(), meta.getLengths(),
|
||||
meta.getPieceLength(0), meta.getPieceHashes(), meta.getTotalLength(), meta.isPrivate(),
|
||||
newAnnList, newCreatedBy, meta.getWebSeedURLs(), newComment);
|
||||
if (!DataHelper.eq(meta.getInfoHash(), newMeta.getInfoHash())) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage("Torrent edit failed, infohash mismatch");
|
||||
return;
|
||||
}
|
||||
File f = new File(_manager.util().getTempDir(), "edit-" + _manager.util().getContext().random().nextLong() + ".torrent");
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new SecureFileOutputStream(f);
|
||||
out.write(newMeta.getTorrentData());
|
||||
out.close();
|
||||
boolean ok = FileUtil.rename(f, new File(snark.getName()));
|
||||
if (!ok) {
|
||||
_manager.addMessage("Save edit changes failed");
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
try { if (out != null) out.close(); } catch (IOException ioe2) {}
|
||||
_manager.addMessage("Save edit changes failed: " + ioe);
|
||||
return;
|
||||
} finally {
|
||||
f.delete();
|
||||
}
|
||||
snark.replaceMetaInfo(newMeta);
|
||||
_manager.addMessage("Torrent changes saved");
|
||||
}
|
||||
|
||||
|
||||
/** @since 0.9.32 */
|
||||
private static boolean noCollapsePanels(HttpServletRequest req) {
|
||||
// check for user agents that can't toggle the collapsible panels...
|
||||
|
||||
Reference in New Issue
Block a user