forked from I2P_Developers/i2p.i2p
- Protection against modifying metainfos
- Announce peers to local tracker - Ping node on port reception - More info on directory pages - Cleanups
This commit is contained in:
@ -54,28 +54,30 @@ public class MetaInfo
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
private final String name_utf8;
|
||||
private final List files;
|
||||
private final List files_utf8;
|
||||
private final List lengths;
|
||||
private final List<List<String>> files;
|
||||
private final List<List<String>> files_utf8;
|
||||
private final List<Long> lengths;
|
||||
private final int piece_length;
|
||||
private final byte[] piece_hashes;
|
||||
private final long length;
|
||||
private Map infoMap;
|
||||
private Map<String, BEValue> infoMap;
|
||||
|
||||
/**
|
||||
* Called by Storage when creating a new torrent from local data
|
||||
*
|
||||
* @param announce may be null
|
||||
* @param files null for single-file torrent
|
||||
* @param lengths null for single-file torrent
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
this.name_utf8 = name_utf8;
|
||||
this.files = files;
|
||||
this.files = files == null ? null : Collections.unmodifiableList(files);
|
||||
this.files_utf8 = null;
|
||||
this.lengths = lengths;
|
||||
this.lengths = lengths == null ? null : Collections.unmodifiableList(lengths);
|
||||
this.piece_length = piece_length;
|
||||
this.piece_hashes = piece_hashes;
|
||||
this.length = length;
|
||||
@ -131,7 +133,7 @@ public class MetaInfo
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing info map");
|
||||
Map info = val.getMap();
|
||||
infoMap = info;
|
||||
infoMap = Collections.unmodifiableMap(info);
|
||||
|
||||
val = (BEValue)info.get("name");
|
||||
if (val == null)
|
||||
@ -171,39 +173,39 @@ public class MetaInfo
|
||||
throw new InvalidBEncodingException
|
||||
("Missing length number and/or files list");
|
||||
|
||||
List list = val.getList();
|
||||
List<BEValue> list = val.getList();
|
||||
int size = list.size();
|
||||
if (size == 0)
|
||||
throw new InvalidBEncodingException("zero size files list");
|
||||
|
||||
files = new ArrayList(size);
|
||||
files_utf8 = new ArrayList(size);
|
||||
lengths = new ArrayList(size);
|
||||
List<List<String>> m_files = new ArrayList(size);
|
||||
List<List<String>> m_files_utf8 = new ArrayList(size);
|
||||
List<Long> m_lengths = new ArrayList(size);
|
||||
long l = 0;
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
{
|
||||
Map desc = ((BEValue)list.get(i)).getMap();
|
||||
val = (BEValue)desc.get("length");
|
||||
Map<String, BEValue> desc = list.get(i).getMap();
|
||||
val = desc.get("length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing length number");
|
||||
long len = val.getLong();
|
||||
lengths.add(new Long(len));
|
||||
m_lengths.add(new Long(len));
|
||||
l += len;
|
||||
|
||||
val = (BEValue)desc.get("path");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing path list");
|
||||
List path_list = val.getList();
|
||||
List<BEValue> path_list = val.getList();
|
||||
int path_length = path_list.size();
|
||||
if (path_length == 0)
|
||||
throw new InvalidBEncodingException("zero size file path list");
|
||||
|
||||
List file = new ArrayList(path_length);
|
||||
Iterator it = path_list.iterator();
|
||||
List<String> file = new ArrayList(path_length);
|
||||
Iterator<BEValue> it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
file.add(it.next().getString());
|
||||
|
||||
files.add(file);
|
||||
m_files.add(Collections.unmodifiableList(file));
|
||||
|
||||
val = (BEValue)desc.get("path.utf-8");
|
||||
if (val != null) {
|
||||
@ -213,11 +215,14 @@ public class MetaInfo
|
||||
file = new ArrayList(path_length);
|
||||
it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
files_utf8.add(file);
|
||||
file.add(it.next().getString());
|
||||
m_files_utf8.add(Collections.unmodifiableList(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
files = Collections.unmodifiableList(m_files);
|
||||
files_utf8 = Collections.unmodifiableList(m_files_utf8);
|
||||
lengths = Collections.unmodifiableList(m_lengths);
|
||||
length = l;
|
||||
}
|
||||
|
||||
@ -265,9 +270,8 @@ public class MetaInfo
|
||||
* a single name. It has the same size as the list returned by
|
||||
* getLengths().
|
||||
*/
|
||||
public List getFiles()
|
||||
public List<List<String>> getFiles()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return files;
|
||||
}
|
||||
|
||||
@ -276,9 +280,8 @@ public class MetaInfo
|
||||
* files, or null if it is a single file. It has the same size as
|
||||
* the list returned by getFiles().
|
||||
*/
|
||||
public List getLengths()
|
||||
public List<Long> getLengths()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return lengths;
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ import java.util.Map;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
@ -384,6 +385,13 @@ public class Peer implements Comparable
|
||||
return options;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
public Destination getDestination() {
|
||||
if (sock == null)
|
||||
return null;
|
||||
return sock.getPeerDestination();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared state across all peers, callers must sync on returned object
|
||||
* @return non-null
|
||||
|
@ -37,7 +37,8 @@ class PeerCheckerTask extends TimerTask
|
||||
private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
public I2PSnarkUtil _util;
|
||||
private final I2PSnarkUtil _util;
|
||||
private int _runCount;
|
||||
|
||||
PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
|
||||
{
|
||||
@ -49,6 +50,7 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
public void run()
|
||||
{
|
||||
_runCount++;
|
||||
List<Peer> peerList = coordinator.peerList();
|
||||
if (peerList.isEmpty() || coordinator.halted()) {
|
||||
coordinator.setRateHistory(0, 0);
|
||||
@ -204,6 +206,10 @@ class PeerCheckerTask extends TimerTask
|
||||
}
|
||||
peer.retransmitRequests();
|
||||
peer.keepAlive();
|
||||
// announce them to local tracker (TrackerClient does this too)
|
||||
if (_util.getDHT() != null && (_runCount % 5) == 0) {
|
||||
_util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash());
|
||||
}
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
@ -245,8 +251,13 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
// close out unused files, but we don't need to do it every time
|
||||
Storage storage = coordinator.getStorage();
|
||||
if (storage != null && random.nextInt(4) == 0) {
|
||||
if (storage != null && (_runCount % 4) == 0) {
|
||||
storage.cleanRAFs();
|
||||
}
|
||||
|
||||
// announce ourselves to local tracker (TrackerClient does this too)
|
||||
if (_util.getDHT() != null && (_runCount % 16) == 0) {
|
||||
_util.getDHT().announce(coordinator.getInfoHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.dht.KRPC;
|
||||
|
||||
/**
|
||||
* Coordinates what peer does what.
|
||||
*/
|
||||
@ -1186,10 +1188,13 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
/**
|
||||
* PeerListener callback
|
||||
* Tell the DHT to ping it, this will get back the node info
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void gotPort(Peer peer, int port) {
|
||||
// send to DHT
|
||||
KRPC krpc = _util.getDHT();
|
||||
if (krpc != null)
|
||||
krpc.ping(peer.getDestination(), port);
|
||||
}
|
||||
|
||||
/** Return number of allowed uploaders for this torrent.
|
||||
|
@ -776,7 +776,7 @@ public class Snark
|
||||
}
|
||||
|
||||
/**
|
||||
* @return needed of all torrent files, or total of metainfo file if fetching magnet, or -1
|
||||
* @return number of pieces still needed (magnet mode or not), or -1 if unknown
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public long getNeeded() {
|
||||
@ -786,7 +786,7 @@ public class Snark
|
||||
// FIXME subtract chunks we have
|
||||
return meta.getTotalLength();
|
||||
// FIXME fake
|
||||
return 16 * 16 * 1024;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -800,6 +800,17 @@ public class Snark
|
||||
return 16*1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of pieces
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public int getPieces() {
|
||||
if (meta != null)
|
||||
return meta.getPieces();
|
||||
// FIXME else return metainfo pieces if available
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if restarted
|
||||
* @since 0.8.4
|
||||
|
@ -68,6 +68,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
|
||||
public static final String PROP_META_MAGNET_SUFFIX = ".magnet";
|
||||
|
||||
private static final String CONFIG_FILE = "i2psnark.config";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
@ -1031,6 +1032,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
//start magnets
|
||||
|
||||
|
||||
// here because we need to delay until I2CP is up
|
||||
// although the user will see the default until then
|
||||
getBWLimit();
|
||||
@ -1245,6 +1249,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if ( (snark != null) && (!snark.isStopped()) )
|
||||
snark.stopTorrent();
|
||||
}
|
||||
//save magnets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class Storage
|
||||
getFiles(baseFile);
|
||||
|
||||
long total = 0;
|
||||
ArrayList lengthsList = new ArrayList();
|
||||
ArrayList<Long> lengthsList = new ArrayList();
|
||||
for (int i = 0; i < lengths.length; i++)
|
||||
{
|
||||
long length = lengths[i];
|
||||
@ -122,10 +122,10 @@ public class Storage
|
||||
bitfield = new BitField(pieces);
|
||||
needed = 0;
|
||||
|
||||
List files = new ArrayList();
|
||||
List<List<String>> files = new ArrayList();
|
||||
for (int i = 0; i < names.length; i++)
|
||||
{
|
||||
List file = new ArrayList();
|
||||
List<String> file = new ArrayList();
|
||||
StringTokenizer st = new StringTokenizer(names[i], File.separator);
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
|
@ -277,18 +277,26 @@ public class TrackerClient extends I2PAppThread
|
||||
runStarted = true;
|
||||
tr.started = true;
|
||||
|
||||
Set peers = info.getPeers();
|
||||
Set<Peer> peers = info.getPeers();
|
||||
tr.seenPeers = info.getPeerCount();
|
||||
if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
|
||||
snark.setTrackerSeenPeers(tr.seenPeers);
|
||||
|
||||
// pass everybody over to our tracker
|
||||
if (_util.getDHT() != null) {
|
||||
for (Peer peer : peers) {
|
||||
_util.getDHT().announce(snark.getInfoHash(), peer.getPeerID().getDestHash());
|
||||
}
|
||||
}
|
||||
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List ordered = new ArrayList(peers);
|
||||
List<Peer> ordered = new ArrayList(peers);
|
||||
Collections.shuffle(ordered, r);
|
||||
Iterator it = ordered.iterator();
|
||||
Iterator<Peer> it = ordered.iterator();
|
||||
while ((!stop) && it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
Peer cur = it.next();
|
||||
// FIXME if id == us || dest == us continue;
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
|
@ -6,6 +6,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.Collator;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@ -875,8 +876,7 @@ public class I2PSnarkServlet extends Default {
|
||||
else if (isValid)
|
||||
icon = toIcon(meta.getName());
|
||||
else
|
||||
// todo get a nice magnet icon?
|
||||
icon = "page_white";
|
||||
icon = "magnet";
|
||||
if (remaining == 0 || isMultiFile) {
|
||||
out.write(toImg(icon, _("Open")));
|
||||
out.write("</a>");
|
||||
@ -1375,6 +1375,7 @@ public class I2PSnarkServlet extends Default {
|
||||
if (ihash.length() == 32) {
|
||||
ih = Base32.decode(ihash);
|
||||
} else if (ihash.length() == 40) {
|
||||
// Like DataHelper.fromHexString() but ensures no loss of leading zero bytes
|
||||
ih = new byte[20];
|
||||
try {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
@ -1539,6 +1540,7 @@ public class I2PSnarkServlet extends Default {
|
||||
|
||||
if (title.endsWith("/"))
|
||||
title = title.substring(0, title.length() - 1);
|
||||
String directory = title;
|
||||
title = _("Torrent") + ": " + title;
|
||||
buf.append(title);
|
||||
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
|
||||
@ -1550,10 +1552,40 @@ public class I2PSnarkServlet extends Default {
|
||||
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete();
|
||||
if (showPriority)
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
|
||||
buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" >" +
|
||||
"<thead><tr><th>")
|
||||
buf.append("<TABLE BORDER=0 class=\"snarkTorrents\" ><thead>");
|
||||
if (snark != null) {
|
||||
// first row - torrent info
|
||||
// FIXME center
|
||||
buf.append("<tr><th colspan=\"" + (showPriority ? '4' : '3') + "\"><div>")
|
||||
.append(_("Torrent")).append(": ").append(snark.getBaseName());
|
||||
int pieces = snark.getPieces();
|
||||
double completion = (pieces - snark.getNeeded()) / (double) pieces;
|
||||
if (completion < 1.0)
|
||||
buf.append("<br>").append(_("Completion")).append(": ").append((new DecimalFormat("0.00%")).format(completion));
|
||||
else
|
||||
buf.append("<br>").append(_("Complete"));
|
||||
// else unknown
|
||||
buf.append("<br>").append(_("Size")).append(": ").append(formatSize(snark.getTotalLength()));
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta != null) {
|
||||
List files = meta.getFiles();
|
||||
int fileCount = files != null ? files.size() : 1;
|
||||
buf.append("<br>").append(_("Files")).append(": ").append(fileCount);
|
||||
}
|
||||
buf.append("<br>").append(_("Pieces")).append(": ").append(pieces);
|
||||
buf.append("<br>").append(_("Piece size")).append(": ").append(formatSize(snark.getPieceLength(0)));
|
||||
String hex = toHex(snark.getInfoHash());
|
||||
buf.append("<br>").append(_("Magnet link")).append(": <a href=\"").append(MAGNET).append(hex).append("\">")
|
||||
.append(MAGNET).append(hex).append("</a>");
|
||||
// We don't have the hash of the torrent file
|
||||
//buf.append("<br>").append(_("Maggot link")).append(": <a href=\"").append(MAGGOT).append(hex).append(':').append(hex).append("\">")
|
||||
// .append(MAGGOT).append(hex).append(':').append(hex).append("</a>");
|
||||
buf.append("</div></th></tr>");
|
||||
}
|
||||
// second row - dir info
|
||||
buf.append("<tr><th>")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "file.png\" > ")
|
||||
.append(title).append("</th><th align=\"right\">")
|
||||
.append(_("Directory")).append(": ").append(directory).append("</th><th align=\"right\">")
|
||||
.append("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "size.png\" > ")
|
||||
.append(_("Size"));
|
||||
buf.append("</th><th class=\"headerstatus\">")
|
||||
@ -1767,6 +1799,21 @@ public class I2PSnarkServlet extends Default {
|
||||
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Like DataHelper.toHexString but ensures no loss of leading zero bytes
|
||||
* @since 0.8.4
|
||||
*/
|
||||
private static String toHex(byte[] b) {
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
int bi = b[i] & 0xff;
|
||||
if (bi < 16)
|
||||
buf.append('0');
|
||||
buf.append(Integer.toHexString(bi));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
private void savePriorities(Snark snark, Map postParams) {
|
||||
Storage storage = snark.getStorage();
|
||||
|
Reference in New Issue
Block a user