- i2psnark: Add announce list support (BEP 12) (ticket #778)

Preliminary. Still todo: TrackerClient
This commit is contained in:
zzz
2012-12-10 22:48:44 +00:00
parent 9f6ebd8e10
commit 4e558320a9
4 changed files with 150 additions and 37 deletions

View File

@ -61,6 +61,7 @@ public class MetaInfo
private final byte[] piece_hashes; private final byte[] piece_hashes;
private final long length; private final long length;
private final boolean privateTorrent; private final boolean privateTorrent;
private final List<List<String>> announce_list;
private Map<String, BEValue> infoMap; private Map<String, BEValue> infoMap;
/** /**
@ -69,9 +70,11 @@ public class MetaInfo
* @param announce may be null * @param announce may be null
* @param files null for single-file torrent * @param files null for single-file torrent
* @param lengths null for single-file torrent * @param lengths null for single-file torrent
* @param announce_list may be null
*/ */
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths, 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) int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
List<List<String>> announce_list)
{ {
this.announce = announce; this.announce = announce;
this.name = name; this.name = name;
@ -83,6 +86,7 @@ public class MetaInfo
this.piece_hashes = piece_hashes; this.piece_hashes = piece_hashes;
this.length = length; this.length = length;
this.privateTorrent = privateTorrent; this.privateTorrent = privateTorrent;
this.announce_list = announce_list;
// TODO if we add a parameter for other keys // TODO if we add a parameter for other keys
//if (other != null) { //if (other != null) {
@ -141,6 +145,23 @@ public class MetaInfo
this.announce = val.getString(); this.announce = val.getString();
} }
// BEP 12
val = m.get("announce-list");
if (val == null) {
this.announce_list = null;
} else {
this.announce_list = new ArrayList();
List<BEValue> bl1 = val.getList();
for (BEValue bev : bl1) {
List<BEValue> bl2 = bev.getList();
List<String> sl2 = new ArrayList();
for (BEValue bev2 : bl2) {
sl2.add(bev2.getString());
}
this.announce_list.add(sl2);
}
}
val = m.get("info"); val = m.get("info");
if (val == null) if (val == null)
throw new InvalidBEncodingException("Missing info map"); throw new InvalidBEncodingException("Missing info map");
@ -296,6 +317,15 @@ public class MetaInfo
return announce; return announce;
} }
/**
* Returns a list of lists of urls.
*
* @since 0.9.5
*/
public List<List<String>> getAnnounceList() {
return announce_list;
}
/** /**
* Returns the original 20 byte SHA1 hash over the bencoded info map. * Returns the original 20 byte SHA1 hash over the bencoded info map.
*/ */
@ -470,12 +500,13 @@ public class MetaInfo
/** /**
* Creates a copy of this MetaInfo that shares everything except the * Creates a copy of this MetaInfo that shares everything except the
* announce URL. * announce URL.
* Drops any announce-list.
*/ */
public MetaInfo reannounce(String announce) public MetaInfo reannounce(String announce)
{ {
return new MetaInfo(announce, name, name_utf8, files, return new MetaInfo(announce, name, name_utf8, files,
lengths, piece_length, lengths, piece_length,
piece_hashes, length, privateTorrent); piece_hashes, length, privateTorrent, null);
} }
/** /**
@ -486,6 +517,8 @@ public class MetaInfo
Map m = new HashMap(); Map m = new HashMap();
if (announce != null) if (announce != null)
m.put("announce", announce); m.put("announce", announce);
if (announce_list != null)
m.put("announce-list", announce_list);
Map info = createInfoMap(); Map info = createInfoMap();
m.put("info", info); m.put("info", info);
// don't save this locally, we should only do this once // don't save this locally, we should only do this once

View File

@ -886,7 +886,9 @@ public class SnarkManager implements CompleteListener {
} }
} }
} catch (IOException ioe) { } catch (IOException ioe) {
addMessage(_("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage()); String err = _("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage();
addMessage(err);
_log.error(err, ioe);
if (sfile.exists()) if (sfile.exists())
sfile.delete(); sfile.delete();
return; return;

View File

@ -122,6 +122,7 @@ public class Storage
* @throws IOException when creating and/or checking files fails. * @throws IOException when creating and/or checking files fails.
*/ */
public Storage(I2PSnarkUtil util, File baseFile, String announce, public Storage(I2PSnarkUtil util, File baseFile, String announce,
List<List<String>> announce_list,
boolean privateTorrent, StorageListener listener) boolean privateTorrent, StorageListener listener)
throws IOException throws IOException
{ {
@ -182,7 +183,8 @@ public class Storage
// TODO thread this so we can return and show something on the UI // TODO thread this so we can return and show something on the UI
byte[] piece_hashes = fast_digestCreate(); byte[] piece_hashes = fast_digestCreate();
metainfo = new MetaInfo(announce, baseFile.getName(), null, files, metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
lengthsList, piece_size, piece_hashes, total, privateTorrent); lengthsList, piece_size, piece_hashes, total, privateTorrent,
announce_list);
} }
@ -1225,7 +1227,7 @@ public class Storage
File file = null; File file = null;
FileOutputStream out = null; FileOutputStream out = null;
try { try {
Storage storage = new Storage(util, base, announce, false, null); Storage storage = new Storage(util, base, announce, null, false, null);
MetaInfo meta = storage.getMetaInfo(); MetaInfo meta = storage.getMetaInfo();
file = new File(storage.getBaseName() + ".torrent"); file = new File(storage.getBaseName() + ".torrent");
out = new FileOutputStream(file); out = new FileOutputStream(file);

View File

@ -61,7 +61,7 @@ public class I2PSnarkServlet extends DefaultServlet {
private Resource _resourceBase; private Resource _resourceBase;
private String _themePath; private String _themePath;
private String _imgPath; private String _imgPath;
private String _lastAnnounceURL = ""; private String _lastAnnounceURL;
public static final String PROP_CONFIG_FILE = "i2psnark.configFile"; public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
@ -730,18 +730,54 @@ public class I2PSnarkServlet extends DefaultServlet {
//if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) ) //if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
// announceURL = announceURLOther; // announceURL = announceURLOther;
if (announceURL == null || announceURL.length() <= 0) if (baseFile.exists()) {
_manager.addMessage(_("Error creating torrent - you must select a tracker"));
else if (baseFile.exists()) {
_lastAnnounceURL = announceURL;
if (announceURL.equals("none")) if (announceURL.equals("none"))
announceURL = null; announceURL = null;
_lastAnnounceURL = announceURL;
List<String> backupURLs = new ArrayList();
Enumeration e = req.getParameterNames();
while (e.hasMoreElements()) {
Object o = e.nextElement();
if (!(o instanceof String))
continue;
String k = (String) o;
if (k.startsWith("backup_")) {
String url = k.substring(7);
if (!url.equals(announceURL))
backupURLs.add(url);
}
}
List<List<String>> announceList = null;
if (!backupURLs.isEmpty()) {
// BEP 12 - Put primary first, then the others, each as the sole entry in their own list
if (announceURL == null) {
_manager.addMessage(_("Error - Cannot include alternate trackers without a primary tracker"));
return;
}
backupURLs.add(0, announceURL);
boolean hasPrivate = false;
boolean hasPublic = false;
for (String url : backupURLs) {
if (_manager.getPrivateTrackers().contains(announceURL))
hasPrivate = true;
else
hasPublic = true;
}
if (hasPrivate && hasPublic) {
_manager.addMessage(_("Error - Cannot mix private and public trackers in a torrent"));
return;
}
announceList = new ArrayList(backupURLs.size());
for (String url : backupURLs) {
announceList.add(Collections.singletonList(url));
}
}
try { try {
// This may take a long time to check the storage, but since it already exists, // This may take a long time to check the storage, but since it already exists,
// it shouldn't be THAT bad, so keep it in this thread. // it shouldn't be THAT bad, so keep it in this thread.
// TODO thread it for big torrents, perhaps a la FetchAndAdd // TODO thread it for big torrents, perhaps a la FetchAndAdd
boolean isPrivate = _manager.getPrivateTrackers().contains(announceURL); boolean isPrivate = _manager.getPrivateTrackers().contains(announceURL);
Storage s = new Storage(_manager.util(), baseFile, announceURL, isPrivate, null); Storage s = new Storage(_manager.util(), baseFile, announceURL, announceList, isPrivate, null);
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
MetaInfo info = s.getMetaInfo(); MetaInfo info = s.getMetaInfo();
File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent"); File torrentFile = new File(_manager.getDataDir(), s.getBaseName() + ".torrent");
@ -1372,6 +1408,7 @@ public class I2PSnarkServlet extends DefaultServlet {
} }
/** /**
* Start of anchor only, caller must add anchor text or img and close anchor
* @return string or null * @return string or null
* @since 0.8.4 * @since 0.8.4
*/ */
@ -1399,6 +1436,7 @@ public class I2PSnarkServlet extends DefaultServlet {
} }
/** /**
* Full anchor with img
* @return string or null * @return string or null
* @since 0.8.4 * @since 0.8.4
*/ */
@ -1414,6 +1452,29 @@ public class I2PSnarkServlet extends DefaultServlet {
return null; return null;
} }
/**
* Full anchor with shortened URL as anchor text
* @return string, non-null
* @since 0.9.5
*/
private String getShortTrackerLink(String announce, byte[] infohash) {
StringBuilder buf = new StringBuilder(128);
String trackerLinkUrl = getTrackerLinkUrl(announce, infohash);
if (trackerLinkUrl != null)
buf.append(trackerLinkUrl);
if (announce.startsWith("http://"))
announce = announce.substring(7);
int slsh = announce.indexOf('/');
if (slsh > 0)
announce = announce.substring(0, slsh);
if (announce.length() > 67)
announce = announce.substring(0, 40) + "&hellip;" + announce.substring(announce.length() - 8);
buf.append(announce);
if (trackerLinkUrl != null)
buf.append("</a>");
return buf.toString();
}
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException { private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
// display incoming parameter if a GET so links will work // display incoming parameter if a GET so links will work
String newURL = req.getParameter("newURL"); String newURL = req.getParameter("newURL");
@ -1482,23 +1543,32 @@ public class I2PSnarkServlet extends DefaultServlet {
+ "\" title=\""); + "\" title=\"");
out.write(_("File or directory to seed (must be within the specified path)")); out.write(_("File or directory to seed (must be within the specified path)"));
out.write("\" ><tr><td>\n"); out.write("\" ><tr><td>\n");
out.write(_("Tracker")); out.write(_("Trackers"));
out.write(":<td><select name=\"announceURL\"><option value=\"\">"); out.write(":<td><table style=\"width: 20%;\"><tr><td></td><td align=\"center\">");
out.write(_("Select a tracker")); out.write(_("Primary"));
out.write("</option>\n"); out.write("</td><td align=\"center\">");
// todo remember this one with _lastAnnounceURL also out.write(_("Alternates"));
out.write("<option value=\"none\">"); out.write("</td></tr>\n");
//out.write(_("Open trackers and DHT only"));
out.write(_("Open trackers only"));
out.write("</option>\n");
for (Tracker t : sortedTrackers) { for (Tracker t : sortedTrackers) {
String name = t.name; String name = t.name;
String announceURL = t.announceURL.replace("&#61;", "="); String announceURL = t.announceURL.replace("&#61;", "=");
out.write("<tr><td>");
out.write(name);
out.write("</td><td align=\"center\"><input type=\"radio\" name=\"announceURL\" value=\"");
out.write(announceURL);
out.write("\"");
if (announceURL.equals(_lastAnnounceURL)) if (announceURL.equals(_lastAnnounceURL))
announceURL += "\" selected=\"selected"; out.write(" checked");
out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n"); out.write("></td><td align=\"center\"><input type=\"checkbox\" name=\"backup_");
out.write(announceURL);
out.write("\" value=\"foo\"></td></tr>\n");
} }
out.write("</select>\n"); out.write("<tr><td>");
out.write(_("none"));
out.write("</td><td align=\"center\"><input type=\"radio\" name=\"announceURL\" value=\"none\"");
if (_lastAnnounceURL == null)
out.write(" checked");
out.write("></td><td></td></tr></table>\n");
// make the user add a tracker on the config form now // make the user add a tracker on the config form now
//out.write(_("or")); //out.write(_("or"));
//out.write("&nbsp;<input type=\"text\" name=\"announceURLOther\" size=\"57\" value=\"http://\" " + //out.write("&nbsp;<input type=\"text\" name=\"announceURLOther\" size=\"57\" value=\"http://\" " +
@ -1998,20 +2068,26 @@ public class I2PSnarkServlet extends DefaultServlet {
String trackerLink = getTrackerLink(announce, snark.getInfoHash()); String trackerLink = getTrackerLink(announce, snark.getInfoHash());
if (trackerLink != null) if (trackerLink != null)
buf.append(trackerLink).append(' '); buf.append(trackerLink).append(' ');
buf.append("<b>").append(_("Tracker")).append(":</b> "); buf.append("<b>").append(_("Primary Tracker")).append(":</b> ");
String trackerLinkUrl = getTrackerLinkUrl(announce, snark.getInfoHash()); buf.append(getShortTrackerLink(announce, snark.getInfoHash()));
if (trackerLinkUrl != null) buf.append("</td></tr>");
buf.append(trackerLinkUrl); }
if (announce.startsWith("http://")) List<List<String>> alist = meta.getAnnounceList();
announce = announce.substring(7); if (alist != null) {
int slsh = announce.indexOf('/'); buf.append("<tr><td><b>");
if (slsh > 0) buf.append(_("Tracker List")).append(":</b> ");
announce = announce.substring(0, slsh); for (List<String> alist2 : alist) {
if (announce.length() > 67) buf.append('[');
announce = announce.substring(0, 40) + "&hellip;" + announce.substring(announce.length() - 8); boolean more = false;
buf.append(announce); for (String s : alist2) {
if (trackerLinkUrl != null) if (more)
buf.append("</a>"); buf.append(' ');
else
more = true;
buf.append(getShortTrackerLink(s, snark.getInfoHash()));
}
buf.append("] ");
}
buf.append("</td></tr>"); buf.append("</td></tr>");
} }
} }