forked from I2P_Developers/i2p.i2p
Compare commits
423 Commits
i2p_0_6_1_
...
i2p-0.6.3
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bf51741134 | ||
33e8abfc3e | |||
258d01f0d9 | |||
49af13a3ca | |||
719ba3f66f | |||
9652db9623 | |||
481af00bab | |||
11d267bc9a | |||
40f0cb65a1 | |||
2ba9929277 | |||
616abba328 | |||
![]() |
14a6352d9a | ||
![]() |
5782c42d25 | ||
![]() |
f261deaf16 | ||
5228543236 | |||
e173a47e01 | |||
07b895a069 | |||
4d8ffc85e2 | |||
53e2e0d1c9 | |||
e0dcf82697 | |||
0cfac58adb | |||
2768bef991 | |||
bae712ad96 | |||
49cb4c13b3 | |||
28da17276c | |||
14099ace69 | |||
9289799c97 | |||
f057666ac2 | |||
a11b74b2d8 | |||
0e018c5b4d | |||
107a90fa33 | |||
![]() |
01259cc07d | ||
![]() |
4d955f3be5 | ||
49e429c166 | |||
53c5b1446a | |||
dc68ebbaeb | |||
f3d73a6c15 | |||
91950a37f5 | |||
a8c266402e | |||
d78fb4df3c | |||
fb5a8ee0d8 | |||
7b81062816 | |||
c3a2adc97e | |||
bff685f7ca | |||
7e51c86c38 | |||
df069ec9d1 | |||
eb3164d0e0 | |||
5a69de3650 | |||
87b933fd3a | |||
2404078bfa | |||
6b33378a0a | |||
![]() |
c46c9b2b7c | ||
![]() |
acf22bf8fc | ||
f3b8c73e96 | |||
a8e625072b | |||
88e26224c2 | |||
db9db18bdf | |||
9c06bb3fca | |||
8edfa746e5 | |||
5729b34f8b | |||
2f80f7fa63 | |||
592e609291 | |||
3396a8813f | |||
6345e669bc | |||
74a5abbc11 | |||
19992b1d1b | |||
02e7a19f65 | |||
c6a697df57 | |||
26bb479957 | |||
0c42e7e4b2 | |||
699a62a9b9 | |||
ffc67d1e5a | |||
2f72f5ca67 | |||
![]() |
955e7823ad | ||
7e3800a5cb | |||
![]() |
6c7691cecb | ||
![]() |
760c316486 | ||
![]() |
5d9d82879f | ||
9b8772a470 | |||
bc5d87e6f0 | |||
d81bff267a | |||
042399f293 | |||
5be7ea1fc5 | |||
db34665bb1 | |||
ed9a03ebc7 | |||
619b5c0e45 | |||
4c2c5ca232 | |||
3a203c3018 | |||
3e86ee9746 | |||
![]() |
d0855e1fc6 | ||
0bde8a24e4 | |||
![]() |
4049ff5167 | ||
![]() |
89389ccc13 | ||
959e308578 | |||
8d4cbd8556 | |||
99b9c93636 | |||
47c666c582 | |||
a3c330fd9d | |||
a6f3478db3 | |||
b1af22a15e | |||
65ec41c48f | |||
aebf16add7 | |||
1de0563c94 | |||
![]() |
c6059b9d85 | ||
![]() |
f68c9242a9 | ||
51838ba051 | |||
cf50b7eac1 | |||
2edd84e088 | |||
5ba1e458c6 | |||
0b600669c7 | |||
![]() |
215eb14d38 | ||
20ff2f5bdc | |||
edc02e5efa | |||
d57356f807 | |||
a7a6c75ac5 | |||
b9e2def552 | |||
4d7417401c | |||
40a9e959e8 | |||
42bbb4a9ff | |||
9500a55532 | |||
0556f15068 | |||
6e981874a5 | |||
8886d61caa | |||
b6fe81a982 | |||
e7cdb965ba | |||
4fa4357bf1 | |||
46307c60d4 | |||
e6a0c2f4f0 | |||
cb41bf6023 | |||
d23c8a8331 | |||
b1beb46ca2 | |||
![]() |
6606c83cb2 | ||
5998f5c9bd | |||
cffcbe5f94 | |||
0c75725f5e | |||
5ef325408c | |||
d957712e88 | |||
9233c2ed4c | |||
7ad54eb749 | |||
5740e20c62 | |||
0a9114fc2f | |||
71ddfa42e1 | |||
1ecb84f3fb | |||
c46b06fb81 | |||
a7397879aa | |||
9b86da7ce5 | |||
c68977ca8c | |||
bc7bd628db | |||
bc7ab39131 | |||
100163e03b | |||
49c02f13b2 | |||
40f072e25e | |||
918b1acb8f | |||
bc16078e3f | |||
4a8dbd0634 | |||
38c0184f95 | |||
68829ddb99 | |||
19089bd6a7 | |||
69cc0afd1b | |||
d2f3a262db | |||
![]() |
c1703b872d | ||
![]() |
f7b0e8181b | ||
43f2695901 | |||
![]() |
ea0d4ffd7f | ||
![]() |
0ed29573a7 | ||
1365d3e3b9 | |||
40befb5a92 | |||
9c16eec361 | |||
093c69637d | |||
134ec7acea | |||
![]() |
0802a5ae40 | ||
![]() |
14ddfb360f | ||
![]() |
78ad831028 | ||
22f1684262 | |||
83f51b4a66 | |||
9c28de0704 | |||
c69fda2298 | |||
cabb22331b | |||
![]() |
5b3aca29a8 | ||
![]() |
f35cbf59d8 | ||
![]() |
a96119d09b | ||
![]() |
2711294aee | ||
![]() |
f838b1828b | ||
![]() |
fbf6282c1a | ||
![]() |
5195a5c1fc | ||
![]() |
62b18b18b5 | ||
![]() |
7c8f519b35 | ||
![]() |
d6fb979616 | ||
![]() |
f568d21969 | ||
![]() |
7fe9d590f5 | ||
![]() |
0a1240ebfd | ||
![]() |
4e68f2a157 | ||
![]() |
e9bd6907d1 | ||
![]() |
b20495c39f | ||
![]() |
0ecbc4c27b | ||
![]() |
4d5b1d4c3f | ||
![]() |
17b719f3f7 | ||
![]() |
979a3e98d8 | ||
![]() |
c6a1112f0a | ||
![]() |
4ebcc95d9f | ||
![]() |
7e59ce27fa | ||
![]() |
22345a264e | ||
![]() |
03739996da | ||
![]() |
819a72d4f6 | ||
![]() |
e480931e20 | ||
![]() |
3f01d0a69e | ||
![]() |
f67f47f0cd | ||
![]() |
5ad6ee60eb | ||
![]() |
5accba6cdc | ||
![]() |
313e1704df | ||
![]() |
cf4d2b17c9 | ||
![]() |
9145eedc35 | ||
![]() |
b772179077 | ||
![]() |
9054a196ce | ||
![]() |
d28a96ac7d | ||
![]() |
9c73f80ac3 | ||
![]() |
20c46cff04 | ||
![]() |
f332513755 | ||
![]() |
cb69a66498 | ||
![]() |
1c66543938 | ||
![]() |
53ab3c472e | ||
![]() |
a4b221fa71 | ||
![]() |
e3e1d0842d | ||
![]() |
99b5bf9609 | ||
![]() |
da10fe0df7 | ||
![]() |
9fd5ba7b2d | ||
![]() |
05b5df9d76 | ||
![]() |
5c1dc79767 | ||
![]() |
4acd2996c5 | ||
![]() |
16fa6a89bc | ||
![]() |
2a72e8574b | ||
![]() |
d4a1bcf28f | ||
![]() |
409b71def5 | ||
![]() |
2dc5fbda02 | ||
![]() |
71aaf03d09 | ||
![]() |
30c99e630b | ||
![]() |
445b39171a | ||
![]() |
571c2d6047 | ||
![]() |
42ff763933 | ||
![]() |
727edc3ff9 | ||
![]() |
82a4758a0a | ||
![]() |
915914ebb3 | ||
![]() |
c438b56378 | ||
![]() |
307ccfb1b4 | ||
![]() |
34e23259b4 | ||
![]() |
036802d66a | ||
![]() |
6a7dbc8e3a | ||
![]() |
cf4a9ffc27 | ||
![]() |
6ef4adf318 | ||
![]() |
f84c9bf3b1 | ||
![]() |
da0837bd58 | ||
![]() |
9094a62273 | ||
![]() |
026183a655 | ||
![]() |
b033c7945c | ||
![]() |
0c2dcf0845 | ||
![]() |
b6e597e5bf | ||
![]() |
ae402baa71 | ||
![]() |
d6c8a4d9eb | ||
![]() |
8e2849b7e5 | ||
![]() |
0aa0cd330f | ||
![]() |
0960cafaf5 | ||
![]() |
2088a28053 | ||
![]() |
a5c4ba3bff | ||
![]() |
1bbd2cf52e | ||
![]() |
ce50efa60c | ||
![]() |
a3c64a9ba3 | ||
![]() |
1447164a8a | ||
![]() |
bc0bf8d7ff | ||
![]() |
9f346f488e | ||
![]() |
760d7d9704 | ||
![]() |
77310e17d1 | ||
![]() |
e54b964929 | ||
![]() |
809f3e847b | ||
![]() |
f4beebe60d | ||
![]() |
827e427f0b | ||
![]() |
c02125511d | ||
![]() |
1aa1069b6f | ||
![]() |
91d281077d | ||
![]() |
f339dec024 | ||
![]() |
2aeef44f8d | ||
![]() |
0fd41a9490 | ||
![]() |
58f10d14b2 | ||
![]() |
46ca42ddf8 | ||
![]() |
e6e6d6f4ee | ||
![]() |
8a87df605b | ||
![]() |
8ca085bceb | ||
![]() |
df47587db0 | ||
![]() |
d705e0ad04 | ||
![]() |
40d209dd7c | ||
![]() |
7f2a0457bf | ||
![]() |
f4749f2483 | ||
![]() |
9c42830076 | ||
![]() |
53ba6c2a64 | ||
![]() |
61b3f21f69 | ||
![]() |
506fd5f889 | ||
![]() |
d538f888b4 | ||
![]() |
976c5fdd47 | ||
![]() |
b63f3437f2 | ||
![]() |
e760f2e538 | ||
![]() |
17c8fca779 | ||
![]() |
87fda382c3 | ||
![]() |
1e404cd7ac | ||
![]() |
098f99d806 | ||
![]() |
da93f96035 | ||
![]() |
ead39cc87e | ||
![]() |
e4e3c44459 | ||
![]() |
af151e32e5 | ||
![]() |
12819a2a17 | ||
![]() |
87eedff254 | ||
![]() |
4c59cd7621 | ||
![]() |
ef707e7956 | ||
![]() |
73cf3fb299 | ||
![]() |
80b0c97d72 | ||
![]() |
5cf85c1d7b | ||
![]() |
c14e52ceb5 | ||
![]() |
32a579e480 | ||
![]() |
0a240a4436 | ||
![]() |
9325b806e4 | ||
![]() |
ef2e24ea11 | ||
![]() |
373934c6e0 | ||
![]() |
e8e8bac694 | ||
![]() |
23e8a558c2 | ||
![]() |
46f2645834 | ||
![]() |
2329439034 | ||
![]() |
6d400368b9 | ||
![]() |
26c13b40fe | ||
![]() |
9fd0e95fe8 | ||
![]() |
7e21f2c92b | ||
![]() |
c9d8e796c6 | ||
![]() |
e7203f5d46 | ||
![]() |
22d76a1b64 | ||
![]() |
0903dc46c6 | ||
![]() |
0f56ec8078 | ||
![]() |
70ee1df2bf | ||
![]() |
61a6a29bec | ||
![]() |
678f7d8f72 | ||
![]() |
b92ee364bc | ||
![]() |
aef19fcd38 | ||
![]() |
3b01df1d2c | ||
![]() |
4aed23b198 | ||
![]() |
03e8875c27 | ||
![]() |
48921a0875 | ||
![]() |
633fabb09e | ||
![]() |
bc42c26d94 | ||
![]() |
3c09ca3359 | ||
![]() |
1e9e7dd345 | ||
![]() |
034803add7 | ||
![]() |
b25bb053bb | ||
![]() |
9bd0c79441 | ||
![]() |
06b8670410 | ||
![]() |
6577ae499f | ||
![]() |
54bc5485ec | ||
![]() |
84b741ac98 | ||
![]() |
c48c419d74 | ||
![]() |
fb2e795add | ||
![]() |
ec215777ec | ||
![]() |
d4e0f27c56 | ||
![]() |
e1c686baa6 | ||
![]() |
d57af1aef4 | ||
![]() |
a52dd57215 | ||
![]() |
65138357d3 | ||
![]() |
f6320696dd | ||
![]() |
900d8a2026 | ||
![]() |
ccc9a87e8c | ||
![]() |
208634e5de | ||
![]() |
3d07205c9d | ||
![]() |
f0a424a93f | ||
![]() |
f9b59ee07d | ||
![]() |
b92b9d2618 | ||
![]() |
a3db9429a7 | ||
![]() |
291a5c9578 | ||
![]() |
0a3281c279 | ||
![]() |
23f30ba576 | ||
![]() |
f3de85c4de | ||
![]() |
a3a4888e0b | ||
![]() |
6fd7881f8e | ||
![]() |
381f716769 | ||
![]() |
f2078e1523 | ||
![]() |
f2fb87c88b | ||
![]() |
fcbea19478 | ||
![]() |
92f25bd4fa | ||
![]() |
85c2c11217 | ||
![]() |
de1ca4aea4 | ||
![]() |
a0f865fb99 | ||
![]() |
2c3fea5605 | ||
![]() |
ba1d88b5c9 | ||
![]() |
2ad715c668 | ||
![]() |
5f17557e54 | ||
![]() |
2ad5a6f907 | ||
![]() |
0920462060 | ||
![]() |
870e94e184 | ||
![]() |
6b0d507644 | ||
![]() |
70cf9e4ca7 | ||
![]() |
2a3974c71d | ||
![]() |
46ac9292e8 | ||
![]() |
4307097472 | ||
![]() |
ed3fdaf4f1 | ||
![]() |
378a9a8f5c | ||
![]() |
4ef6180455 | ||
![]() |
d4970e23c0 | ||
![]() |
0c9f165016 | ||
![]() |
be3a899ecb | ||
![]() |
7a6a749004 | ||
![]() |
17271ee3f0 | ||
![]() |
99bcfa90df | ||
![]() |
eb36e993c1 | ||
![]() |
e5eca5fa45 | ||
![]() |
8cba2f4236 | ||
![]() |
40d5ed31ac | ||
![]() |
181275fe35 | ||
![]() |
23d8c01ce7 | ||
![]() |
de83944486 | ||
![]() |
90cd7ff23a | ||
![]() |
8d0a9b4ccd | ||
![]() |
230d4cd23f | ||
![]() |
e9b6fcc0a4 | ||
![]() |
8fcb871409 | ||
![]() |
83bef43fd5 | ||
![]() |
b4fc6ca31b | ||
![]() |
ab3f1b708d | ||
![]() |
c76402a160 | ||
![]() |
a50c73aa5e | ||
![]() |
5aa66795d2 |
@@ -17,6 +17,10 @@
|
||||
# Contains the addresses from your master address book
|
||||
# and your subscribed address books. (../userhosts.txt)
|
||||
#
|
||||
# private_addressbook The path to the private address book used by the router.
|
||||
# This is used only by the router and SusiDNS.
|
||||
# It is not published by addressbook. (../privatehosts.txt)
|
||||
#
|
||||
# published_addressbook The path to the copy of your address book made
|
||||
# available on i2p. (../eepsite/docroot/hosts.txt)
|
||||
#
|
||||
@@ -35,6 +39,7 @@ proxy_host=localhost
|
||||
proxy_port=4444
|
||||
master_addressbook=myhosts.txt
|
||||
router_addressbook=../userhosts.txt
|
||||
private_addressbook=../privatehosts.txt
|
||||
published_addressbook=../eepsite/docroot/hosts.txt
|
||||
log=log.txt
|
||||
subscriptions=subscriptions.txt
|
||||
|
@@ -21,11 +21,11 @@
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
@@ -66,6 +66,7 @@ public class AddressBook {
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
/* unused
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
@@ -79,22 +80,26 @@ public class AddressBook {
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
* address book at subscription has not changed since the last time it was
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
* Set a maximum size of the remote book to make it a little harder for a malicious book-sender.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
this.location = subscription.getLocation();
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp",
|
||||
subscription.getLocation(), true, subscription.getEtag());
|
||||
get.fetch();
|
||||
subscription.setEtag(get.getETag());
|
||||
proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, "addressbook.tmp", null,
|
||||
subscription.getLocation(), true, subscription.getEtag(), subscription.getLastModified(), null);
|
||||
if (get.fetch()) {
|
||||
subscription.setEtag(get.getETag());
|
||||
subscription.setLastModified(get.getLastModified());
|
||||
}
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
@@ -151,6 +156,37 @@ public class AddressBook {
|
||||
return this.addresses.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do basic validation of the hostname and dest
|
||||
* hostname was already converted to lower case by ConfigParser.parse()
|
||||
*/
|
||||
private static boolean valid(String host, String dest) {
|
||||
return
|
||||
host.endsWith(".i2p") &&
|
||||
host.length() > 4 &&
|
||||
host.length() <= 67 && // 63 + ".i2p"
|
||||
(! host.startsWith(".")) &&
|
||||
(! host.startsWith("-")) &&
|
||||
host.indexOf(".-") < 0 &&
|
||||
host.indexOf("-.") < 0 &&
|
||||
host.indexOf("..") < 0 &&
|
||||
// IDN - basic check, not complete validation
|
||||
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
// some reserved names that may be used for local configuration someday
|
||||
(! host.equals("proxy.i2p")) &&
|
||||
(! host.equals("router.i2p")) &&
|
||||
(! host.equals("console.i2p")) &&
|
||||
(! host.endsWith(".proxy.i2p")) &&
|
||||
(! host.endsWith(".router.i2p")) &&
|
||||
(! host.endsWith(".console.i2p")) &&
|
||||
|
||||
dest.length() == 516 &&
|
||||
dest.endsWith("AAAA") &&
|
||||
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with AddressBook other, writing messages about new
|
||||
* addresses or conflicts to log. Addresses in AddressBook other that are
|
||||
@@ -169,7 +205,7 @@ public class AddressBook {
|
||||
String otherKey = (String) otherIter.next();
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
|
||||
if (valid(otherKey, otherValue)) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
@@ -184,7 +220,7 @@ public class AddressBook {
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
+ " added to address book. From: " + other.location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,12 +21,19 @@
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
@@ -60,6 +67,7 @@ public class ConfigParser {
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
* The key is converted to lower case.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
@@ -77,7 +85,7 @@ public class ConfigParser {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim(), splitLine[1].trim());
|
||||
result.put(splitLine[0].trim().toLowerCase(), splitLine[1].trim());
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
@@ -301,4 +309,4 @@ public class ConfigParser {
|
||||
new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -21,12 +21,12 @@
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
@@ -98,7 +98,8 @@ public class Daemon {
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
|
||||
List defaultSubs = new LinkedList();
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
defaultSubs.add("http://www.i2p2.i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
|
@@ -22,10 +22,10 @@
|
||||
package addressbook;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
/**
|
||||
* A wrapper for addressbook to allow it to be started as a web application.
|
||||
|
@@ -21,13 +21,13 @@
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A list of Subscriptions loaded from a file.
|
||||
|
@@ -10,16 +10,14 @@
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.log4j.DailyRollingFileAppender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
|
||||
import org.jibble.pircbot.IrcException;
|
||||
import org.jibble.pircbot.NickAlreadyInUseException;
|
||||
import org.jibble.pircbot.PircBot;
|
||||
|
@@ -14,7 +14,7 @@
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
classpath="../../../core/java/build/i2p.jar:../../../router/java/build/router.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class">
|
||||
|
@@ -20,9 +20,6 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
@@ -32,6 +29,7 @@ public class BitField
|
||||
|
||||
private final byte[] bitfield;
|
||||
private final int size;
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> unset bits.
|
||||
@@ -41,6 +39,7 @@ public class BitField
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
bitfield = new byte[arraysize];
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +59,11 @@ public class BitField
|
||||
// XXX - More correct would be to check that unused bits are
|
||||
// cleared or clear them explicitly ourselves.
|
||||
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
|
||||
|
||||
this.count = 0;
|
||||
for (int i = 0; i < size; i++)
|
||||
if (get(i))
|
||||
this.count++;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +99,10 @@ public class BitField
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
bitfield[index] |= mask;
|
||||
if ((bitfield[index] & mask) == 0) {
|
||||
count++;
|
||||
bitfield[index] |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,6 +121,22 @@ public class BitField
|
||||
return (bitfield[index] & mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of set bits.
|
||||
*/
|
||||
public int count()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all bits are set.
|
||||
*/
|
||||
public boolean complete()
|
||||
{
|
||||
return count >= size;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
// Not very efficient
|
||||
@@ -129,4 +152,5 @@ public class BitField
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,8 +20,10 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
|
@@ -1,20 +1,28 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
*/
|
||||
@@ -33,6 +41,8 @@ public class I2PSnarkUtil {
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
private Set _shitlist;
|
||||
private int _maxUploaders;
|
||||
private int _maxUpBW;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
@@ -42,6 +52,7 @@ public class I2PSnarkUtil {
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_shitlist = new HashSet(64);
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,17 +83,29 @@ public class I2PSnarkUtil {
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setMaxUploaders(int limit) {
|
||||
_maxUploaders = limit;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setMaxUpBW(int limit) {
|
||||
_maxUpBW = limit;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
public int getMaxUploaders() { return _maxUploaders; }
|
||||
public int getMaxUpBW() { return _maxUpBW; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
public boolean connect() {
|
||||
synchronized public boolean connect() {
|
||||
if (_manager == null) {
|
||||
Properties opts = new Properties();
|
||||
if (_opts != null) {
|
||||
@@ -93,10 +116,16 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
if (opts.getProperty("inbound.nickname") == null)
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("outbound.nickname") == null)
|
||||
opts.setProperty("outbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "2"); // 1 == disconnect, 2 == ping
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "1"); // 1 == disconnect, 2 == ping
|
||||
if (opts.getProperty("i2p.streaming.initialWindowSize") == null)
|
||||
opts.setProperty("i2p.streaming.initialWindowSize", "1");
|
||||
if (opts.getProperty("i2p.streaming.slowStartGrowthRateFactor") == null)
|
||||
opts.setProperty("i2p.streaming.slowStartGrowthRateFactor", "1");
|
||||
//if (opts.getProperty("i2p.streaming.writeTimeout") == null)
|
||||
// opts.setProperty("i2p.streaming.writeTimeout", "90000");
|
||||
//if (opts.getProperty("i2p.streaming.readTimeout") == null)
|
||||
@@ -146,8 +175,10 @@ public class I2PSnarkUtil {
|
||||
/**
|
||||
* fetch the given URL, returning the file it is stored in, or null on error
|
||||
*/
|
||||
public File get(String url) { return get(url, true); }
|
||||
public File get(String url, boolean rewrite) {
|
||||
public File get(String url) { return get(url, true, 0); }
|
||||
public File get(String url, boolean rewrite) { return get(url, rewrite, 0); }
|
||||
public File get(String url, int retries) { return get(url, true, retries); }
|
||||
public File get(String url, boolean rewrite, int retries) {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
@@ -161,7 +192,7 @@ public class I2PSnarkUtil {
|
||||
if (rewrite)
|
||||
fetchURL = rewriteAnnounce(url);
|
||||
//_log.debug("Rewritten url [" + fetchURL + "]");
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), fetchURL);
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, retries, out.getAbsolutePath(), fetchURL);
|
||||
if (get.fetch()) {
|
||||
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
|
||||
return out;
|
||||
@@ -181,6 +212,8 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
if (_manager == null)
|
||||
return "unknown";
|
||||
I2PSession sess = _manager.getSession();
|
||||
if (sess != null) {
|
||||
Destination dest = sess.getMyDestination();
|
||||
@@ -192,15 +225,15 @@ public class I2PSnarkUtil {
|
||||
Destination getDestination(String ip) {
|
||||
if (ip == null) return null;
|
||||
if (ip.endsWith(".i2p")) {
|
||||
Destination dest = _context.namingService().lookup(ip);
|
||||
if (dest != null) {
|
||||
return dest;
|
||||
} else {
|
||||
try {
|
||||
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
if (ip.length() < 520) { // key + ".i2p"
|
||||
Destination dest = _context.namingService().lookup(ip);
|
||||
if (dest != null)
|
||||
return dest;
|
||||
}
|
||||
try {
|
||||
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -211,12 +244,22 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public String lookup(String name) {
|
||||
Destination dest = getDestination(name);
|
||||
if (dest == null)
|
||||
return null;
|
||||
return dest.toBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given http://blah.i2p/foo/announce turn it into http://i2p/blah/foo/announce
|
||||
* Given http://KEY.i2p/foo/announce turn it into http://i2p/KEY/foo/announce
|
||||
* Given http://tracker.blah.i2p/foo/announce leave it alone
|
||||
*/
|
||||
String rewriteAnnounce(String origAnnounce) {
|
||||
int destStart = "http://".length();
|
||||
int destEnd = origAnnounce.indexOf(".i2p");
|
||||
if (destEnd < destStart + 516)
|
||||
return origAnnounce;
|
||||
int pathStart = origAnnounce.indexOf('/', destEnd);
|
||||
String rv = "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||
//_log.debug("Rewriting [" + origAnnounce + "] as [" + rv + "]");
|
||||
|
@@ -22,20 +22,22 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
import org.klomp.snark.bencode.BEncoder;
|
||||
import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
|
||||
/**
|
||||
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
|
||||
|
@@ -20,15 +20,15 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class Peer implements Comparable
|
||||
@@ -54,6 +54,10 @@ public class Peer implements Comparable
|
||||
private boolean deregister = true;
|
||||
private static long __id;
|
||||
private long _id;
|
||||
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
|
||||
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
@@ -110,6 +114,14 @@ public class Peer implements Comparable
|
||||
return "[unknown id] " + _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns socket (for debug printing)
|
||||
*/
|
||||
public String getSocket()
|
||||
{
|
||||
return sock.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a Peer is the hash code of the peerID.
|
||||
*/
|
||||
@@ -318,6 +330,14 @@ public class Peer implements Comparable
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
// try to save partial piece
|
||||
if (this.deregister) {
|
||||
PeerListener p = s.listener;
|
||||
if (p != null) {
|
||||
p.savePeerPartial(s);
|
||||
p.markUnrequested(this);
|
||||
}
|
||||
}
|
||||
state = null;
|
||||
|
||||
PeerConnectionIn in = s.in;
|
||||
@@ -449,13 +469,105 @@ public class Peer implements Comparable
|
||||
public long getInactiveTime() {
|
||||
PeerState s = state;
|
||||
if (s != null) {
|
||||
PeerConnectionIn in = s.in;
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
return System.currentTimeMillis() - out.lastSent;
|
||||
else
|
||||
if (in != null && out != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
return Math.max(now - out.lastSent, now - in.lastRcvd);
|
||||
} else
|
||||
return -1; //"state, no out";
|
||||
} else {
|
||||
return -1; //"no state";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send keepalive
|
||||
*/
|
||||
public void keepAlive()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.keepAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retransmit outstanding requests if necessary
|
||||
*/
|
||||
public void retransmitRequests()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.retransmitRequests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return how much the peer has
|
||||
*/
|
||||
public int completed()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s == null || s.bitfield == null)
|
||||
return 0;
|
||||
return s.bitfield.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a peer is a seeder
|
||||
*/
|
||||
public boolean isCompleted()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s == null || s.bitfield == null)
|
||||
return false;
|
||||
return s.bitfield.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++){
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,8 +20,12 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
|
@@ -20,7 +20,11 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* TimerTask that checks for good/bad up/downloader. Works together
|
||||
@@ -37,10 +41,23 @@ class PeerCheckerTask extends TimerTask
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
public void run()
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
if ((!it.hasNext()) || coordinator.halted()) {
|
||||
coordinator.peerCount = 0;
|
||||
coordinator.interestedAndChoking = 0;
|
||||
coordinator.setRateHistory(0, 0);
|
||||
coordinator.uploaders = 0;
|
||||
if (coordinator.halted())
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate total uploading and worst downloader.
|
||||
long worstdownload = Long.MAX_VALUE;
|
||||
Peer worstDownloader = null;
|
||||
@@ -48,10 +65,7 @@ class PeerCheckerTask extends TimerTask
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
int removedCount = 0;
|
||||
|
||||
long uploaded = 0;
|
||||
long downloaded = 0;
|
||||
@@ -59,8 +73,8 @@ class PeerCheckerTask extends TimerTask
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List removed = new ArrayList();
|
||||
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
@@ -80,48 +94,43 @@ class PeerCheckerTask extends TimerTask
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
|
||||
// XXX - We should calculate the up/download rate a bit
|
||||
// more intelligently
|
||||
long upload = peer.getUploaded();
|
||||
uploaded += upload;
|
||||
long download = peer.getDownloaded();
|
||||
downloaded += download;
|
||||
peer.setRateHistory(upload, download);
|
||||
peer.resetCounters();
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
{
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
}
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
|
||||
// Choke half of them rather than all so it isn't so drastic...
|
||||
// unless this torrent is over the limit all by itself.
|
||||
boolean overBWLimitChoke = upload > 0 &&
|
||||
((overBWLimit && random.nextBoolean()) ||
|
||||
(coordinator.overUpBWLimit(uploaded)));
|
||||
|
||||
// If we are at our max uploaders and we have lots of other
|
||||
// interested peers try to make some room.
|
||||
// (Note use of coordinator.uploaders)
|
||||
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
if (((coordinator.uploaders == uploadLimit
|
||||
&& coordinator.interestedAndChoking > 0)
|
||||
|| coordinator.uploaders > uploadLimit
|
||||
|| overBWLimitChoke)
|
||||
&& !peer.isChoking())
|
||||
{
|
||||
// Check if it still wants pieces from us.
|
||||
if (!peer.isInterested())
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
@@ -130,14 +139,40 @@ class PeerCheckerTask extends TimerTask
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isChoked())
|
||||
else if (overBWLimitChoke)
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
Snark.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list for fairness, even though we won't be unchoking this time
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting() && peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isInteresting() && !coordinator.completed())
|
||||
{
|
||||
// If they aren't interesting make someone else a downloader
|
||||
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
@@ -148,41 +183,51 @@ class PeerCheckerTask extends TimerTask
|
||||
&& download == 0)
|
||||
{
|
||||
// We are downloading but didn't receive anything...
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isChoking() && download < worstdownload)
|
||||
else if (peer.isInteresting() && !peer.isChoked() &&
|
||||
download < worstdownload)
|
||||
{
|
||||
// Make sure download is good if we are uploading
|
||||
worstdownload = download;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
else if (upload < worstdownload && coordinator.completed())
|
||||
{
|
||||
// Make sure upload is good if we are seeding
|
||||
worstdownload = upload;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
}
|
||||
peer.retransmitRequests();
|
||||
peer.keepAlive();
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
// (can shift a bit by disconnecting peers)
|
||||
coordinator.uploaders = uploaders;
|
||||
|
||||
// Remove the worst downloader if needed.
|
||||
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
// Remove the worst downloader if needed. (uploader if seeding)
|
||||
if (((uploaders == uploadLimit
|
||||
&& coordinator.interestedAndChoking > 0)
|
||||
|| uploaders > uploadLimit)
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
@@ -191,14 +236,17 @@ class PeerCheckerTask extends TimerTask
|
||||
}
|
||||
|
||||
// Optimistically unchoke a peer
|
||||
coordinator.unchokePeer();
|
||||
if ((!overBWLimit) && !coordinator.overUpBWLimit(uploaded))
|
||||
coordinator.unchokePeer();
|
||||
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
coordinator.interestedAndChoking += removedCount;
|
||||
|
||||
// store the rates
|
||||
coordinator.setRateHistory(uploaded, downloaded);
|
||||
|
||||
}
|
||||
if (coordinator.halted()) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,9 +20,8 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -33,12 +32,15 @@ class PeerConnectionIn implements Runnable
|
||||
private final DataInputStream din;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
private volatile boolean quit;
|
||||
|
||||
long lastRcvd;
|
||||
|
||||
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.din = din;
|
||||
lastRcvd = System.currentTimeMillis();
|
||||
quit = false;
|
||||
}
|
||||
|
||||
@@ -51,6 +53,13 @@ class PeerConnectionIn implements Runnable
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
if (din != null) {
|
||||
try {
|
||||
din.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream from " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run()
|
||||
@@ -68,8 +77,12 @@ class PeerConnectionIn implements Runnable
|
||||
|
||||
// Wait till we hear something...
|
||||
// The length of a complete message in bytes.
|
||||
// The biggest is the piece message, for which the length is the
|
||||
// request size (32K) plus 9. (we could also check if Storage.MAX_PIECES / 8
|
||||
// in the bitfield message is bigger but it's currently 5000/8 = 625 so don't bother)
|
||||
int i = din.readInt();
|
||||
if (i < 0)
|
||||
lastRcvd = System.currentTimeMillis();
|
||||
if (i < 0 || i > PeerState.PARTSIZE + 9)
|
||||
throw new IOException("Unexpected length prefix: " + i);
|
||||
|
||||
if (i == 0)
|
||||
|
@@ -20,9 +20,11 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@@ -72,6 +74,16 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
boolean shouldFlush;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
shouldFlush = !quit && peer.isConnected() && sendQueue.isEmpty();
|
||||
}
|
||||
if (shouldFlush)
|
||||
// Make sure everything will reach the other side.
|
||||
// flush while not holding lock, could take a long time
|
||||
dout.flush();
|
||||
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && peer.isConnected() && sendQueue.isEmpty())
|
||||
@@ -79,7 +91,8 @@ class PeerConnectionOut implements Runnable
|
||||
try
|
||||
{
|
||||
// Make sure everything will reach the other side.
|
||||
dout.flush();
|
||||
// don't flush while holding lock, could take a long time
|
||||
// dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait(60*1000);
|
||||
@@ -197,10 +210,12 @@ class PeerConnectionOut implements Runnable
|
||||
/**
|
||||
* Adds a message to the sendQueue and notifies the method waiting
|
||||
* on the sendQueue to change.
|
||||
* If a PIECE message only, add a timeout.
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
if (m.type == Message.PIECE)
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
@@ -259,7 +274,13 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.KEEP_ALIVE;
|
||||
addMessage(m);
|
||||
// addMessage(m);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void sendChoke(boolean choke)
|
||||
@@ -318,6 +339,23 @@ class PeerConnectionOut implements Runnable
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
|
||||
void retransmitRequests(List requests)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if(now > req.sendTime + REQ_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Retransmit request " + req + " to peer " + peer);
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
@@ -330,12 +368,47 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendRequest(Request req)
|
||||
{
|
||||
// Check for duplicate requests to deal with fibrillating i2p-bt
|
||||
// (multiple choke/unchokes received cause duplicate requests in the queue)
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST && m.piece == req.piece &&
|
||||
m.begin == req.off && m.length == req.len)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Discarding duplicate request " + req + " to peer " + peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
req.sendTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// Used by PeerState to limit pipelined requests
|
||||
int queuedBytes()
|
||||
{
|
||||
int total = 0;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.PIECE)
|
||||
total += m.length;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
@@ -346,7 +419,7 @@ class PeerConnectionOut implements Runnable
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.data = bytes;
|
||||
m.off = begin;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
@@ -20,8 +20,14 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@@ -37,19 +43,23 @@ public class PeerCoordinator implements PeerListener
|
||||
final Snark snark;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
final static int MAX_CONNECTIONS = 24;
|
||||
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||
final static long CHECK_PERIOD = 40*1000; // 40 seconds
|
||||
final static int MAX_CONNECTIONS = 16;
|
||||
final static int MAX_UPLOADERS = 6;
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
// Resynced by PeerChecker once in a while.
|
||||
int uploaders = 0;
|
||||
int interestedAndChoking = 0;
|
||||
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
@@ -62,7 +72,7 @@ public class PeerCoordinator implements PeerListener
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private final List wantedPieces;
|
||||
private List wantedPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
@@ -80,6 +90,18 @@ public class PeerCoordinator implements PeerListener
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
setWantedPieces();
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
// this will help the behavior with global limits
|
||||
Random r = new Random();
|
||||
timer.schedule(new PeerCheckerTask(this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
@@ -87,14 +109,20 @@ public class PeerCoordinator implements PeerListener
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
// for web page detailed stats
|
||||
public List peerList()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return new ArrayList(peers);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
@@ -123,7 +151,7 @@ public class PeerCoordinator implements PeerListener
|
||||
public long getLeft()
|
||||
{
|
||||
// XXX - Only an approximation.
|
||||
return storage.needed() * metainfo.getPieceLength(0);
|
||||
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +170,62 @@ public class PeerCoordinator implements PeerListener
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private static void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getCurrentUploadRate()
|
||||
{
|
||||
// no need to synchronize, only one value
|
||||
long r = uploaded_old[0];
|
||||
if (r <= 0)
|
||||
return 0;
|
||||
return (r * 1000) / CHECK_PERIOD;
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++) {
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
@@ -177,6 +261,8 @@ public class PeerCoordinator implements PeerListener
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
// delete any saved orphan partial piece
|
||||
savedRequest = null;
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
@@ -191,8 +277,10 @@ public class PeerCoordinator implements PeerListener
|
||||
synchronized(peers)
|
||||
{
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
|
||||
// idle for 2 minutes, kill the old con
|
||||
if ( (old != null) && (old.getInactiveTime() > 8*60*1000) ) {
|
||||
// idle for 8 minutes, kill the old con (32KB/8min = 68B/sec minimum for one block)
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
peers.remove(old);
|
||||
toDisconnect = old;
|
||||
old = null;
|
||||
@@ -201,8 +289,17 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
// toDisconnect = peer to get out of synchronized(peers)
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
// This is already checked in addPeer() but we could have gone over the limit since then
|
||||
else if (peers.size() >= MAX_CONNECTIONS)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already at MAX_CONNECTIONS in connected() with peer: " + peer);
|
||||
// toDisconnect = peer to get out of synchronized(peers)
|
||||
peer.disconnect(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -224,6 +321,7 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
// caller must synchronize on peers
|
||||
private static Peer peerIDInList(PeerID pid, List peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
@@ -235,18 +333,26 @@ public class PeerCoordinator implements PeerListener
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
// returns true if actual attempt to add peer occurs
|
||||
public boolean addPeer(final Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean need_more;
|
||||
int peersize = 0;
|
||||
synchronized(peers)
|
||||
{
|
||||
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
|
||||
peersize = peers.size();
|
||||
// This isn't a strict limit, as we may have several pending connections;
|
||||
// thus there is an additional check in connected()
|
||||
need_more = (!peer.isConnected()) && peersize < MAX_CONNECTIONS;
|
||||
// Check if we already have this peer before we build the connection
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
|
||||
}
|
||||
|
||||
if (need_more)
|
||||
@@ -265,15 +371,16 @@ public class PeerCoordinator implements PeerListener
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new I2PThread(r, threadName).start();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (peer.isConnected())
|
||||
_log.info("Add peer already connected: " + peer);
|
||||
else
|
||||
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (peer.isConnected())
|
||||
_log.info("Add peer already connected: " + peer);
|
||||
else
|
||||
_log.info("Connections: " + peersize + "/" + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -285,34 +392,40 @@ public class PeerCoordinator implements PeerListener
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
synchronized (peers) {
|
||||
int count = 0;
|
||||
int unchokedCount = 0;
|
||||
int maxUploaders = allowedUploaders();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
if (peer.isChoking() && peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
count++;
|
||||
if (uploaders < maxUploaders)
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(unchokedCount++, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
while (uploaders < maxUploaders && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
count--;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
}
|
||||
interestedAndChoking = count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,9 +464,10 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
if (bitfield.get(i)) {
|
||||
p.addPeer(peer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -392,6 +506,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
@@ -403,9 +519,17 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
+ " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
|
||||
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here - limit # of parallel attempts and
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
// This is where the flaws of the snark data model are really exposed.
|
||||
// Could also randomize within the duplicate set rather than strict rarest-first
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
}
|
||||
}
|
||||
piece.setRequested(true);
|
||||
@@ -417,14 +541,14 @@ public class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*/
|
||||
public byte[] gotRequest(Peer peer, int piece)
|
||||
public byte[] gotRequest(Peer peer, int piece, int off, int len)
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return storage.getPiece(piece);
|
||||
return storage.getPiece(piece, off, len);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -504,14 +628,27 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
|
||||
// Announce to the world we have it!
|
||||
// Disconnect from other seeders when we get the last piece
|
||||
synchronized(peers)
|
||||
{
|
||||
List toDisconnect = new ArrayList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.isConnected())
|
||||
p.have(piece);
|
||||
{
|
||||
if (completed() && p.isCompleted())
|
||||
toDisconnect.add(p);
|
||||
else
|
||||
p.have(piece);
|
||||
}
|
||||
}
|
||||
it = toDisconnect.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
p.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +670,7 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
if (uploaders < allowedUploaders())
|
||||
{
|
||||
if(peer.isChoking())
|
||||
{
|
||||
@@ -582,4 +719,151 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simple method to save a partial piece on peer disconnection
|
||||
* and hopefully restart it later.
|
||||
* Only one partial piece is saved at a time.
|
||||
* Replace it if a new one is bigger or the old one is too old.
|
||||
* Storage method is private so we can expand to save multiple partials
|
||||
* if we wish.
|
||||
*/
|
||||
private Request savedRequest = null;
|
||||
private long savedRequestTime = 0;
|
||||
public void savePeerPartial(PeerState state)
|
||||
{
|
||||
if (halted)
|
||||
return;
|
||||
Request req = state.getPartialRequest();
|
||||
if (req == null)
|
||||
return;
|
||||
if (savedRequest == null ||
|
||||
req.off > savedRequest.off ||
|
||||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
|
||||
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(" Saving orphaned partial piece " + req);
|
||||
if (savedRequest != null)
|
||||
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
|
||||
}
|
||||
}
|
||||
savedRequest = req;
|
||||
savedRequestTime = System.currentTimeMillis();
|
||||
} else {
|
||||
if (req.piece != savedRequest.piece)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(" Discarding orphaned partial piece " + req);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return partial piece if it's still wanted and peer has it.
|
||||
*/
|
||||
public Request getPeerPartial(BitField havePieces) {
|
||||
if (savedRequest == null)
|
||||
return null;
|
||||
if (! havePieces.get(savedRequest.piece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
|
||||
return null;
|
||||
}
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
if (piece.getId() == savedRequest.piece) {
|
||||
Request req = savedRequest;
|
||||
piece.setRequested(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Restoring orphaned partial piece " + req);
|
||||
savedRequest = null;
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We no longer want orphaned piece " + savedRequest);
|
||||
savedRequest = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Clear the requested flag for a piece if the peer
|
||||
** is the only one requesting it
|
||||
*/
|
||||
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
|
||||
{
|
||||
// see if anybody else is requesting
|
||||
synchronized (peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.equals(peer))
|
||||
continue;
|
||||
if (p.state == null)
|
||||
continue;
|
||||
int[] arr = p.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
if(arr[i] == piece) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Another peer is requesting piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nobody is, so mark unrequested
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext()) {
|
||||
Piece p = (Piece)it.next();
|
||||
if (p.getId() == piece) {
|
||||
p.setRequested(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing from request list piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
** Once for each piece
|
||||
** This is enough trouble, maybe would be easier just to regenerate
|
||||
** the requested list from scratch instead.
|
||||
*/
|
||||
public void markUnrequested(Peer peer)
|
||||
{
|
||||
if (halted || peer.state == null)
|
||||
return;
|
||||
int[] arr = peer.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
markUnrequestedIfOnlyOne(peer, arr[i]);
|
||||
}
|
||||
|
||||
/** Return number of allowed uploaders for this torrent.
|
||||
** Check with Snark to see if we are over the total upload limit.
|
||||
*/
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (Snark.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
} else if (uploaders < MAX_UPLOADERS)
|
||||
return uploaders + 1;
|
||||
else
|
||||
return MAX_UPLOADERS;
|
||||
}
|
||||
|
||||
public boolean overUpBWLimit()
|
||||
{
|
||||
return Snark.overUpBWLimit();
|
||||
}
|
||||
|
||||
public boolean overUpBWLimit(long total)
|
||||
{
|
||||
return Snark.overUpBWLimit(total * 1000 / CHECK_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Hmm, any guesses as to what this is? Used by the multitorrent functionality
|
||||
|
@@ -21,15 +21,15 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
|
||||
public class PeerID implements Comparable
|
||||
{
|
||||
@@ -167,7 +167,9 @@ public class PeerID implements Comparable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String "id@address" where id is the base64 encoded id.
|
||||
* Returns the String "id@address" where id is the base64 encoded id
|
||||
* and address is the base64 dest (was the base64 hash of the dest) which
|
||||
* should match what the bytemonsoon tracker reports on its web pages.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
@@ -178,7 +180,7 @@ public class PeerID implements Comparable
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.calculateHash().toBase64().substring(0,6);
|
||||
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.toBase64().substring(0,6);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -107,11 +107,13 @@ public interface PeerListener
|
||||
*
|
||||
* @param peer the Peer that wants the piece.
|
||||
* @param piece the piece number requested.
|
||||
* @param off byte offset into the piece.
|
||||
* @param len length of the chunk requested.
|
||||
*
|
||||
* @return a byte array containing the piece or null when the piece
|
||||
* is not available (which is a protocol error).
|
||||
*/
|
||||
byte[] gotRequest(Peer peer, int piece);
|
||||
byte[] gotRequest(Peer peer, int piece, int off, int len);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been downloaded from the peer.
|
||||
@@ -142,4 +144,29 @@ public interface PeerListener
|
||||
* we are no longer interested in the peer.
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when the peer has disconnected and the peer task may have a partially
|
||||
* downloaded piece that the PeerCoordinator can save
|
||||
*
|
||||
* @param state the PeerState for the peer
|
||||
*/
|
||||
void savePeerPartial(PeerState state);
|
||||
|
||||
/**
|
||||
* Called when a peer has connected and there may be a partially
|
||||
* downloaded piece that the coordinatorator can give the peer task
|
||||
*
|
||||
* @param havePieces the have-pieces bitmask for the peer
|
||||
*
|
||||
* @return request (contains the partial data and valid length)
|
||||
*/
|
||||
Request getPeerPartial(BitField havePieces);
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
* This prevents premature end game
|
||||
*
|
||||
* @param peer the peer that is disconnecting
|
||||
*/
|
||||
void markUnrequested(Peer peer);
|
||||
}
|
||||
|
@@ -20,7 +20,8 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* TimerTask that monitors the peers and total up/download speeds.
|
||||
|
@@ -23,8 +23,6 @@ package org.klomp.snark;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -62,8 +60,10 @@ class PeerState
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 1;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
private final static int MAX_PIPELINE = 2; // this is for outbound requests
|
||||
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
|
||||
public final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
|
||||
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
PeerConnectionIn in, PeerConnectionOut out)
|
||||
@@ -173,7 +173,7 @@ class PeerState
|
||||
|| begin < 0
|
||||
|| begin > metainfo.getPieceLength(piece)
|
||||
|| length <= 0
|
||||
|| length > 4*PARTSIZE)
|
||||
|| length > MAX_PARTSIZE)
|
||||
{
|
||||
// XXX - Protocol error -> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -184,7 +184,16 @@ class PeerState
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||
// Limit total pipelined requests to MAX_PIPELINE bytes
|
||||
// to conserve memory and prevent DOS
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
@@ -194,7 +203,7 @@ class PeerState
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
if (length != pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -221,6 +230,10 @@ class PeerState
|
||||
listener.uploaded(peer, size);
|
||||
}
|
||||
|
||||
// This is used to flag that we have to back up from the firstOutstandingRequest
|
||||
// when calculating how far we've gotten
|
||||
Request pendingRequest = null;
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* PeerConnectionIn.
|
||||
@@ -231,6 +244,8 @@ class PeerState
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
pendingRequest = null;
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
@@ -318,14 +333,8 @@ class PeerState
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
// We used to rerequest the missing chunks but that mostly
|
||||
// just confuses the other side. So now we just keep
|
||||
// waiting for them. They will be rerequested when we get
|
||||
// choked/unchoked again.
|
||||
/*
|
||||
if (!choked)
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("dropped " + dropReq + " with peer " + peer);
|
||||
}
|
||||
@@ -336,10 +345,58 @@ class PeerState
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
pendingRequest = req;
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
// get longest partial piece
|
||||
Request getPartialRequest()
|
||||
{
|
||||
Request req = null;
|
||||
for (int i = 0; i < outstandingRequests.size(); i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
int j = getFirstOutstandingRequest(r1.piece);
|
||||
if (j == -1)
|
||||
continue;
|
||||
Request r2 = (Request)outstandingRequests.get(j);
|
||||
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
|
||||
req = r2;
|
||||
}
|
||||
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
|
||||
if (pendingRequest.off != 0)
|
||||
req = pendingRequest;
|
||||
else
|
||||
req = null;
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
// return array of pieces terminated by -1
|
||||
// remove most duplicates
|
||||
// but still could be some duplicates, not guaranteed
|
||||
int[] getRequestedPieces()
|
||||
{
|
||||
int size = outstandingRequests.size();
|
||||
int[] arr = new int[size+2];
|
||||
int pc = -1;
|
||||
int pos = 0;
|
||||
if (pendingRequest != null) {
|
||||
pc = pendingRequest.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
Request req = null;
|
||||
for (int i = 0; i < size; i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
if (pc != r1.piece) {
|
||||
pc = r1.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
}
|
||||
arr[pos] = -1;
|
||||
return(arr);
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -414,16 +471,12 @@ class PeerState
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
*/
|
||||
private void addRequest()
|
||||
synchronized private void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
}
|
||||
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
more_pieces = requestNextPiece();
|
||||
@@ -431,19 +484,14 @@ class PeerState
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
synchronized(this)
|
||||
{
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
}
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
|
||||
// Last part of a piece?
|
||||
if (isLastChunk)
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
@@ -456,7 +504,6 @@ class PeerState
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -472,16 +519,41 @@ class PeerState
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
// Check for adopting an orphaned partial piece
|
||||
Request r = listener.getPeerPartial(bitfield);
|
||||
if (r != null) {
|
||||
// Check that r not already in outstandingRequests
|
||||
int[] arr = getRequestedPieces();
|
||||
boolean found = false;
|
||||
for (int i = 0; arr[i] >= 0; i++) {
|
||||
if (arr[i] == r.piece) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
outstandingRequests.add(r);
|
||||
if (!choked)
|
||||
out.sendRequest(r);
|
||||
lastRequest = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
byte[] bs = new byte[piece_length];
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[piece_length];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = Math.min(piece_length, PARTSIZE);
|
||||
Request req = new Request(nextPiece, bs, 0, length);
|
||||
@@ -491,7 +563,6 @@ class PeerState
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -523,4 +594,15 @@ class PeerState
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
|
||||
void keepAlive()
|
||||
{
|
||||
out.sendAlive();
|
||||
}
|
||||
|
||||
synchronized void retransmitRequests()
|
||||
{
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
|
||||
|
@@ -29,6 +29,7 @@ class Request
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
long sendTime;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
|
@@ -20,17 +20,26 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
*
|
||||
@@ -63,7 +72,7 @@ public class Snark
|
||||
/**
|
||||
* What level of debug info to show.
|
||||
*/
|
||||
public static int debug = NOTICE;
|
||||
//public static int debug = NOTICE;
|
||||
|
||||
// Whether or not to ask the user for commands while sharing
|
||||
private static boolean command_interpreter = true;
|
||||
@@ -234,6 +243,7 @@ public class Snark
|
||||
public String rootDataDir = ".";
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
byte[] id;
|
||||
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
@@ -268,7 +278,7 @@ public class Snark
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] id = new byte[20];
|
||||
id = new byte[20];
|
||||
Random random = new Random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
@@ -283,6 +293,11 @@ public class Snark
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
/*
|
||||
* Don't start a tunnel if the torrent isn't going to be started.
|
||||
* If we are starting,
|
||||
* startTorrent() will force a connect.
|
||||
*
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
@@ -292,6 +307,7 @@ public class Snark
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
*/
|
||||
|
||||
// Figure out what the torrent argument represents.
|
||||
meta = null;
|
||||
@@ -305,7 +321,7 @@ public class Snark
|
||||
else
|
||||
{
|
||||
activity = "Getting torrent";
|
||||
File torrentFile = I2PSnarkUtil.instance().get(torrent);
|
||||
File torrentFile = I2PSnarkUtil.instance().get(torrent, 3);
|
||||
if (torrentFile == null) {
|
||||
fatal("Unable to fetch " + torrent);
|
||||
if (false) return; // never reached - fatal(..) throws
|
||||
@@ -361,6 +377,9 @@ public class Snark
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check(rootDataDir);
|
||||
// have to figure out when to reopen
|
||||
// if (!start)
|
||||
// storage.close();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -371,14 +390,19 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* see comment above
|
||||
*
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
*/
|
||||
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
@@ -386,6 +410,26 @@ public class Snark
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
if (coordinator == null) {
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else {
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, this, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
}
|
||||
|
||||
stopped = false;
|
||||
boolean coordinatorChanged = false;
|
||||
if (coordinator.halted()) {
|
||||
@@ -402,6 +446,17 @@ public class Snark
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
try
|
||||
{
|
||||
storage.reopen(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
@@ -422,6 +477,8 @@ public class Snark
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (st != null) {
|
||||
if (storage.changed)
|
||||
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
@@ -457,6 +514,7 @@ public class Snark
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
/*
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
debug = INFO;
|
||||
@@ -477,7 +535,7 @@ 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");
|
||||
@@ -693,6 +751,11 @@ public class Snark
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
@@ -703,4 +766,43 @@ public class Snark
|
||||
public interface CompleteListener {
|
||||
public void torrentComplete(Snark snark);
|
||||
}
|
||||
|
||||
/** Maintain a configurable total uploader cap
|
||||
*/
|
||||
final static int MIN_TOTAL_UPLOADERS = 4;
|
||||
final static int MAX_TOTAL_UPLOADERS = 10;
|
||||
public static boolean overUploadLimit(int uploaders) {
|
||||
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
|
||||
if (coordinators == null || uploaders <= 0)
|
||||
return false;
|
||||
int totalUploaders = 0;
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = (PeerCoordinator)iter.next();
|
||||
if (!c.halted())
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
|
||||
public static boolean overUpBWLimit() {
|
||||
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
|
||||
if (coordinators == null)
|
||||
return false;
|
||||
long total = 0;
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = (PeerCoordinator)iter.next();
|
||||
if (!c.halted())
|
||||
total += c.getCurrentUploadRate();
|
||||
}
|
||||
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
|
||||
Snark.debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
|
||||
return total > limit;
|
||||
}
|
||||
|
||||
public static boolean overUpBWLimit(long total) {
|
||||
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
|
||||
return total > limit;
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,24 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -16,6 +31,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private Object _addSnarkLock;
|
||||
private String _configFile;
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
@@ -27,22 +43,36 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
|
||||
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
|
||||
public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
||||
public static final String DEFAULT_USE_OPENTRACKERS = "true";
|
||||
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
|
||||
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
||||
|
||||
public static final int MIN_UP_BW = 2;
|
||||
public static final int DEFAULT_MAX_UP_BW = 10;
|
||||
|
||||
private SnarkManager() {
|
||||
_snarks = new HashMap();
|
||||
_addSnarkLock = new Object();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
if (_context instanceof RouterContext)
|
||||
((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
|
||||
}
|
||||
|
||||
private static final int MAX_MESSAGES = 5;
|
||||
@@ -66,7 +96,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
|
||||
}
|
||||
private int getStartupDelayMinutes() { return 1; }
|
||||
public boolean shouldUseOpenTrackers() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_USE_OPENTRACKERS, DEFAULT_USE_OPENTRACKERS)).booleanValue();
|
||||
}
|
||||
private int getStartupDelayMinutes() { return 3; }
|
||||
public File getDataDir() {
|
||||
String dir = _config.getProperty(PROP_DIR);
|
||||
if ( (dir == null) || (dir.trim().length() <= 0) )
|
||||
@@ -91,10 +124,24 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_config.setProperty(PROP_I2CP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_I2CP_PORT))
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_I2CP_OPTS))
|
||||
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=1 inbound.lengthVariance=1 outbound.length=1 outbound.lengthVariance=1");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
_config.setProperty(PROP_EEP_PORT, "4444");
|
||||
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
|
||||
if (!_config.containsKey(PROP_UPBW_MAX)) {
|
||||
try {
|
||||
if (_context instanceof RouterContext)
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + (((RouterContext)_context).bandwidthLimiter().getOutboundKBytesPerSecond() / 2));
|
||||
else
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
|
||||
} catch (NoClassDefFoundError ncdfe) {
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + DEFAULT_MAX_UP_BW);
|
||||
}
|
||||
}
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, "i2psnark");
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
@@ -125,6 +172,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
if (eepHost != null)
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
|
||||
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
|
||||
I2PSnarkUtil.instance().setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
@@ -140,7 +189,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
|
||||
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
int port = I2PSnarkUtil.instance().getEepProxyPort();
|
||||
@@ -155,6 +205,34 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
}
|
||||
}
|
||||
if (upLimit != null) {
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != I2PSnarkUtil.instance().getMaxUploaders()) {
|
||||
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
|
||||
I2PSnarkUtil.instance().setMaxUploaders(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
|
||||
addMessage("Total uploaders limit changed to " + limit);
|
||||
} else {
|
||||
addMessage("Minimum total uploaders limit is " + Snark.MIN_TOTAL_UPLOADERS);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (upBW != null) {
|
||||
int limit = I2PSnarkUtil.instance().getMaxUpBW();
|
||||
try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != I2PSnarkUtil.instance().getMaxUpBW()) {
|
||||
if ( limit >= MIN_UP_BW ) {
|
||||
I2PSnarkUtil.instance().setMaxUpBW(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + limit);
|
||||
addMessage("Up BW limit changed to " + limit + "KBps");
|
||||
} else {
|
||||
addMessage("Minimum Up BW limit is " + MIN_UP_BW + "KBps");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
|
||||
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
|
||||
@@ -239,6 +317,18 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
addMessage("Adjusted autostart to " + autoStart);
|
||||
changed = true;
|
||||
}
|
||||
if (shouldUseOpenTrackers() != useOpenTrackers) {
|
||||
_config.setProperty(PROP_USE_OPENTRACKERS, useOpenTrackers + "");
|
||||
addMessage((useOpenTrackers ? "En" : "Dis") + "abled open trackers - torrent restart required to take effect");
|
||||
changed = true;
|
||||
}
|
||||
if (openTrackers != null) {
|
||||
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(getOpenTrackerString())) {
|
||||
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
|
||||
addMessage("Open Tracker list changed - torrent restart required to take effect");
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
saveConfig();
|
||||
} else {
|
||||
@@ -248,7 +338,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
public void saveConfig() {
|
||||
try {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
synchronized (_configFile) {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Unable to save the config to '" + _configFile + "'");
|
||||
}
|
||||
@@ -267,7 +359,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
@@ -287,7 +379,16 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
if (torrent == null) {
|
||||
}
|
||||
// don't hold the _snarks lock while verifying the torrent
|
||||
if (torrent == null) {
|
||||
synchronized (_addSnarkLock) {
|
||||
// double-check
|
||||
synchronized (_snarks) {
|
||||
if(_snarks.get(filename) != null)
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
@@ -303,7 +404,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
_snarks.put(filename, torrent);
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
|
||||
@@ -313,9 +416,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
File f = new File(filename);
|
||||
@@ -327,14 +430,104 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for a torrent from the config file
|
||||
*/
|
||||
public long getSavedTorrentTime(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
if (time == null)
|
||||
return 0;
|
||||
int comma = time.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return 0;
|
||||
time = time.substring(0, comma);
|
||||
try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved bitfield for a torrent from the config file.
|
||||
* Convert "." to a full bitfield.
|
||||
*/
|
||||
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
if (bf == null)
|
||||
return null;
|
||||
int comma = bf.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return null;
|
||||
bf = bf.substring(comma + 1).trim();
|
||||
int len = metainfo.getPieces();
|
||||
if (bf.equals(".")) {
|
||||
BitField bitfield = new BitField(len);
|
||||
for (int i = 0; i < len; i++)
|
||||
bitfield.set(i);
|
||||
return bitfield;
|
||||
}
|
||||
byte[] bitfield = Base64.decode(bf);
|
||||
if (bitfield == null)
|
||||
return null;
|
||||
if (bitfield.length * 8 < len)
|
||||
return null;
|
||||
return new BitField(bitfield, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the completion status of a torrent and the current time in the config file
|
||||
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
|
||||
* The config file property key is appended with the Base64 of the infohash,
|
||||
* with the '=' changed to '$' since a key can't contain '='.
|
||||
* The time is a standard long converted to string.
|
||||
* The status is either a bitfield converted to Base64 or "." for a completed
|
||||
* torrent to save space in the config file and in memory.
|
||||
*/
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String now = "" + System.currentTimeMillis();
|
||||
String bfs;
|
||||
if (bitfield.complete()) {
|
||||
bfs = ".";
|
||||
} else {
|
||||
byte[] bf = bitfield.getFieldBytes();
|
||||
bfs = Base64.encode(bf);
|
||||
}
|
||||
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the status of a torrent from the config file.
|
||||
* This may help the config file from growing too big.
|
||||
*/
|
||||
public void removeTorrentStatus(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
private String locked_validateTorrent(MetaInfo info) throws IOException {
|
||||
String announce = info.getAnnounce();
|
||||
// basic validation of url
|
||||
if ((!announce.startsWith("http://")) ||
|
||||
(announce.indexOf(".i2p/") < 0))
|
||||
return "Non-i2p tracker in " + info.getName() + ", deleting it";
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
|
||||
} else if (info.getPieces() <= 0) {
|
||||
return "No pieces in " + info.getName() + "? deleting it";
|
||||
} else if (info.getPieceLength(0) > 10*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
|
||||
} else if (info.getPieceLength(0) > 1*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB), deleting it";
|
||||
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
List lengths = info.getLengths();
|
||||
@@ -392,6 +585,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (torrent != null) {
|
||||
File torrentFile = new File(filename);
|
||||
torrentFile.delete();
|
||||
if (torrent.storage != null)
|
||||
removeTorrentStatus(torrent.storage.getMetaInfo());
|
||||
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
|
||||
}
|
||||
}
|
||||
@@ -444,10 +639,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connect())
|
||||
addTorrent((String)foundNames.get(i));
|
||||
else
|
||||
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
|
||||
addMessage("Unable to connect to I2P");
|
||||
addTorrent((String)foundNames.get(i), !shouldAutoStart());
|
||||
}
|
||||
}
|
||||
// now lets see which ones have been removed...
|
||||
@@ -463,22 +657,32 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman's tracker", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php"
|
||||
, "Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php"
|
||||
"Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/"
|
||||
// , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/"
|
||||
// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/"
|
||||
, "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/"
|
||||
// , "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/"
|
||||
// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/"
|
||||
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
, "welterde", "http://BGKmlDOoH3RzFbPRfRpZV2FjpVj8~3moFftw5-dZfDf2070TOe8Tf2~DAVeaM6ZRLdmFEt~9wyFL8YMLMoLoiwGEH6IGW6rc45tstN68KsBDWZqkTohV1q9XFgK9JnCwE~Oi89xLBHsLMTHOabowWM6dkC8nI6QqJC2JODqLPIRfOVrDdkjLwtCrsckzLybNdFmgfoqF05UITDyczPsFVaHtpF1sRggOVmdvCM66otyonlzNcJbn59PA-R808vUrCPMGU~O9Wys0i-NoqtIbtWfOKnjCRFMNw5ex4n9m5Sxm9e20UkpKG6qzEuvKZWi8vTLe1NW~CBrj~vG7I3Ok4wybUFflBFOaBabxYJLlx4xTE1zJIVxlsekmAjckB4v-cQwulFeikR4LxPQ6mCQknW2HZ4JQIq6hL9AMabxjOlYnzh7kjOfRGkck8YgeozcyTvcDUcUsOuSTk06L4kdrv8h2Cozjbloi5zl6KTbj5ZTciKCxi73Pn9grICn-HQqEAAAA.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
|
||||
, "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
|
||||
, "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
|
||||
};
|
||||
|
||||
/** comma delimited list of name=announceURL for the trackers to be displayed */
|
||||
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
|
||||
public static final String PROP_TRACKERS = "i2psnark.trackers";
|
||||
/** unordered map of announceURL to name */
|
||||
private static Map trackerMap = null;
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public Map getTrackers() {
|
||||
HashMap rv = new HashMap();
|
||||
if (trackerMap != null) // only do this once, can't be updated while running
|
||||
return trackerMap;
|
||||
Map rv = new TreeMap();
|
||||
String trackers = _config.getProperty(PROP_TRACKERS);
|
||||
if ( (trackers == null) || (trackers.trim().length() <= 0) )
|
||||
trackers = _context.getProperty(PROP_TRACKERS);
|
||||
if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
|
||||
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2)
|
||||
rv.put(DEFAULT_TRACKERS[i+1], DEFAULT_TRACKERS[i]);
|
||||
rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
@@ -489,10 +693,31 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String name = pair.substring(0, split).trim();
|
||||
String url = pair.substring(split+1).trim();
|
||||
if ( (name.length() > 0) && (url.length() > 0) )
|
||||
rv.put(url, name);
|
||||
rv.put(name, url);
|
||||
}
|
||||
}
|
||||
|
||||
trackerMap = rv;
|
||||
return trackerMap;
|
||||
}
|
||||
|
||||
public String getOpenTrackerString() {
|
||||
return _config.getProperty(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
|
||||
}
|
||||
|
||||
/** comma delimited list open trackers to use as backups */
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public List getOpenTrackers() {
|
||||
if (!shouldUseOpenTrackers())
|
||||
return null;
|
||||
List rv = new ArrayList(1);
|
||||
String trackers = getOpenTrackerString();
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
|
||||
if (rv.size() <= 0)
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -503,4 +728,15 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
return (name != null) && (name.endsWith(".torrent"));
|
||||
}
|
||||
}
|
||||
|
||||
public class SnarkManagerShutdown extends I2PThread {
|
||||
public void run() {
|
||||
Set names = listTorrentFiles();
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
Snark snark = getTorrent((String)iter.next());
|
||||
if ( (snark != null) && (!snark.stopped) )
|
||||
snark.stopTorrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,10 +20,6 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
/**
|
||||
* Main snark startup class for staticly linking with gcj.
|
||||
|
@@ -20,10 +20,13 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
@@ -39,15 +42,17 @@ public class Storage
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield; // BitField to represent the pieces
|
||||
private BitField bitfield; // BitField to represent the pieces
|
||||
private int needed; // Number of pieces needed
|
||||
|
||||
// XXX - Not always set correctly
|
||||
int piece_size;
|
||||
int pieces;
|
||||
boolean changed;
|
||||
|
||||
/** The default piece size. */
|
||||
private static int MIN_PIECE_SIZE = 256*1024;
|
||||
private static int MAX_PIECE_SIZE = 1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
private static long MAX_PIECES = 100*1024/20;
|
||||
|
||||
@@ -75,7 +80,6 @@ public class Storage
|
||||
throws IOException
|
||||
{
|
||||
this.listener = listener;
|
||||
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
|
||||
@@ -90,7 +94,7 @@ public class Storage
|
||||
|
||||
piece_size = MIN_PIECE_SIZE;
|
||||
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||
while (pieces > MAX_PIECES)
|
||||
while (pieces > MAX_PIECES && piece_size < MAX_PIECE_SIZE)
|
||||
{
|
||||
piece_size = piece_size*2;
|
||||
pieces = (int) ((total - 1)/piece_size) +1;
|
||||
@@ -116,7 +120,8 @@ public class Storage
|
||||
}
|
||||
|
||||
String name = baseFile.getName();
|
||||
if (files.size() == 1)
|
||||
if (files.size() == 1) // FIXME: ...and if base file not a directory or should this be the only check?
|
||||
// this makes a bad metainfo if the directory has only one file in it
|
||||
{
|
||||
files = null;
|
||||
lengthsList = null;
|
||||
@@ -131,13 +136,14 @@ public class Storage
|
||||
// Creates piece hases for a new storage.
|
||||
public void create() throws IOException
|
||||
{
|
||||
if (true) {
|
||||
// if (true) {
|
||||
fast_digestCreate();
|
||||
} else {
|
||||
orig_digestCreate();
|
||||
}
|
||||
// } else {
|
||||
// orig_digestCreate();
|
||||
// }
|
||||
}
|
||||
|
||||
/*
|
||||
private void orig_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
MessageDigest digest = null;
|
||||
@@ -155,7 +161,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -173,6 +179,7 @@ public class Storage
|
||||
// Reannounce to force recalculating the info_hash.
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
*/
|
||||
|
||||
private void fast_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
@@ -183,7 +190,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -218,6 +225,8 @@ public class Storage
|
||||
{
|
||||
File f = (File)it.next();
|
||||
names[i] = f.getPath();
|
||||
if (base.isDirectory() && names[i].startsWith(base.getPath()))
|
||||
names[i] = names[i].substring(base.getPath().length() + 1);
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
i++;
|
||||
@@ -281,6 +290,10 @@ public class Storage
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
// look for saved bitfield and timestamp in the config file
|
||||
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
|
||||
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
@@ -294,7 +307,12 @@ public class Storage
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (base.exists() && ((useSavedBitField && savedBitField.complete()) || !base.canWrite()))
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
@@ -318,7 +336,12 @@ public class Storage
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
total += lengths[i];
|
||||
if (f.exists() && !f.canWrite()) // see above re: only seeding
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (f.exists() && ((useSavedBitField && savedBitField.complete()) || !f.canWrite()))
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
@@ -331,13 +354,63 @@ public class Storage
|
||||
throw new IOException("File lengths do not add up "
|
||||
+ total + " != " + metalength);
|
||||
}
|
||||
checkCreateFiles();
|
||||
if (useSavedBitField) {
|
||||
bitfield = savedBitField;
|
||||
needed = metainfo.getPieces() - bitfield.count();
|
||||
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
|
||||
} else {
|
||||
checkCreateFiles();
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen the file descriptors for a restart
|
||||
* Do existence check but no length check or data reverification
|
||||
*/
|
||||
public void reopen(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Reopen base as file.
|
||||
Snark.debug("Reopening file: " + base, Snark.NOTICE);
|
||||
if (!base.exists())
|
||||
throw new IOException("Could not reopen file " + base);
|
||||
|
||||
if (complete() || !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reopen base as dir.
|
||||
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
|
||||
if (!base.isDirectory())
|
||||
throw new IOException("Could not reopen directory " + base);
|
||||
|
||||
int size = files.size();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = getFileFromNames(base, (List)files.get(i));
|
||||
if (!f.exists())
|
||||
throw new IOException("Could not reopen file " + f);
|
||||
if (complete() || !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
*/
|
||||
private String filterName(String name)
|
||||
private static String filterName(String name)
|
||||
{
|
||||
// XXX - Is this enough?
|
||||
return name.replace(File.separatorChar, '_');
|
||||
@@ -369,6 +442,17 @@ public class Storage
|
||||
return f;
|
||||
}
|
||||
|
||||
public static File getFileFromNames(File base, List names)
|
||||
{
|
||||
Iterator it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName((String)it.next());
|
||||
base = new File(base, name);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private void checkCreateFiles() throws IOException
|
||||
{
|
||||
// Whether we are resuming or not,
|
||||
@@ -387,9 +471,10 @@ public class Storage
|
||||
}
|
||||
else if (length == 0)
|
||||
allocateFile(i);
|
||||
else
|
||||
throw new IOException("File '" + names[i]
|
||||
+ "' exists, but has wrong length");
|
||||
else {
|
||||
Snark.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Check which pieces match and which don't
|
||||
@@ -399,7 +484,7 @@ public class Storage
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
if (correctHash)
|
||||
{
|
||||
@@ -459,19 +544,27 @@ public class Storage
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
changed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null if
|
||||
* Returns a byte array containing a portion of the requested piece or null if
|
||||
* the storage doesn't contain the piece yet.
|
||||
*/
|
||||
public byte[] getPiece(int piece) throws IOException
|
||||
public byte[] getPiece(int piece, int off, int len) throws IOException
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
return null;
|
||||
|
||||
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||
getUncheckedPiece(piece, bs, 0);
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[len];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
|
||||
return null;
|
||||
}
|
||||
getUncheckedPiece(piece, bs, off, len);
|
||||
return bs;
|
||||
}
|
||||
|
||||
@@ -482,10 +575,11 @@ public class Storage
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||
public boolean putPiece(int piece, byte[] ba) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// If we were paranoia we could copy the array first.
|
||||
// Copy the array first to be paranoid.
|
||||
byte[] bs = (byte[]) ba.clone();
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
@@ -504,6 +598,7 @@ public class Storage
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
@@ -537,24 +632,48 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
changed = true;
|
||||
if (complete) {
|
||||
listener.storageCompleted(this);
|
||||
// listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
|
||||
// Do a complete check to be sure.
|
||||
// Temporarily resets the 'needed' variable and 'bitfield', then call
|
||||
// checkCreateFiles() which will set 'needed' and 'bitfield'
|
||||
// and also call listener.storageCompleted() if the double-check
|
||||
// was successful.
|
||||
// Todo: set a listener variable so the web shows "checking" and don't
|
||||
// have the user panic when completed amount goes to zero temporarily?
|
||||
needed = metainfo.getPieces();
|
||||
bitfield = new BitField(needed);
|
||||
checkCreateFiles();
|
||||
if (needed > 0) {
|
||||
listener.setWantedPieces(this);
|
||||
Snark.debug("WARNING: Not really done, missing " + needed
|
||||
+ " pieces", Snark.WARNING);
|
||||
} else {
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||
private int getUncheckedPiece(int piece, byte[] bs)
|
||||
throws IOException
|
||||
{
|
||||
return getUncheckedPiece(piece, bs, 0, metainfo.getPieceLength(piece));
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off, int length)
|
||||
throws IOException
|
||||
{
|
||||
// XXX - copy/paste code from putPiece().
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) metainfo.getPieceLength(0);
|
||||
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
|
||||
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
@@ -572,7 +691,7 @@ public class Storage
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, off + read, len);
|
||||
rafs[i].readFully(bs, read, len);
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
|
@@ -55,4 +55,11 @@ public interface StorageListener
|
||||
*
|
||||
*/
|
||||
void storageCompleted(Storage storage);
|
||||
|
||||
/** Reset the peer's wanted pieces table
|
||||
* Call after the storage double-check fails
|
||||
*
|
||||
* @param peer the peer
|
||||
*/
|
||||
void setWantedPieces(Storage storage);
|
||||
}
|
||||
|
@@ -20,11 +20,17 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -41,8 +47,15 @@ public class TrackerClient extends I2PThread
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
private final static int DELAY_MIN = 2000; // 2 secs.
|
||||
private final static int DELAY_MUL = 1500; // 1.5 secs.
|
||||
private final static int MAX_REGISTER_FAILS = 10; // * INITIAL_SLEEP = 15m to register
|
||||
private final static int INITIAL_SLEEP = 90*1000;
|
||||
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
|
||||
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
|
||||
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
@@ -51,8 +64,7 @@ public class TrackerClient extends I2PThread
|
||||
private boolean stop;
|
||||
private boolean started;
|
||||
|
||||
private long interval;
|
||||
private long lastRequestTime;
|
||||
private List trackers;
|
||||
|
||||
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
|
||||
{
|
||||
@@ -97,79 +109,83 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
public void run()
|
||||
{
|
||||
// XXX - Support other IPs
|
||||
String announce = meta.getAnnounce(); //I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||
String infoHash = urlencode(meta.getInfoHash());
|
||||
String peerID = urlencode(coordinator.getID());
|
||||
|
||||
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash
|
||||
+ " xmitAnnounce: [" + announce + "]");
|
||||
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash);
|
||||
|
||||
// Construct the list of trackers for this torrent,
|
||||
// starting with the primary one listed in the metainfo,
|
||||
// followed by the secondary open trackers
|
||||
// It's painful, but try to make sure if an open tracker is also
|
||||
// the primary tracker, that we don't add it twice.
|
||||
trackers = new ArrayList(2);
|
||||
trackers.add(new Tracker(meta.getAnnounce(), true));
|
||||
List tlist = SnarkManager.instance().getOpenTrackers();
|
||||
if (tlist != null) {
|
||||
for (int i = 0; i < tlist.size(); i++) {
|
||||
String url = (String)tlist.get(i);
|
||||
if (!url.startsWith("http://")) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
int slash = url.indexOf('/', 7);
|
||||
if (slash <= 7) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
if (meta.getAnnounce().startsWith(url.substring(0, slash)))
|
||||
continue;
|
||||
String dest = I2PSnarkUtil.instance().lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
if (meta.getAnnounce().startsWith("http://" + dest))
|
||||
continue;
|
||||
if (meta.getAnnounce().startsWith("http://i2p/" + dest))
|
||||
continue;
|
||||
trackers.add(new Tracker(url, false));
|
||||
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
}
|
||||
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long left = coordinator.getLeft();
|
||||
|
||||
boolean completed = (left == 0);
|
||||
int sleptTime = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send start.
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
STARTED_EVENT);
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if (!completed) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
started = true;
|
||||
coordinator.trackerProblems = null;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
{
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
try
|
||||
{
|
||||
// Sleep one minutes...
|
||||
Thread.sleep(60*1000);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean firstTime = true;
|
||||
int consecutiveFails = 0;
|
||||
Random r = new Random();
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
Thread.sleep(delay);
|
||||
// Sleep the minimum interval for all the trackers, but 60s minimum
|
||||
// except for the first time...
|
||||
int delay;
|
||||
int random = r.nextInt(120*1000);
|
||||
if (firstTime) {
|
||||
delay = r.nextInt(30*1000);
|
||||
firstTime = false;
|
||||
} else if (completed && started)
|
||||
delay = 3*SLEEP*60*1000 + random;
|
||||
else if (coordinator.trackerProblems != null && ++consecutiveFails < MAX_CONSEC_FAILS)
|
||||
delay = INITIAL_SLEEP;
|
||||
else
|
||||
// sleep a while, when we wake up we will contact only the trackers whose intervals have passed
|
||||
delay = SLEEP*60*1000 + random;
|
||||
|
||||
if (delay > 0)
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
@@ -195,19 +211,37 @@ public class TrackerClient extends I2PThread
|
||||
else
|
||||
event = NO_EVENT;
|
||||
|
||||
// *** loop once for each tracker
|
||||
// Only do a request when necessary.
|
||||
if (event == COMPLETED_EVENT
|
||||
|| coordinator.needPeers()
|
||||
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||
sleptTime = 0;
|
||||
int maxSeenPeers = 0;
|
||||
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
|
||||
Tracker tr = (Tracker)iter.next();
|
||||
if ((!stop) && (!tr.stop) &&
|
||||
(completed || coordinator.needPeers()) &&
|
||||
(event == COMPLETED_EVENT || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
|
||||
{
|
||||
try
|
||||
{
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
if (!tr.started)
|
||||
event = STARTED_EVENT;
|
||||
TrackerInfo info = doRequest(tr, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
coordinator.trackerProblems = null;
|
||||
tr.trackerProblems = null;
|
||||
tr.registerFails = 0;
|
||||
tr.consecutiveFails = 0;
|
||||
if (tr.isPrimary)
|
||||
consecutiveFails = 0;
|
||||
started = true;
|
||||
tr.started = true;
|
||||
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
tr.seenPeers = peers.size();
|
||||
if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
|
||||
coordinator.trackerSeenPeers = tr.seenPeers;
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
@@ -216,10 +250,14 @@ public class TrackerClient extends I2PThread
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay += DELAY_MIN;
|
||||
sleptTime += delay;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,11 +266,38 @@ public class TrackerClient extends I2PThread
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
+ tr.announce + "': " + ioe, Snark.WARNING);
|
||||
tr.trackerProblems = ioe.getMessage();
|
||||
// don't show secondary tracker problems to the user
|
||||
if (tr.isPrimary)
|
||||
coordinator.trackerProblems = tr.trackerProblems;
|
||||
if (tr.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
|
||||
// Give a guy some time to register it if using opentrackers too
|
||||
if (trackers.size() == 1) {
|
||||
stop = true;
|
||||
coordinator.snark.stopTorrent();
|
||||
} else { // hopefully each on the opentrackers list is really open
|
||||
if (tr.registerFails++ > MAX_REGISTER_FAILS)
|
||||
tr.stop = true;
|
||||
}
|
||||
}
|
||||
if (++tr.consecutiveFails == MAX_CONSEC_FAILS) {
|
||||
tr.seenPeers = 0;
|
||||
if (tr.interval < LONG_SLEEP)
|
||||
tr.interval = LONG_SLEEP; // slow down
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((!tr.stop) && maxSeenPeers < tr.seenPeers)
|
||||
maxSeenPeers = tr.seenPeers;
|
||||
} // *** end of trackers loop here
|
||||
|
||||
// we could try and total the unique peers but that's too hard for now
|
||||
coordinator.trackerSeenPeers = maxSeenPeers;
|
||||
if (!started)
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
} // *** end of while loop
|
||||
} // try
|
||||
catch (Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
|
||||
@@ -243,21 +308,27 @@ public class TrackerClient extends I2PThread
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
|
||||
// try to contact everybody we can
|
||||
// We don't need I2CP connection for eepget
|
||||
// if (!verifyConnected()) return;
|
||||
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
|
||||
Tracker tr = (Tracker)iter.next();
|
||||
if (tr.started && (!tr.stop) && tr.trackerProblems == null)
|
||||
doRequest(tr, infoHash, peerID, uploaded,
|
||||
downloaded, left, STOPPED_EVENT);
|
||||
}
|
||||
}
|
||||
catch(IOException ioe) { /* ignored */ }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private TrackerInfo doRequest(String announce, String infoHash,
|
||||
private TrackerInfo doRequest(Tracker tr, String infoHash,
|
||||
String peerID, long uploaded,
|
||||
long downloaded, long left, String event)
|
||||
throws IOException
|
||||
{
|
||||
String s = announce
|
||||
String s = tr.announce
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
+ "&port=" + port
|
||||
@@ -266,13 +337,14 @@ public class TrackerClient extends I2PThread
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
tr.lastRequestTime = System.currentTimeMillis();
|
||||
File fetched = I2PSnarkUtil.instance().get(s);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
fetched.deleteOnExit();
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
@@ -280,15 +352,13 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
throw new IOException(failure);
|
||||
|
||||
interval = info.getInterval() * 1000;
|
||||
tr.interval = info.getInterval() * 1000;
|
||||
return info;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
@@ -300,7 +370,7 @@ public class TrackerClient extends I2PThread
|
||||
* Very lazy byte[] to URL encoder. Just encodes everything, even
|
||||
* "normal" chars.
|
||||
*/
|
||||
static String urlencode(byte[] bs)
|
||||
public static String urlencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*3);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
@@ -314,4 +384,32 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private class Tracker
|
||||
{
|
||||
String announce;
|
||||
boolean isPrimary;
|
||||
long interval;
|
||||
long lastRequestTime;
|
||||
String trackerProblems;
|
||||
boolean stop;
|
||||
boolean started;
|
||||
int registerFails;
|
||||
int consecutiveFails;
|
||||
int seenPeers;
|
||||
|
||||
public Tracker(String a, boolean p)
|
||||
{
|
||||
announce = a;
|
||||
isPrimary = p;
|
||||
interval = INITIAL_SLEEP;
|
||||
lastRequestTime = 0;
|
||||
trackerProblems = null;
|
||||
stop = false;
|
||||
started = false;
|
||||
registerFails = 0;
|
||||
consecutiveFails = 0;
|
||||
seenPeers = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -22,14 +22,15 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
|
||||
public class TrackerInfo
|
||||
{
|
||||
@@ -95,7 +96,14 @@ public class TrackerInfo
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
PeerID peerID;
|
||||
try {
|
||||
peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// don't let one bad entry spoil the whole list
|
||||
Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
|
||||
continue;
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
}
|
||||
|
||||
|
@@ -20,20 +20,16 @@
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Decodes a bencoded stream to <code>BEValue</code>s.
|
||||
|
@@ -21,7 +21,6 @@
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@@ -20,11 +20,9 @@
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@@ -1,12 +1,23 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
@@ -14,7 +25,14 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import org.klomp.snark.*;
|
||||
|
||||
import org.klomp.snark.I2PSnarkUtil;
|
||||
import org.klomp.snark.MetaInfo;
|
||||
import org.klomp.snark.Peer;
|
||||
import org.klomp.snark.Snark;
|
||||
import org.klomp.snark.SnarkManager;
|
||||
import org.klomp.snark.Storage;
|
||||
import org.klomp.snark.TrackerClient;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,22 +61,49 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
long stats[] = {0,0,0,0};
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
processRequest(req);
|
||||
|
||||
String peerParam = req.getParameter("p");
|
||||
String peerString;
|
||||
if (peerParam == null) {
|
||||
peerString = "";
|
||||
} else {
|
||||
peerString = "?p=" + peerParam;
|
||||
}
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.write(HEADER_BEGIN);
|
||||
// we want it to go to the base URI so we don't refresh with some funky action= value
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\n");
|
||||
out.write(HEADER);
|
||||
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("<tr><td width=\"20%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("I2PSnark<br />\n");
|
||||
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
|
||||
int count = 0;
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String baseURL = (String)trackers.get(name);
|
||||
int e = baseURL.indexOf('=');
|
||||
if (e < 0)
|
||||
continue;
|
||||
baseURL = baseURL.substring(e + 1);
|
||||
if (count++ % 2 == 0)
|
||||
out.write("<tr>");
|
||||
out.write("<td><a href=\"" + baseURL + "\" class=\"snarkRefresh\">" + name + "</a>\n");
|
||||
}
|
||||
if (count % 2 == 1)
|
||||
out.write("<td> \n");
|
||||
out.write("</table>\n");
|
||||
out.write("</td><td width=\"80%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
List msgs = _manager.getMessages();
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = (String)msgs.get(i);
|
||||
@@ -66,19 +111,46 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
out.write("</pre></td></tr></table>\n");
|
||||
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
out.write(TABLE_HEADER);
|
||||
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
|
||||
if (peerParam != null)
|
||||
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
|
||||
else
|
||||
out.write("(<a href=\"" + req.getRequestURI() + "?p=1" + "\">Show Peers</a>)<br />\n");
|
||||
}
|
||||
out.write(TABLE_HEADER2);
|
||||
out.write("<th align=\"left\" valign=\"top\">");
|
||||
if (I2PSnarkUtil.instance().connected())
|
||||
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
|
||||
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
|
||||
else if (snarks.size() > 0)
|
||||
out.write("<a href=\"" + uri + "?action=StartAll&nonce=" + _nonce +
|
||||
"\" title=\"Start all torrents and the i2p tunnel\">Start All</a>");
|
||||
else
|
||||
out.write(" ");
|
||||
out.write("</th></tr></thead>\n");
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
displaySnark(out, snark, uri, i);
|
||||
boolean showDebug = "2".equals(peerParam);
|
||||
boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
|
||||
displaySnark(out, snark, uri, i, stats, showPeers, showDebug);
|
||||
}
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
} else if (snarks.size() > 1) {
|
||||
out.write(TABLE_TOTAL);
|
||||
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</tfoot>\n");
|
||||
}
|
||||
|
||||
out.write(TABLE_FOOTER);
|
||||
|
||||
writeAddForm(out, req);
|
||||
if (true) // seeding needs to register the torrent first, so we can't start it automatically (boo, hiss)
|
||||
writeSeedForm(out, req);
|
||||
@@ -198,23 +270,31 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
List files = snark.meta.getFiles();
|
||||
String dataFile = snark.meta.getName();
|
||||
for (int i = 0; files != null && i < files.size(); i++) {
|
||||
// multifile torrents have the getFiles() return lists of lists of filenames, but
|
||||
// each of those lists just contain a single file afaict...
|
||||
File df = new File(_manager.getDataDir(), files.get(i).toString());
|
||||
boolean deleted = FileUtil.rmdir(df, false);
|
||||
if (deleted)
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
if (dataFile != null) {
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
boolean deleted = f.delete();
|
||||
if (deleted)
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
if (files == null) { // single file torrent
|
||||
if (f.delete())
|
||||
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < files.size(); i++) { // pass 1 delete files
|
||||
// multifile torrents have the getFiles() return lists of lists of filenames, but
|
||||
// each of those lists just contain a single file afaict...
|
||||
File df = Storage.getFileFromNames(f, (List) files.get(i));
|
||||
if (df.delete())
|
||||
_manager.addMessage("Data file deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
for (int i = files.size() - 1; i >= 0; i--) { // pass 2 delete dirs - not foolproof,
|
||||
// we could sort and do a strict bottom-up
|
||||
File df = Storage.getFileFromNames(f, (List) files.get(i));
|
||||
df = df.getParentFile();
|
||||
if (df == null || !df.exists())
|
||||
continue;
|
||||
if(df.delete())
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -230,7 +310,11 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String i2cpHost = req.getParameter("i2cpHost");
|
||||
String i2cpPort = req.getParameter("i2cpPort");
|
||||
String i2cpOpts = req.getParameter("i2cpOpts");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
|
||||
String upLimit = req.getParameter("upLimit");
|
||||
String upBW = req.getParameter("upBW");
|
||||
boolean useOpenTrackers = req.getParameter("useOpenTrackers") != null;
|
||||
String openTrackers = req.getParameter("openTrackers");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit, upBW, useOpenTrackers, openTrackers);
|
||||
} else if ("Create torrent".equals(action)) {
|
||||
String baseData = req.getParameter("baseFile");
|
||||
if (baseData != null) {
|
||||
@@ -240,30 +324,53 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists() && baseFile.isFile()) {
|
||||
if (announceURL == null || announceURL.length() <= 0)
|
||||
_manager.addMessage("Error creating torrent - you must select a tracker");
|
||||
else if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
s.create();
|
||||
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
|
||||
MetaInfo info = s.getMetaInfo();
|
||||
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
|
||||
if (torrentFile.exists())
|
||||
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
|
||||
_manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck
|
||||
// DirMonitor could grab this first, maybe hold _snarks lock?
|
||||
FileOutputStream out = new FileOutputStream(torrentFile);
|
||||
out.write(info.getTorrentData());
|
||||
out.close();
|
||||
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
|
||||
// now fire it up, but don't automatically seed it
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath(), false);
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath(), true);
|
||||
_manager.addMessage("Many I2P trackers require you to register new torrents before seeding - please do so before starting " + baseFile.getName());
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
} else if (baseFile.exists()) {
|
||||
_manager.addMessage("I2PSnark doesn't yet support creating multifile torrents");
|
||||
} else {
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistent data: " + baseFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} else if ("StopAll".equals(action)) {
|
||||
_manager.addMessage("Stopping all torrents and closing the I2P tunnel");
|
||||
List snarks = getSortedSnarks(req);
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
if (!snark.stopped)
|
||||
_manager.stopTorrent(snark.torrent, false);
|
||||
}
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
_manager.addMessage("I2P tunnel closed");
|
||||
}
|
||||
} else if ("StartAll".equals(action)) {
|
||||
_manager.addMessage("Opening the I2P tunnel and starting all torrents");
|
||||
List snarks = getSortedSnarks(req);
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
if (snark.stopped)
|
||||
snark.startTorrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,12 +386,16 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
|
||||
private static final int MAX_DISPLAYED_ERROR_LENGTH = 40;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers, boolean showDebug) throws IOException {
|
||||
String filename = snark.torrent;
|
||||
File f = new File(filename);
|
||||
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
|
||||
int i = filename.lastIndexOf(".torrent");
|
||||
if (i > 0)
|
||||
filename = filename.substring(0, i);
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
|
||||
long total = snark.meta.getTotalLength();
|
||||
@@ -292,32 +403,78 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
|
||||
if (remaining > total)
|
||||
remaining = total;
|
||||
int totalBps = 4096; // should probably grab this from the snark...
|
||||
long remainingSeconds = remaining / totalBps;
|
||||
long uploaded = snark.coordinator.getUploaded();
|
||||
|
||||
long downBps = 0;
|
||||
long upBps = 0;
|
||||
if (snark.coordinator != null) {
|
||||
downBps = snark.coordinator.getDownloadRate();
|
||||
upBps = snark.coordinator.getUploadRate();
|
||||
}
|
||||
long remainingSeconds;
|
||||
if (downBps > 0)
|
||||
remainingSeconds = remaining / downBps;
|
||||
else
|
||||
remainingSeconds = -1;
|
||||
boolean isRunning = !snark.stopped;
|
||||
long uploaded = 0;
|
||||
if (snark.coordinator != null) {
|
||||
uploaded = snark.coordinator.getUploaded();
|
||||
stats[0] += snark.coordinator.getDownloaded();
|
||||
}
|
||||
stats[1] += uploaded;
|
||||
if (isRunning) {
|
||||
stats[2] += downBps;
|
||||
stats[3] += upBps;
|
||||
}
|
||||
|
||||
boolean isValid = snark.meta != null;
|
||||
boolean singleFile = snark.meta.getFiles() == null;
|
||||
|
||||
String err = snark.coordinator.trackerProblems;
|
||||
int curPeers = snark.coordinator.getPeerCount();
|
||||
int knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
String err = null;
|
||||
int curPeers = 0;
|
||||
int knownPeers = 0;
|
||||
if (snark.coordinator != null) {
|
||||
err = snark.coordinator.trackerProblems;
|
||||
curPeers = snark.coordinator.getPeerCount();
|
||||
knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
}
|
||||
|
||||
String statusString = "Unknown";
|
||||
if (err != null) {
|
||||
if (isRunning)
|
||||
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "TrackerErr (" + err + ")";
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "<a title=\"" + err + "\">TrackerErr</a> (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning)
|
||||
statusString = "<a title=\"" + err + "\">TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else {
|
||||
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
|
||||
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
|
||||
statusString = "TrackerErr<br />(" + err + ")";
|
||||
}
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning)
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "Seeding (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning)
|
||||
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Complete";
|
||||
} else {
|
||||
if (isRunning)
|
||||
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
|
||||
statusString = "OK (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning && curPeers > 0 && downBps > 0)
|
||||
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "Stalled (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning && curPeers > 0)
|
||||
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning)
|
||||
statusString = "No Peers (0/" + knownPeers + ")";
|
||||
else
|
||||
statusString = "Stopped";
|
||||
}
|
||||
@@ -334,40 +491,142 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write(filename);
|
||||
if (remaining == 0)
|
||||
out.write("</a>");
|
||||
out.write("</td>\n\t");
|
||||
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
// lets hold off on the ETA until we have rates sorted...
|
||||
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
|
||||
} else {
|
||||
out.write(formatSize(total)); // 3GB
|
||||
// temporarily hardcoded for postman and anonymity, requires bytemonsoon patch for lookup by info_hash
|
||||
String announce = snark.meta.getAnnounce();
|
||||
if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr")) {
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String baseURL = (String)trackers.get(name);
|
||||
if (!baseURL.startsWith(announce))
|
||||
continue;
|
||||
int e = baseURL.indexOf('=');
|
||||
if (e < 0)
|
||||
continue;
|
||||
baseURL = baseURL.substring(e + 1);
|
||||
out.write(" (<a href=\"" + baseURL + "details.php?dllist=1&filelist=1&info_hash=");
|
||||
out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
|
||||
out.write("\" title=\"" + name + " Tracker\">Details</a>)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
|
||||
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
|
||||
if(isRunning && remainingSeconds > 0)
|
||||
out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0)
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
else
|
||||
out.write(formatSize(total)); // 3GB
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentUploaded " + rowClass
|
||||
+ "\">" + formatSize(uploaded) + "</td>\n\t");
|
||||
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
|
||||
//out.write("n/a"); //2KBps/12KBps/4KBps
|
||||
//out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning && remaining > 0)
|
||||
out.write(formatSize(downBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning)
|
||||
out.write(formatSize(upBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
|
||||
if (showPeers)
|
||||
parameters = parameters + "&p=1";
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Stop" + parameters
|
||||
+ "\" title=\"Stop the torrent\">Stop</a>");
|
||||
} else {
|
||||
if (isValid)
|
||||
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Start" + parameters
|
||||
+ "\" title=\"Start the torrent\">Start</a> ");
|
||||
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Remove" + parameters
|
||||
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
|
||||
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Delete" + parameters
|
||||
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
|
||||
}
|
||||
out.write("</td>\n</tr>\n");
|
||||
if(showPeers && isRunning && curPeers > 0) {
|
||||
List peers = snark.coordinator.peerList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer peer = (Peer)it.next();
|
||||
if (!peer.isConnected())
|
||||
continue;
|
||||
out.write("<tr class=\"" + rowClass + "\">");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
String ch = peer.toString().substring(0, 4);
|
||||
String client;
|
||||
if ("AwMD".equals(ch))
|
||||
client = "I2PSnark";
|
||||
else if ("BFJT".equals(ch))
|
||||
client = "I2PRufus";
|
||||
else if ("TTMt".equals(ch))
|
||||
client = "I2P-BT";
|
||||
else if ("LUFa".equals(ch))
|
||||
client = "Azureus";
|
||||
else
|
||||
client = "Unknown";
|
||||
out.write("<font size=-1>" + client + "</font> <tt>" + peer.toString().substring(5, 9) + "</tt>");
|
||||
if (showDebug)
|
||||
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
|
||||
if (pct == 100.0)
|
||||
out.write("<font size=-1>Seed</font>");
|
||||
else {
|
||||
String ps = String.valueOf(pct);
|
||||
if (ps.length() > 5)
|
||||
ps = ps.substring(0, 5);
|
||||
out.write("<font size=-1>" + ps + "%</font>");
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
if (peer.isInteresting() && !peer.isChoked()) {
|
||||
out.write("<font color=#008000>");
|
||||
out.write("<font size=-1>" + formatSize(peer.getDownloadRate()) + "ps</font></font>");
|
||||
} else {
|
||||
out.write("<font color=#a00000><font size=-1><a title=\"");
|
||||
if (!peer.isInteresting())
|
||||
out.write("Uninteresting\">");
|
||||
else
|
||||
out.write("Choked\">");
|
||||
out.write(formatSize(peer.getDownloadRate()) + "ps</a></font></font>");
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
if (pct != 100.0) {
|
||||
if (peer.isInterested() && !peer.isChoking()) {
|
||||
out.write("<font color=#008000>");
|
||||
out.write("<font size=-1>" + formatSize(peer.getUploadRate()) + "ps</font></font>");
|
||||
} else {
|
||||
out.write("<font color=#a00000><font size=-1><a title=\"");
|
||||
if (!peer.isInterested())
|
||||
out.write("Uninterested\">");
|
||||
else
|
||||
out.write("Choking\">");
|
||||
out.write(formatSize(peer.getUploadRate()) + "ps</a></font></font>");
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td></tr>\n\t");
|
||||
if (showDebug)
|
||||
out.write("<tr><td colspan=\"8\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">" + peer.getSocket() + "</td></tr>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
@@ -381,7 +640,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Add Torrent:</span><br />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"80\" value=\"" + newURL + "\" /> \n");
|
||||
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
|
||||
@@ -396,21 +656,24 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if (baseFile == null)
|
||||
baseFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
out.write("<span class=\"snarkNewTorrent\"><hr />\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Create Torrent:</span><br />\n");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("Data to seed: " + _manager.getDataDir().getAbsolutePath() + File.separatorChar
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
|
||||
+ "\" title=\"File to seed (must be within the specified path)\" /><br />\n");
|
||||
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
|
||||
Map trackers = sort(_manager.getTrackers());
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String announceURL = (String)trackers.get(name);
|
||||
// we inject whitespace in sort(...) to guarantee uniqueness, but we can strip it off here
|
||||
out.write("\t<option value=\"" + announceURL + "\">" + name.trim() + "</option>\n");
|
||||
int e = announceURL.indexOf('=');
|
||||
if (e > 0)
|
||||
announceURL = announceURL.substring(0, e);
|
||||
out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
|
||||
}
|
||||
out.write("</select>\n");
|
||||
out.write("or <input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
|
||||
@@ -419,36 +682,27 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private Map sort(Map trackers) {
|
||||
TreeMap rv = new TreeMap();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String url = (String)iter.next();
|
||||
String name = (String)trackers.get(url);
|
||||
while (rv.containsKey(name))
|
||||
name = name + " ";
|
||||
rv.put(name, url);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String dataDir = _manager.getDataDir().getAbsolutePath();
|
||||
boolean autoStart = _manager.shouldAutoStart();
|
||||
int seedPct = 0;
|
||||
boolean useOpenTrackers = _manager.shouldUseOpenTrackers();
|
||||
String openTrackers = _manager.getOpenTrackerString();
|
||||
//int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<span class=\"snarkConfig\"><hr />\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
|
||||
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
|
||||
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /><br />\n");
|
||||
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /> <i>(Edit i2psnark.config and restart to change)</i><br />\n");
|
||||
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
|
||||
+ (autoStart ? "checked " : "")
|
||||
+ "title=\"If true, automatically start torrents that are added\" />");
|
||||
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
|
||||
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
|
||||
//out.write("<br />\n");
|
||||
/*
|
||||
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
|
||||
if (seedPct <= 0)
|
||||
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
|
||||
@@ -463,16 +717,27 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
else
|
||||
out.write("<option value=\"150\">150%</option>\n\t");
|
||||
out.write("</select><br />\n");
|
||||
*/
|
||||
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
|
||||
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
|
||||
out.write("Up bandwidth limit: <input type=\"text\" name=\"upBW\" value=\""
|
||||
+ I2PSnarkUtil.instance().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
|
||||
|
||||
out.write("Use open trackers also: <input type=\"checkbox\" name=\"useOpenTrackers\" value=\"true\" "
|
||||
+ (useOpenTrackers ? "checked " : "")
|
||||
+ "title=\"If true, uses open trackers in addition\" /> ");
|
||||
out.write("Announce URLs: <input type=\"text\" name=\"openTrackers\" value=\""
|
||||
+ openTrackers + "\" size=\"50\" /><br />\n");
|
||||
|
||||
//out.write("<hr />\n");
|
||||
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /><br />\n");
|
||||
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
|
||||
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
|
||||
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
|
||||
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
|
||||
StringBuffer opts = new StringBuffer(64);
|
||||
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
|
||||
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
|
||||
@@ -480,22 +745,23 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String val = (String)options.get(key);
|
||||
opts.append(key).append('=').append(val).append(' ');
|
||||
}
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
|
||||
+ opts.toString() + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
|
||||
out.write("</span>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
// rounding makes us look faster :)
|
||||
private String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + "B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return (bytes/1024) + "KB";
|
||||
return ((bytes + 512)/1024) + "KB";
|
||||
else if (bytes < 5*1024*1024*1024l)
|
||||
return (bytes/(1024*1024)) + "MB";
|
||||
return ((bytes + 512*1024)/(1024*1024)) + "MB";
|
||||
else
|
||||
return (bytes/(1024*1024*1024)) + "GB";
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
|
||||
}
|
||||
|
||||
private static final String HEADER_BEGIN = "<html>\n" +
|
||||
@@ -540,7 +806,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
"th {\n" +
|
||||
" background-color: #C7D5D5;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
"td {\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentEven {\n" +
|
||||
" background-color: #E7E7E7;\n" +
|
||||
@@ -550,8 +819,6 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
@@ -568,19 +835,26 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"<body>\n";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
|
||||
"<thead>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status \n";
|
||||
|
||||
private static final String TABLE_HEADER2 = "</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
|
||||
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</thead>\n";
|
||||
" <th align=\"right\" valign=\"top\">ETA</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Uploaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
|
||||
|
||||
private static final String TABLE_TOTAL = "<tfoot>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th> </th>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
|
||||
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
|
||||
|
||||
private static final String TABLE_FOOTER = "</table>\n";
|
||||
|
||||
@@ -597,7 +871,8 @@ class FetchAndAdd implements Runnable {
|
||||
}
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
File file = I2PSnarkUtil.instance().get(_url, false);
|
||||
// 3 retries
|
||||
File file = I2PSnarkUtil.instance().get(_url, false, 3);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
|
@@ -3,6 +3,7 @@ package org.klomp.snark.web;
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
public class RunStandalone {
|
||||
|
@@ -8,11 +8,15 @@ package net.i2p.i2ptunnel;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.ByteCache;
|
||||
|
@@ -16,8 +16,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
@@ -26,8 +26,8 @@ import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runnable {
|
||||
|
||||
|
@@ -11,10 +11,10 @@ import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -112,6 +112,35 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 403 Bad Protocol\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: NON-HTTP PROTOCOL</H1>"+
|
||||
"The request uses a bad protocol. "+
|
||||
"The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_LOCALHOST =
|
||||
("HTTP/1.1 403 Access Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
|
||||
private final static byte[] ERR_MAXPOST =
|
||||
("HTTP/1.1 503 Bad POST\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@@ -384,6 +413,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
usingWWWProxy = true;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
} else if (host.toLowerCase().startsWith("localhost:")) {
|
||||
if (out != null) {
|
||||
out.write(ERR_LOCALHOST);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
@@ -474,16 +513,32 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
int postbytes = 0;
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
newRequest.append((char) i);
|
||||
if (++postbytes > MAX_POSTBYTES) {
|
||||
if (out != null) {
|
||||
out.write(ERR_MAXPOST);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method == null || destination == null) {
|
||||
l.log("No HTTP method found in the request.");
|
||||
if (out != null) {
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
if ("http://".equalsIgnoreCase(protocol))
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
else
|
||||
out.write(ERR_BAD_PROTOCOL);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
@@ -546,6 +601,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
|
||||
IOException ex = new IOException("OOM (in POST?)");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +645,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private static String jumpServers[] = {
|
||||
"http://i2host.i2p/cgi-bin/i2hostjump?",
|
||||
// "http://orion.i2p/jump/",
|
||||
"http://stats.i2p/cgi-bin/jump.cgi?a=",
|
||||
// "http://trevorreznik.i2p/cgi-bin/jump.php?hostname=",
|
||||
"http://i2jump.i2p/"
|
||||
};
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
|
||||
if (out != null) {
|
||||
@@ -602,11 +670,26 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
if (showAddrHelper) {
|
||||
out.write("<br><br>Click below to try an address helper link:<br><br><a href=\"http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
out.write("<br><br>Click a link below to look for an address helper by using a \"jump\" service:<br>".getBytes());
|
||||
for (int i = 0; i < jumpServers.length; i++) {
|
||||
// Skip jump servers we don't know
|
||||
String jumphost = jumpServers[i].substring(7); // "http://"
|
||||
jumphost = jumphost.substring(0, jumphost.indexOf('/'));
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(jumphost);
|
||||
if (dest == null) continue;
|
||||
} catch (DataFormatException dfe) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.write("<br><a href=\"".getBytes());
|
||||
out.write(jumpServers[i].getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">".getBytes());
|
||||
out.write(jumpServers[i].getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
|
@@ -3,17 +3,14 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
|
@@ -3,18 +3,17 @@
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
|
@@ -1,16 +1,17 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.lang.IndexOutOfBoundsException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PThread;
|
||||
@@ -27,8 +28,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
protected List dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
/** this is the pong response the client expects for their last ping. at least, i hope so... */
|
||||
private String _expectedPong;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
@@ -47,8 +46,6 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
notifyThis,
|
||||
"IRCHandler " + (++__clientId), tunnel);
|
||||
|
||||
_expectedPong = null;
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
@@ -85,9 +82,10 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
try {
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
|
||||
in.start();
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -123,10 +121,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -153,7 +153,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("in: [" + inmsg + "]");
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
String outmsg = inboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
@@ -165,7 +165,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("inbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -195,10 +195,12 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -225,39 +227,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("out: [" + inmsg + "]");
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
String outmsg = outboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if (outmsg.indexOf("PING") >= 0) {
|
||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||
// like BitchX, actually expect certain behavior. It sends two different pings:
|
||||
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
||||
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
|
||||
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
|
||||
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
|
||||
// sent to the server, but when we get a PONG back, use what we expected, rather than
|
||||
// what they sent.
|
||||
//
|
||||
// Yuck.
|
||||
StringTokenizer tok = new StringTokenizer(inmsg, " ");
|
||||
int tokens = tok.countTokens();
|
||||
if ( (tokens <= 2) || (tokens == 3) ) { // "PING nonce" or "PING nonce serverIP"
|
||||
tok.nextToken(); // skip
|
||||
//_expectedPong = "PONG 127.0.0.1 :" + tok.nextToken();
|
||||
_expectedPong = "PONG " + tok.nextToken();
|
||||
} else {
|
||||
// if it isn't one of those two, we will filter out all PONGs, which means
|
||||
// the client will fail. whee!
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IRC client sent a PING we don't understand (\"" + inmsg + "\"), so we're filtering it");
|
||||
_expectedPong = null;
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("outbound rewritten PING: "+outmsg + ", waiting for [" + _expectedPong + "]");
|
||||
_log.warn(" - outbound was: "+inmsg);
|
||||
}
|
||||
}
|
||||
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("outbound FILTERED: "+outmsg);
|
||||
@@ -267,7 +239,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("outbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -294,14 +266,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
|
||||
public String inboundFilter(String s) {
|
||||
public String inboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",4);
|
||||
String command;
|
||||
int idx=0;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
// "NOTICE", // can contain CTCP
|
||||
//"PING",
|
||||
//"PONG",
|
||||
"MODE",
|
||||
@@ -311,6 +283,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR",
|
||||
"KICK",
|
||||
"H", // "hide operator status" (after kicking an op)
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
@@ -333,34 +307,36 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
} catch(NumberFormatException nfe){}
|
||||
|
||||
|
||||
if ("PING".equals(command))
|
||||
if ("PING".equalsIgnoreCase(command))
|
||||
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||
if ("PONG".equals(command)) {
|
||||
if ("PONG".equalsIgnoreCase(command)) {
|
||||
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
|
||||
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
|
||||
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
|
||||
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
|
||||
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
|
||||
// is null.
|
||||
String pong = _expectedPong;
|
||||
_expectedPong = null;
|
||||
// is blank.
|
||||
//
|
||||
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
|
||||
// If we aren't going to rewrite it, pass it through
|
||||
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
|
||||
expectedPong.setLength(0);
|
||||
return pong;
|
||||
}
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++) {
|
||||
if(allowedCommands[i].equals(command))
|
||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP.
|
||||
if("PRIVMSG".equals(command))
|
||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[idx++];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
@@ -377,13 +353,13 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String outboundFilter(String s) {
|
||||
public String outboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",3);
|
||||
String command;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
// "NOTICE", // can contain CTCP
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
@@ -414,38 +390,69 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
command = field[0].toUpperCase();
|
||||
|
||||
if ("PING".equals(command)) {
|
||||
// e.g. "PING", "PING nonce", or "PING nonce serverIP"
|
||||
if (field.length == 1) {
|
||||
return "PING";
|
||||
} else if (field.length == 2) {
|
||||
return "PING " + field[1];
|
||||
} else if (field.length == 3) {
|
||||
return "PING " + field[1];
|
||||
if ("PING".equalsIgnoreCase(command)) {
|
||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||
// like BitchX, actually expect certain behavior. It sends two different pings:
|
||||
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
||||
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
|
||||
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
|
||||
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
|
||||
// sent to the server, but when we get a PONG back, use what we expected, rather than
|
||||
// what they sent.
|
||||
//
|
||||
// Yuck.
|
||||
|
||||
String rv = null;
|
||||
expectedPong.setLength(0);
|
||||
if (field.length == 1) { // PING
|
||||
rv = "PING";
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG 127.0.0.1");
|
||||
} else if (field.length == 2) { // PING nonce
|
||||
rv = "PING " + field[1];
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG ").append(field[1]);
|
||||
} else if (field.length == 3) { // PING nonce serverLocation
|
||||
rv = "PING " + field[1];
|
||||
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
||||
return null;
|
||||
rv = null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
|
||||
|
||||
return rv;
|
||||
}
|
||||
if ("PONG".equals(command))
|
||||
if ("PONG".equalsIgnoreCase(command))
|
||||
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++)
|
||||
{
|
||||
if(allowedCommands[i].equals(command))
|
||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// mIRC sends "NOTICE user :DCC Send file (IP)"
|
||||
// in addition to the CTCP version
|
||||
if("NOTICE".equalsIgnoreCase(command))
|
||||
{
|
||||
String msg = field[2];
|
||||
if(msg.startsWith(":DCC "))
|
||||
return null;
|
||||
// fall through
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||
if("PRIVMSG".equals(command))
|
||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[2];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
@@ -458,14 +465,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return s;
|
||||
}
|
||||
|
||||
if("USER".equals(command)) {
|
||||
if("USER".equalsIgnoreCase(command)) {
|
||||
int idx = field[2].lastIndexOf(":");
|
||||
if(idx<0)
|
||||
return "USER user hostname localhost :realname";
|
||||
String realname = field[2].substring(idx+1);
|
||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||
return ret;
|
||||
} else if ("QUIT".equals(command)) {
|
||||
} else if ("QUIT".equalsIgnoreCase(command)) {
|
||||
return "QUIT :leaving";
|
||||
}
|
||||
|
||||
|
@@ -8,10 +8,10 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
|
@@ -1,9 +1,8 @@
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@@ -13,10 +13,8 @@ import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.i2ptunnel.TunnelController;
|
||||
import net.i2p.i2ptunnel.TunnelControllerGroup;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Ugly little accessor for the edit page
|
||||
|
@@ -190,6 +190,7 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
// Get current tunnel controller
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
@@ -205,21 +206,28 @@ public class IndexBean {
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
// Only modify other shared tunnels
|
||||
// if the current tunnel is shared, and of supported type
|
||||
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
|
||||
("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType()))) {
|
||||
// all clients use the same I2CP session, and as such, use the same I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
|
||||
// Current tunnel modified by user, skip
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType())||
|
||||
"client".equals(c.getType())
|
||||
) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
|
||||
// Only modify this non-current tunnel
|
||||
// if it belongs to a shared destination, and is of supported type
|
||||
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
|
||||
("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType()) ||
|
||||
"client".equals(c.getType()))) {
|
||||
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
|
@@ -186,8 +186,9 @@
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<option value="3"<%=(tunnelDepth == 3 ? " selected=\"selected\"" : "") %>>3 hop tunnel (very high anonymity, poor performance)</option>
|
||||
<% if (tunnelDepth > 3) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel (very poor performance)</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -213,11 +214,11 @@
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound, 1 outbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -228,9 +229,9 @@
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel each direction (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels each direction (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels each direction (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
@@ -284,4 +285,4 @@
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@@ -158,8 +158,9 @@
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<option value="3"<%=(tunnelDepth == 3 ? " selected=\"selected\"" : "") %>>3 hop tunnel (very high anonymity, poor performance)</option>
|
||||
<% if (tunnelDepth > 3) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel (very poor performance)</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -185,11 +186,11 @@
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound, 1 outbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -200,9 +201,9 @@
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel each direction (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels each direction (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels each direction (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
@@ -256,4 +257,4 @@
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@@ -1,31 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="jetty">
|
||||
|
||||
|
||||
<property name="jetty.sha1" value="021164f84da7304bd1ff07c268b45aa3e0b13322" />
|
||||
<property name="jetty.md5" value="a61adc832be6baf2678935506743cfc3" />
|
||||
<property name="jetty.url" value="http://dist.codehaus.org/jetty/jetty-5.1.x/jetty-5.1.12.zip" />
|
||||
<property name="jetty.filename" value="jetty-5.1.12.zip" />
|
||||
|
||||
<target name="all" depends="build" />
|
||||
<target name="fetchJettylib" >
|
||||
<available property="jetty.available" file="jetty-5.1.6.zip" />
|
||||
<ant target="doFetchJettylib" />
|
||||
|
||||
<target name="ensureJettylib" >
|
||||
<available property="jetty.zip.available" file="${jetty.filename}" type="file" />
|
||||
<available property="jetty.zip.extracted" file="jettylib" type="dir" />
|
||||
<ant target="fetchJettylib" />
|
||||
<ant target="verifyJettylib" />
|
||||
<ant target="extractJettylib" />
|
||||
</target>
|
||||
<target name="doFetchJettylib" unless="jetty.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
|
||||
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
|
||||
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
|
||||
<echo message="such as the routerconsole." />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
|
||||
<ant target="doExtract" />
|
||||
|
||||
<target name="fetchJettylib" unless="jetty.zip.available" >
|
||||
<echo message="It seems that you don't have '${jetty.filename}' deployed." />
|
||||
<echo message="The build script can download this file for you automatically," />
|
||||
<echo message="or alternatively you can obtain it manually from:" />
|
||||
<echo message="${jetty.url}" />
|
||||
<echo message="" />
|
||||
<echo message="The libraries contained in the fetched file provide the Jetty web server" />
|
||||
<echo message="(http://jetty.mortbay.org/). They are not absolutely necessary" />
|
||||
<echo message="but strongly recommended, since they are used by some applications" />
|
||||
<echo message="on top of I2P, like the router console." />
|
||||
<echo message="" />
|
||||
<echo message="Even if you deploy the Jetty archive manually, the build script will" />
|
||||
<echo message="still attempt to verify its checksums, which must be:" />
|
||||
<echo message="SHA1 ${jetty.sha1}" />
|
||||
<echo message="MD5 ${jetty.md5}" />
|
||||
<echo message="" />
|
||||
<input message="Download Jetty archive automatically?" validargs="y,n" addproperty="jetty.download" />
|
||||
<fail message="Aborting as requested. Please deploy the Jetty archive manually." >
|
||||
<condition>
|
||||
<equals arg1="${jetty.download}" arg2="n"/>
|
||||
</condition>
|
||||
</fail>
|
||||
<get src="${jetty.url}" verbose="true" dest="${jetty.filename}" />
|
||||
</target>
|
||||
<target name="doExtract">
|
||||
<unzip src="jetty-5.1.6.zip" dest="." />
|
||||
|
||||
<target name="verifyJettylib" >
|
||||
<condition property="jetty.zip.verified" >
|
||||
<and>
|
||||
<checksum file="${jetty.filename}" algorithm="SHA" property="${jetty.sha1}" />
|
||||
<checksum file="${jetty.filename}" algorithm="MD5" property="${jetty.md5}" />
|
||||
</and>
|
||||
</condition>
|
||||
<fail message="Jetty archive does not match its checksums!" >
|
||||
<condition>
|
||||
<not>
|
||||
<istrue value="${jetty.zip.verified}" />
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
</target>
|
||||
|
||||
<target name="extractJettylib" unless="jetty.zip.extracted" >
|
||||
<unzip src="${jetty.filename}" dest="." />
|
||||
<mkdir dir="jettylib" />
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.6/lib">
|
||||
<fileset dir="jetty-5.1.12/lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.6/ext">
|
||||
<fileset dir="jetty-5.1.12/ext">
|
||||
<include name="ant.jar" />
|
||||
<include name="commons-el.jar" />
|
||||
<include name="commons-logging.jar" />
|
||||
<include name="jasper-compiler.jar" />
|
||||
<include name="jasper-runtime.jar" />
|
||||
<include name="javax.servlet.jar" />
|
||||
@@ -33,11 +77,10 @@
|
||||
<include name="xercesImpl.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
|
||||
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
|
||||
<delete dir="jetty-5.1.6" />
|
||||
<delete dir="jetty-5.1.12" />
|
||||
</target>
|
||||
<target name="build" depends="fetchJettylib" />
|
||||
|
||||
<target name="build" depends="ensureJettylib" />
|
||||
<target name="builddep" />
|
||||
<target name="compile" />
|
||||
<target name="jar" />
|
||||
|
@@ -138,8 +138,6 @@ public class I2PSocketManagerFactory {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
I2PSocketManager sockMgr = createManager(session, opts, "manager");
|
||||
if (sockMgr != null)
|
||||
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
|
||||
return sockMgr;
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error creating session for socket manager", ise);
|
||||
@@ -199,4 +197,4 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
return i2cpPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,17 +4,15 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
@@ -4,14 +4,13 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
|
@@ -4,10 +4,7 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
@@ -1,15 +1,16 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Sit around on a destination, receiving lots of data and sending lots of
|
||||
|
@@ -127,8 +127,8 @@ public class MergeTypedPropertiesTask extends Task {
|
||||
while (strtokString.hasMoreTokens())
|
||||
_propertiesOut.setProperty(strtokString.nextToken().trim(), "");
|
||||
|
||||
for (Enumeration enum = _propertiesIn.elements(); enum.hasMoreElements(); ) {
|
||||
property = (String) enum.nextElement();
|
||||
for (Enumeration enumm = _propertiesIn.elements(); enumm.hasMoreElements(); ) {
|
||||
property = (String) enumm.nextElement();
|
||||
|
||||
if (_onlyExpected && !_propertiesOut.containsKey(property))
|
||||
continue;
|
||||
|
@@ -34,13 +34,28 @@
|
||||
*/
|
||||
|
||||
package HTML;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
import HTML.Tmpl.Element.*;
|
||||
import HTML.Tmpl.Parsers.*;
|
||||
import HTML.Tmpl.Util;
|
||||
import HTML.Tmpl.Filter;
|
||||
import HTML.Tmpl.Util;
|
||||
import HTML.Tmpl.Element.Conditional;
|
||||
import HTML.Tmpl.Element.Element;
|
||||
import HTML.Tmpl.Element.If;
|
||||
import HTML.Tmpl.Element.Var;
|
||||
import HTML.Tmpl.Parsers.Parser;
|
||||
|
||||
/**
|
||||
* Use HTML Templates with java.
|
||||
|
@@ -35,12 +35,12 @@
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Vector;
|
||||
|
||||
import HTML.*;
|
||||
import HTML.Template;
|
||||
|
||||
public class Conditional extends Element
|
||||
{
|
||||
|
@@ -29,10 +29,10 @@
|
||||
*/
|
||||
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Vector;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Vector;
|
||||
|
||||
public class Loop extends Element
|
||||
{
|
||||
|
@@ -35,8 +35,9 @@
|
||||
package HTML.Tmpl.Element;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import HTML.Tmpl.Util;
|
||||
|
||||
import HTML.Template;
|
||||
import HTML.Tmpl.Util;
|
||||
|
||||
public class Var extends Element
|
||||
{
|
||||
|
@@ -30,9 +30,16 @@
|
||||
|
||||
|
||||
package HTML.Tmpl.Parsers;
|
||||
import java.util.*;
|
||||
import HTML.Tmpl.Element.*;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Vector;
|
||||
|
||||
import HTML.Tmpl.Util;
|
||||
import HTML.Tmpl.Element.Element;
|
||||
import HTML.Tmpl.Element.If;
|
||||
import HTML.Tmpl.Element.Loop;
|
||||
import HTML.Tmpl.Element.Unless;
|
||||
|
||||
public class Parser
|
||||
{
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.util.*;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* similar in some ways to Properties, except that duplicate keys
|
||||
|
@@ -4,16 +4,22 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
|
@@ -1,17 +1,21 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.ConnectException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* a simple program which illustrates the use of I2P stream
|
||||
|
@@ -2,15 +2,10 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* A simple program which runs the EchoServer and EchoClient
|
||||
|
@@ -6,9 +6,9 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* <p>Implements a queue of objects, where each object is 'embargoed'
|
||||
|
@@ -3,18 +3,26 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.naming.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.naming.HostsTxtNamingService;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A I2P equivalent of the much-beloved 'netcat' utility.
|
||||
|
@@ -1,16 +1,6 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* Class which wraps an I2PSocket object with convenient methods.
|
||||
|
@@ -1,15 +1,7 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.i2ptunnel.I2PTunnelXMLWrapper;
|
||||
|
||||
/**
|
||||
|
@@ -1,15 +1,7 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import org.apache.xmlrpc.WebServer;
|
||||
|
||||
/**
|
||||
* Provides a means for programs in any language to dynamically manage
|
||||
|
@@ -1,19 +1,13 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import org.apache.xmlrpc.XmlRpcClient;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -1,19 +1,16 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Properties;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import org.apache.xmlrpc.XmlRpcClient;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -1,19 +1,6 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/**
|
||||
* A simple class providing callable xmlrpc server methods, gets linked in to
|
||||
|
@@ -1,18 +1,23 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import org.apache.xmlrpc.XmlRpcServer;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -1,18 +1,13 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -1,19 +1,23 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A convenience class for encapsulating and manipulating I2P private keys
|
||||
|
@@ -6,9 +6,17 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* builds on Properties with methods to load/save directly to/from file
|
||||
|
@@ -1,11 +1,9 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
|
@@ -1,11 +1,9 @@
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SimpleFile - subclass of File which adds some python-like
|
||||
|
@@ -6,9 +6,7 @@
|
||||
|
||||
package net.i2p.aum;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Implements simething similar to python's 'Queue' class
|
||||
|
@@ -6,9 +6,9 @@
|
||||
|
||||
package net.i2p.aum.http;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.DupHashtable;
|
||||
|
||||
/**
|
||||
* Framework for building up a page of HTML by method calls alone, breaking
|
||||
|
@@ -6,15 +6,12 @@
|
||||
|
||||
package net.i2p.aum.http;
|
||||
|
||||
import java.lang.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@@ -6,15 +6,16 @@
|
||||
|
||||
package net.i2p.aum.http;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.aum.PrivDestination;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@@ -7,12 +7,19 @@
|
||||
|
||||
package net.i2p.aum.http;
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.lang.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Vector;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.DupHashtable;
|
||||
import net.i2p.aum.Mimetypes;
|
||||
|
||||
public abstract class MiniHttpRequestHandler implements Runnable {
|
||||
final static String CRLF = "\r\n";
|
||||
|
@@ -13,13 +13,13 @@ package net.i2p.aum.http;
|
||||
// server implements HTTP GET method
|
||||
//***************************************
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.lang.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import org.apache.xmlrpc.XmlRpcServer;
|
||||
|
||||
public class MiniHttpServer extends Thread
|
||||
{
|
||||
|
@@ -6,9 +6,11 @@
|
||||
|
||||
package net.i2p.aum.http;
|
||||
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Base class for building up quick-n-dirty HTML by code alone;
|
||||
|
@@ -6,13 +6,13 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.lang.*;
|
||||
import java.net.*;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import org.apache.xmlrpc.XmlRpcClient;
|
||||
import org.apache.xmlrpc.XmlRpcException;
|
||||
|
||||
/**
|
||||
* <p>The official Java API for client applications wishing to access the Q
|
||||
|
@@ -6,20 +6,18 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.lang.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.http.*;
|
||||
|
||||
import HTML.Template;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.aum.Mimetypes;
|
||||
import net.i2p.aum.http.I2PHttpServer;
|
||||
import net.i2p.aum.http.MiniHttpServer;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Implements Q client nodes.
|
||||
|
@@ -6,16 +6,20 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.lang.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.Socket;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
import net.i2p.aum.http.HtmlPage;
|
||||
import net.i2p.aum.http.I2PHttpRequestHandler;
|
||||
import net.i2p.aum.http.MiniHttpServer;
|
||||
import HTML.Template;
|
||||
|
||||
import net.i2p.aum.http.*;
|
||||
|
||||
|
||||
/**
|
||||
* Request handler for Q Client nodes that listens within I2P
|
||||
|
@@ -6,16 +6,23 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.*;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import HTML.*;
|
||||
import org.apache.xmlrpc.XmlRpcClient;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@@ -6,20 +6,17 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.crypto.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.Mimetypes;
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -6,10 +6,13 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* <p>Implements a binary-searchable file for storing (time, hash) records.
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Implements an Iterator for index files
|
||||
|
@@ -6,8 +6,8 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.util.*;
|
||||
import java.math.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* implements a Comparator class which compares two QPeerRec objects
|
||||
|
@@ -6,16 +6,22 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import net.i2p.aum.I2PXmlRpcClientFactory;
|
||||
import net.i2p.aum.PropertiesFile;
|
||||
import net.i2p.aum.SimpleFile;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import org.apache.xmlrpc.XmlRpcClient;
|
||||
|
||||
/**
|
||||
* <p>Command Line Interface (CLI) for starting/stopping Q nodes,
|
||||
|
@@ -6,21 +6,46 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.jar.*;
|
||||
import java.net.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
import java.util.Vector;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.aum.EmbargoedQueue;
|
||||
import net.i2p.aum.I2PXmlRpcClient;
|
||||
import net.i2p.aum.I2PXmlRpcClientFactory;
|
||||
import net.i2p.aum.I2PXmlRpcServer;
|
||||
import net.i2p.aum.PrivDestination;
|
||||
import net.i2p.aum.PropertiesFile;
|
||||
import net.i2p.aum.SimpleFile;
|
||||
import net.i2p.aum.SimpleSemaphore;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.crypto.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import org.apache.xmlrpc.XmlRpcException;
|
||||
|
||||
//import gnu.crypto.hash.*;
|
||||
|
||||
|
@@ -6,12 +6,12 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.util.*;
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.PropertiesFile;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Wrapper for a peer record file.
|
||||
|
@@ -6,9 +6,9 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.lang.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Vector;
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -6,17 +6,14 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.aum.http.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.aum.I2PXmlRpcServerFactory;
|
||||
import net.i2p.aum.http.I2PHttpServer;
|
||||
import net.i2p.aum.http.MiniHttpServer;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@@ -6,15 +6,11 @@
|
||||
|
||||
package net.i2p.aum.q;
|
||||
|
||||
import java.*;
|
||||
import java.lang.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import net.i2p.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
import net.i2p.aum.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user