forked from I2P_Developers/i2p.i2p
Compare commits
992 Commits
Author | SHA1 | Date | |
---|---|---|---|
f8a54bde19 | |||
822ec4aa53 | |||
7fe8573df4 | |||
445e4301d5 | |||
736da22bba | |||
f29c64cd70 | |||
aa4b4b9d2b | |||
9b361ac445 | |||
0ff423fc57 | |||
707f616498 | |||
c0ef19a281 | |||
ce0596d5b1 | |||
35b6926e4f | |||
dbdf36d85c | |||
60aa8c57a4 | |||
001070f677 | |||
ffa03f2b83 | |||
54fb91ba8e | |||
fdb0097934 | |||
0dde4162e6 | |||
57144f3e6a | |||
0454639db8 | |||
c32b451733 | |||
2f4765665d | |||
bff79cdae8 | |||
4bddf8ae0b | |||
ae79deff39 | |||
e3aeb267f8 | |||
c5c26c440d | |||
77971624b4 | |||
f5621c5082 | |||
3fa7fe9733 | |||
c97d07e10a | |||
567c328331 | |||
3aa982529e | |||
0c07f9ff96 | |||
f0055ccbfe | |||
693cc828c2 | |||
688dd23111 | |||
e38db5eb44 | |||
817f531619 | |||
53623da2eb | |||
4910266d9b | |||
24ae66df6d | |||
228bd980db | |||
f161a2dfc9 | |||
abe1dc676e | |||
9de57a5d5a | |||
2cc742c3ed | |||
5bcfe025d5 | |||
6dc6ca7713 | |||
eabcc96a99 | |||
28b6675979 | |||
413ad6b0e6 | |||
a7a7e96188 | |||
796dbc5d2e | |||
c86845078c | |||
89dcceefee | |||
bacce17990 | |||
e61e950713 | |||
244209d3b7 | |||
7e3e08532f | |||
1d4190734d | |||
96cf1d60c2 | |||
3aa33378c1 | |||
747bd0c5a3 | |||
ea7b42810f | |||
19022baa27 | |||
e8248f5005 | |||
f8178b7165 | |||
79b5d9748d | |||
b53ed94e8f | |||
df84a2fcd0 | |||
25e7dea370 | |||
90919ebf6b | |||
76078deb3f | |||
1b95a03d2e | |||
108039de08 | |||
addd2e6d6a | |||
5c38d5a6c9 | |||
69489dd19e | |||
3fce0e8e45 | |||
35fb332c2c | |||
1b5309be05 | |||
d2a1025b3f | |||
0a8f79f0e3 | |||
18e4c2ac63 | |||
90c2e08489 | |||
598ef67c4e | |||
1b9d870b91 | |||
68f67b7c8e | |||
d2f0c251c0 | |||
2b2f34b3f1 | |||
4e680479da | |||
d1b93e0705 | |||
4382def62f | |||
952a56c537 | |||
67aead214b | |||
50f45a50a7 | |||
6b326c3705 | |||
919ec3af01 | |||
ca5a301a4f | |||
ae76a6ee1a | |||
5cbecb3599 | |||
5a34e1de4f | |||
c810694e07 | |||
ca866d48e6 | |||
f1e77499e2 | |||
9007db1485 | |||
85c998e500 | |||
8296f8229e | |||
059ae3a80e | |||
67e242c441 | |||
e23f3b4875 | |||
06ea9af733 | |||
884818f518 | |||
3f39bd0f7b | |||
48cce6435b | |||
777e08c8b6 | |||
8c4b0b7c00 | |||
dae8b25374 | |||
2ae293444e | |||
0f11d3566a | |||
fa70d439c3 | |||
0010581405 | |||
1d659e4f8a | |||
509f00c5e2 | |||
aeb3241abb | |||
8909df3c88 | |||
1cffcae36b | |||
d2ee5b96ad | |||
0506a5915b | |||
91ef3fd0bc | |||
79f5484f87 | |||
6afd2c4b97 | |||
06b09f89de | |||
bd0eee6aa9 | |||
5bc13c16dc | |||
626daeb86e | |||
a92913da4c | |||
f0f363e8c3 | |||
7839c0fec3 | |||
4d24d65c1f | |||
ddf761b1f8 | |||
2814fe75b1 | |||
7316c82ef3 | |||
e04646bd37 | |||
8f8022347d | |||
acc0ab66a3 | |||
5a6acf1d85 | |||
ca45194c30 | |||
102506ebe8 | |||
d06f1c4a30 | |||
c732c1c038 | |||
4aa1bba575 | |||
9d3925eb20 | |||
80fdf4e917 | |||
35a86e603b | |||
8f7b31aed3 | |||
0f5a0b6b1b | |||
4cf3906ed2 | |||
57875586cf | |||
ad8ec011d0 | |||
0d93b86a56 | |||
63712002e2 | |||
67af1a17c1 | |||
9cac546547 | |||
3ffb321f46 | |||
14ea6d8d0a | |||
8e0dbf31ba | |||
5187bf1eae | |||
99471d8e1b | |||
012e999354 | |||
bdd9900d0d | |||
c71b485083 | |||
a78d34ab4b | |||
255ebe7efb | |||
5f7a761e42 | |||
09548358fa | |||
df381c37ff | |||
31e96b416d | |||
53b0f7b579 | |||
45deaa3a87 | |||
0c8eabcdf6 | |||
f9571740ae | |||
eb2af2b5fd | |||
ded00300b4 | |||
811819af69 | |||
1804c852bb | |||
3ec602865d | |||
0c0a25b038 | |||
208192f445 | |||
d0f635e30c | |||
20b2f7dcb1 | |||
cf66951818 | |||
c6f41cc8fa | |||
45a579403a | |||
74a57abfb4 | |||
380783c1ba | |||
c8843a736d | |||
e69fefda62 | |||
513da3b743 | |||
7513d42e9e | |||
8872437caf | |||
712c77a4b6 | |||
38cef14cf4 | |||
05c3b0d391 | |||
854090e9d8 | |||
f035815f7a | |||
df4302dda0 | |||
31f117e74c | |||
890f40b2ac | |||
3ac8083faf | |||
249319f76f | |||
efe87060b4 | |||
afe3ff57cf | |||
6bb1505d3b | |||
a1c8e3eae3 | |||
aa171bbaa6 | |||
845b70fe0c | |||
82b1eb7c18 | |||
4bd27ea1d3 | |||
d0f6be3161 | |||
7764257e41 | |||
af0e72ac4d | |||
0534440695 | |||
c2fa2d0c5b | |||
887017b54c | |||
3a4f5a2f1b | |||
3fb4643742 | |||
a5e3bc9b85 | |||
8a0c3f10f4 | |||
e1d808a284 | |||
e755051ebe | |||
d7c3ffa4de | |||
cba3b249dd | |||
32f250003e | |||
e004b0e6e9 | |||
a5c5917a5f | |||
cbd24946b6 | |||
9b4842931a | |||
e04cf132cc | |||
7d237b4cf6 | |||
3cbfd09722 | |||
0ae774dd68 | |||
2884df873e | |||
d4d1424c4f | |||
33827f9aaf | |||
30a666c833 | |||
9a00621fa4 | |||
46bc479884 | |||
6ab6abf4dd | |||
0c6a9ff2a0 | |||
aefc5b5317 | |||
25682fdea7 | |||
9318099845 | |||
fdf38a952d | |||
fb40ab1f00 | |||
3499ed7bb0 | |||
b05906a3c2 | |||
61d5f46295 | |||
9ebfccd8f6 | |||
4fb3e86e4d | |||
837517e94e | |||
f47ec65b8f | |||
6fede7f524 | |||
bd0c18b2e3 | |||
fba596c78c | |||
61f2b49022 | |||
e71a1a5c4d | |||
683ce3254f | |||
df555731c4 | |||
641fc0cae9 | |||
5ab1d6896a | |||
0ae2d92fcd | |||
26c8201e03 | |||
37521c69a2 | |||
43383a5b3c | |||
bfea3e4dd6 | |||
35b02a52e1 | |||
8e3e566915 | |||
968b9a0304 | |||
c97f0f3d22 | |||
65b1124d81 | |||
89034e1f9d | |||
9f2fa6a8be | |||
19cf8787d8 | |||
a80c34c1df | |||
ab8900f910 | |||
ce2d0b0e12 | |||
87d98781a9 | |||
79dc95dd66 | |||
c6533202f7 | |||
b5dc9bc0ba | |||
79891c6677 | |||
68aa1aea8e | |||
4ffaf4128e | |||
801ca47a0c | |||
43f5062169 | |||
7ab4dd7f4b | |||
71c0104236 | |||
a608d21571 | |||
935ddaa0b2 | |||
945e7b75fd | |||
5e90780590 | |||
a8a21ea7ce | |||
23444e4b81 | |||
a3ea1f9429 | |||
78d4b6d8a7 | |||
3e3399adc6 | |||
1e554dd0fe | |||
388e7088e1 | |||
e65289cd0d | |||
c4d68a8352 | |||
7be0a93251 | |||
175f47293a | |||
27936fce04 | |||
592680302f | |||
55318cf14b | |||
83ead0c304 | |||
38ec55bc72 | |||
c4f97ed65e | |||
78a426e9ac | |||
928b4bbbe5 | |||
d27c465371 | |||
4d62f63c71 | |||
f4039b085a | |||
53ed10cfc8 | |||
0859dbe57f | |||
42bc4bb1f4 | |||
caead8a3a4 | |||
7394c7997b | |||
0298e4ab4c | |||
e3a5cdbbc2 | |||
6ae46abac0 | |||
615a5f3c39 | |||
6812dc1db8 | |||
41595cafce | |||
d6c4e411be | |||
6ca797ec1f | |||
8655988c66 | |||
de5f2940ce | |||
1933e6239b | |||
8aec1e2eb6 | |||
def30c5903 | |||
193f0bbc42 | |||
b7a3b7bf05 | |||
a2bd45fa9b | |||
fd297118f9 | |||
7171edad24 | |||
d8466333f3 | |||
a5e4d586eb | |||
28a1c22438 | |||
74e238322d | |||
1f3227409b | |||
afda1da9c3 | |||
f2857e8f97 | |||
4802b1e2cd | |||
0328304f04 | |||
06d2db0046 | |||
0539610219 | |||
170be8f033 | |||
ca0bb1ab76 | |||
cdccb51456 | |||
870ecb847f | |||
8ba493c60e | |||
f3affff5be | |||
5941a52a0d | |||
04e6beb43c | |||
1284c7ace0 | |||
63414f0348 | |||
c8f22fdfd0 | |||
7737bf5212 | |||
4340f70d72 | |||
6dbd8a6d1a | |||
076871fe44 | |||
be753d7a1a | |||
e3f02553fd | |||
767ef8c489 | |||
482787fbc3 | |||
b2d72f90ce | |||
dd181a90e1 | |||
19faa352e3 | |||
ffda7f6326 | |||
8ebacf4c10 | |||
a02cc25844 | |||
8aeca5b433 | |||
7b4855d7cf | |||
803d7ff282 | |||
a1c724f866 | |||
96609e9173 | |||
f5518739e2 | |||
e7c8d28b99 | |||
dff357a658 | |||
cc271de7df | |||
a7485ab5a3 | |||
7133736702 | |||
2313d82369 | |||
1b42d99e66 | |||
d709f46183 | |||
97c1676bcb | |||
02b92ac3fe | |||
29eb1d5dc5 | |||
a87fc68cfd | |||
bc1cf64df4 | |||
b607d7b223 | |||
4e00eaf9a3 | |||
90cc71d14d | |||
554a3a6b0e | |||
8505e8a1ca | |||
54ec878698 | |||
ea4606fe79 | |||
96de87fdde | |||
55d571ffec | |||
ae347c4fa1 | |||
e93beb7c63 | |||
018098b8ef | |||
1e2fb4bea5 | |||
171f0d2671 | |||
175cb0817e | |||
3b46acc285 | |||
d31ce49e77 | |||
8937c4bf2a | |||
2902a708f9 | |||
20e152e79a | |||
c1210b1c04 | |||
71038c311f | |||
1cf9ae381d | |||
4cb5a27a05 | |||
b0b0124138 | |||
9e12801503 | |||
70a8ab1d1a | |||
f3c4a26483 | |||
9a1e1a92ca | |||
732eddd1b9 | |||
2caa6ad975 | |||
d3e0161a6b | |||
67859f67b0 | |||
b486ae5c26 | |||
aab4a3ab44 | |||
e9e550fb55 | |||
f80ea386a0 | |||
7429762d2e | |||
aabbdc1c1b | |||
3af766bd6e | |||
614b8b4cdd | |||
bec62c1be7 | |||
7f8efca0ba | |||
76de4faf62 | |||
dfc4948a6f | |||
ba0e58e66a | |||
18531f0c09 | |||
93df048bd6 | |||
2927382a2b | |||
b4780d16eb | |||
6f5f4d179b | |||
b9a5dd48f6 | |||
0db7e2873c | |||
b84bfd575f | |||
70bb81bcc3 | |||
7ebb26b734 | |||
fb93609d8b | |||
eb051d64c7 | |||
58bb94a960 | |||
98d932a0f5 | |||
de4b0198b7 | |||
570f8526b0 | |||
d173b79949 | |||
67f73d7198 | |||
6e36d374ea | |||
740b37b70c | |||
782e38bdcf | |||
937404b39c | |||
a0bf223031 | |||
6b15caab4b | |||
3aafea0d98 | |||
1c68852f45 | |||
4f6065b4fa | |||
14944982fb | |||
10bf74e045 | |||
a9d9e6b572 | |||
79f8e88e5f | |||
bddfe3ed86 | |||
a308179d81 | |||
f8648ff4c4 | |||
552f91b6b8 | |||
726eb58724 | |||
eb5a23fc5b | |||
d4c8e03f86 | |||
46d13d2b08 | |||
003dc37817 | |||
847a441d59 | |||
a5f3220df0 | |||
a5df6d419d | |||
78a25f0b17 | |||
dc7ea9c126 | |||
70adc4df32 | |||
c47f491e2f | |||
1d9b89db23 | |||
ec70f2420c | |||
f525685765 | |||
4970fd22dc | |||
ac9392b9e6 | |||
5ba86ca254 | |||
87826daae9 | |||
7df52a155e | |||
d2184f418f | |||
f91f81158f | |||
bb100de702 | |||
322e76d2a9 | |||
1444f1239f | |||
5bd028bff5 | |||
25feb745bc | |||
7e0654ae0a | |||
00d1b7519f | |||
faadbf700d | |||
180d42541a | |||
bdc4eff1c4 | |||
fa0b52fc3a | |||
f9f1391057 | |||
34b7081303 | |||
e0cd71069f | |||
36e898d668 | |||
a90827c9b2 | |||
dd451d3ccd | |||
937f4f2f40 | |||
e7718b1fba | |||
29b599bc8d | |||
6bbd34eed9 | |||
f939f689fc | |||
a48fba0102 | |||
02923138d0 | |||
87d142bace | |||
933ad52398 | |||
9d52ef5fbe | |||
34748d23be | |||
15dae0fd92 | |||
ccf6cf5e20 | |||
36d4b20bdc | |||
a70810ffae | |||
526df43233 | |||
660be7d579 | |||
876109d3a5 | |||
39493e0f24 | |||
62413331da | |||
68d25afcba | |||
182fe900b8 | |||
0fb4f6ab6a | |||
ebc5e72908 | |||
081b692736 | |||
e1c68d22a3 | |||
b7dc8f425e | |||
4a65676738 | |||
000ca7c7b7 | |||
9386270b57 | |||
f0886c5f6e | |||
239cd2b744 | |||
db9827779e | |||
905eed6643 | |||
5711d96744 | |||
fc2734c484 | |||
75261a0ce4 | |||
7fc2cd9cde | |||
e72a763019 | |||
ff20174572 | |||
7d08183334 | |||
66f7505baa | |||
e54465b226 | |||
78c17ba353 | |||
bfac9e398d | |||
eef5661008 | |||
4b9a7323ad | |||
429ccf21b6 | |||
4805a77d40 | |||
3a707a143d | |||
b4264063f4 | |||
535c782b7c | |||
3833ad534f | |||
3d42946ff5 | |||
a1afa1c1b0 | |||
8fb65292cf | |||
29ce84ff33 | |||
378c5a0d4e | |||
ca569038e8 | |||
4092eba606 | |||
63e71d8a3d | |||
278caf72e0 | |||
ff5abfb4b7 | |||
4d6b7556c3 | |||
e5e7dbbb58 | |||
e394d3d4c5 | |||
370d9dfea1 | |||
2a00272efe | |||
6c62c1f362 | |||
3daf287de8 | |||
5c4c02161c | |||
1bd4937a4b | |||
0ac2abd5eb | |||
efe5098f24 | |||
ba859fc9ad | |||
c73163f525 | |||
bf317f61c5 | |||
9a4cd11748 | |||
b1b13c41f0 | |||
47c3a56aca | |||
8acf5f3079 | |||
2f39574123 | |||
8b1ab4b8d2 | |||
c0350702fd | |||
55880844a5 | |||
729282c0c4 | |||
d603c3b5cd | |||
5cda1ec703 | |||
ec3756a69f | |||
0b49fa98f9 | |||
226c7eb8e3 | |||
addffcffcb | |||
be262c6a70 | |||
a374f00613 | |||
fcdf837f33 | |||
febc0a5237 | |||
2e0a1b9a0e | |||
4fae18a719 | |||
d9beaa7591 | |||
2ba5ad558b | |||
de6bb12b95 | |||
aa2715cced | |||
b096834a54 | |||
a19140e186 | |||
e0b25cdcf9 | |||
e332c8bc27 | |||
7318632db9 | |||
1b38a6478b | |||
6ceea60c92 | |||
c1da7f778b | |||
fcaebb4416 | |||
0be3beb30e | |||
3210dd8d3e | |||
5e51c6abef | |||
5e953b0857 | |||
c76c80043f | |||
3a49d6d28f | |||
94e34ff366 | |||
af27c76b2c | |||
60336c9555 | |||
a85b7aa9f8 | |||
dca5e9889a | |||
67beebf859 | |||
16c8a19be8 | |||
0c03b6ba82 | |||
228e6d7d03 | |||
cd6376e368 | |||
c26eba9693 | |||
31531ee882 | |||
b7fca3af42 | |||
368c2073b2 | |||
7527a02c60 | |||
757df8c726 | |||
c6121cb31e | |||
5e734088e3 | |||
eecab472eb | |||
b71631d2ec | |||
3ec78e27b4 | |||
6265bdf026 | |||
0d78ddf872 | |||
10efecaa9c | |||
689b045a9b | |||
7692905ba5 | |||
0ef3bb1deb | |||
4c279a192a | |||
af7eaf1f05 | |||
c198e216fd | |||
2325bffbcb | |||
3d3e05d43d | |||
c62ae69fe5 | |||
686aa870ea | |||
ecac69134d | |||
8a99be1db3 | |||
26f0c98ef8 | |||
5375e425ac | |||
650b920e11 | |||
7a43bd87c2 | |||
ebb2f1396b | |||
3a4ac1fc4e | |||
188ff3392d | |||
0cf7e91475 | |||
609bbac8d5 | |||
f4431b8d1e | |||
45bf2e0715 | |||
d7040a23e4 | |||
6f8fe0ecac | |||
7181e3eb87 | |||
011e91140c | |||
0f1224de98 | |||
2e356172d4 | |||
c6bf9a7cf6 | |||
0816cfe273 | |||
1cea18346b | |||
0d4bc500ee | |||
ff313e0301 | |||
85001d2622 | |||
654b240e9d | |||
85f3f5615f | |||
813a1981d9 | |||
57fd46d3a1 | |||
ffbbfdfc0d | |||
31bc67a1cd | |||
ec4f2d2100 | |||
5b40914552 | |||
e8025f09bd | |||
aa547a1610 | |||
22025b0c3a | |||
4358d11191 | |||
5fd63c12a8 | |||
37ff4090b4 | |||
9550de6760 | |||
f5838ffefb | |||
2a374c9b22 | |||
59ba47eca5 | |||
60d0b2976b | |||
a44e75201f | |||
eb3de929bf | |||
d0fa9f8f1e | |||
a3886b0080 | |||
b872764624 | |||
075b1fd6f6 | |||
2430e180f3 | |||
0c22af9578 | |||
4976e52389 | |||
88afb23a8c | |||
a7a0ca87c9 | |||
7371718afc | |||
1e5ffe636f | |||
ca1e8d09cc | |||
ddc5e2c23f | |||
3086fd3ce0 | |||
5ea2832ae0 | |||
5cb449efed | |||
46f8344d30 | |||
b370fe6838 | |||
d6b28a4eb1 | |||
72ead2bbcc | |||
648701afdd | |||
389f540f44 | |||
ceda25fb36 | |||
c4e2019657 | |||
b64b2629b9 | |||
9443a96f0c | |||
6af73d087b | |||
c61f2af8b3 | |||
a3aee79e9c | |||
7d0f626fd5 | |||
e34a98620c | |||
6c32a05378 | |||
ec4c830c09 | |||
9e5d809650 | |||
1746a81234 | |||
efe7a7536d | |||
11dd7f6b8c | |||
e29bb5b88b | |||
57b794f72a | |||
8bfe3f632e | |||
21e47e61f0 | |||
49cc6b5100 | |||
10a42c8b0d | |||
f59ea790ca | |||
28f1170d95 | |||
8eb7cf7bae | |||
1be0695a21 | |||
65480456cd | |||
5962577b53 | |||
1222776da3 | |||
13633a0532 | |||
1eda9e9053 | |||
2557a0bd84 | |||
e1c533e9de | |||
bb8183d0ee | |||
9478a84af7 | |||
56eba28a50 | |||
f8d323bc7b | |||
8857fe5550 | |||
45a38a5425 | |||
7f471910ed | |||
f6190dd82d | |||
51f072cc72 | |||
b65898e0dd | |||
7b753c9d30 | |||
dc8d70102c | |||
2cbb157f2d | |||
2c47c21038 | |||
5904d5764c | |||
d5443a34ea | |||
af79b74c8c | |||
bfc327833c | |||
427abb081c | |||
6992090cda | |||
9b0c481525 | |||
77cfe0be01 | |||
041da814d2 | |||
7b7f3ea025 | |||
53d5c0854f | |||
b2f1e78d62 | |||
9ba17d2e90 | |||
cc18f62fb5 | |||
8950cc48a6 | |||
51edaed610 | |||
3a2accdebb | |||
6cef4f90e1 | |||
f5e416d6bf | |||
5eba38a24e | |||
7f5d6ca1c7 | |||
e4318e95a5 | |||
eaa86664bd | |||
5a1053e4fb | |||
0052ebf334 | |||
d9f7b24cc7 | |||
67ca0a4d20 | |||
fea91a35f6 | |||
3214bc4f81 | |||
a0befe59c3 | |||
5f614db59b | |||
cc4b03604d | |||
573692dbdf | |||
78dcfd830c | |||
95d0dc0419 | |||
9247dc898c | |||
bd900d8d55 | |||
a9eb48c4c6 | |||
8afe7c261f | |||
543870ff02 | |||
92707efe8a | |||
42040eb6c8 | |||
18e369bcf4 | |||
4ba8f02f59 | |||
a7fc8bdf53 | |||
3710346764 | |||
bb0d2ef17c | |||
d5a870226c | |||
34aa3ac207 | |||
7d38041d23 | |||
e643d0a086 | |||
dcd655fa4b | |||
f57f49c3c5 | |||
4f146772e7 | |||
083dffe8ed | |||
c43a73e756 | |||
0c94680a45 | |||
832c0ff683 | |||
95b4fe7378 | |||
ed12bcefdb | |||
41af00a7d6 | |||
e34cd0ba3f | |||
18664d39f3 | |||
680c31b843 | |||
ba5005c467 | |||
7a8fde6637 | |||
973e0e7448 | |||
101702552f | |||
8aa7433a80 | |||
e7d48f1d3c | |||
7e7a68a61d | |||
c558f5af85 | |||
a33457ff7f | |||
16be8deb00 | |||
dfcf1c1575 | |||
d1dc7cd269 | |||
88c2b3da58 | |||
0bfd747c95 | |||
d150403395 | |||
1939aaca93 | |||
d0cb714f69 | |||
54a35df9e9 | |||
b1a29c9514 | |||
af21093012 | |||
cea1b08a98 | |||
c7f1329c04 | |||
a02f9313ff | |||
5a7d975ed6 | |||
455618dc26 | |||
bddfc5b526 | |||
bcbf7e6270 | |||
83886cdcfb | |||
dbfb4cbbbb | |||
fe477f0a0b | |||
dd24ab6f70 | |||
47592377f2 | |||
e3ecc42e88 | |||
999b8d3c68 | |||
8e5c26270e | |||
e67aa430cd | |||
8e57a2e386 | |||
d28184ce72 | |||
94827d6d55 | |||
6c676869a0 | |||
2c8f2ae404 | |||
3eb00c526d | |||
83e25ef26c | |||
8f4f7a677f | |||
b54c5f8545 | |||
17ac0e4b5f | |||
4730690978 | |||
9d77cd7761 | |||
5b81a1a6d5 | |||
f788ef97de | |||
e4ec046363 | |||
cdc3682baa | |||
dae66d7f73 | |||
d6d1b51970 | |||
6f301f01dc | |||
71607fff2d | |||
6ed602309f | |||
20cc48cd87 | |||
f2331b0603 | |||
8c2ddec400 | |||
c8e12b9ac9 | |||
452d1d01b8 | |||
e375ffe8f1 | |||
2ea9fc5d61 | |||
912e29f8af | |||
72054a7d30 | |||
ab2c5ef9bb | |||
ab0b4936ec | |||
2dd1aaab63 | |||
c05cd07ff7 | |||
adfc22499c | |||
44498ca8c7 | |||
a40566eefb | |||
77f0dd653a | |||
8ed70084db | |||
2f4e3862e3 | |||
667393e8cf | |||
c6dd7b4cc5 | |||
db0501f31b | |||
3be5002f15 | |||
4389f277d6 | |||
cf10cb1c09 | |||
38214cf5be | |||
f4740d2639 | |||
48309c0f6d | |||
cf1f42ebf8 | |||
7c8bb0ba69 | |||
14eedaa029 | |||
73e25aad76 | |||
f3f4529d84 | |||
5dbe6294fb | |||
91c9bfed3a | |||
420ccad91b | |||
1d0f8b4c6d | |||
3396626a0c | |||
8c13d32036 | |||
5d523723ed | |||
6d2fa690dc | |||
470b8c59e7 | |||
81975e919b | |||
436d8f0785 | |||
fa235d97af | |||
42f8c71d4e | |||
9a241af241 | |||
69d22b84f9 | |||
7ea1bffea2 | |||
c1f4155cd8 | |||
85fda3ed7f | |||
8998bdec17 | |||
c9b6a3f01c | |||
05c5f66012 | |||
7fd59c4f10 | |||
6fe127286f | |||
406bcbef9d | |||
9eb25f60c3 | |||
b7c10d2adb | |||
816149efd3 | |||
aa6eefcc76 | |||
9ef9e48da9 | |||
166e36aaef | |||
667b548d3b | |||
5dfef69688 | |||
4e558320a9 | |||
9f6ebd8e10 | |||
c4a0fcbf43 | |||
8104cb40cd | |||
8d2eff76f2 | |||
009b0bfdde | |||
924963eba0 | |||
de175b80fe | |||
9fc7258537 | |||
50df4b53db | |||
02ad4d5200 | |||
56ae54c2ff | |||
a70e040e33 | |||
c0d82fe83f | |||
f1dd77982a | |||
be8697cb9a |
27
.mtn-ignore
27
.mtn-ignore
@ -1,7 +1,10 @@
|
||||
# Just to try and prevent some noob disasters.
|
||||
# Use mtn add --no-respect-ignore foo.jar to ignore this ignore list
|
||||
|
||||
# Temporary/build files
|
||||
_jsp\.java$
|
||||
\.bz2$
|
||||
\.tar$
|
||||
\.class$
|
||||
\.diff$
|
||||
\.exe$
|
||||
@ -15,12 +18,30 @@ _jsp\.java$
|
||||
\.su2$
|
||||
\.tar$
|
||||
\.war$
|
||||
.\deb$
|
||||
\.zip$
|
||||
^\.
|
||||
^build
|
||||
^pkg-temp/
|
||||
~$
|
||||
web-fragment.xml
|
||||
web-out.xml
|
||||
|
||||
# Temporary/build dirs
|
||||
^build
|
||||
^pkg-temp
|
||||
/build
|
||||
/classes/
|
||||
/classes
|
||||
/dist
|
||||
/mo
|
||||
/tmp
|
||||
^apps/jetty/jettylib
|
||||
|
||||
# Debian-related
|
||||
^debian/copyright
|
||||
^debian/changelog
|
||||
|
||||
# Build property overrides
|
||||
override.properties
|
||||
|
||||
# Reporting
|
||||
sloccount.sc
|
||||
^reports/
|
||||
|
114
.tx/config
114
.tx/config
@ -9,14 +9,38 @@ trans.es = apps/i2ptunnel/locale/messages_es.po
|
||||
trans.fr = apps/i2ptunnel/locale/messages_fr.po
|
||||
trans.hu = apps/i2ptunnel/locale/messages_hu.po
|
||||
trans.it = apps/i2ptunnel/locale/messages_it.po
|
||||
trans.ja = apps/i2ptunnel/locale/messages_ja.po
|
||||
trans.nb = apps/i2ptunnel/locale/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale/messages_nl.po
|
||||
trans.pl = apps/i2ptunnel/locale/messages_pl.po
|
||||
trans.pt = apps/i2ptunnel/locale/messages_pt.po
|
||||
trans.ru = apps/i2ptunnel/locale/messages_ru.po
|
||||
trans.ru_RU = apps/i2ptunnel/locale/messages_ru.po
|
||||
trans.sv_SE = apps/i2ptunnel/locale/messages_sv.po
|
||||
trans.uk_UA = apps/i2ptunnel/locale/messages_uk.po
|
||||
trans.vi = apps/i2ptunnel/locale/messages_vi.po
|
||||
trans.zh_CN = apps/i2ptunnel/locale/messages_zh.po
|
||||
|
||||
[I2P.proxy]
|
||||
source_file = apps/i2ptunnel/locale-proxy/messages_en.po
|
||||
source_lang = en
|
||||
trans.ar = apps/i2ptunnel/locale-proxy/messages_ar.po
|
||||
trans.cs = apps/i2ptunnel/locale-proxy/messages_cs.po
|
||||
trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
|
||||
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
|
||||
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
|
||||
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
|
||||
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
|
||||
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
|
||||
trans.pl = apps/i2ptunnel/locale-proxy/messages_pl.po
|
||||
trans.pt = apps/i2ptunnel/locale-proxy/messages_pt.po
|
||||
trans.ro = apps/i2ptunnel/locale-proxy/messages_ro.po
|
||||
trans.ru_RU = apps/i2ptunnel/locale-proxy/messages_ru.po
|
||||
trans.sv_SE = apps/i2ptunnel/locale-proxy/messages_sv.po
|
||||
trans.uk_UA = apps/i2ptunnel/locale-proxy/messages_uk.po
|
||||
trans.vi = apps/i2ptunnel/locale-proxy/messages_vi.po
|
||||
trans.zh_CN = apps/i2ptunnel/locale-proxy/messages_zh.po
|
||||
|
||||
[I2P.routerconsole]
|
||||
source_file = apps/routerconsole/locale/messages_en.po
|
||||
source_lang = en
|
||||
@ -26,20 +50,67 @@ trans.da = apps/routerconsole/locale/messages_da.po
|
||||
trans.de = apps/routerconsole/locale/messages_de.po
|
||||
trans.el = apps/routerconsole/locale/messages_el.po
|
||||
trans.es = apps/routerconsole/locale/messages_es.po
|
||||
trans.et_EE = apps/routerconsole/locale/messages_ee.po
|
||||
trans.et_EE = apps/routerconsole/locale/messages_et.po
|
||||
trans.fi = apps/routerconsole/locale/messages_fi.po
|
||||
trans.fr = apps/routerconsole/locale/messages_fr.po
|
||||
trans.hu = apps/routerconsole/locale/messages_hu.po
|
||||
trans.it = apps/routerconsole/locale/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale/messages_ja.po
|
||||
trans.nb = apps/routerconsole/locale/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale/messages_pl.po
|
||||
trans.pt = apps/routerconsole/locale/messages_pt.po
|
||||
trans.ru = apps/routerconsole/locale/messages_ru.po
|
||||
trans.ro = apps/routerconsole/locale/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale/messages_ru.po
|
||||
trans.sv_SE = apps/routerconsole/locale/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale/messages_tr.po
|
||||
trans.uk_UA = apps/routerconsole/locale/messages_uk.po
|
||||
trans.vi = apps/routerconsole/locale/messages_vi.po
|
||||
trans.zh_CN = apps/routerconsole/locale/messages_zh.po
|
||||
|
||||
[I2P.welcome]
|
||||
source_file = apps/routerconsole/locale-news/messages_en.po
|
||||
source_lang = en
|
||||
trans.ar = apps/routerconsole/locale-news/messages_ar.po
|
||||
trans.de = apps/routerconsole/locale-news/messages_de.po
|
||||
trans.es = apps/routerconsole/locale-news/messages_es.po
|
||||
trans.fr = apps/routerconsole/locale-news/messages_fr.po
|
||||
trans.ja = apps/routerconsole/locale-news/messages_ja.po
|
||||
trans.it = apps/routerconsole/locale-news/messages_it.po
|
||||
trans.nl = apps/routerconsole/locale-news/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-news/messages_pl.po
|
||||
trans.pt = apps/routerconsole/locale-news/messages_pt.po
|
||||
trans.ro = apps/routerconsole/locale-news/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
|
||||
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
|
||||
trans.zh_CN = apps/routerconsole/locale-news/messages_zh.po
|
||||
|
||||
[I2P.countries]
|
||||
type = PO
|
||||
source_file = apps/routerconsole/locale-countries/messages_en.po
|
||||
source_lang = en
|
||||
trans.da = apps/routerconsole/locale-countries/messages_da.po
|
||||
trans.de = apps/routerconsole/locale-countries/messages_de.po
|
||||
trans.el = apps/routerconsole/locale-countries/messages_el.po
|
||||
trans.es = apps/routerconsole/locale-countries/messages_es.po
|
||||
trans.et_EE = apps/routerconsole/locale-countries/messages_et.po
|
||||
trans.fi = apps/routerconsole/locale-countries/messages_fi.po
|
||||
trans.fr = apps/routerconsole/locale-countries/messages_fr.po
|
||||
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
|
||||
trans.it = apps/routerconsole/locale-countries/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
|
||||
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
|
||||
trans.pt = apps/routerconsole/locale-countries/messages_pt.po
|
||||
trans.ro = apps/routerconsole/locale-countries/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-countries/messages_ru.po
|
||||
trans.sv_SE = apps/routerconsole/locale-countries/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale-countries/messages_tr.po
|
||||
trans.vi = apps/routerconsole/locale-countries/messages_vi.po
|
||||
trans.zh_CN = apps/routerconsole/locale-countries/messages_zh.po
|
||||
|
||||
[I2P.i2psnark]
|
||||
source_file = apps/i2psnark/locale/messages_en.po
|
||||
source_lang = en
|
||||
@ -50,10 +121,12 @@ trans.es = apps/i2psnark/locale/messages_es.po
|
||||
trans.fr = apps/i2psnark/locale/messages_fr.po
|
||||
trans.hu = apps/i2psnark/locale/messages_hu.po
|
||||
trans.it = apps/i2psnark/locale/messages_it.po
|
||||
trans.nb = apps/i2psnark/locale/messages_nb.po
|
||||
trans.nl = apps/i2psnark/locale/messages_nl.po
|
||||
trans.pl = apps/i2psnark/locale/messages_pl.po
|
||||
trans.pt = apps/i2psnark/locale/messages_pt.po
|
||||
trans.ru = apps/i2psnark/locale/messages_ru.po
|
||||
trans.ro = apps/i2psnark/locale/messages_ro.po
|
||||
trans.ru_RU = apps/i2psnark/locale/messages_ru.po
|
||||
trans.sv_SE = apps/i2psnark/locale/messages_sv.po
|
||||
trans.vi = apps/i2psnark/locale/messages_vi.po
|
||||
trans.zh_CN = apps/i2psnark/locale/messages_zh.po
|
||||
@ -70,10 +143,12 @@ trans.es = apps/susidns/locale/messages_es.po
|
||||
trans.fr = apps/susidns/locale/messages_fr.po
|
||||
trans.hu = apps/susidns/locale/messages_hu.po
|
||||
trans.it = apps/susidns/locale/messages_it.po
|
||||
trans.ja = apps/susidns/locale/messages_ja.po
|
||||
trans.nl = apps/susidns/locale/messages_nl.po
|
||||
trans.pl = apps/susidns/locale/messages_pl.po
|
||||
trans.pt = apps/susidns/locale/messages_pt.po
|
||||
trans.ru = apps/susidns/locale/messages_ru.po
|
||||
trans.ro = apps/susidns/locale/messages_ro.po
|
||||
trans.ru_RU = apps/susidns/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susidns/locale/messages_sv.po
|
||||
trans.uk_UA = apps/susidns/locale/messages_uk.po
|
||||
trans.vi = apps/susidns/locale/messages_vi.po
|
||||
@ -91,11 +166,14 @@ trans.es = apps/desktopgui/locale/messages_es.po
|
||||
trans.fr = apps/desktopgui/locale/messages_fr.po
|
||||
trans.hu = apps/desktopgui/locale/messages_hu.po
|
||||
trans.it = apps/desktopgui/locale/messages_it.po
|
||||
trans.ja = apps/desktopgui/locale/messages_ja.po
|
||||
trans.nl = apps/desktopgui/locale/messages_nl.po
|
||||
trans.pl = apps/desktopgui/locale/messages_pl.po
|
||||
trans.ru = apps/desktopgui/locale/messages_ru.po
|
||||
trans.ro = apps/desktopgui/locale/messages_ro.po
|
||||
trans.ru_RU = apps/desktopgui/locale/messages_ru.po
|
||||
trans.sv_SE = apps/desktopgui/locale/messages_sv.po
|
||||
trans.uk_UA = apps/desktopgui/locale/messages_uk.po
|
||||
trans.tr_TR = apps/desktopgui/locale/messages_tr.po
|
||||
trans.vi = apps/desktopgui/locale/messages_vi.po
|
||||
trans.zh_CN = apps/desktopgui/locale/messages_zh.po
|
||||
|
||||
@ -109,9 +187,11 @@ trans.fr = apps/susimail/locale/messages_fr.po
|
||||
trans.hu = apps/susimail/locale/messages_hu.po
|
||||
trans.it = apps/susimail/locale/messages_it.po
|
||||
trans.nl = apps/susimail/locale/messages_nl.po
|
||||
trans.ru = apps/susimail/locale/messages_ru.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.pl = apps/susimail/locale/messages_pl.po
|
||||
trans.pt = apps/susimail/locale/messages_pt.po
|
||||
trans.ro = apps/susimail/locale/messages_ro.po
|
||||
trans.uk_UA = apps/susimail/locale/messages_uk.po
|
||||
trans.vi = apps/susimail/locale/messages_vi.po
|
||||
trans.zh_CN = apps/susimail/locale/messages_zh.po
|
||||
@ -127,9 +207,27 @@ trans.fr = debian/po/fr.po
|
||||
trans.it = debian/po/it.po
|
||||
trans.hu = debian/po/hu.po
|
||||
trans.pl = debian/po/pl.po
|
||||
trans.ru = debian/po/ru.po
|
||||
trans.pt = debian/po/pt.po
|
||||
trans.ro = debian/po/ro.po
|
||||
trans.ru_RU = debian/po/ru.po
|
||||
trans.sv_SE = debian/po/sv.po
|
||||
trans.uk_UA = debian/po/uk.po
|
||||
trans.tr_TR = debian/po/tr.po
|
||||
trans.zh_CN = debian/po/zh.po
|
||||
|
||||
[I2P.i2prouter-script]
|
||||
source_file = installer/resources/locale/po/messages_en.po
|
||||
source_lang = en
|
||||
trans.de = installer/resources/locale/po/messages_de.po
|
||||
trans.es = installer/resources/locale/po/messages_es.po
|
||||
trans.fr = installer/resources/locale/po/messages_fr.po
|
||||
trans.it = installer/resources/locale/po/messages_it.po
|
||||
trans.pt = installer/resources/locale/po/messages_pt.po
|
||||
trans.ro = installer/resources/locale/po/messages_ro.po
|
||||
trans.sv_SE = installer/resources/locale/po/messages_sv.po
|
||||
trans.ru_RU = installer/resources/locale/po/messages_ru.po
|
||||
trans.tr_TR = installer/resources/locale/po/messages_tr.po
|
||||
trans.zh_CN = installer/resources/locale/po/messages_zh.po
|
||||
|
||||
[main]
|
||||
host = http://www.transifex.net
|
||||
|
@ -42,4 +42,4 @@ Supported JVMs:
|
||||
Windows: Latest available from http://java.com/download (1.5+ supported)
|
||||
Linux: Latest available from http://java.com/download (1.5+ supported)
|
||||
FreeBSD: 1.5-compatible (NIO required)
|
||||
Other operating systems and JVMs: See http://trac.i2p2.de/wiki/java
|
||||
Other operating systems and JVMs: See https://trac.i2p2.de/wiki/java
|
||||
|
@ -2,7 +2,7 @@ I2P source installation instructions
|
||||
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
Non-linux operating systems and JVMs: See http://trac.i2p2.de/wiki/java
|
||||
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
|
||||
Apache Ant 1.7.0 or higher
|
||||
The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
|
19
LICENSE.txt
19
LICENSE.txt
@ -72,6 +72,9 @@ Public domain except as listed below:
|
||||
Copyright (c) 2006, Matthew Estes
|
||||
See licenses/LICENSE-BlockFile.txt
|
||||
|
||||
SipHashInline:
|
||||
Copyright 2012 Hiroshi Nakamura <nahi@ruby-lang.org>
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
Router (router.jar):
|
||||
Public domain except as listed below:
|
||||
@ -129,7 +132,7 @@ Installer:
|
||||
|
||||
|
||||
|
||||
Java Service Wrapper Community Edition 32-bit 3.5.13:
|
||||
Java Service Wrapper Community Edition 32-bit 3.5.19:
|
||||
Copyright (C) 1999-2011 Tanuki Software, Ltd. All Rights Reserved.
|
||||
See licenses/LICENSE-Wrapper.txt
|
||||
|
||||
@ -174,10 +177,11 @@ Applications:
|
||||
By welterde.
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Jetty 6.1.26:
|
||||
Copyright 1995-2009 Mort Bay Consulting Pty Ltd
|
||||
See licenses/LICENSE-Jetty.txt
|
||||
Jetty 7.6.13.v20130916:
|
||||
See licenses/ABOUT-Jetty.html
|
||||
See licenses/NOTICE-Jetty.html
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/LICENSE-ECLIPSE-1.0.html
|
||||
See licenses/NOTICE-Commons-Logging.txt
|
||||
|
||||
JRobin 1.5.9.1:
|
||||
@ -197,12 +201,13 @@ Applications:
|
||||
- Jersey and EU flag icons: public domain, courtesy Xrmap flag
|
||||
collection http://www.arvernes.com/wiki/index.php/Xrmap
|
||||
- Guernsey and Isle of Man flags from the Open Clip Art Library, released into the public domain
|
||||
- Curaçao, courtesy of David Benbennick, released into the public domain
|
||||
- All other flag icons: public domain, courtesy mjames@gmail.com http://www.famfamfam.com/
|
||||
Silk icons: See licenses/LICENSE-SilkIcons.txt
|
||||
FatCow icons: See licenses/LICENSE-FatCowIcons.txt
|
||||
|
||||
GeoIP Data:
|
||||
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
|
||||
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com/
|
||||
See licenses/LICENSE-GeoIP.txt
|
||||
|
||||
Router Console and I2PSnark themes:
|
||||
@ -238,8 +243,8 @@ Applications:
|
||||
Bundles systray4j-2.4.1:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
Tomcat 6.0.35:
|
||||
Copyright 1999-2011 The Apache Software Foundation
|
||||
Tomcat 6.0.37:
|
||||
Copyright 1999-2013 The Apache Software Foundation
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Tomcat.txt
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
Non-linux operating systems and JVMs: See http://trac.i2p2.de/wiki/java
|
||||
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
|
||||
Apache Ant 1.7.0 or higher
|
||||
The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
@ -22,13 +22,13 @@ Documentation:
|
||||
API: run 'ant javadoc' then start at build/javadoc/index.html
|
||||
|
||||
Latest release:
|
||||
http://www.i2p2.de/download.html
|
||||
http://www.i2p2.de/download
|
||||
|
||||
To get development branch from source control:
|
||||
http://www.i2p2.de/newdevelopers.html
|
||||
http://www.i2p2.de/newdevelopers
|
||||
|
||||
FAQ:
|
||||
http://www.i2p2.de/faq.html
|
||||
http://www.i2p2.de/faq
|
||||
|
||||
Need help?
|
||||
IRC irc.freenode.net #i2p
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
INST_DIR=directory
|
||||
|
||||
|
8
apps/BOB/.classpath
Normal file
8
apps/BOB/.classpath
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ministreaming"/>
|
||||
<classpathentry kind="output" path="build"/>
|
||||
</classpath>
|
17
apps/BOB/.project
Normal file
17
apps/BOB/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>BOB</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -83,7 +83,7 @@
|
||||
<pathelement path="${javac.classpath}" />
|
||||
</path>
|
||||
</copy>
|
||||
<copy todir="${dist.dir}/lib" file="../../installer/lib/jbigi/jbigi.jar" />
|
||||
<copy todir="${dist.dir}/lib" file="../../build/jbigi.jar" />
|
||||
|
||||
<!-- Extract the classes inside the jar files -->
|
||||
<unjar dest="${dist.dir}/classes" >
|
||||
|
@ -30,11 +30,11 @@ excludes=**/*.html,**/*.txt
|
||||
file.reference.build-javadoc=../../i2p.i2p/build/javadoc
|
||||
file.reference.i2p.jar=../../core/java/build/i2p.jar
|
||||
file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar
|
||||
file.reference.jbigi.jar=../../installer/lib/jbigi/jbigi.jar
|
||||
file.reference.jbigi.jar=../../build/jbigi.jar
|
||||
file.reference.mstreaming.jar=../ministreaming/java/build/mstreaming.jar
|
||||
file.reference.router.jar=../../router/java/build/router.jar
|
||||
file.reference.streaming.jar=../streaming/java/build/streaming.jar
|
||||
file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar
|
||||
file.reference.wrapper.jar=../../installer/lib/wrapper/all/wrapper.jar
|
||||
includes=**
|
||||
jar.compress=true
|
||||
javac.classpath=\
|
||||
|
@ -308,7 +308,7 @@ public class BOB {
|
||||
database.releaseReadLock();
|
||||
database.getWriteLock();
|
||||
nickinfo.getWriteLock();
|
||||
nickinfo.add(P_STOPPING, new Boolean(true));
|
||||
nickinfo.add(P_STOPPING, Boolean.valueOf(true));
|
||||
nickinfo.releaseWriteLock();
|
||||
database.releaseWriteLock();
|
||||
} else {
|
||||
|
@ -388,7 +388,7 @@ public class DoCMDS implements Runnable {
|
||||
* Does the base64 information look OK
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @return OK
|
||||
*/
|
||||
private boolean is64ok(String data) {
|
||||
try {
|
||||
@ -665,7 +665,7 @@ public class DoCMDS implements Runnable {
|
||||
break die;
|
||||
}
|
||||
try {
|
||||
nickinfo.add(P_QUIET, new Boolean(Boolean.parseBoolean(Arg) == true));
|
||||
nickinfo.add(P_QUIET, Boolean.valueOf(Arg));
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
wunlock();
|
||||
@ -817,10 +817,10 @@ public class DoCMDS implements Runnable {
|
||||
try {
|
||||
database.add(Arg, nickinfo);
|
||||
nickinfo.add(P_NICKNAME, Arg);
|
||||
nickinfo.add(P_STARTING, new Boolean(false));
|
||||
nickinfo.add(P_RUNNING, new Boolean(false));
|
||||
nickinfo.add(P_STOPPING, new Boolean(false));
|
||||
nickinfo.add(P_QUIET, new Boolean(false));
|
||||
nickinfo.add(P_STARTING, Boolean.valueOf(false));
|
||||
nickinfo.add(P_RUNNING, Boolean.valueOf(false));
|
||||
nickinfo.add(P_STOPPING, Boolean.valueOf(false));
|
||||
nickinfo.add(P_QUIET, Boolean.valueOf(false));
|
||||
nickinfo.add(P_INHOST, "localhost");
|
||||
nickinfo.add(P_OUTHOST, "localhost");
|
||||
Properties Q = new Properties();
|
||||
@ -989,7 +989,7 @@ public class DoCMDS implements Runnable {
|
||||
prt = Integer.parseInt(Arg);
|
||||
if (prt > 1 && prt < 65536) {
|
||||
try {
|
||||
nickinfo.add(P_INPORT, new Integer(prt));
|
||||
nickinfo.add(P_INPORT, Integer.valueOf(prt));
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
wunlock();
|
||||
@ -1076,7 +1076,7 @@ public class DoCMDS implements Runnable {
|
||||
prt = Integer.parseInt(Arg);
|
||||
if (prt > 1 && prt < 65536) {
|
||||
try {
|
||||
nickinfo.add(P_OUTPORT, new Integer(prt));
|
||||
nickinfo.add(P_OUTPORT, Integer.valueOf(prt));
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
wunlock();
|
||||
@ -1355,7 +1355,7 @@ public class DoCMDS implements Runnable {
|
||||
break die;
|
||||
}
|
||||
|
||||
nickinfo.add(P_STOPPING, new Boolean(true));
|
||||
nickinfo.add(P_STOPPING, Boolean.valueOf(true));
|
||||
try {
|
||||
wunlock();
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class MUXlisten implements Runnable {
|
||||
|
||||
this.database.getWriteLock();
|
||||
this.info.getWriteLock();
|
||||
this.info.add("STARTING", new Boolean(true));
|
||||
this.info.add("STARTING", Boolean.valueOf(true));
|
||||
this.info.releaseWriteLock();
|
||||
this.database.releaseWriteLock();
|
||||
this.database.getReadLock();
|
||||
@ -104,7 +104,7 @@ public class MUXlisten implements Runnable {
|
||||
// Something went bad.
|
||||
this.database.getWriteLock();
|
||||
this.info.getWriteLock();
|
||||
this.info.add("STARTING", new Boolean(false));
|
||||
this.info.add("STARTING", Boolean.valueOf(false));
|
||||
this.info.releaseWriteLock();
|
||||
this.database.releaseWriteLock();
|
||||
throw new IOException(e.toString());
|
||||
@ -112,7 +112,7 @@ public class MUXlisten implements Runnable {
|
||||
// Something went bad.
|
||||
this.database.getWriteLock();
|
||||
this.info.getWriteLock();
|
||||
this.info.add("STARTING", new Boolean(false));
|
||||
this.info.add("STARTING", Boolean.valueOf(false));
|
||||
this.info.releaseWriteLock();
|
||||
this.database.releaseWriteLock();
|
||||
throw new RuntimeException(e);
|
||||
@ -120,7 +120,7 @@ public class MUXlisten implements Runnable {
|
||||
// Something else went bad.
|
||||
this.database.getWriteLock();
|
||||
this.info.getWriteLock();
|
||||
this.info.add("STARTING", new Boolean(false));
|
||||
this.info.add("STARTING", Boolean.valueOf(false));
|
||||
this.info.releaseWriteLock();
|
||||
this.database.releaseWriteLock();
|
||||
e.printStackTrace();
|
||||
@ -160,7 +160,7 @@ public class MUXlisten implements Runnable {
|
||||
try {
|
||||
wlock();
|
||||
try {
|
||||
info.add("RUNNING", new Boolean(true));
|
||||
info.add("RUNNING", Boolean.valueOf(true));
|
||||
} catch (Exception e) {
|
||||
lock.set(false);
|
||||
wunlock();
|
||||
@ -204,7 +204,7 @@ public class MUXlisten implements Runnable {
|
||||
try {
|
||||
wlock();
|
||||
try {
|
||||
info.add("STARTING", new Boolean(false));
|
||||
info.add("STARTING", Boolean.valueOf(false));
|
||||
} catch (Exception e) {
|
||||
wunlock();
|
||||
break quit;
|
||||
@ -258,9 +258,9 @@ public class MUXlisten implements Runnable {
|
||||
try {
|
||||
wlock();
|
||||
try {
|
||||
info.add("STARTING", new Boolean(false));
|
||||
info.add("STOPPING", new Boolean(true));
|
||||
info.add("RUNNING", new Boolean(false));
|
||||
info.add("STARTING", Boolean.valueOf(false));
|
||||
info.add("STOPPING", Boolean.valueOf(true));
|
||||
info.add("RUNNING", Boolean.valueOf(false));
|
||||
} catch (Exception e) {
|
||||
lock.set(false);
|
||||
wunlock();
|
||||
@ -309,9 +309,9 @@ public class MUXlisten implements Runnable {
|
||||
try {
|
||||
wlock();
|
||||
try {
|
||||
info.add("STARTING", new Boolean(false));
|
||||
info.add("STOPPING", new Boolean(false));
|
||||
info.add("RUNNING", new Boolean(false));
|
||||
info.add("STARTING", Boolean.valueOf(false));
|
||||
info.add("STOPPING", Boolean.valueOf(false));
|
||||
info.add("RUNNING", Boolean.valueOf(false));
|
||||
} catch (Exception e) {
|
||||
lock.set(false);
|
||||
wunlock();
|
||||
|
@ -23,7 +23,7 @@ package net.i2p.BOB;
|
||||
public class NamedDB {
|
||||
|
||||
private volatile Object[][] data;
|
||||
private volatile int index, writersWaiting, readers;
|
||||
private int index, writersWaiting, readers;
|
||||
|
||||
/**
|
||||
* make initial NULL object
|
||||
@ -31,7 +31,6 @@ public class NamedDB {
|
||||
*/
|
||||
public NamedDB() {
|
||||
this.data = new Object[1][2];
|
||||
this.index = this.writersWaiting = this.readers = 0;
|
||||
}
|
||||
|
||||
synchronized public void getReadLock() {
|
||||
|
@ -116,7 +116,6 @@ public class TCPio implements Runnable {
|
||||
Aout.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,12 +70,10 @@ public class TCPtoI2P implements Runnable {
|
||||
* @throws IOException
|
||||
*/
|
||||
private static String lnRead(InputStream in) throws IOException {
|
||||
String S;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int b;
|
||||
char c;
|
||||
|
||||
S = new String();
|
||||
|
||||
while (true) {
|
||||
b = in.read();
|
||||
if (b == 13) {
|
||||
@ -87,9 +85,9 @@ public class TCPtoI2P implements Runnable {
|
||||
break;
|
||||
}
|
||||
c = (char) (b & 0x7f); // We only care about ASCII
|
||||
S = new String(S + c);
|
||||
builder.append(c);
|
||||
}
|
||||
return S;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +99,7 @@ public class TCPtoI2P implements Runnable {
|
||||
*/
|
||||
private void Emsg(String e, OutputStream out) throws IOException {
|
||||
// Debugging System.out.println("ERROR TCPtoI2P: " + e);
|
||||
out.write("ERROR ".concat(e).getBytes());
|
||||
out.write("ERROR ".concat(e).getBytes("UTF-8"));
|
||||
out.write(13);
|
||||
out.write(10);
|
||||
out.flush();
|
||||
|
8
apps/addressbook/.classpath
Normal file
8
apps/addressbook/.classpath
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="java/src"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="/jetty/jettylib/javax.servlet.jar"/>
|
||||
<classpathentry kind="output" path="build"/>
|
||||
</classpath>
|
17
apps/addressbook/.project
Normal file
17
apps/addressbook/.project
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>addressbook</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -120,11 +120,11 @@ class AddressBook {
|
||||
subscription.setLastFetched(I2PAppContext.getGlobalContext().clock().now());
|
||||
subf = tmp;
|
||||
} else {
|
||||
a = Collections.EMPTY_MAP;
|
||||
a = Collections.emptyMap();
|
||||
tmp.delete();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
a = Collections.EMPTY_MAP;
|
||||
a = Collections.emptyMap();
|
||||
}
|
||||
this.addresses = a;
|
||||
this.subFile = subf;
|
||||
@ -148,7 +148,7 @@ class AddressBook {
|
||||
try {
|
||||
a = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
a = new HashMap();
|
||||
a = new HashMap<String, String>();
|
||||
}
|
||||
this.addresses = a;
|
||||
this.subFile = null;
|
||||
@ -260,7 +260,7 @@ class AddressBook {
|
||||
* An AddressBook to merge with.
|
||||
* @param overwrite True to overwrite
|
||||
* @param log
|
||||
* The log to write messages about new addresses or conflicts to.
|
||||
* The log to write messages about new addresses or conflicts to. May be null.
|
||||
*
|
||||
* @throws IllegalStateException if this was created with the Subscription constructor.
|
||||
*/
|
||||
|
@ -138,7 +138,8 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Map.Entry))
|
||||
return false;
|
||||
Map.Entry e = (Map.Entry) o;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map.Entry<Object, Object> e = (Map.Entry<Object, Object>) o;
|
||||
return key.equals(e.getKey()) && value.equals(e.getValue());
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class ConfigParser {
|
||||
*
|
||||
*/
|
||||
public static Map<String, String> parse(BufferedReader input) throws IOException {
|
||||
Map<String, String> result = new HashMap();
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
String inputLine;
|
||||
inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
@ -179,7 +179,7 @@ class ConfigParser {
|
||||
*/
|
||||
public static List<String> parseSubscriptions(BufferedReader input)
|
||||
throws IOException {
|
||||
List<String> result = new LinkedList();
|
||||
List<String> result = new LinkedList<String>();
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine).trim();
|
||||
|
@ -37,6 +37,7 @@ import net.i2p.client.naming.SingleFileNamingService;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
@ -47,7 +48,7 @@ import net.i2p.util.SecureDirectory;
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.4";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
private boolean _running;
|
||||
private volatile boolean _running;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
@ -168,7 +169,7 @@ public class Daemon {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.putIfAbsent(key, dest);
|
||||
if (!success) {
|
||||
if (log != null && !success) {
|
||||
try {
|
||||
log.append("Save to published address book " + published.getCanonicalPath() + " failed for new key " + key);
|
||||
} catch (IOException ioe) {}
|
||||
@ -250,14 +251,14 @@ public class Daemon {
|
||||
}
|
||||
delay *= 60 * 60 * 1000;
|
||||
|
||||
List<String> defaultSubs = new LinkedList();
|
||||
List<String> defaultSubs = new LinkedList<String>();
|
||||
// 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, lastFetchedFile, delay, defaultSubs, settings
|
||||
.get("proxy_host"), Integer.parseInt(settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
Log log = SystemVersion.isAndroid() ? null : new Log(logFile);
|
||||
|
||||
// If false, add hosts via naming service; if true, write hosts.txt file directly
|
||||
// Default false
|
||||
@ -330,7 +331,7 @@ public class Daemon {
|
||||
homeFile = new SecureDirectory(System.getProperty("user.dir"));
|
||||
}
|
||||
|
||||
Map<String, String> defaultSettings = new HashMap();
|
||||
Map<String, String> defaultSettings = new HashMap<String, String>();
|
||||
defaultSettings.put("proxy_host", "127.0.0.1");
|
||||
defaultSettings.put("proxy_port", "4444");
|
||||
defaultSettings.put("master_addressbook", "../userhosts.txt");
|
||||
|
@ -81,7 +81,7 @@ class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
// DataHelper.formatDuration(I2PAppContext.getGlobalContext().clock().now() - sub.getLastFetched()) +
|
||||
// " ago but the minimum delay is " +
|
||||
// DataHelper.formatDuration(this.delay));
|
||||
return new AddressBook(Collections.EMPTY_MAP);
|
||||
return new AddressBook(Collections.<String, String> emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class SubscriptionList {
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, File lastFetchedFile, long delay, List<String> defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.subscriptions = new LinkedList<Subscription>();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
this.lastFetchedFile = lastFetchedFile;
|
||||
@ -84,17 +84,17 @@ class SubscriptionList {
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
etags = new HashMap();
|
||||
etags = new HashMap<String, String>();
|
||||
}
|
||||
try {
|
||||
lastModified = ConfigParser.parse(lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap();
|
||||
lastModified = new HashMap<String, String>();
|
||||
}
|
||||
try {
|
||||
lastFetched = ConfigParser.parse(lastFetchedFile);
|
||||
} catch (IOException exp) {
|
||||
lastFetched = new HashMap();
|
||||
lastFetched = new HashMap<String, String>();
|
||||
}
|
||||
for (String location : locations) {
|
||||
this.subscriptions.add(new Subscription(location, etags.get(location),
|
||||
@ -121,9 +121,9 @@ class SubscriptionList {
|
||||
* won't be read back correctly; the '=' should be escaped.
|
||||
*/
|
||||
public void write() {
|
||||
Map<String, String> etags = new HashMap();
|
||||
Map<String, String> lastModified = new HashMap();
|
||||
Map<String, String> lastFetched = new HashMap();
|
||||
Map<String, String> etags = new HashMap<String, String>();
|
||||
Map<String, String> lastModified = new HashMap<String, String>();
|
||||
Map<String, String> lastFetched = new HashMap<String, String>();
|
||||
for (Subscription sub : this.subscriptions) {
|
||||
if (sub.getEtag() != null) {
|
||||
etags.put(sub.getLocation(), sub.getEtag());
|
||||
|
@ -8,12 +8,15 @@
|
||||
<property name="resources" value="resources"/>
|
||||
<property name="javadoc" value="javadoc"/>
|
||||
|
||||
<condition property="no.bundle">
|
||||
<isfalse value="${require.gettext}" />
|
||||
</condition>
|
||||
<property name="javac.compilerargs" value=""/>
|
||||
<property name="require.gettext" value="true" />
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${build}/${resources}"/>
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${build}/${resources}"/>
|
||||
<mkdir dir="${build}/${javadoc}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
</target>
|
||||
@ -39,8 +42,9 @@
|
||||
</copy>
|
||||
</target>
|
||||
|
||||
<target name="bundle" >
|
||||
<target name="bundle" unless="no.bundle">
|
||||
<exec executable="sh" osfamily="unix" failifexecutionfails="true" failonerror="${require.gettext}" >
|
||||
<env key="JAVA_HOME" value="${java.home}" />
|
||||
<arg value="./bundle-messages.sh" />
|
||||
</exec>
|
||||
<exec executable="sh" osfamily="mac" failifexecutionfails="true" failonerror="${require.gettext}" >
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Update messages_xx.po and messages_xx.class files,
|
||||
# from both java and jsp sources.
|
||||
@ -15,6 +16,10 @@ TMPFILE=build/javafiles.txt
|
||||
export TZ=UTC
|
||||
RC=0
|
||||
|
||||
if ! $(which javac > /dev/null 2>&1); then
|
||||
export JAVAC=${JAVA_HOME}/../bin/javac
|
||||
fi
|
||||
|
||||
if [ "$1" = "-p" ]
|
||||
then
|
||||
POUPDATE=1
|
||||
|
@ -11,6 +11,7 @@ msgstr ""
|
||||
"POT-Creation-Date: 2011-02-20 11:53+0000\n"
|
||||
"PO-Revision-Date: 2011-02-26 19:46-0000\n"
|
||||
"Last-Translator: hamada <hamada@mail.i2p>\n"
|
||||
"Language: ar\n"
|
||||
"Language-Team: duck <duck@mail.i2p>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -12,7 +12,7 @@ msgstr ""
|
||||
"PO-Revision-Date: 2010-06-15 14:09+0100\n"
|
||||
"Last-Translator: duck <duck@mail.i2p>\n"
|
||||
"Language-Team: duck <duck@mail.i2p>\n"
|
||||
"Language: \n"
|
||||
"Language: en\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
@ -2,21 +2,25 @@
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
#
|
||||
# Translators:
|
||||
# blabla <blabla@trash-mail.com>, 2011
|
||||
# ducki2p <ducki2p@gmail.com>, 2011
|
||||
# foo <foo@bar>, 2009
|
||||
# Boxoa590, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P desktopgui\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2011-03-30 21:58+0100\n"
|
||||
"Last-Translator: magma <magma@mail.i2p>\n"
|
||||
"Language-Team: duck <duck@mail.i2p>\n"
|
||||
"Language: \n"
|
||||
"PO-Revision-Date: 2013-06-08 04:50+0000\n"
|
||||
"Last-Translator: Boxoa590\n"
|
||||
"Language-Team: French (http://www.transifex.com/projects/p/I2P/language/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@ -24,7 +28,7 @@ msgstr "Démarrer I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P démarre!"
|
||||
msgstr "I2P démarre !"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "Starting"
|
||||
@ -32,7 +36,7 @@ msgstr "Démarrage"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Lancer le navigateur"
|
||||
msgstr "Lancer le navigateur I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
@ -52,5 +56,4 @@ msgstr "Configuration de l'icône de notification"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Activer l'icône de notification"
|
||||
|
||||
msgstr "Activer l'icône de notification ?"
|
||||
|
56
apps/desktopgui/locale/messages_ja.po
Normal file
56
apps/desktopgui/locale/messages_ja.po
Normal file
@ -0,0 +1,56 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# plazmism <gomidori@live.jp>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2013-11-26 10:38+0000\n"
|
||||
"Last-Translator: plazmism <gomidori@live.jp>\n"
|
||||
"Language-Team: Japanese (http://www.transifex.com/projects/p/I2P/language/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ja\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
msgstr "I2P を開始"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P 起動中!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "Starting"
|
||||
msgstr "起動中"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "I2P ブラウザを起動"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
msgstr "desktopgui を設定"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
|
||||
msgid "Restart I2P"
|
||||
msgstr "I2P を再起動"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:85
|
||||
msgid "Stop I2P"
|
||||
msgstr "I2P を停止"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "トレイアイコン設定"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "トレイアイコンを有効にしますか?"
|
55
apps/desktopgui/locale/messages_ro.po
Normal file
55
apps/desktopgui/locale/messages_ro.po
Normal file
@ -0,0 +1,55 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2013-11-11 11:31+0000\n"
|
||||
"Last-Translator: polearnik <polearnik@mail.ru>\n"
|
||||
"Language-Team: Romanian (http://www.transifex.com/projects/p/I2P/language/ro/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ro\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
msgstr "Start I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P se pornește!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "Starting"
|
||||
msgstr "Începere"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Lansare I2P Browser"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
msgstr "Configurarea desktopgui"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
|
||||
msgid "Restart I2P"
|
||||
msgstr "Restart I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:85
|
||||
msgid "Stop I2P"
|
||||
msgstr "Stop I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Configurare pictogramei din bara de sistem"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Ar trebui să fie activata pictograma din bara de sistem?"
|
@ -2,21 +2,24 @@
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
#
|
||||
# Translators:
|
||||
# ducki2p <ducki2p@gmail.com>, 2011
|
||||
# foo <foo@bar>, 2009
|
||||
# Roman Azarenko <x12ozmouse@ya.ru>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P desktopgui\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-02-19 17:26+0000\n"
|
||||
"PO-Revision-Date: 2011-02-23 10:23+0500\n"
|
||||
"Last-Translator: Hidden Z <hiddenz@mail.i2p>\n"
|
||||
"Language-Team: duck <duck@mail.i2p>\n"
|
||||
"Language: \n"
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2013-07-07 11:44+0000\n"
|
||||
"Last-Translator: Roman Azarenko <x12ozmouse@ya.ru>\n"
|
||||
"Language-Team: Russian (Russia) (http://www.transifex.com/projects/p/I2P/language/ru_RU/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"Language: ru_RU\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@ -32,7 +35,7 @@ msgstr "Запускается"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Запустить I2P браузер"
|
||||
msgstr "Запустить браузер I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
@ -48,9 +51,8 @@ msgstr "Остановить I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Настройка иконки в трее"
|
||||
msgstr "Конфигурация значка в области уведомлений"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Отображать ли иконку в трее?"
|
||||
|
||||
msgstr "Отображать ли значок в области уведомлений?"
|
||||
|
56
apps/desktopgui/locale/messages_tr.po
Normal file
56
apps/desktopgui/locale/messages_tr.po
Normal file
@ -0,0 +1,56 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Kaya Zeren <kayazeren@gmail.com>, 2013
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2013-04-26 06:07+0000\n"
|
||||
"Last-Translator: Kaya Zeren <kayazeren@gmail.com>\n"
|
||||
"Language-Team: Turkish (Turkey) (http://www.transifex.com/projects/p/I2P/language/tr_TR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: tr_TR\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
msgstr "I2P başlasın"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P başlatılıyor!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "Starting"
|
||||
msgstr "Başlatılıyor"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "I2P Tarayıcısını Açın"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
msgstr "Masaüstü Arayüzünü Ayarlayın"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
|
||||
msgid "Restart I2P"
|
||||
msgstr "I2P Yeniden Başlasın"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:85
|
||||
msgid "Stop I2P"
|
||||
msgstr "I2P Durdurulsun"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Sistem tepsisi simgesi ayarı"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Sistem tepsisi simgesi kullanılsın"
|
@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="java/src"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ministreaming"/>
|
||||
<classpathentry kind="lib" path="/jetty/jettylib/javax.servlet.jar"/>
|
||||
<classpathentry kind="lib" path="/jetty/jettylib/jetty-util.jar"/>
|
||||
<classpathentry kind="lib" path="/jetty/jettylib/org.mortbay.jetty.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/i2p_sdk"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/jetty"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ministreaming"/>
|
||||
<classpathentry kind="output" path="java/build/obj"/>
|
||||
</classpath>
|
||||
|
@ -19,11 +19,15 @@
|
||||
<pathelement location="../../ministreaming/java/build/obj" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/jetty-servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/jetty-util.jar" />
|
||||
</classpath>
|
||||
</depend>
|
||||
</target>
|
||||
|
||||
<condition property="no.bundle">
|
||||
<isfalse value="${require.gettext}" />
|
||||
</condition>
|
||||
<property name="javac.compilerargs" value="" />
|
||||
<property name="require.gettext" value="true" />
|
||||
|
||||
@ -35,7 +39,7 @@
|
||||
debug="true" deprecation="on" source="1.5" target="1.5"
|
||||
destdir="./build/obj"
|
||||
includeAntRuntime="false"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../jetty/jettylib/jetty-util.jar:../../ministreaming/java/build/mstreaming.jar" >
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../jetty/jettylib/jetty-servlet.jar:../../jetty/jettylib/jetty-util.jar:../../ministreaming/java/build/mstreaming.jar" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
@ -57,7 +61,7 @@
|
||||
<target name="jar" depends="builddep, compile, jarUpToDate, listChangedFiles" unless="jar.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.tr" value="" />
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/FetchAndAdd*.class **/messages_*.class">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/web/* **/messages_*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
|
||||
@ -72,7 +76,7 @@
|
||||
|
||||
<target name="jarUpToDate">
|
||||
<uptodate property="jar.uptodate" targetfile="build/i2psnark.jar" >
|
||||
<srcfiles dir= "build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/FetchAndAdd*.class **/messages_*.class" />
|
||||
<srcfiles dir= "build/obj" includes="**/*.class" excludes="**/web/* **/messages_*.class" />
|
||||
</uptodate>
|
||||
<condition property="shouldListChanges" >
|
||||
<and>
|
||||
@ -100,9 +104,11 @@
|
||||
<copy todir="build/icons/.icons" >
|
||||
<fileset dir="../icons/" />
|
||||
</copy>
|
||||
<!-- mime.properties must be in with the classes -->
|
||||
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml" >
|
||||
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
|
||||
<classes dir="./build/obj" includes="**/web/*.class" />
|
||||
<classes dir="./build/obj" includes="**/web/*" />
|
||||
<fileset dir="build/icons/" />
|
||||
<manifest>
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
@ -120,10 +126,11 @@
|
||||
</uptodate>
|
||||
</target>
|
||||
|
||||
<target name="bundle" depends="compile">
|
||||
<target name="bundle" depends="compile" unless="no.bundle">
|
||||
<!-- Update the messages_*.po files.
|
||||
We need to supply the bat file for windows, and then change the fail property to true -->
|
||||
<exec executable="sh" osfamily="unix" failifexecutionfails="true" failonerror="${require.gettext}" >
|
||||
<env key="JAVA_HOME" value="${java.home}" />
|
||||
<arg value="./bundle-messages.sh" />
|
||||
</exec>
|
||||
<exec executable="sh" osfamily="mac" failifexecutionfails="true" failonerror="${require.gettext}" >
|
||||
@ -169,7 +176,6 @@
|
||||
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
|
||||
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
|
||||
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
|
||||
<copy file="../../ministreaming/java/build/mstreaming.jar" tofile="./dist/lib/mstreaming.jar" />
|
||||
<copy file="../../streaming/java/build/streaming.jar" tofile="./dist/lib/streaming.jar" />
|
||||
|
@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Update messages_xx.po and messages_xx.class files,
|
||||
# from both java and jsp sources.
|
||||
@ -14,6 +15,10 @@ TMPFILE=build/javafiles.txt
|
||||
export TZ=UTC
|
||||
RC=0
|
||||
|
||||
if ! $(which javac > /dev/null 2>&1); then
|
||||
export JAVAC=${JAVA_HOME}/../bin/javac
|
||||
fi
|
||||
|
||||
if [ "$1" = "-p" ]
|
||||
then
|
||||
POUPDATE=1
|
||||
|
@ -53,7 +53,7 @@ class KBucketImpl<T extends SimpleDataStructure> implements KBucket<T> {
|
||||
/** include if no bits higher than this bit (inclusive) are set */
|
||||
private final int _end;
|
||||
private final int _max;
|
||||
private final KBucketTrimmer _trimmer;
|
||||
private final KBucketTrimmer<T> _trimmer;
|
||||
/** when did we last shake things up */
|
||||
private long _lastChanged;
|
||||
private final I2PAppContext _context;
|
||||
@ -62,11 +62,11 @@ class KBucketImpl<T extends SimpleDataStructure> implements KBucket<T> {
|
||||
* All entries in this bucket will have at least one bit different
|
||||
* from us in the range [begin, end] inclusive.
|
||||
*/
|
||||
public KBucketImpl(I2PAppContext context, int begin, int end, int max, KBucketTrimmer trimmer) {
|
||||
public KBucketImpl(I2PAppContext context, int begin, int end, int max, KBucketTrimmer<T> trimmer) {
|
||||
if (begin > end)
|
||||
throw new IllegalArgumentException(begin + " > " + end);
|
||||
_context = context;
|
||||
_entries = new ConcurrentHashSet(max + 4);
|
||||
_entries = new ConcurrentHashSet<T>(max + 4);
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
_max = max;
|
||||
|
@ -50,9 +50,9 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
*
|
||||
* Closest values are in bucket 0, furthest are in the last bucket.
|
||||
*/
|
||||
private final List<KBucket> _buckets;
|
||||
private final List<KBucket<T>> _buckets;
|
||||
private final Range<T> _rangeCalc;
|
||||
private final KBucketTrimmer _trimmer;
|
||||
private final KBucketTrimmer<T> _trimmer;
|
||||
|
||||
/**
|
||||
* Locked for reading only when traversing all the buckets.
|
||||
@ -76,13 +76,13 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* b > 0, use 1 for bittorrent, Kademlia paper recommends 5
|
||||
*/
|
||||
public KBucketSet(I2PAppContext context, T us, int max, int b) {
|
||||
this(context, us, max, b, new RandomTrimmer(context, max));
|
||||
this(context, us, max, b, new RandomTrimmer<T>(context, max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the supplied trim strategy.
|
||||
*/
|
||||
public KBucketSet(I2PAppContext context, T us, int max, int b, KBucketTrimmer trimmer) {
|
||||
public KBucketSet(I2PAppContext context, T us, int max, int b, KBucketTrimmer<T> trimmer) {
|
||||
_us = us;
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(KBucketSet.class);
|
||||
@ -95,7 +95,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
NUM_BUCKETS = KEYSIZE_BITS * B_FACTOR;
|
||||
BUCKET_SIZE = max;
|
||||
_buckets = createBuckets();
|
||||
_rangeCalc = new Range(us, B_VALUE);
|
||||
_rangeCalc = new Range<T>(us, B_VALUE);
|
||||
// this verifies the zero-argument constructor
|
||||
makeKey(new byte[us.length()]);
|
||||
}
|
||||
@ -137,7 +137,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
*
|
||||
*/
|
||||
public boolean add(T peer) {
|
||||
KBucket bucket;
|
||||
KBucket<T> bucket;
|
||||
getReadLock();
|
||||
try {
|
||||
bucket = getBucket(peer);
|
||||
@ -170,7 +170,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* FIXME will split the closest buckets too far if B > 1 and K < 2**B
|
||||
* Won't ever really happen and if it does it still works.
|
||||
*/
|
||||
private boolean shouldSplit(KBucket b) {
|
||||
private boolean shouldSplit(KBucket<T> b) {
|
||||
return
|
||||
b.getRangeBegin() != b.getRangeEnd() &&
|
||||
b.getKeyCount() > BUCKET_SIZE;
|
||||
@ -263,7 +263,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
int rv = 0;
|
||||
getReadLock();
|
||||
try {
|
||||
for (KBucket b : _buckets) {
|
||||
for (KBucket<T> b : _buckets) {
|
||||
rv += b.getKeyCount();
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
@ -271,7 +271,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
}
|
||||
|
||||
public boolean remove(T entry) {
|
||||
KBucket kbucket;
|
||||
KBucket<T> kbucket;
|
||||
getReadLock();
|
||||
try {
|
||||
kbucket = getBucket(entry);
|
||||
@ -284,7 +284,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
public void clear() {
|
||||
getReadLock();
|
||||
try {
|
||||
for (KBucket b : _buckets) {
|
||||
for (KBucket<T> b : _buckets) {
|
||||
b.clear();
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
@ -295,10 +295,10 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* @return a copy in a new set
|
||||
*/
|
||||
public Set<T> getAll() {
|
||||
Set<T> all = new HashSet(256);
|
||||
Set<T> all = new HashSet<T>(256);
|
||||
getReadLock();
|
||||
try {
|
||||
for (KBucket b : _buckets) {
|
||||
for (KBucket<T> b : _buckets) {
|
||||
all.addAll(b.getEntries());
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
@ -317,7 +317,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
public void getAll(SelectionCollector<T> collector) {
|
||||
getReadLock();
|
||||
try {
|
||||
for (KBucket b : _buckets) {
|
||||
for (KBucket<T> b : _buckets) {
|
||||
b.getEntries(collector);
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
@ -329,7 +329,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* @return non-null, closest first
|
||||
*/
|
||||
public List<T> getClosest(int max) {
|
||||
return getClosest(max, Collections.EMPTY_SET);
|
||||
return getClosest(max, Collections.<T> emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,7 +338,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* @return non-null, closest first
|
||||
*/
|
||||
public List<T> getClosest(int max, Collection<T> toIgnore) {
|
||||
List<T> rv = new ArrayList(max);
|
||||
List<T> rv = new ArrayList<T>(max);
|
||||
int count = 0;
|
||||
getReadLock();
|
||||
try {
|
||||
@ -355,7 +355,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
}
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
Comparator comp = new XORComparator(_us);
|
||||
Comparator<T> comp = new XORComparator<T>(_us);
|
||||
Collections.sort(rv, comp);
|
||||
int sz = rv.size();
|
||||
for (int i = sz - 1; i >= max; i--) {
|
||||
@ -370,7 +370,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* @return non-null, closest first
|
||||
*/
|
||||
public List<T> getClosest(T key, int max) {
|
||||
return getClosest(key, max, Collections.EMPTY_SET);
|
||||
return getClosest(key, max, Collections.<T> emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,7 +381,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
public List<T> getClosest(T key, int max, Collection<T> toIgnore) {
|
||||
if (key.equals(_us))
|
||||
return getClosest(max, toIgnore);
|
||||
List<T> rv = new ArrayList(max);
|
||||
List<T> rv = new ArrayList<T>(max);
|
||||
int count = 0;
|
||||
getReadLock();
|
||||
try {
|
||||
@ -407,7 +407,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
}
|
||||
}
|
||||
} finally { releaseReadLock(); }
|
||||
Comparator comp = new XORComparator(key);
|
||||
Comparator<T> comp = new XORComparator<T>(key);
|
||||
Collections.sort(rv, comp);
|
||||
int sz = rv.size();
|
||||
for (int i = sz - 1; i >= max; i--) {
|
||||
@ -452,7 +452,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
List<KBucket<T>> getBuckets() {
|
||||
getReadLock();
|
||||
try {
|
||||
return new ArrayList(_buckets);
|
||||
return new ArrayList<KBucket<T>>(_buckets);
|
||||
} finally { releaseReadLock(); }
|
||||
}
|
||||
|
||||
@ -461,7 +461,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* Caller must hold read lock
|
||||
* @return null if key is us
|
||||
*/
|
||||
private KBucket getBucket(T key) {
|
||||
private KBucket<T> getBucket(T key) {
|
||||
int bucket = pickBucket(key);
|
||||
if (bucket < 0)
|
||||
return null;
|
||||
@ -480,30 +480,30 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
// of equal size to be checked so a binary search is better
|
||||
if (B_VALUE <= 3) {
|
||||
for (int i = _buckets.size() - 1; i >= 0; i--) {
|
||||
KBucket b = _buckets.get(i);
|
||||
KBucket<T> b = _buckets.get(i);
|
||||
if (range >= b.getRangeBegin() && range <= b.getRangeEnd())
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} else {
|
||||
KBucket dummy = new DummyBucket(range);
|
||||
return Collections.binarySearch(_buckets, dummy, new BucketComparator());
|
||||
KBucket<T> dummy = new DummyBucket<T>(range);
|
||||
return Collections.binarySearch(_buckets, dummy, new BucketComparator<T>());
|
||||
}
|
||||
}
|
||||
|
||||
private List<KBucket> createBuckets() {
|
||||
private List<KBucket<T>> createBuckets() {
|
||||
// just an initial size
|
||||
List<KBucket> buckets = new ArrayList(4 * B_FACTOR);
|
||||
List<KBucket<T>> buckets = new ArrayList<KBucket<T>>(4 * B_FACTOR);
|
||||
buckets.add(createBucket(0, NUM_BUCKETS -1));
|
||||
return buckets;
|
||||
}
|
||||
|
||||
private KBucket createBucket(int start, int end) {
|
||||
private KBucket<T> createBucket(int start, int end) {
|
||||
if (end - start >= B_FACTOR &&
|
||||
(((end + 1) & B_FACTOR - 1) != 0 ||
|
||||
(start & B_FACTOR - 1) != 0))
|
||||
throw new IllegalArgumentException("Sub-bkt crosses K-bkt boundary: " + start + '-' + end);
|
||||
KBucket bucket = new KBucketImpl(_context, start, end, BUCKET_SIZE, _trimmer);
|
||||
KBucket<T> bucket = new KBucketImpl<T>(_context, start, end, BUCKET_SIZE, _trimmer);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
@ -524,11 +524,11 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* @return non-null, closest first
|
||||
*/
|
||||
public List<T> getExploreKeys(long age) {
|
||||
List<T> rv = new ArrayList(_buckets.size());
|
||||
List<T> rv = new ArrayList<T>(_buckets.size());
|
||||
long old = _context.clock().now() - age;
|
||||
getReadLock();
|
||||
try {
|
||||
for (KBucket b : _buckets) {
|
||||
for (KBucket<T> b : _buckets) {
|
||||
int curSize = b.getKeyCount();
|
||||
// Always explore the closest bucket
|
||||
if ((b.getRangeBegin() == 0) ||
|
||||
@ -543,7 +543,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* Generate a random key to go within this bucket
|
||||
* Package private for testing only. Others shouldn't need this.
|
||||
*/
|
||||
T generateRandomKey(KBucket bucket) {
|
||||
T generateRandomKey(KBucket<T> bucket) {
|
||||
int begin = bucket.getRangeBegin();
|
||||
int end = bucket.getRangeEnd();
|
||||
// number of fixed bits, out of B_VALUE - 1 bits
|
||||
@ -662,7 +662,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
public Range(T us, int bValue) {
|
||||
_bValue = bValue;
|
||||
_bigUs = new BigInteger(1, us.getData());
|
||||
_distanceCache = new LHMCache(256);
|
||||
_distanceCache = new LHMCache<T, Integer>(256);
|
||||
}
|
||||
|
||||
/** @return 0 to max-1 or -1 for us */
|
||||
@ -748,8 +748,8 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
* For Collections.binarySearch.
|
||||
* Returns equal for any overlap.
|
||||
*/
|
||||
private static class BucketComparator implements Comparator<KBucket> {
|
||||
public int compare(KBucket l, KBucket r) {
|
||||
private static class BucketComparator<T extends SimpleDataStructure> implements Comparator<KBucket<T>> {
|
||||
public int compare(KBucket<T> l, KBucket<T> r) {
|
||||
if (l.getRangeEnd() < r.getRangeBegin())
|
||||
return -1;
|
||||
if (l.getRangeBegin() > r.getRangeEnd())
|
||||
@ -770,7 +770,7 @@ public class KBucketSet<T extends SimpleDataStructure> {
|
||||
try {
|
||||
int len = _buckets.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
KBucket b = _buckets.get(i);
|
||||
KBucket<T> b = _buckets.get(i);
|
||||
buf.append("* Bucket ").append(i).append("/").append(len).append(": ");
|
||||
buf.append(b.toString()).append("\n");
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public class RandomTrimmer<T extends SimpleDataStructure> implements KBucketTrim
|
||||
}
|
||||
|
||||
public boolean trim(KBucket<T> kbucket, T toAdd) {
|
||||
List<T> e = new ArrayList(kbucket.getEntries());
|
||||
List<T> e = new ArrayList<T>(kbucket.getEntries());
|
||||
int sz = e.size();
|
||||
// concurrency
|
||||
if (sz < _max)
|
||||
|
@ -2,7 +2,6 @@ package net.i2p.kademlia;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
|
||||
/**
|
||||
@ -21,8 +20,20 @@ class XORComparator<T extends SimpleDataStructure> implements Comparator<T> {
|
||||
}
|
||||
|
||||
public int compare(T lhs, T rhs) {
|
||||
byte lhsDelta[] = DataHelper.xor(lhs.getData(), _base);
|
||||
byte rhsDelta[] = DataHelper.xor(rhs.getData(), _base);
|
||||
return DataHelper.compareTo(lhsDelta, rhsDelta);
|
||||
// same as the following but byte-by-byte for efficiency
|
||||
//byte lhsDelta[] = DataHelper.xor(lhs.getData(), _base);
|
||||
//byte rhsDelta[] = DataHelper.xor(rhs.getData(), _base);
|
||||
//return DataHelper.compareTo(lhsDelta, rhsDelta);
|
||||
byte lhsb[] = lhs.getData();
|
||||
byte rhsb[] = rhs.getData();
|
||||
for (int i = 0; i < _base.length; i++) {
|
||||
int ld = (lhsb[i] ^ _base[i]) & 0xff;
|
||||
int rd = (rhsb[i] ^ _base[i]) & 0xff;
|
||||
if (ld < rd)
|
||||
return -1;
|
||||
if (ld > rd)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
*/
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.I2PSessionException;
|
||||
@ -37,7 +36,9 @@ class BWLimits {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String args[]) {
|
||||
System.out.println(Arrays.toString(getBWLimits("127.0.0.1", 7654)));
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -33,79 +33,106 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Accepts connections on a TCP port and routes them to sub-acceptors.
|
||||
* Accepts connections on a I2PServerSocket and routes them to PeerAcceptors.
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ConnectionAcceptor.class);
|
||||
private I2PServerSocket serverSocket;
|
||||
private PeerAcceptor peeracceptor;
|
||||
private final PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
private final I2PSnarkUtil _util;
|
||||
private final ObjectCounter<Hash> _badCounter = new ObjectCounter();
|
||||
private final ObjectCounter<Hash> _badCounter = new ObjectCounter<Hash>();
|
||||
private final SimpleTimer2.TimedEvent _cleaner;
|
||||
|
||||
private boolean stop;
|
||||
private boolean socketChanged;
|
||||
private volatile boolean stop;
|
||||
|
||||
private static final int MAX_BAD = 2;
|
||||
// protocol errors before blacklisting.
|
||||
private static final int MAX_BAD = 1;
|
||||
private static final long BAD_CLEAN_INTERVAL = 30*60*1000;
|
||||
|
||||
public ConnectionAcceptor(I2PSnarkUtil util) { _util = util; }
|
||||
/**
|
||||
* Multitorrent. Caller MUST call startAccepting()
|
||||
*/
|
||||
public ConnectionAcceptor(I2PSnarkUtil util, PeerCoordinatorSet set) {
|
||||
_util = util;
|
||||
_cleaner = new Cleaner();
|
||||
peeracceptor = new PeerAcceptor(set);
|
||||
}
|
||||
|
||||
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
|
||||
if (serverSocket != socket) {
|
||||
if ( (peeracceptor == null) || (peeracceptor.coordinators != set) )
|
||||
peeracceptor = new PeerAcceptor(set);
|
||||
serverSocket = socket;
|
||||
/**
|
||||
* May be called even when already running. May be called to start up again after halt().
|
||||
*/
|
||||
public synchronized void startAccepting() {
|
||||
stop = false;
|
||||
socketChanged = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("ConnectionAcceptor startAccepting new thread? " + (thread == null));
|
||||
if (thread == null) {
|
||||
thread = new I2PAppThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
_util.getContext().simpleScheduler().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
|
||||
_cleaner.reschedule(BAD_CLEAN_INTERVAL, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionAcceptor(I2PSnarkUtil util, I2PServerSocket serverSocket,
|
||||
/**
|
||||
* Unused (single torrent).
|
||||
* Do NOT call startAccepting().
|
||||
*/
|
||||
public ConnectionAcceptor(I2PSnarkUtil util,
|
||||
PeerAcceptor peeracceptor)
|
||||
{
|
||||
this.serverSocket = serverSocket;
|
||||
this.peeracceptor = peeracceptor;
|
||||
_util = util;
|
||||
|
||||
thread = new I2PAppThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
_util.getContext().simpleScheduler().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
|
||||
_cleaner = new Cleaner();
|
||||
}
|
||||
|
||||
public void halt()
|
||||
/**
|
||||
* May be restarted later with startAccepting().
|
||||
*/
|
||||
public synchronized void halt()
|
||||
{
|
||||
if (stop) return;
|
||||
stop = true;
|
||||
locked_halt();
|
||||
Thread t = thread;
|
||||
if (t != null) {
|
||||
t.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
I2PServerSocket ss = serverSocket;
|
||||
if (ss != null)
|
||||
|
||||
/**
|
||||
* Caller must synch
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private void locked_halt()
|
||||
{
|
||||
I2PServerSocket ss = _util.getServerSocket();
|
||||
if (ss != null) {
|
||||
try
|
||||
{
|
||||
ss.close();
|
||||
}
|
||||
catch(I2PException ioe) { }
|
||||
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
_badCounter.clear();
|
||||
_cleaner.cancel();
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
serverSocket = _util.getServerSocket();
|
||||
socketChanged = true;
|
||||
/**
|
||||
* Effectively unused, would only be called if we changed
|
||||
* I2CP host/port, which is hidden in the gui if in router context
|
||||
* FIXME this only works if already running
|
||||
*/
|
||||
public synchronized void restart() {
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
@ -113,21 +140,32 @@ public class ConnectionAcceptor implements Runnable
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return 6881; // serverSocket.getLocalPort();
|
||||
return TrackerClient.PORT; // serverSocket.getLocalPort();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
run2();
|
||||
} finally {
|
||||
synchronized(this) {
|
||||
thread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void run2()
|
||||
{
|
||||
while(!stop)
|
||||
{
|
||||
if (socketChanged) {
|
||||
// ok, already updated
|
||||
socketChanged = false;
|
||||
}
|
||||
I2PServerSocket serverSocket = _util.getServerSocket();
|
||||
while ( (serverSocket == null) && (!stop)) {
|
||||
if (!(_util.isConnecting() || _util.connected())) {
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
serverSocket = _util.getServerSocket();
|
||||
if (serverSocket == null)
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
if(stop)
|
||||
break;
|
||||
@ -135,24 +173,26 @@ public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
I2PSocket socket = serverSocket.accept();
|
||||
if (socket == null) {
|
||||
if (socketChanged) {
|
||||
continue;
|
||||
} else {
|
||||
I2PServerSocket ss = _util.getServerSocket();
|
||||
if (ss != serverSocket) {
|
||||
serverSocket = ss;
|
||||
socketChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (socket.getPeerDestination().equals(_util.getMyDestination())) {
|
||||
_log.error("Incoming connection from myself");
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
continue;
|
||||
}
|
||||
if (_badCounter.count(socket.getPeerDestination().calculateHash()) >= MAX_BAD) {
|
||||
Hash h = socket.getPeerDestination().calculateHash();
|
||||
if (socket.getLocalPort() == 80) {
|
||||
_badCounter.increment(h);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting connection from " + socket.getPeerDestination().calculateHash() + " after " + MAX_BAD + " failures");
|
||||
_log.error("Dropping incoming HTTP from " + h);
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
continue;
|
||||
}
|
||||
int bad = _badCounter.count(h);
|
||||
if (bad >= MAX_BAD) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Rejecting connection from " + h +
|
||||
" after " + bad + " failures, max is " + MAX_BAD);
|
||||
try { socket.close(); } catch (IOException ioe) {}
|
||||
continue;
|
||||
}
|
||||
@ -162,26 +202,34 @@ public class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
catch (I2PException ioe)
|
||||
{
|
||||
if (!socketChanged) {
|
||||
_log.error("Error while accepting", ioe);
|
||||
stop = true;
|
||||
int level = stop ? Log.WARN : Log.ERROR;
|
||||
if (_log.shouldLog(level))
|
||||
_log.log(level, "Error while accepting", ioe);
|
||||
synchronized(this) {
|
||||
if (!stop) {
|
||||
locked_halt();
|
||||
thread = null;
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
_log.error("Error while accepting", ioe);
|
||||
stop = true;
|
||||
int level = stop ? Log.WARN : Log.ERROR;
|
||||
if (_log.shouldLog(level))
|
||||
_log.log(level, "Error while accepting", ioe);
|
||||
synchronized(this) {
|
||||
if (!stop) {
|
||||
locked_halt();
|
||||
thread = null;
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// catch oom?
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("ConnectionAcceptor closed");
|
||||
}
|
||||
|
||||
private class Handler implements Runnable {
|
||||
@ -214,7 +262,17 @@ public class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
|
||||
/** @since 0.9.1 */
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
public void timeReached() { _badCounter.clear(); }
|
||||
private class Cleaner extends SimpleTimer2.TimedEvent {
|
||||
|
||||
public Cleaner() {
|
||||
super(_util.getContext().simpleTimer2());
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
if (stop)
|
||||
return;
|
||||
_badCounter.clear();
|
||||
schedule(BAD_CLEAN_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,20 @@ interface CoordinatorListener
|
||||
*/
|
||||
void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo);
|
||||
|
||||
/**
|
||||
* Is this number of uploaders over the per-torrent limit?
|
||||
*/
|
||||
public boolean overUploadLimit(int uploaders);
|
||||
|
||||
/**
|
||||
* Are we currently over the upstream bandwidth limit?
|
||||
*/
|
||||
public boolean overUpBWLimit();
|
||||
|
||||
/**
|
||||
* Is the total (in Bps) over the upstream bandwidth limit?
|
||||
*/
|
||||
public boolean overUpBWLimit(long total);
|
||||
|
||||
public void addMessage(String message);
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ abstract class ExtensionHandler {
|
||||
* @return bencoded outgoing handshake message
|
||||
*/
|
||||
public static byte[] getHandshake(int metasize, boolean pexAndMetadata, boolean dht) {
|
||||
Map<String, Object> handshake = new HashMap();
|
||||
Map<String, Integer> m = new HashMap();
|
||||
Map<String, Object> handshake = new HashMap<String, Object>();
|
||||
Map<String, Integer> m = new HashMap<String, Integer>();
|
||||
if (pexAndMetadata) {
|
||||
m.put(TYPE_METADATA, Integer.valueOf(ID_METADATA));
|
||||
m.put(TYPE_PEX, Integer.valueOf(ID_PEX));
|
||||
@ -56,7 +56,7 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
// include the map even if empty so the far-end doesn't NPE
|
||||
handshake.put("m", m);
|
||||
handshake.put("p", Integer.valueOf(6881));
|
||||
handshake.put("p", Integer.valueOf(TrackerClient.PORT));
|
||||
handshake.put("v", "I2PSnark");
|
||||
handshake.put("reqq", Integer.valueOf(5));
|
||||
return BEncoder.bencode(handshake);
|
||||
@ -110,7 +110,8 @@ abstract class ExtensionHandler {
|
||||
// drop if we need metainfo and we haven't found anybody yet
|
||||
synchronized(state) {
|
||||
if (!state.isInitialized()) {
|
||||
log.debug("Dropping peer, we need metadata! " + peer);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Dropping peer, we need metadata! " + peer);
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
@ -124,7 +125,8 @@ abstract class ExtensionHandler {
|
||||
// drop if we need metainfo and we haven't found anybody yet
|
||||
synchronized(state) {
|
||||
if (!state.isInitialized()) {
|
||||
log.debug("Dropping peer, we need metadata! " + peer);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Dropping peer, we need metadata! " + peer);
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
@ -274,7 +276,7 @@ abstract class ExtensionHandler {
|
||||
|
||||
/** REQUEST and REJECT are the same except for message type */
|
||||
private static void sendMessage(Peer peer, int type, int piece) {
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("msg_type", Integer.valueOf(type));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
byte[] payload = BEncoder.bencode(map);
|
||||
@ -289,7 +291,7 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data) {
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("msg_type", Integer.valueOf(TYPE_DATA));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
map.put("total_size", Integer.valueOf(data.length));
|
||||
@ -332,7 +334,7 @@ abstract class ExtensionHandler {
|
||||
if (ids.length < HASH_LENGTH)
|
||||
return;
|
||||
int len = Math.min(ids.length, (I2PSnarkUtil.MAX_CONNECTIONS - 1) * HASH_LENGTH);
|
||||
List<PeerID> peers = new ArrayList(len / HASH_LENGTH);
|
||||
List<PeerID> peers = new ArrayList<PeerID>(len / HASH_LENGTH);
|
||||
for (int off = 0; off < len; off += HASH_LENGTH) {
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
System.arraycopy(ids, off, hash, 0, HASH_LENGTH);
|
||||
@ -380,7 +382,7 @@ abstract class ExtensionHandler {
|
||||
public static void sendPEX(Peer peer, List<Peer> pList) {
|
||||
if (pList.isEmpty())
|
||||
return;
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
byte[] peers = new byte[HASH_LENGTH * pList.size()];
|
||||
int off = 0;
|
||||
for (Peer p : pList) {
|
||||
@ -404,7 +406,7 @@ abstract class ExtensionHandler {
|
||||
* @since DHT
|
||||
*/
|
||||
public static void sendDHT(Peer peer, int qport, int rport) {
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("port", Integer.valueOf(qport));
|
||||
map.put("rport", Integer.valueOf(rport));
|
||||
byte[] payload = BEncoder.bencode(map);
|
||||
|
@ -3,16 +3,12 @@ package org.klomp.snark;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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 net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
@ -33,7 +29,6 @@ import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Translate;
|
||||
|
||||
@ -49,6 +44,7 @@ import org.klomp.snark.dht.KRPC;
|
||||
public class I2PSnarkUtil {
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final String _baseName;
|
||||
|
||||
private boolean _shouldProxy;
|
||||
private String _proxyHost;
|
||||
@ -82,12 +78,21 @@ public class I2PSnarkUtil {
|
||||
public static final boolean DEFAULT_USE_DHT = true;
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
this(ctx, "i2psnark");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseName generally "i2psnark"
|
||||
* @since Jetty 7
|
||||
*/
|
||||
public I2PSnarkUtil(I2PAppContext ctx, String baseName) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
_baseName = baseName;
|
||||
_opts = new HashMap<String, String>();
|
||||
//setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_banlist = new ConcurrentHashSet();
|
||||
_banlist = new ConcurrentHashSet<Hash>();
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
@ -99,7 +104,7 @@ public class I2PSnarkUtil {
|
||||
// This is used for both announce replies and .torrent file downloads,
|
||||
// so it must be available even if not connected to I2CP.
|
||||
// so much for multiple instances
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), "i2psnark");
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
@ -148,7 +153,7 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* This updates the session options and tells the router
|
||||
* This updates ALL the session options (not just the bw) and tells the router
|
||||
* @param limit KBps
|
||||
*/
|
||||
public void setMaxUpBW(int limit) {
|
||||
@ -210,15 +215,13 @@ public class I2PSnarkUtil {
|
||||
_log.debug("Connecting to I2P", new Exception("I did it"));
|
||||
Properties opts = _context.getProperties();
|
||||
if (_opts != null) {
|
||||
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
opts.setProperty(key, _opts.get(key).toString());
|
||||
}
|
||||
for (Map.Entry<String, String> entry : _opts.entrySet() )
|
||||
opts.setProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (opts.getProperty("inbound.nickname") == null)
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
opts.setProperty("inbound.nickname", _baseName.replace("i2psnark", "I2PSnark"));
|
||||
if (opts.getProperty("outbound.nickname") == null)
|
||||
opts.setProperty("outbound.nickname", "I2PSnark");
|
||||
opts.setProperty("outbound.nickname", _baseName.replace("i2psnark", "I2PSnark"));
|
||||
if (opts.getProperty("outbound.priority") == null)
|
||||
opts.setProperty("outbound.priority", "-10");
|
||||
// Dont do this for now, it is set in I2PSocketEepGet for announces,
|
||||
@ -253,7 +256,7 @@ public class I2PSnarkUtil {
|
||||
_connecting = false;
|
||||
}
|
||||
if (_shouldUseDHT && _manager != null && _dht == null)
|
||||
_dht = new KRPC(_context, _manager.getSession());
|
||||
_dht = new KRPC(_context, _baseName, _manager.getSession());
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
@ -314,6 +317,8 @@ public class I2PSnarkUtil {
|
||||
if (_banlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are banlisted");
|
||||
try {
|
||||
// TODO opts.setPort(xxx); connect(addr, opts)
|
||||
// DHT moved above 6881 in 0.9.9
|
||||
I2PSocket rv = _manager.connect(addr);
|
||||
if (rv != null)
|
||||
_banlist.remove(dest);
|
||||
@ -321,7 +326,9 @@ public class I2PSnarkUtil {
|
||||
} catch (I2PException ie) {
|
||||
_banlist.add(dest);
|
||||
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
IOException ioe = new IOException("Unable to reach the peer " + peer);
|
||||
ioe.initCause(ie);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
@ -503,7 +510,7 @@ public class I2PSnarkUtil {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Using existing session for lookup of " + ip);
|
||||
try {
|
||||
return sess.lookupDest(h);
|
||||
return sess.lookupDest(h, 15*1000);
|
||||
} catch (I2PSessionException ise) {
|
||||
}
|
||||
}
|
||||
@ -563,7 +570,7 @@ public class I2PSnarkUtil {
|
||||
*/
|
||||
public List<String> getOpenTrackers() {
|
||||
if (!shouldUseOpenTrackers())
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
return _openTrackers;
|
||||
}
|
||||
|
||||
@ -588,7 +595,7 @@ public class I2PSnarkUtil {
|
||||
public synchronized void setUseDHT(boolean yes) {
|
||||
_shouldUseDHT = yes;
|
||||
if (yes && _manager != null && _dht == null) {
|
||||
_dht = new KRPC(_context, _manager.getSession());
|
||||
_dht = new KRPC(_context, _baseName, _manager.getSession());
|
||||
} else if (!yes && _dht != null) {
|
||||
_dht.stop();
|
||||
_dht = null;
|
||||
|
143
apps/i2psnark/java/src/org/klomp/snark/IdleChecker.java
Normal file
143
apps/i2psnark/java/src/org/klomp/snark/IdleChecker.java
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
*/
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Periodically check for idle condition based on connected peers,
|
||||
* and reduce/restore tunnel count as necessary.
|
||||
* We can't use the I2CP idle detector because it's based on traffic,
|
||||
* so DHT and announces would keep it non-idle.
|
||||
*
|
||||
* @since 0.9.7
|
||||
*/
|
||||
class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
|
||||
private final SnarkManager _mgr;
|
||||
private final I2PSnarkUtil _util;
|
||||
private final PeerCoordinatorSet _pcs;
|
||||
private final Log _log;
|
||||
private int _consec;
|
||||
private int _consecNotRunning;
|
||||
private boolean _isIdle;
|
||||
|
||||
private static final long CHECK_TIME = 63*1000;
|
||||
private static final int MAX_CONSEC_IDLE = 4;
|
||||
private static final int MAX_CONSEC_NOT_RUNNING = 20;
|
||||
|
||||
/**
|
||||
* Caller must schedule
|
||||
*/
|
||||
public IdleChecker(SnarkManager mgr, PeerCoordinatorSet pcs) {
|
||||
super(mgr.util().getContext().simpleTimer2());
|
||||
_util = mgr.util();
|
||||
_log = _util.getContext().logManager().getLog(IdleChecker.class);
|
||||
_mgr = mgr;
|
||||
_pcs = pcs;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
if (_util.connected()) {
|
||||
boolean torrentRunning = false;
|
||||
boolean hasPeers = false;
|
||||
for (PeerCoordinator pc : _pcs) {
|
||||
if (!pc.halted()) {
|
||||
torrentRunning = true;
|
||||
if (pc.getPeers() > 0) {
|
||||
hasPeers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (torrentRunning) {
|
||||
_consecNotRunning = 0;
|
||||
} else {
|
||||
if (_consecNotRunning++ >= MAX_CONSEC_NOT_RUNNING) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Closing tunnels on idle");
|
||||
_util.disconnect();
|
||||
_mgr.addMessage(_util.getString("I2P tunnel closed."));
|
||||
schedule(3 * CHECK_TIME);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPeers) {
|
||||
if (_isIdle)
|
||||
restoreTunnels();
|
||||
} else {
|
||||
if (!_isIdle) {
|
||||
if (_consec++ >= MAX_CONSEC_IDLE)
|
||||
reduceTunnels();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_isIdle = false;
|
||||
_consec = 0;
|
||||
_consecNotRunning = 0;
|
||||
}
|
||||
schedule(CHECK_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce to 1 in / 1 out tunnel
|
||||
*/
|
||||
private void reduceTunnels() {
|
||||
_isIdle = true;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Reducing tunnels on idle");
|
||||
setTunnels("1", "1", "0", "0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore tunnel count
|
||||
*/
|
||||
private void restoreTunnels() {
|
||||
_isIdle = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Restoring tunnels on activity");
|
||||
Map<String, String> opts = _util.getI2CPOptions();
|
||||
String i = opts.get("inbound.quantity");
|
||||
if (i == null)
|
||||
i = "3";
|
||||
String o = opts.get("outbound.quantity");
|
||||
if (o == null)
|
||||
o = "3";
|
||||
String ib = opts.get("inbound.backupQuantity");
|
||||
if (ib == null)
|
||||
ib = "0";
|
||||
String ob= opts.get("outbound.backupQuantity");
|
||||
if (ob == null)
|
||||
ob = "0";
|
||||
setTunnels(i, o, ib, ob);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set in / out / in backup / out backup tunnel counts
|
||||
*/
|
||||
private void setTunnels(String i, String o, String ib, String ob) {
|
||||
_consec = 0;
|
||||
I2PSocketManager mgr = _util.getSocketManager();
|
||||
if (mgr != null) {
|
||||
I2PSession sess = mgr.getSession();
|
||||
if (sess != null) {
|
||||
Properties newProps = new Properties();
|
||||
newProps.setProperty("inbound.quantity", i);
|
||||
newProps.setProperty("outbound.quantity", o);
|
||||
newProps.setProperty("inbound.backupQuantity", ib);
|
||||
newProps.setProperty("outbound.backupQuantity", ob);
|
||||
sess.updateOptions(newProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.RandomSource;
|
||||
|
||||
@ -190,7 +189,7 @@ class MagnetState {
|
||||
*/
|
||||
public MetaInfo buildMetaInfo() throws Exception {
|
||||
// top map has nothing in it but the info map (no announce)
|
||||
Map<String, BEValue> map = new HashMap();
|
||||
Map<String, BEValue> map = new HashMap<String, BEValue>();
|
||||
InputStream is = new ByteArrayInputStream(metainfoBytes);
|
||||
BDecoder dec = new BDecoder(is);
|
||||
BEValue bev = dec.bdecodeMap();
|
||||
|
@ -133,7 +133,7 @@ public class MagnetURI {
|
||||
}
|
||||
if (idx < 0 || idx > uri.length())
|
||||
return null;
|
||||
List<String> rv = new ArrayList();
|
||||
List<String> rv = new ArrayList<String>();
|
||||
while (true) {
|
||||
String p = uri.substring(idx);
|
||||
uri = p;
|
||||
|
@ -61,6 +61,10 @@ public class MetaInfo
|
||||
private final byte[] piece_hashes;
|
||||
private final long length;
|
||||
private final boolean privateTorrent;
|
||||
private final List<List<String>> announce_list;
|
||||
private final String comment;
|
||||
private final String created_by;
|
||||
private final long creation_date;
|
||||
private Map<String, BEValue> infoMap;
|
||||
|
||||
/**
|
||||
@ -69,9 +73,11 @@ public class MetaInfo
|
||||
* @param announce may be null
|
||||
* @param files null for single-file torrent
|
||||
* @param lengths null for single-file torrent
|
||||
* @param announce_list may be null
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent)
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
|
||||
List<List<String>> announce_list)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
@ -83,6 +89,10 @@ public class MetaInfo
|
||||
this.piece_hashes = piece_hashes;
|
||||
this.length = length;
|
||||
this.privateTorrent = privateTorrent;
|
||||
this.announce_list = announce_list;
|
||||
this.comment = null;
|
||||
this.created_by = null;
|
||||
this.creation_date = 0;
|
||||
|
||||
// TODO if we add a parameter for other keys
|
||||
//if (other != null) {
|
||||
@ -141,6 +151,49 @@ public class MetaInfo
|
||||
this.announce = val.getString();
|
||||
}
|
||||
|
||||
// BEP 12
|
||||
val = m.get("announce-list");
|
||||
if (val == null) {
|
||||
this.announce_list = null;
|
||||
} else {
|
||||
this.announce_list = new ArrayList();
|
||||
List<BEValue> bl1 = val.getList();
|
||||
for (BEValue bev : bl1) {
|
||||
List<BEValue> bl2 = bev.getList();
|
||||
List<String> sl2 = new ArrayList();
|
||||
for (BEValue bev2 : bl2) {
|
||||
sl2.add(bev2.getString());
|
||||
}
|
||||
this.announce_list.add(sl2);
|
||||
}
|
||||
}
|
||||
|
||||
// misc. optional top-level stuff
|
||||
val = m.get("comment");
|
||||
String st = null;
|
||||
if (val != null) {
|
||||
try {
|
||||
st = val.getString();
|
||||
} catch (InvalidBEncodingException ibee) {}
|
||||
}
|
||||
this.comment = st;
|
||||
val = m.get("created by");
|
||||
st = null;
|
||||
if (val != null) {
|
||||
try {
|
||||
st = val.getString();
|
||||
} catch (InvalidBEncodingException ibee) {}
|
||||
}
|
||||
this.created_by = st;
|
||||
val = m.get("creation date");
|
||||
long time = 0;
|
||||
if (val != null) {
|
||||
try {
|
||||
time = val.getLong() * 1000;
|
||||
} catch (InvalidBEncodingException ibee) {}
|
||||
}
|
||||
this.creation_date = time;
|
||||
|
||||
val = m.get("info");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing info map");
|
||||
@ -163,7 +216,16 @@ public class MetaInfo
|
||||
|
||||
// BEP 27
|
||||
val = info.get("private");
|
||||
privateTorrent = val != null && val.getString().equals("1");
|
||||
if (val != null) {
|
||||
Object o = val.getValue();
|
||||
// Is it supposed to be a number or a string?
|
||||
// i2psnark does it as a string. BEP 27 doesn't say.
|
||||
// Transmission does numbers.
|
||||
privateTorrent = "1".equals(o) ||
|
||||
((o instanceof Number) && ((Number) o).intValue() == 1);
|
||||
} else {
|
||||
privateTorrent = false;
|
||||
}
|
||||
|
||||
val = info.get("piece length");
|
||||
if (val == null)
|
||||
@ -296,6 +358,15 @@ public class MetaInfo
|
||||
return announce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of lists of urls.
|
||||
*
|
||||
* @since 0.9.5
|
||||
*/
|
||||
public List<List<String>> getAnnounceList() {
|
||||
return announce_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original 20 byte SHA1 hash over the bencoded info map.
|
||||
*/
|
||||
@ -351,6 +422,33 @@ public class MetaInfo
|
||||
return lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* The comment string or null.
|
||||
* Not available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public String getComment() {
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* The created-by string or null.
|
||||
* Not available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public String getCreatedBy() {
|
||||
return this.created_by;
|
||||
}
|
||||
|
||||
/**
|
||||
* The creation date (ms) or zero.
|
||||
* Not available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public long getCreationDate() {
|
||||
return this.creation_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pieces.
|
||||
*/
|
||||
@ -428,7 +526,6 @@ public class MetaInfo
|
||||
* @since 0.9.1
|
||||
*/
|
||||
boolean checkPiece(PartialPiece pp) {
|
||||
MessageDigest sha1 = SHA1.getInstance();
|
||||
int piece = pp.getPiece();
|
||||
byte[] hash;
|
||||
try {
|
||||
@ -470,12 +567,18 @@ public class MetaInfo
|
||||
/**
|
||||
* Creates a copy of this MetaInfo that shares everything except the
|
||||
* announce URL.
|
||||
* Drops any announce-list.
|
||||
* Preserves infohash and info map, including any non-standard fields.
|
||||
* @param announce may be null
|
||||
*/
|
||||
public MetaInfo reannounce(String announce)
|
||||
public MetaInfo reannounce(String announce) throws InvalidBEncodingException
|
||||
{
|
||||
return new MetaInfo(announce, name, name_utf8, files,
|
||||
lengths, piece_length,
|
||||
piece_hashes, length, privateTorrent);
|
||||
Map<String, BEValue> m = new HashMap();
|
||||
if (announce != null)
|
||||
m.put("announce", new BEValue(DataHelper.getUTF8(announce)));
|
||||
Map info = createInfoMap();
|
||||
m.put("info", new BEValue(info));
|
||||
return new MetaInfo(m);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -486,6 +589,8 @@ public class MetaInfo
|
||||
Map m = new HashMap();
|
||||
if (announce != null)
|
||||
m.put("announce", announce);
|
||||
if (announce_list != null)
|
||||
m.put("announce-list", announce_list);
|
||||
Map info = createInfoMap();
|
||||
m.put("info", info);
|
||||
// don't save this locally, we should only do this once
|
||||
@ -506,6 +611,9 @@ public class MetaInfo
|
||||
// or else we will lose any non-standard keys and corrupt the infohash.
|
||||
if (infoMap != null)
|
||||
return Collections.unmodifiableMap(infoMap);
|
||||
// we should only get here if serving a magnet on a torrent we created
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Creating new infomap", new Exception());
|
||||
// otherwise we must create it
|
||||
Map info = new HashMap();
|
||||
info.put("name", name);
|
||||
|
@ -28,6 +28,8 @@ import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@ -68,8 +70,10 @@ public class Peer implements Comparable
|
||||
private I2PSocket sock;
|
||||
|
||||
private boolean deregister = true;
|
||||
private static long __id;
|
||||
private long _id;
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private final long _id;
|
||||
private final AtomicBoolean _disconnected = new AtomicBoolean();
|
||||
|
||||
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};
|
||||
@ -98,7 +102,7 @@ public class Peer implements Comparable
|
||||
this.my_id = my_id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
_id = ++__id;
|
||||
_id = __id.incrementAndGet();
|
||||
//_log.debug("Creating a new peer with " + peerID.toString(), new Exception("creating"));
|
||||
}
|
||||
|
||||
@ -123,7 +127,7 @@ public class Peer implements Comparable
|
||||
|
||||
byte[] id = handshake(in, out);
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
_id = ++__id;
|
||||
_id = __id.incrementAndGet();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Creating a new peer " + peerID.toString(), new Exception("creating " + _id));
|
||||
}
|
||||
@ -359,7 +363,7 @@ public class Peer implements Comparable
|
||||
String bittorrentProtocol = new String(bs, "UTF-8");
|
||||
if (!"BitTorrent protocol".equals(bittorrentProtocol))
|
||||
throw new IOException("Handshake failure, expected "
|
||||
+ "'Bittorrent protocol', got '"
|
||||
+ "'BitTorrent protocol', got '"
|
||||
+ bittorrentProtocol + "'");
|
||||
|
||||
// Handshake read - options
|
||||
@ -457,6 +461,8 @@ public class Peer implements Comparable
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (!_disconnected.compareAndSet(false, true))
|
||||
return;
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
@ -476,9 +482,11 @@ public class Peer implements Comparable
|
||||
PeerConnectionIn in = s.in;
|
||||
if (in != null)
|
||||
in.disconnect();
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
out.disconnect();
|
||||
// this is blocking in streaming, so do this after closing the socket
|
||||
// so it won't really block
|
||||
//PeerConnectionOut out = s.out;
|
||||
//if (out != null)
|
||||
// out.disconnect();
|
||||
PeerListener pl = s.listener;
|
||||
if (pl != null)
|
||||
pl.disconnected(this);
|
||||
@ -492,6 +500,13 @@ public class Peer implements Comparable
|
||||
_log.warn("Error disconnecting " + toString(), ioe);
|
||||
}
|
||||
}
|
||||
if (s != null) {
|
||||
// this is blocking in streaming, so do this after closing the socket
|
||||
// so it won't really block
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
out.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,8 +26,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Base64;
|
||||
@ -40,7 +38,7 @@ import net.i2p.util.Log;
|
||||
* protocol connection. The PeerAcceptor will then create a new peer
|
||||
* if the PeerCoordinator wants more peers.
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
class PeerAcceptor
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerAcceptor.class);
|
||||
private final PeerCoordinator coordinator;
|
||||
@ -170,8 +168,7 @@ public class PeerAcceptor
|
||||
if (b != PROTO[i])
|
||||
throw new IOException("Bad protocol 0x" + Integer.toHexString(b) + " at byte " + i);
|
||||
}
|
||||
if (in.skip(8) != 8)
|
||||
throw new IOException("EOF before hash");
|
||||
DataHelper.skip(in, 8);
|
||||
byte buf[] = new byte[20];
|
||||
int read = DataHelper.read(in, buf);
|
||||
if (read != buf.length)
|
||||
|
@ -24,7 +24,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -73,7 +72,7 @@ class PeerCheckerTask implements Runnable
|
||||
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List<Peer> removed = new ArrayList();
|
||||
List<Peer> removed = new ArrayList<Peer>();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
DHT dht = _util.getDHT();
|
||||
|
@ -65,7 +65,7 @@ class PeerConnectionIn implements Runnable
|
||||
try {
|
||||
din.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream from " + peer, ioe);
|
||||
//_log.warn("Error closing the stream from " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -42,10 +43,10 @@ class PeerConnectionOut implements Runnable
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private final List<Message> sendQueue = new ArrayList();
|
||||
private final List<Message> sendQueue = new ArrayList<Message>();
|
||||
|
||||
private static long __id = 0;
|
||||
private long _id;
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private final long _id;
|
||||
|
||||
long lastSent;
|
||||
|
||||
@ -53,10 +54,9 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
this.peer = peer;
|
||||
this.dout = dout;
|
||||
_id = ++__id;
|
||||
_id = __id.incrementAndGet();
|
||||
|
||||
lastSent = System.currentTimeMillis();
|
||||
quit = false;
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
@ -66,7 +66,7 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
/**
|
||||
* Continuesly monitors for more outgoing messages that have to be send.
|
||||
* Stops if quit is true of an IOException occurs.
|
||||
* Stops if quit is true or an IOException occurs.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
@ -116,10 +116,10 @@ class PeerConnectionOut implements Runnable
|
||||
// And remove piece messages if we are choking.
|
||||
|
||||
// this should get fixed for starvation
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (m == null && it.hasNext())
|
||||
{
|
||||
Message nm = (Message)it.next();
|
||||
Message nm = it.next();
|
||||
if (nm.type == Message.PIECE)
|
||||
{
|
||||
if (state.choking) {
|
||||
@ -215,13 +215,13 @@ class PeerConnectionOut implements Runnable
|
||||
thread.interrupt();
|
||||
|
||||
sendQueue.clear();
|
||||
sendQueue.notify();
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
if (dout != null) {
|
||||
try {
|
||||
dout.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream to " + peer, ioe);
|
||||
//_log.warn("Error closing the stream to " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,10 +274,10 @@ class PeerConnectionOut implements Runnable
|
||||
boolean removed = false;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
Message m = it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
it.remove();
|
||||
@ -360,13 +360,13 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
|
||||
void retransmitRequests(List requests)
|
||||
void retransmitRequests(List<Request> requests)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator it = requests.iterator();
|
||||
Iterator<Request> it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
Request req = it.next();
|
||||
if(now > req.sendTime + REQ_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Retransmit request " + req + " to peer " + peer);
|
||||
@ -375,12 +375,12 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
void sendRequests(List<Request> requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
Iterator<Request> it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
Request req = it.next();
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
@ -391,10 +391,10 @@ class PeerConnectionOut implements Runnable
|
||||
// (multiple choke/unchokes received cause duplicate requests in the queue)
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
Message m = it.next();
|
||||
if (m.type == Message.REQUEST && m.piece == req.getPiece() &&
|
||||
m.begin == req.off && m.length == req.len)
|
||||
{
|
||||
@ -419,10 +419,10 @@ class PeerConnectionOut implements Runnable
|
||||
int total = 0;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
Message m = it.next();
|
||||
if (m.type == Message.PIECE)
|
||||
total += m.length;
|
||||
}
|
||||
@ -489,10 +489,10 @@ class PeerConnectionOut implements Runnable
|
||||
// See if it is still in our send queue
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
Message m = it.next();
|
||||
if (m.type == Message.REQUEST
|
||||
&& m.piece == req.getPiece()
|
||||
&& m.begin == req.off
|
||||
@ -530,10 +530,10 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
Iterator<Message> it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
Message m = it.next();
|
||||
if (m.type == Message.PIECE
|
||||
&& m.piece == piece
|
||||
&& m.begin == begin
|
||||
|
@ -151,12 +151,12 @@ class PeerCoordinator implements PeerListener
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
wantedPieces = new ArrayList();
|
||||
wantedPieces = new ArrayList<Piece>();
|
||||
setWantedPieces();
|
||||
partialPieces = new ArrayList(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue();
|
||||
partialPieces = new ArrayList<PartialPiece>(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue<Peer>();
|
||||
magnetState = new MagnetState(infohash, metainfo);
|
||||
pexPeers = new ConcurrentHashSet();
|
||||
pexPeers = new ConcurrentHashSet<PeerID>();
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
@ -218,7 +218,7 @@ class PeerCoordinator implements PeerListener
|
||||
/** for web page detailed stats */
|
||||
public List<Peer> peerList()
|
||||
{
|
||||
return new ArrayList(peers);
|
||||
return new ArrayList<Peer>(peers);
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
@ -376,10 +376,10 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public boolean needOutboundPeers() {
|
||||
//return wantedBytes != 0 && needPeers();
|
||||
// minus one to make it a little easier for new peers to get in on large swarms
|
||||
// minus two to make it a little easier for new peers to get in on large swarms
|
||||
return wantedBytes != 0 &&
|
||||
!halted &&
|
||||
peers.size() < getMaxConnections() - 1 &&
|
||||
peers.size() < getMaxConnections() - 2 &&
|
||||
(storage == null || !storage.isChecking());
|
||||
}
|
||||
|
||||
@ -412,7 +412,7 @@ class PeerCoordinator implements PeerListener
|
||||
public void halt()
|
||||
{
|
||||
halted = true;
|
||||
List<Peer> removed = new ArrayList();
|
||||
List<Peer> removed = new ArrayList<Peer>();
|
||||
synchronized(peers)
|
||||
{
|
||||
// Stop peer checker task.
|
||||
@ -613,7 +613,7 @@ class PeerCoordinator implements PeerListener
|
||||
// linked list will contain all interested peers that we choke.
|
||||
// At the start are the peers that have us unchoked at the end the
|
||||
// other peer that are interested, but are choking us.
|
||||
List<Peer> interested = new LinkedList();
|
||||
List<Peer> interested = new LinkedList<Peer>();
|
||||
int count = 0;
|
||||
int unchokedCount = 0;
|
||||
int maxUploaders = allowedUploaders();
|
||||
@ -729,7 +729,7 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
|
||||
Piece piece = null;
|
||||
List<Piece> requested = new ArrayList();
|
||||
List<Piece> requested = new ArrayList<Piece>();
|
||||
int wantedSize = END_GAME_THRESHOLD + 1;
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
@ -833,7 +833,7 @@ class PeerCoordinator implements PeerListener
|
||||
_log.debug("Updated piece priorities called but no priorities to set?");
|
||||
return;
|
||||
}
|
||||
List<Piece> toCancel = new ArrayList();
|
||||
List<Piece> toCancel = new ArrayList<Piece>();
|
||||
synchronized(wantedPieces) {
|
||||
// Add incomplete and previously unwanted pieces to the list
|
||||
// Temp to avoid O(n**2)
|
||||
@ -999,13 +999,13 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
snark.stopTorrent();
|
||||
String msg = "Error writing storage (piece " + piece + ") for " + metainfo.getName() + ": " + ioe;
|
||||
_log.error(msg, ioe);
|
||||
if (listener != null) {
|
||||
listener.addMessage(msg);
|
||||
listener.addMessage("Fatal storage error: Stopping torrent " + metainfo.getName());
|
||||
}
|
||||
snark.stopTorrent();
|
||||
throw new RuntimeException(msg, ioe);
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
@ -1019,7 +1019,7 @@ class PeerCoordinator implements PeerListener
|
||||
|
||||
// Announce to the world we have it!
|
||||
// Disconnect from other seeders when we get the last piece
|
||||
List<Peer> toDisconnect = done ? new ArrayList() : null;
|
||||
List<Peer> toDisconnect = done ? new ArrayList<Peer>() : null;
|
||||
for (Peer p : peers) {
|
||||
if (p.isConnected())
|
||||
{
|
||||
@ -1231,16 +1231,16 @@ class PeerCoordinator implements PeerListener
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
if (skipped)
|
||||
_log.warn("Partial piece " + pp + " with multiple peers skipped for seeder");
|
||||
_log.info("Partial piece " + pp + " with multiple peers skipped for seeder");
|
||||
else
|
||||
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
|
||||
_log.info("Partial piece " + pp + " NOT in wantedPieces??");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN) && !partialPieces.isEmpty())
|
||||
_log.warn("Peer " + peer + " has none of our partials " + partialPieces);
|
||||
if (_log.shouldLog(Log.INFO) && !partialPieces.isEmpty())
|
||||
_log.info("Peer " + peer + " has none of our partials " + partialPieces);
|
||||
}
|
||||
// ...and this section turns this into the general move-requests-around code!
|
||||
// Temporary? So PeerState never calls wantPiece() directly for now...
|
||||
|
@ -12,11 +12,11 @@ import net.i2p.crypto.SHA1Hash;
|
||||
* Each PeerCoordinator is added to the set from within the Snark (and removed
|
||||
* from it there too)
|
||||
*/
|
||||
public class PeerCoordinatorSet {
|
||||
class PeerCoordinatorSet implements Iterable<PeerCoordinator> {
|
||||
private final Map<SHA1Hash, PeerCoordinator> _coordinators;
|
||||
|
||||
public PeerCoordinatorSet() {
|
||||
_coordinators = new ConcurrentHashMap();
|
||||
_coordinators = new ConcurrentHashMap<SHA1Hash, PeerCoordinator>();
|
||||
}
|
||||
|
||||
public Iterator<PeerCoordinator> iterator() {
|
||||
|
@ -24,7 +24,6 @@ import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -43,7 +42,7 @@ import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
* and the PeerID is not required.
|
||||
* Equality is now determined solely by the dest hash.
|
||||
*/
|
||||
public class PeerID implements Comparable
|
||||
class PeerID implements Comparable
|
||||
{
|
||||
private byte[] id;
|
||||
private Destination address;
|
||||
@ -58,7 +57,7 @@ public class PeerID implements Comparable
|
||||
{
|
||||
this.id = id;
|
||||
this.address = address;
|
||||
this.port = 6881;
|
||||
this.port = TrackerClient.PORT;
|
||||
this.destHash = address.calculateHash().getData();
|
||||
hash = calculateHash();
|
||||
util = null;
|
||||
@ -92,7 +91,7 @@ public class PeerID implements Comparable
|
||||
if (address == null)
|
||||
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||
|
||||
port = 6881;
|
||||
port = TrackerClient.PORT;
|
||||
this.destHash = address.calculateHash().getData();
|
||||
hash = calculateHash();
|
||||
util = null;
|
||||
@ -106,7 +105,7 @@ public class PeerID implements Comparable
|
||||
public PeerID(byte[] dest_hash, I2PSnarkUtil util) throws InvalidBEncodingException
|
||||
{
|
||||
// id and address remain null
|
||||
port = 6881;
|
||||
port = TrackerClient.PORT;
|
||||
if (dest_hash.length != 32)
|
||||
throw new InvalidBEncodingException("bad hash length");
|
||||
destHash = dest_hash;
|
||||
|
@ -27,6 +27,8 @@ import net.i2p.data.DataHelper;
|
||||
/**
|
||||
* TimerTask that monitors the peers and total up/download speeds.
|
||||
* Works together with the main Snark class to report periodical statistics.
|
||||
*
|
||||
* @deprecated unused, for command line client only, commented out in Snark.java
|
||||
*/
|
||||
class PeerMonitorTask implements Runnable
|
||||
{
|
||||
@ -45,6 +47,7 @@ class PeerMonitorTask implements Runnable
|
||||
|
||||
public void run()
|
||||
{
|
||||
/*****
|
||||
// Get some statistics
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
@ -117,5 +120,6 @@ class PeerMonitorTask implements Runnable
|
||||
|
||||
lastDownloaded = downloaded;
|
||||
lastUploaded = uploaded;
|
||||
****/
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ class PeerState implements DataLoader
|
||||
final PeerConnectionOut out;
|
||||
|
||||
// Outstanding request
|
||||
private final List<Request> outstandingRequests = new ArrayList();
|
||||
private final List<Request> outstandingRequests = new ArrayList<Request>();
|
||||
/** the tail (NOT the head) of the request queue */
|
||||
private Request lastRequest = null;
|
||||
|
||||
@ -451,7 +451,7 @@ class PeerState implements DataLoader
|
||||
synchronized List<Request> returnPartialPieces()
|
||||
{
|
||||
Set<Integer> pcs = getRequestedPieces();
|
||||
List<Request> rv = new ArrayList(pcs.size());
|
||||
List<Request> rv = new ArrayList<Request>(pcs.size());
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null) {
|
||||
@ -469,7 +469,7 @@ class PeerState implements DataLoader
|
||||
* @return all pieces we are currently requesting, or empty Set
|
||||
*/
|
||||
synchronized private Set<Integer> getRequestedPieces() {
|
||||
Set<Integer> rv = new HashSet(outstandingRequests.size() + 1);
|
||||
Set<Integer> rv = new HashSet<Integer>(outstandingRequests.size() + 1);
|
||||
for (Request req : outstandingRequests) {
|
||||
rv.add(Integer.valueOf(req.getPiece()));
|
||||
if (pendingRequest != null)
|
||||
|
@ -18,7 +18,7 @@ class Piece implements Comparable {
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = new HashSet(I2PSnarkUtil.MAX_CONNECTIONS / 2);
|
||||
this.peers = new HashSet<PeerID>(I2PSnarkUtil.MAX_CONNECTIONS / 2);
|
||||
// defer creating requests to save memory
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ class Piece implements Comparable {
|
||||
public void setRequested(Peer peer, boolean requested) {
|
||||
if (requested) {
|
||||
if (this.requests == null)
|
||||
this.requests = new HashSet(2);
|
||||
this.requests = new HashSet<PeerID>(2);
|
||||
this.requests.add(peer.getPeerID());
|
||||
} else {
|
||||
if (this.requests != null)
|
||||
|
@ -25,7 +25,6 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
@ -34,7 +33,6 @@ import java.util.StringTokenizer;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -232,11 +230,12 @@ public class Snark
|
||||
private byte[] id;
|
||||
private final byte[] infoHash;
|
||||
private String additionalTrackerURL;
|
||||
private final I2PSnarkUtil _util;
|
||||
protected final I2PSnarkUtil _util;
|
||||
private final Log _log;
|
||||
private final PeerCoordinatorSet _peerCoordinatorSet;
|
||||
private String trackerProblems;
|
||||
private int trackerSeenPeers;
|
||||
private volatile String trackerProblems;
|
||||
private volatile int trackerSeenPeers;
|
||||
private volatile boolean _autoStoppable;
|
||||
|
||||
|
||||
/** from main() via parseArguments() single torrent */
|
||||
@ -526,18 +525,17 @@ public class Snark
|
||||
if (_peerCoordinatorSet != null) {
|
||||
// multitorrent
|
||||
_peerCoordinatorSet.add(coordinator);
|
||||
if (acceptor != null) {
|
||||
acceptor.startAccepting(_peerCoordinatorSet, serversocket);
|
||||
} else {
|
||||
// error
|
||||
}
|
||||
} else {
|
||||
// single torrent
|
||||
acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator));
|
||||
acceptor = new ConnectionAcceptor(_util, new PeerAcceptor(coordinator));
|
||||
}
|
||||
// TODO pass saved closest DHT nodes to the tracker? or direct to the coordinator?
|
||||
trackerclient = new TrackerClient(_util, meta, additionalTrackerURL, coordinator, this);
|
||||
}
|
||||
// ensure acceptor is running when in multitorrent
|
||||
if (_peerCoordinatorSet != null && acceptor != null) {
|
||||
acceptor.startAccepting();
|
||||
}
|
||||
|
||||
stopped = false;
|
||||
if (coordinator.halted()) {
|
||||
@ -761,7 +759,7 @@ public class Snark
|
||||
PeerCoordinator coord = coordinator;
|
||||
if (coord != null)
|
||||
return coord.peerList();
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -905,6 +903,16 @@ public class Snark
|
||||
return additionalTrackerURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public boolean isAutoStoppable() { return _autoStoppable; }
|
||||
|
||||
/**
|
||||
* @since 0.9.9
|
||||
*/
|
||||
public void setAutoStoppable(boolean yes) { _autoStoppable = yes; }
|
||||
|
||||
/**
|
||||
* Sets debug, ip and torrent variables then creates a Snark
|
||||
* instance. Calls usage(), which terminates the program, if
|
||||
@ -1226,8 +1234,7 @@ public class Snark
|
||||
if (_peerCoordinatorSet == null || uploaders <= 0)
|
||||
return false;
|
||||
int totalUploaders = 0;
|
||||
for (Iterator<PeerCoordinator> iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = iter.next();
|
||||
for (PeerCoordinator c : _peerCoordinatorSet) {
|
||||
if (!c.halted())
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
@ -1240,8 +1247,7 @@ public class Snark
|
||||
if (_peerCoordinatorSet == null)
|
||||
return false;
|
||||
long total = 0;
|
||||
for (Iterator<PeerCoordinator> iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = iter.next();
|
||||
for (PeerCoordinator c : _peerCoordinatorSet) {
|
||||
if (!c.halted())
|
||||
total += c.getCurrentUploadRate();
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
@ -35,8 +34,8 @@ import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.dht.DHT;
|
||||
|
||||
@ -57,6 +56,8 @@ public class SnarkManager implements CompleteListener {
|
||||
private /* FIXME final FIXME */ File _configFile;
|
||||
private Properties _config;
|
||||
private final I2PAppContext _context;
|
||||
private final String _contextPath;
|
||||
private final String _contextName;
|
||||
private final Log _log;
|
||||
private final Queue<String> _messages;
|
||||
private final I2PSnarkUtil _util;
|
||||
@ -68,6 +69,7 @@ public class SnarkManager implements CompleteListener {
|
||||
private final Map<String, Tracker> _trackerMap;
|
||||
private UpdateManager _umgr;
|
||||
private UpdateHandler _uhandler;
|
||||
private SimpleTimer2.TimedEvent _idleChecker;
|
||||
|
||||
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
|
||||
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
|
||||
@ -82,7 +84,7 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final String PROP_META_PRIORITY_SUFFIX = ".priority";
|
||||
public static final String PROP_META_MAGNET_PREFIX = "i2psnark.magnet.";
|
||||
|
||||
private static final String CONFIG_FILE = "i2psnark.config";
|
||||
private static final String CONFIG_FILE_SUFFIX = ".config";
|
||||
public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
@ -90,6 +92,7 @@ public class SnarkManager implements CompleteListener {
|
||||
//public static final String DEFAULT_LINK_PREFIX = "file:///";
|
||||
public static final String PROP_STARTUP_DELAY = "i2psnark.startupDelay";
|
||||
public static final String PROP_REFRESH_DELAY = "i2psnark.refreshSeconds";
|
||||
public static final String PROP_PAGE_SIZE = "i2psnark.pageSize";
|
||||
public static final String RC_PROP_THEME = "routerconsole.theme";
|
||||
public static final String RC_PROP_UNIVERSAL_THEMING = "routerconsole.universal.theme";
|
||||
public static final String PROP_THEME = "i2psnark.theme";
|
||||
@ -103,6 +106,7 @@ public class SnarkManager implements CompleteListener {
|
||||
public static final int DEFAULT_MAX_UP_BW = 10;
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final int DEFAULT_REFRESH_DELAY_SECS = 60;
|
||||
private static final int DEFAULT_PAGE_SIZE = 50;
|
||||
|
||||
/**
|
||||
* "name", "announceURL=websiteURL" pairs
|
||||
@ -120,7 +124,7 @@ public class SnarkManager implements CompleteListener {
|
||||
// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
|
||||
"Postman", "http://tracker2.postman.i2p/announce.php=http://tracker2.postman.i2p/"
|
||||
,"Welterde", "http://tracker.welterde.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
|
||||
,"Diftracker", "http://n--XWjHjUPjnMNrSwXA2OYXpMIUL~u4FNXnrt2HtjK3y6j~4SOClyyeKzd0zRPlixxkCe2wfBIYye3bZsaqAD8bd0QMmowxbq91WpjsPfKMiphJbePKXtYAVARiy0cqyvh1d2LyDE-6wkvgaw45hknmS0U-Dg3YTJZbAQRU2SKXgIlAbWCv4R0kDFBLEVpReDiJef3rzAWHiW8yjmJuJilkYjMwlfRjw8xx1nl2s~yhlljk1pl13jGYb0nfawQnuOWeP-ASQWvAAyVgKvZRJE2O43S7iveu9piuv7plXWbt36ef7ndu2GNoNyPOBdpo9KUZ-NOXm4Kgh659YtEibL15dEPAOdxprY0sYUurVw8OIWqrpX7yn08nbi6qHVGqQwTpxH35vkL8qrCbm-ym7oQJQnNmSDrNTyWYRFSq5s5~7DAdFDzqRPW-pX~g0zEivWj5tzkhvG9rVFgFo0bpQX3X0PUAV9Xbyf8u~v8Zbr9K1pCPqBq9XEr4TqaLHw~bfAAAA.i2p/announce.php=http://diftracker.i2p/"
|
||||
,"Diftracker", "http://diftracker.i2p/announce.php=http://diftracker.i2p/"
|
||||
// , "CRSTRACK", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/"
|
||||
// ,"Exotrack", "http://blbgywsjubw3d2zih2giokakhe3o2cko7jtte4risb3hohbcoyva.b32.i2p/announce.php=http://exotrack.i2p/"
|
||||
};
|
||||
@ -128,18 +132,34 @@ public class SnarkManager implements CompleteListener {
|
||||
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
|
||||
public static final String PROP_TRACKERS = "i2psnark.trackers";
|
||||
|
||||
/**
|
||||
* For embedded.
|
||||
*/
|
||||
public SnarkManager(I2PAppContext ctx) {
|
||||
_snarks = new ConcurrentHashMap();
|
||||
_magnets = new ConcurrentHashSet();
|
||||
this(ctx, "/i2psnark", "i2psnark");
|
||||
}
|
||||
|
||||
/**
|
||||
* For webapp.
|
||||
* @param ctxPath generally "/i2psnark"
|
||||
* @param ctxName generally "i2psnark"
|
||||
* @since 0.9.6
|
||||
*/
|
||||
public SnarkManager(I2PAppContext ctx, String ctxPath, String ctxName) {
|
||||
_snarks = new ConcurrentHashMap<String, Snark>();
|
||||
_magnets = new ConcurrentHashSet<String>();
|
||||
_addSnarkLock = new Object();
|
||||
_context = ctx;
|
||||
_contextPath = ctxPath;
|
||||
_contextName = ctxName;
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new LinkedBlockingQueue();
|
||||
_util = new I2PSnarkUtil(_context);
|
||||
_configFile = new File(CONFIG_FILE);
|
||||
_messages = new LinkedBlockingQueue<String>();
|
||||
_util = new I2PSnarkUtil(_context, ctxName);
|
||||
String cfile = ctxName + CONFIG_FILE_SUFFIX;
|
||||
_configFile = new File(cfile);
|
||||
if (!_configFile.isAbsolute())
|
||||
_configFile = new File(_context.getConfigDir(), CONFIG_FILE);
|
||||
_trackerMap = new ConcurrentHashMap(4);
|
||||
_configFile = new File(_context.getConfigDir(), cfile);
|
||||
_trackerMap = new ConcurrentHashMap<String, Tracker>(4);
|
||||
loadConfig(null);
|
||||
}
|
||||
|
||||
@ -149,13 +169,17 @@ public class SnarkManager implements CompleteListener {
|
||||
public void start() {
|
||||
_running = true;
|
||||
_peerCoordinatorSet = new PeerCoordinatorSet();
|
||||
_connectionAcceptor = new ConnectionAcceptor(_util);
|
||||
_connectionAcceptor = new ConnectionAcceptor(_util, _peerCoordinatorSet);
|
||||
_monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
|
||||
_monitor.start();
|
||||
// delay until UpdateManager is there
|
||||
_context.simpleScheduler().addEvent(new Register(), 4*60*1000);
|
||||
// only if default instance
|
||||
if ("i2psnark".equals(_contextName))
|
||||
// delay until UpdateManager is there
|
||||
_context.simpleScheduler().addEvent(new Register(), 4*60*1000);
|
||||
// Not required, Jetty has a shutdown hook
|
||||
//_context.addShutdownTask(new SnarkManagerShutdown());
|
||||
_idleChecker = new IdleChecker(this, _peerCoordinatorSet);
|
||||
_idleChecker.schedule(5*60*1000);
|
||||
}
|
||||
|
||||
/** @since 0.9.4 */
|
||||
@ -167,6 +191,7 @@ public class SnarkManager implements CompleteListener {
|
||||
if (_umgr != null) {
|
||||
_uhandler = new UpdateHandler(_context, _umgr, SnarkManager.this);
|
||||
_umgr.register(_uhandler, UpdateType.ROUTER_SIGNED, UpdateMethod.TORRENT, 10);
|
||||
_umgr.register(_uhandler, UpdateType.ROUTER_SIGNED_SU3, UpdateMethod.TORRENT, 10);
|
||||
_log.warn("Registering with update manager");
|
||||
} else {
|
||||
_log.warn("No update manager to register with");
|
||||
@ -184,10 +209,12 @@ public class SnarkManager implements CompleteListener {
|
||||
if (_umgr != null && _uhandler != null) {
|
||||
//_uhandler.shutdown();
|
||||
_umgr.unregister(_uhandler, UpdateType.ROUTER_SIGNED, UpdateMethod.TORRENT);
|
||||
_umgr.unregister(_uhandler, UpdateType.ROUTER_SIGNED_SU3, UpdateMethod.TORRENT);
|
||||
}
|
||||
_running = false;
|
||||
_monitor.interrupt();
|
||||
_connectionAcceptor.halt();
|
||||
_idleChecker.cancel();
|
||||
stopAllTorrents(true);
|
||||
}
|
||||
|
||||
@ -211,8 +238,8 @@ public class SnarkManager implements CompleteListener {
|
||||
/** newest last */
|
||||
public List<String> getMessages() {
|
||||
if (_messages.isEmpty())
|
||||
return Collections.EMPTY_LIST;
|
||||
return new ArrayList(_messages);
|
||||
return Collections.emptyList();
|
||||
return new ArrayList<String>(_messages);
|
||||
}
|
||||
|
||||
/** @since 0.9 */
|
||||
@ -250,6 +277,18 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For GUI
|
||||
* @since 0.9.6
|
||||
*/
|
||||
public int getPageSize() {
|
||||
try {
|
||||
return Integer.parseInt(_config.getProperty(PROP_PAGE_SIZE));
|
||||
} catch (NumberFormatException nfe) {
|
||||
return DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
private int getStartupDelayMinutes() {
|
||||
try {
|
||||
return Integer.parseInt(_config.getProperty(PROP_STARTUP_DELAY));
|
||||
@ -259,7 +298,7 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
|
||||
public File getDataDir() {
|
||||
String dir = _config.getProperty(PROP_DIR, "i2psnark");
|
||||
String dir = _config.getProperty(PROP_DIR, _contextName);
|
||||
File f;
|
||||
if (areFilesPublic())
|
||||
f = new File(dir);
|
||||
@ -305,13 +344,15 @@ public class SnarkManager implements CompleteListener {
|
||||
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, "i2psnark");
|
||||
_config.setProperty(PROP_DIR, _contextName);
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
|
||||
if (!_config.containsKey(PROP_REFRESH_DELAY))
|
||||
_config.setProperty(PROP_REFRESH_DELAY, Integer.toString(DEFAULT_REFRESH_DELAY_SECS));
|
||||
if (!_config.containsKey(PROP_STARTUP_DELAY))
|
||||
_config.setProperty(PROP_STARTUP_DELAY, Integer.toString(DEFAULT_STARTUP_DELAY));
|
||||
if (!_config.containsKey(PROP_PAGE_SIZE))
|
||||
_config.setProperty(PROP_PAGE_SIZE, Integer.toString(DEFAULT_PAGE_SIZE));
|
||||
if (!_config.containsKey(PROP_THEME))
|
||||
_config.setProperty(PROP_THEME, DEFAULT_THEME);
|
||||
// no, so we can switch default to true later
|
||||
@ -429,11 +470,15 @@ public class SnarkManager implements CompleteListener {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* all params may be null or need trimming
|
||||
*/
|
||||
public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
|
||||
String startDelay, String seedPct, String eepHost,
|
||||
String startDelay, String pageSize, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme) {
|
||||
boolean changed = false;
|
||||
boolean interruptMonitor = false;
|
||||
//if (eepHost != null) {
|
||||
// // unused, we use socket eepget
|
||||
// int port = _util.getEepProxyPort();
|
||||
@ -450,12 +495,12 @@ public class SnarkManager implements CompleteListener {
|
||||
//}
|
||||
if (upLimit != null) {
|
||||
int limit = _util.getMaxUploaders();
|
||||
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
|
||||
try { limit = Integer.parseInt(upLimit.trim()); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != _util.getMaxUploaders()) {
|
||||
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
|
||||
_util.setMaxUploaders(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, Integer.toString(limit));
|
||||
addMessage(_("Total uploaders limit changed to {0}", limit));
|
||||
} else {
|
||||
addMessage(_("Minimum total uploaders limit is {0}", Snark.MIN_TOTAL_UPLOADERS));
|
||||
@ -464,12 +509,12 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
if (upBW != null) {
|
||||
int limit = _util.getMaxUpBW();
|
||||
try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
|
||||
try { limit = Integer.parseInt(upBW.trim()); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != _util.getMaxUpBW()) {
|
||||
if ( limit >= MIN_UP_BW ) {
|
||||
_util.setMaxUpBW(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPBW_MAX, "" + limit);
|
||||
_config.setProperty(PROP_UPBW_MAX, Integer.toString(limit));
|
||||
addMessage(_("Up BW limit changed to {0}KBps", limit));
|
||||
} else {
|
||||
addMessage(_("Minimum up bandwidth limit is {0}KBps", MIN_UP_BW));
|
||||
@ -479,21 +524,21 @@ public class SnarkManager implements CompleteListener {
|
||||
|
||||
if (startDelay != null){
|
||||
int minutes = _util.getStartupDelay();
|
||||
try { minutes = Integer.parseInt(startDelay); } catch (NumberFormatException nfe) {}
|
||||
try { minutes = Integer.parseInt(startDelay.trim()); } catch (NumberFormatException nfe) {}
|
||||
if ( minutes != _util.getStartupDelay()) {
|
||||
_util.setStartupDelay(minutes);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_STARTUP_DELAY, "" + minutes);
|
||||
addMessage(_("Startup delay changed to {0}", DataHelper.formatDuration2(minutes * 60 * 1000)));
|
||||
_config.setProperty(PROP_STARTUP_DELAY, Integer.toString(minutes));
|
||||
addMessage(_("Startup delay changed to {0}", DataHelper.formatDuration2(minutes * (60L * 1000))));
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshDelay != null) {
|
||||
try {
|
||||
int secs = Integer.parseInt(refreshDelay);
|
||||
int secs = Integer.parseInt(refreshDelay.trim());
|
||||
if (secs != getRefreshDelaySeconds()) {
|
||||
changed = true;
|
||||
_config.setProperty(PROP_REFRESH_DELAY, refreshDelay);
|
||||
_config.setProperty(PROP_REFRESH_DELAY, Integer.toString(secs));
|
||||
if (secs >= 0)
|
||||
addMessage(_("Refresh time changed to {0}", DataHelper.formatDuration2(secs * 1000)));
|
||||
else
|
||||
@ -502,6 +547,42 @@ public class SnarkManager implements CompleteListener {
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
if (pageSize != null) {
|
||||
try {
|
||||
int size = Integer.parseInt(pageSize.trim());
|
||||
if (size <= 0)
|
||||
size = 999999;
|
||||
else if (size < 5)
|
||||
size = 5;
|
||||
if (size != getPageSize()) {
|
||||
changed = true;
|
||||
pageSize = Integer.toString(size);
|
||||
_config.setProperty(PROP_PAGE_SIZE, pageSize);
|
||||
addMessage(_("Page size changed to {0}", pageSize));
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
if (dataDir != null && !dataDir.equals(getDataDir().getAbsolutePath())) {
|
||||
dataDir = dataDir.trim();
|
||||
File dd = new File(dataDir);
|
||||
if (!dd.isAbsolute()) {
|
||||
addMessage(_("Data directory must be an absolute path") + ": " + dataDir);
|
||||
} else if (!dd.exists()) {
|
||||
addMessage(_("Data directory does not exist") + ": " + dataDir);
|
||||
} else if (!dd.isDirectory()) {
|
||||
addMessage(_("Not a directory") + ": " + dataDir);
|
||||
} else if (!dd.canRead()) {
|
||||
addMessage(_("Unreadable") + ": " + dataDir);
|
||||
} else {
|
||||
changed = true;
|
||||
interruptMonitor = true;
|
||||
_config.setProperty(PROP_DIR, dataDir);
|
||||
addMessage(_("Data directory changed to {0}", dataDir));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Start of I2CP stuff.
|
||||
// i2cpHost will generally be null since it is hidden from the form if in router context.
|
||||
|
||||
@ -512,7 +593,7 @@ public class SnarkManager implements CompleteListener {
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
Map<String, String> opts = new HashMap();
|
||||
Map<String, String> opts = new HashMap<String, String>();
|
||||
if (i2cpOpts == null) i2cpOpts = "";
|
||||
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
|
||||
while (tok.hasMoreTokens()) {
|
||||
@ -521,7 +602,7 @@ public class SnarkManager implements CompleteListener {
|
||||
if (split > 0)
|
||||
opts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
Map<String, String> oldOpts = new HashMap();
|
||||
Map<String, String> oldOpts = new HashMap<String, String>();
|
||||
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
|
||||
if (oldI2CPOpts == null) oldI2CPOpts = "";
|
||||
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
|
||||
@ -559,6 +640,7 @@ public class SnarkManager implements CompleteListener {
|
||||
addMessage(_("I2CP options changed to {0}", i2cpOpts));
|
||||
_util.setI2CPConfig(oldI2CPHost, oldI2CPPort, opts);
|
||||
} else {
|
||||
// Won't happen, I2CP host/port, are hidden in the GUI if in router context
|
||||
if (_util.connected()) {
|
||||
_util.disconnect();
|
||||
addMessage(_("Disconnecting old I2CP destination"));
|
||||
@ -582,6 +664,8 @@ public class SnarkManager implements CompleteListener {
|
||||
for (Snark snark : _snarks.values()) {
|
||||
if (snark.restartAcceptor()) {
|
||||
addMessage(_("I2CP listener restarted for \"{0}\"", snark.getBaseName()));
|
||||
// this is the common ConnectionAcceptor, so we only need to do it once
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -636,6 +720,9 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
if (changed) {
|
||||
saveConfig();
|
||||
if (interruptMonitor)
|
||||
// Data dir changed. this will stop and remove all old torrents, and add the new ones
|
||||
_monitor.interrupt();
|
||||
} else {
|
||||
addMessage(_("Configuration unchanged."));
|
||||
}
|
||||
@ -648,7 +735,7 @@ public class SnarkManager implements CompleteListener {
|
||||
*/
|
||||
private List<String> getOpenTrackers() {
|
||||
if (!_util.shouldUseOpenTrackers())
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
return getListConfig(PROP_OPENTRACKERS, I2PSnarkUtil.DEFAULT_OPENTRACKERS);
|
||||
}
|
||||
|
||||
@ -665,7 +752,7 @@ public class SnarkManager implements CompleteListener {
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void saveOpenTrackers(List<String> ot) {
|
||||
String val = setListConfig(PROP_OPENTRACKERS, ot);
|
||||
setListConfig(PROP_OPENTRACKERS, ot);
|
||||
if (ot == null)
|
||||
ot = Collections.singletonList(I2PSnarkUtil.DEFAULT_OPENTRACKERS);
|
||||
_util.setOpenTrackers(ot);
|
||||
@ -693,7 +780,7 @@ public class SnarkManager implements CompleteListener {
|
||||
if (val == null)
|
||||
val = dflt;
|
||||
if (val == null)
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
return Arrays.asList(val.split(","));
|
||||
}
|
||||
|
||||
@ -731,6 +818,11 @@ public class SnarkManager implements CompleteListener {
|
||||
|
||||
public Properties getConfig() { return _config; }
|
||||
|
||||
/** @since Jetty 7 */
|
||||
public String getConfigFilename() {
|
||||
return _configFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
|
||||
public static final int MAX_FILES_PER_TORRENT = 512;
|
||||
|
||||
@ -739,7 +831,7 @@ public class SnarkManager implements CompleteListener {
|
||||
* An unsynchronized copy.
|
||||
*/
|
||||
public Set<String> listTorrentFiles() {
|
||||
return new HashSet(_snarks.keySet());
|
||||
return new HashSet<String>(_snarks.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -810,7 +902,7 @@ public class SnarkManager implements CompleteListener {
|
||||
filename = sfile.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to add the torrent " + filename, ioe);
|
||||
addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe.getMessage());
|
||||
addMessage(_("Error: Could not add the torrent {0}", filename) + ": " + ioe);
|
||||
return;
|
||||
}
|
||||
File dataDir = getDataDir();
|
||||
@ -886,7 +978,9 @@ public class SnarkManager implements CompleteListener {
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage(_("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage());
|
||||
String err = _("Torrent in \"{0}\" is invalid", sfile.getName()) + ": " + ioe.getMessage();
|
||||
addMessage(err);
|
||||
_log.error(err, ioe);
|
||||
if (sfile.exists())
|
||||
sfile.delete();
|
||||
return;
|
||||
@ -922,7 +1016,8 @@ public class SnarkManager implements CompleteListener {
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void addMagnet(String name, byte[] ih, String trackerURL, boolean updateStatus) {
|
||||
addMagnet(name, ih, trackerURL, updateStatus, shouldAutoStart(), this);
|
||||
// updateStatus is true from UI, false from config file bulk add
|
||||
addMagnet(name, ih, trackerURL, updateStatus, updateStatus, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1289,7 +1384,7 @@ public class SnarkManager implements CompleteListener {
|
||||
* @return failure message or null on success
|
||||
*/
|
||||
private String validateTorrent(MetaInfo info) {
|
||||
List files = info.getFiles();
|
||||
List<List<String>> files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return _("Too many files in \"{0}\" ({1}), deleting it!", info.getName(), files.size());
|
||||
} else if ( (files == null) && (info.getName().endsWith(".torrent")) ) {
|
||||
@ -1305,7 +1400,7 @@ public class SnarkManager implements CompleteListener {
|
||||
return _("Torrent \"{0}\" has no data, deleting it!", info.getName());
|
||||
} else if (info.getTotalLength() > Storage.MAX_TOTAL_SIZE) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
List lengths = info.getLengths();
|
||||
List<Long> lengths = info.getLengths();
|
||||
if (lengths != null)
|
||||
for (int i = 0; i < lengths.size(); i++)
|
||||
System.out.println("File " + i + " is " + lengths.get(i) + " long.");
|
||||
@ -1392,7 +1487,7 @@ public class SnarkManager implements CompleteListener {
|
||||
private class DirMonitor implements Runnable {
|
||||
public void run() {
|
||||
// don't bother delaying if auto start is false
|
||||
long delay = 60 * 1000 * getStartupDelayMinutes();
|
||||
long delay = (60L * 1000) * getStartupDelayMinutes();
|
||||
if (delay > 0 && shouldAutoStart()) {
|
||||
addMessage(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
@ -1443,7 +1538,7 @@ public class SnarkManager implements CompleteListener {
|
||||
if (meta == null || storage == null)
|
||||
return;
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append("<a href=\"/i2psnark/").append(storage.getBaseName());
|
||||
buf.append("<a href=\"").append(_contextPath).append('/').append(storage.getBaseName());
|
||||
if (meta.getFiles() != null)
|
||||
buf.append('/');
|
||||
buf.append("\">").append(storage.getBaseName()).append("</a>");
|
||||
@ -1572,8 +1667,8 @@ public class SnarkManager implements CompleteListener {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("DirMon found: " + DataHelper.toString(foundNames) + " existing: " + DataHelper.toString(existingNames));
|
||||
// lets find new ones first...
|
||||
for (int i = 0; i < foundNames.size(); i++) {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
for (String name : foundNames) {
|
||||
if (existingNames.contains(name)) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (shouldAutoStart() && !_util.connect())
|
||||
@ -1581,17 +1676,17 @@ public class SnarkManager implements CompleteListener {
|
||||
try {
|
||||
// Snark.fatal() throws a RuntimeException
|
||||
// don't let one bad torrent kill the whole loop
|
||||
addTorrent(foundNames.get(i), !shouldAutoStart());
|
||||
addTorrent(name, !shouldAutoStart());
|
||||
} catch (Exception e) {
|
||||
addMessage(_("Unable to add {0}", foundNames.get(i)) + ": " + e);
|
||||
addMessage(_("Error: Could not add the torrent {0}", name) + ": " + e);
|
||||
_log.error("Unable to add the torrent " + name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't remove magnet torrents that don't have a torrent file yet
|
||||
existingNames.removeAll(_magnets);
|
||||
// now lets see which ones have been removed...
|
||||
for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
for (String name : existingNames) {
|
||||
if (foundNames.contains(name)) {
|
||||
// known and still there. noop
|
||||
} else {
|
||||
@ -1769,6 +1864,14 @@ public class SnarkManager implements CompleteListener {
|
||||
private final Snark snark;
|
||||
public ThreadedStarter(Snark s) { snark = s; }
|
||||
public void run() {
|
||||
try {
|
||||
run2();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void run2() {
|
||||
if (snark != null) {
|
||||
if (snark.isStopped())
|
||||
snark.startTorrent();
|
||||
|
@ -28,6 +28,7 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -50,17 +51,7 @@ import net.i2p.util.SystemVersion;
|
||||
public class Storage
|
||||
{
|
||||
private final MetaInfo metainfo;
|
||||
private long[] lengths;
|
||||
private RandomAccessFile[] rafs;
|
||||
private String[] names;
|
||||
private Object[] RAFlock; // lock on RAF access
|
||||
private long[] RAFtime; // when was RAF last accessed, or 0 if closed
|
||||
private File[] RAFfile; // File to make it easier to reopen
|
||||
/** priorities by file; default 0; may be null. @since 0.8.1 */
|
||||
private int[] priorities;
|
||||
/** is the file empty and sparse? */
|
||||
private boolean[] isSparse;
|
||||
|
||||
private final List<TorrentFile> _torrentFiles;
|
||||
private final StorageListener listener;
|
||||
private final I2PSnarkUtil _util;
|
||||
private final Log _log;
|
||||
@ -77,14 +68,14 @@ public class Storage
|
||||
private final AtomicInteger _allocateCount = new AtomicInteger();
|
||||
|
||||
/** The default piece size. */
|
||||
private static final int MIN_PIECE_SIZE = 256*1024;
|
||||
/** note that we start reducing max number of peer connections above 1MB */
|
||||
public static final int MAX_PIECE_SIZE = 2*1024*1024;
|
||||
private static final int DEFAULT_PIECE_SIZE = 256*1024;
|
||||
/** bigger than this will be rejected */
|
||||
public static final int MAX_PIECE_SIZE = 4*1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
public static final int MAX_PIECES = 10*1024;
|
||||
public static final long MAX_TOTAL_SIZE = MAX_PIECE_SIZE * (long) MAX_PIECES;
|
||||
|
||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap();
|
||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap<String, String>();
|
||||
|
||||
private static final boolean _isWindows = SystemVersion.isWindows();
|
||||
|
||||
@ -108,6 +99,9 @@ public class Storage
|
||||
piece_size = metainfo.getPieceLength(0);
|
||||
pieces = needed;
|
||||
total_length = metainfo.getTotalLength();
|
||||
List<List<String>> files = metainfo.getFiles();
|
||||
int sz = files != null ? files.size() : 1;
|
||||
_torrentFiles = new ArrayList<TorrentFile>(sz);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,6 +116,7 @@ public class Storage
|
||||
* @throws IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(I2PSnarkUtil util, File baseFile, String announce,
|
||||
List<List<String>> announce_list,
|
||||
boolean privateTorrent, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
@ -129,13 +124,13 @@ public class Storage
|
||||
_log = util.getContext().logManager().getLog(Storage.class);
|
||||
this.listener = listener;
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
_torrentFiles = getFiles(baseFile);
|
||||
|
||||
long total = 0;
|
||||
ArrayList<Long> lengthsList = new ArrayList();
|
||||
for (int i = 0; i < lengths.length; i++)
|
||||
ArrayList<Long> lengthsList = new ArrayList<Long>();
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
long length = lengths[i];
|
||||
long length = tf.length;
|
||||
total += length;
|
||||
lengthsList.add(Long.valueOf(length));
|
||||
}
|
||||
@ -145,9 +140,15 @@ public class Storage
|
||||
if (total > MAX_TOTAL_SIZE)
|
||||
throw new IOException("Torrent too big (" + total + " bytes), max is " + MAX_TOTAL_SIZE);
|
||||
|
||||
int pc_size = MIN_PIECE_SIZE;
|
||||
int pc_size;
|
||||
if (total <= 5*1024*1024)
|
||||
pc_size = DEFAULT_PIECE_SIZE / 4;
|
||||
else if (total <= 10*1024*1024)
|
||||
pc_size = DEFAULT_PIECE_SIZE / 2;
|
||||
else
|
||||
pc_size = DEFAULT_PIECE_SIZE;
|
||||
int pcs = (int) ((total - 1)/pc_size) + 1;
|
||||
while (pcs > MAX_PIECES && pc_size < MAX_PIECE_SIZE)
|
||||
while (pcs > (MAX_PIECES * 2 / 3) && pc_size < MAX_PIECE_SIZE)
|
||||
{
|
||||
pc_size *= 2;
|
||||
pcs = (int) ((total - 1)/pc_size) +1;
|
||||
@ -159,11 +160,11 @@ public class Storage
|
||||
bitfield = new BitField(pieces);
|
||||
needed = 0;
|
||||
|
||||
List<List<String>> files = new ArrayList();
|
||||
for (int i = 0; i < names.length; i++)
|
||||
List<List<String>> files = new ArrayList<List<String>>();
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
List<String> file = new ArrayList();
|
||||
StringTokenizer st = new StringTokenizer(names[i], File.separator);
|
||||
List<String> file = new ArrayList<String>();
|
||||
StringTokenizer st = new StringTokenizer(tf.name, File.separator);
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
String part = st.nextToken();
|
||||
@ -172,8 +173,7 @@ public class Storage
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
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
|
||||
if (files.size() == 1 && !baseFile.isDirectory())
|
||||
{
|
||||
files = null;
|
||||
lengthsList = null;
|
||||
@ -182,7 +182,8 @@ public class Storage
|
||||
// TODO thread this so we can return and show something on the UI
|
||||
byte[] piece_hashes = fast_digestCreate();
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
|
||||
lengthsList, piece_size, piece_hashes, total, privateTorrent);
|
||||
lengthsList, piece_size, piece_hashes, total, privateTorrent,
|
||||
announce_list);
|
||||
|
||||
}
|
||||
|
||||
@ -213,42 +214,29 @@ public class Storage
|
||||
return piece_hashes;
|
||||
}
|
||||
|
||||
private void getFiles(File base) throws IOException
|
||||
private List<TorrentFile> getFiles(File base) throws IOException
|
||||
{
|
||||
if (base.getAbsolutePath().equals("/"))
|
||||
throw new IOException("Don't seed root");
|
||||
ArrayList files = new ArrayList();
|
||||
List<File> files = new ArrayList<File>();
|
||||
addFiles(files, base);
|
||||
|
||||
int size = files.size();
|
||||
names = new String[size];
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
priorities = new int[size];
|
||||
isSparse = new boolean[size];
|
||||
List<TorrentFile> rv = new ArrayList<TorrentFile>(size);
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
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();
|
||||
RAFlock[i] = new Object();
|
||||
RAFfile[i] = f;
|
||||
i++;
|
||||
}
|
||||
for (File f : files) {
|
||||
rv.add(new TorrentFile(base, f));
|
||||
}
|
||||
// Sort to prevent exposing OS type, and to make it more likely
|
||||
// the same torrent created twice will have the same infohash.
|
||||
Collections.sort(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException if too many total files
|
||||
*/
|
||||
private void addFiles(List l, File f) throws IOException {
|
||||
private void addFiles(List<File> l, File f) throws IOException {
|
||||
if (!f.isDirectory()) {
|
||||
if (l.size() >= SnarkManager.MAX_FILES_PER_TORRENT)
|
||||
throw new IOException("Too many files, limit is " + SnarkManager.MAX_FILES_PER_TORRENT + ", zip them?");
|
||||
@ -323,8 +311,8 @@ public class Storage
|
||||
*/
|
||||
public long remaining(String file) {
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
String canonical = null;
|
||||
if (f != null) {
|
||||
@ -339,11 +327,11 @@ public class Storage
|
||||
return 0;
|
||||
int psz = piece_size;
|
||||
long start = bytes;
|
||||
long end = start + lengths[i];
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / psz);
|
||||
long rv = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rv = Math.min(psz - (start % psz), lengths[i]);
|
||||
rv = Math.min(psz - (start % psz), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*psz < end)
|
||||
@ -354,7 +342,7 @@ public class Storage
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
bytes += lengths[i];
|
||||
bytes += tf.length;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@ -364,16 +352,16 @@ public class Storage
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int getPriority(String file) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
if (complete() || metainfo.getFiles() == null)
|
||||
return 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file))
|
||||
return priorities[i];
|
||||
return tf.priority;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
@ -388,16 +376,16 @@ public class Storage
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void setPriority(String file, int pri) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
if (complete() || metainfo.getFiles() == null)
|
||||
return;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file)) {
|
||||
priorities[i] = pri;
|
||||
tf.priority = pri;
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
@ -411,6 +399,15 @@ public class Storage
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getFilePriorities() {
|
||||
if (complete())
|
||||
return null;
|
||||
int sz = _torrentFiles.size();
|
||||
if (sz <= 1)
|
||||
return null;
|
||||
int[] priorities = new int[sz];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
priorities[i] = _torrentFiles.get(i).priority;
|
||||
}
|
||||
return priorities;
|
||||
}
|
||||
|
||||
@ -421,7 +418,18 @@ public class Storage
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void setFilePriorities(int[] p) {
|
||||
priorities = p;
|
||||
if (p == null) {
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
tf.priority = 0;
|
||||
}
|
||||
} else {
|
||||
int sz = _torrentFiles.size();
|
||||
if (p.length != sz)
|
||||
throw new IllegalArgumentException();
|
||||
for (int i = 0; i < sz; i++) {
|
||||
_torrentFiles.get(i).priority = p[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,22 +442,23 @@ public class Storage
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getPiecePriorities() {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
if (complete() || metainfo.getFiles() == null)
|
||||
return null;
|
||||
int[] rv = new int[metainfo.getPieces()];
|
||||
int file = 0;
|
||||
long pcEnd = -1;
|
||||
long fileEnd = lengths[0] - 1;
|
||||
long fileEnd = _torrentFiles.get(0).length - 1;
|
||||
int psz = piece_size;
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
pcEnd += psz;
|
||||
int pri = priorities[file];
|
||||
while (fileEnd <= pcEnd && file < lengths.length - 1) {
|
||||
int pri = _torrentFiles.get(file).priority;
|
||||
while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) {
|
||||
file++;
|
||||
TorrentFile tf = _torrentFiles.get(file);
|
||||
long oldFileEnd = fileEnd;
|
||||
fileEnd += lengths[file];
|
||||
if (priorities[file] > pri && oldFileEnd < pcEnd)
|
||||
pri = priorities[file];
|
||||
fileEnd += tf.length;
|
||||
if (tf.priority > pri && oldFileEnd < pcEnd)
|
||||
pri = tf.priority;
|
||||
}
|
||||
rv[i] = pri;
|
||||
}
|
||||
@ -479,13 +488,18 @@ public class Storage
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
*/
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
check(rootDir, 0, null);
|
||||
}
|
||||
|
||||
/** use a saved bitfield and timestamp from a config file */
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Use a saved bitfield and timestamp from a config file.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
*/
|
||||
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
|
||||
{
|
||||
File base;
|
||||
@ -496,6 +510,8 @@ public class Storage
|
||||
base = new SecureFile(rootDir, filterName(metainfo.getName()));
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
if (!_torrentFiles.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
List<List<String>> files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
@ -505,22 +521,14 @@ public class Storage
|
||||
if (!base.createNewFile() && !base.exists())
|
||||
throw new IOException("Could not create file " + base);
|
||||
|
||||
lengths = new long[1];
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
RAFlock = new Object[1];
|
||||
RAFtime = new long[1];
|
||||
RAFfile = new File[1];
|
||||
isSparse = new boolean[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
RAFlock[0] = new Object();
|
||||
RAFfile[0] = base;
|
||||
_torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength()));
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
else if (base.length() != metainfo.getTotalLength())
|
||||
useSavedBitField = false;
|
||||
}
|
||||
names[0] = base.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -533,23 +541,16 @@ public class Storage
|
||||
List<Long> ls = metainfo.getLengths();
|
||||
int size = files.size();
|
||||
long total = 0;
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
names = new String[size];
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
isSparse = new boolean[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
List<String> path = files.get(i);
|
||||
File f = createFileFromNames(base, path, areFilesPublic);
|
||||
// dup file name check after filtering
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (f.equals(RAFfile[j])) {
|
||||
if (f.equals(_torrentFiles.get(j).RAFfile)) {
|
||||
// Rename and start the check over again
|
||||
// Copy path since metainfo list is unmodifiable
|
||||
path = new ArrayList(path);
|
||||
path = new ArrayList<String>(path);
|
||||
int last = path.size() - 1;
|
||||
String lastPath = path.get(last);
|
||||
int dot = lastPath.lastIndexOf('.');
|
||||
@ -563,16 +564,16 @@ public class Storage
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
lengths[i] = ls.get(i).longValue();
|
||||
RAFlock[i] = new Object();
|
||||
RAFfile[i] = f;
|
||||
total += lengths[i];
|
||||
long len = ls.get(i).longValue();
|
||||
_torrentFiles.add(new TorrentFile(base, f, len));
|
||||
total += len;
|
||||
if (useSavedBitField) {
|
||||
long lm = f.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
else if (f.length() != len)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
names[i] = f.getName();
|
||||
}
|
||||
|
||||
// Sanity check for metainfo file.
|
||||
@ -590,6 +591,8 @@ public class Storage
|
||||
} else {
|
||||
// the following sets the needed variable
|
||||
changed = true;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Forcing check");
|
||||
checkCreateFiles(false);
|
||||
}
|
||||
if (complete()) {
|
||||
@ -597,8 +600,6 @@ public class Storage
|
||||
_log.info("Torrent is complete");
|
||||
} else {
|
||||
// fixme saved priorities
|
||||
if (files != null)
|
||||
priorities = new int[files.size()];
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Still need " + needed + " out of " + metainfo.getPieces() + " pieces");
|
||||
}
|
||||
@ -613,11 +614,11 @@ public class Storage
|
||||
*/
|
||||
public void reopen(String rootDir) throws IOException
|
||||
{
|
||||
if (RAFfile == null)
|
||||
if (_torrentFiles.isEmpty())
|
||||
throw new IOException("Storage not checked yet");
|
||||
for (int i = 0; i < RAFfile.length; i++) {
|
||||
if (!RAFfile[i].exists())
|
||||
throw new IOException("File does not exist: " + RAFfile[i]);
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
if (!tf.RAFfile.exists())
|
||||
throw new IOException("File does not exist: " + tf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -771,10 +772,10 @@ public class Storage
|
||||
|
||||
// Make sure all files are available and of correct length
|
||||
// The files should all exist as they have been created with zero length by createFilesFromNames()
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
long length = RAFfile[i].length();
|
||||
if(RAFfile[i].exists() && length == lengths[i])
|
||||
long length = tf.RAFfile.length();
|
||||
if(tf.RAFfile.exists() && length == tf.length)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
@ -782,27 +783,27 @@ public class Storage
|
||||
}
|
||||
else if (length == 0) {
|
||||
changed = true;
|
||||
synchronized(RAFlock[i]) {
|
||||
allocateFile(i);
|
||||
synchronized(tf) {
|
||||
allocateFile(tf);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
try {
|
||||
closeRAF(i);
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
String msg = "File '" + names[i] + "' exists, but has wrong length (expected " +
|
||||
lengths[i] + " but found " + length + ") - repairing corruption";
|
||||
String msg = "File '" + tf.name + "' exists, but has wrong length (expected " +
|
||||
tf.length + " but found " + length + ") - repairing corruption";
|
||||
if (listener != null)
|
||||
listener.addMessage(msg);
|
||||
_log.error(msg);
|
||||
changed = true;
|
||||
resume = true;
|
||||
_probablyComplete = false; // to force RW
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
synchronized(tf) {
|
||||
RandomAccessFile raf = tf.checkRAF();
|
||||
raf.setLength(tf.length);
|
||||
try {
|
||||
closeRAF(i);
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
@ -813,7 +814,7 @@ public class Storage
|
||||
{
|
||||
byte[] piece = new byte[piece_size];
|
||||
int file = 0;
|
||||
long fileEnd = lengths[0];
|
||||
long fileEnd = _torrentFiles.get(0).length;
|
||||
long pieceEnd = 0;
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
@ -822,14 +823,15 @@ public class Storage
|
||||
// close as we go so we don't run out of file descriptors
|
||||
pieceEnd += length;
|
||||
while (fileEnd <= pieceEnd) {
|
||||
synchronized(RAFlock[file]) {
|
||||
TorrentFile tf = _torrentFiles.get(file);
|
||||
synchronized(tf) {
|
||||
try {
|
||||
closeRAF(file);
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (++file >= rafs.length)
|
||||
if (++file >= _torrentFiles.size())
|
||||
break;
|
||||
fileEnd += lengths[file];
|
||||
fileEnd += _torrentFiles.get(file).length;
|
||||
}
|
||||
if (correctHash)
|
||||
{
|
||||
@ -875,59 +877,17 @@ public class Storage
|
||||
* Sets isSparse[nr] = true. balloonFile(nr) should be called later to
|
||||
* defrag the file.
|
||||
*
|
||||
* This calls openRAF(); caller must synchronize and call closeRAF().
|
||||
* This calls OpenRAF(); caller must synchronize and call closeRAF().
|
||||
*/
|
||||
private void allocateFile(int nr) throws IOException
|
||||
private void allocateFile(TorrentFile tf) throws IOException
|
||||
{
|
||||
// caller synchronized
|
||||
openRAF(nr, false); // RW
|
||||
long remaining = lengths[nr];
|
||||
if (listener != null)
|
||||
listener.storageCreateFile(this, names[nr], remaining);
|
||||
rafs[nr].setLength(remaining);
|
||||
// don't bother ballooning later on Windows since there is no sparse file support
|
||||
// until JDK7 using the JSR-203 interface.
|
||||
// RAF seeks/writes do not create sparse files.
|
||||
// Windows will zero-fill up to the point of the write, which
|
||||
// will make the file fairly unfragmented, on average, at least until
|
||||
// near the end where it will get exponentially more fragmented.
|
||||
if (!_isWindows)
|
||||
isSparse[nr] = true;
|
||||
// caller will close rafs[nr]
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, lengths[nr]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This "balloons" the file with zeros to eliminate disk fragmentation.,
|
||||
* Overwrites the entire file with zeros. Sets isSparse[nr] = false.
|
||||
*
|
||||
* Caller must synchronize and call checkRAF() or openRAF().
|
||||
* @since 0.9.1
|
||||
*/
|
||||
private void balloonFile(int nr) throws IOException
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ballooning " + nr + ": " + RAFfile[nr]);
|
||||
long remaining = lengths[nr];
|
||||
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
rafs[nr].seek(0);
|
||||
// don't bother setting flag for small files
|
||||
if (remaining > 20*1024*1024)
|
||||
_allocateCount.incrementAndGet();
|
||||
try {
|
||||
while (remaining > 0) {
|
||||
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
|
||||
rafs[nr].write(zeros, 0, size);
|
||||
remaining -= size;
|
||||
}
|
||||
} finally {
|
||||
remaining = lengths[nr];
|
||||
if (remaining > 20*1024*1024)
|
||||
_allocateCount.decrementAndGet();
|
||||
tf.allocateFile();
|
||||
if (listener != null) {
|
||||
listener.storageCreateFile(this, tf.name, tf.length);
|
||||
listener.storageAllocated(this, tf.length);
|
||||
}
|
||||
isSparse[nr] = false;
|
||||
// caller will close rafs[nr]
|
||||
}
|
||||
|
||||
|
||||
@ -937,18 +897,14 @@ public class Storage
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (rafs == null) return;
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
// if we had an IOE in check(), the RAFlock may be null
|
||||
if (RAFlock[i] == null)
|
||||
continue;
|
||||
try {
|
||||
synchronized(RAFlock[i]) {
|
||||
closeRAF(i);
|
||||
synchronized(tf) {
|
||||
tf.closeRAF();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing " + RAFfile[i], ioe);
|
||||
_log.error("Error closing " + tf, ioe);
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
@ -1013,40 +969,50 @@ public class Storage
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) piece_size;
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
long raflen = _torrentFiles.get(i).length;
|
||||
while (start > raflen) {
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
raflen = _torrentFiles.get(i).length;
|
||||
}
|
||||
|
||||
int written = 0;
|
||||
int off = 0;
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
while (written < length) {
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
if (isSparse[i]) {
|
||||
// If the file is a newly created sparse file,
|
||||
// AND we aren't skipping it, balloon it with all
|
||||
// zeros to un-sparse it by allocating the space.
|
||||
// Obviously this could take a while.
|
||||
// Once we have written to it, it isn't empty/sparse any more.
|
||||
if (priorities == null || priorities[i] >= 0)
|
||||
balloonFile(i);
|
||||
else
|
||||
isSparse[i] = false;
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
synchronized(tf) {
|
||||
try {
|
||||
RandomAccessFile raf = tf.checkRAF();
|
||||
if (tf.isSparse) {
|
||||
// If the file is a newly created sparse file,
|
||||
// AND we aren't skipping it, balloon it with all
|
||||
// zeros to un-sparse it by allocating the space.
|
||||
// Obviously this could take a while.
|
||||
// Once we have written to it, it isn't empty/sparse any more.
|
||||
if (tf.priority >= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ballooning " + tf);
|
||||
tf.balloonFile();
|
||||
} else {
|
||||
tf.isSparse = false;
|
||||
}
|
||||
}
|
||||
raf.seek(start);
|
||||
//rafs[i].write(bs, off + written, len);
|
||||
pp.write(raf, written, len);
|
||||
} catch (IOException ioe) {
|
||||
// get the file name in the logs
|
||||
IOException ioe2 = new IOException("Error writing " + tf.RAFfile.getAbsolutePath());
|
||||
ioe2.initCause(ioe);
|
||||
throw ioe2;
|
||||
}
|
||||
rafs[i].seek(start);
|
||||
//rafs[i].write(bs, off + written, len);
|
||||
pp.write(rafs[i], off + written, len);
|
||||
}
|
||||
written += len;
|
||||
if (need - len > 0) {
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
raflen = _torrentFiles.get(i).length;
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
@ -1123,12 +1089,12 @@ public class Storage
|
||||
long start = ((long) piece * (long) piece_size) + off;
|
||||
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
long raflen = _torrentFiles.get(i).length;
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
raflen = _torrentFiles.get(i).length;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
@ -1136,17 +1102,24 @@ public class Storage
|
||||
{
|
||||
int need = length - read;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(RAFlock[i])
|
||||
{
|
||||
checkRAF(i);
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, read, len);
|
||||
}
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
synchronized(tf) {
|
||||
try {
|
||||
RandomAccessFile raf = tf.checkRAF();
|
||||
raf.seek(start);
|
||||
raf.readFully(bs, read, len);
|
||||
} catch (IOException ioe) {
|
||||
// get the file name in the logs
|
||||
IOException ioe2 = new IOException("Error reading " + tf.RAFfile.getAbsolutePath());
|
||||
ioe2.initCause(ioe);
|
||||
throw ioe2;
|
||||
}
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
raflen = _torrentFiles.get(i).length;
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
@ -1154,58 +1127,191 @@ public class Storage
|
||||
return length;
|
||||
}
|
||||
|
||||
private static final long RAFCloseDelay = 4*60*1000;
|
||||
|
||||
/**
|
||||
* Close unused RAFs - call periodically
|
||||
*/
|
||||
private static final long RAFCloseDelay = 4*60*1000;
|
||||
public void cleanRAFs() {
|
||||
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
|
||||
for (int i = 0; i < RAFlock.length; i++) {
|
||||
synchronized(RAFlock[i]) {
|
||||
if (RAFtime[i] > 0 && RAFtime[i] < cutoff) {
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
synchronized(tf) {
|
||||
tf.closeRAF(cutoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For each of the following,
|
||||
* caller must synchronize on RAFlock[i]
|
||||
* ... except at the beginning if you're careful
|
||||
*/
|
||||
|
||||
/**
|
||||
* This must be called before using the RAF to ensure it is open
|
||||
* A single file in a torrent.
|
||||
* @since 0.9.9
|
||||
*/
|
||||
private void checkRAF(int i) throws IOException {
|
||||
if (RAFtime[i] > 0) {
|
||||
RAFtime[i] = System.currentTimeMillis();
|
||||
return;
|
||||
}
|
||||
openRAF(i);
|
||||
}
|
||||
private class TorrentFile implements Comparable<TorrentFile> {
|
||||
public final long length;
|
||||
public final String name;
|
||||
public final File RAFfile;
|
||||
/**
|
||||
* when was RAF last accessed, or 0 if closed
|
||||
* locking: this
|
||||
*/
|
||||
private long RAFtime;
|
||||
/**
|
||||
* null when closed
|
||||
* locking: this
|
||||
*/
|
||||
private RandomAccessFile raf;
|
||||
/**
|
||||
* is the file empty and sparse?
|
||||
* locking: this
|
||||
*/
|
||||
public boolean isSparse;
|
||||
/** priority by file; default 0 */
|
||||
public volatile int priority;
|
||||
|
||||
private void openRAF(int i) throws IOException {
|
||||
openRAF(i, _probablyComplete);
|
||||
}
|
||||
/**
|
||||
* For new metainfo from files;
|
||||
* use base == f for single-file torrent
|
||||
*/
|
||||
public TorrentFile(File base, File f) {
|
||||
this(base, f, f.length());
|
||||
}
|
||||
|
||||
private void openRAF(int i, boolean readonly) throws IOException {
|
||||
rafs[i] = new RandomAccessFile(RAFfile[i], (readonly || !RAFfile[i].canWrite()) ? "r" : "rw");
|
||||
RAFtime[i] = System.currentTimeMillis();
|
||||
}
|
||||
/**
|
||||
* For existing metainfo with specified file length;
|
||||
* use base == f for single-file torrent
|
||||
*/
|
||||
public TorrentFile(File base, File f, long len) {
|
||||
String n = f.getPath();
|
||||
if (base.isDirectory() && n.startsWith(base.getPath()))
|
||||
n = n.substring(base.getPath().length() + 1);
|
||||
name = n;
|
||||
length = len;
|
||||
RAFfile = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called even if not open
|
||||
*/
|
||||
private void closeRAF(int i) throws IOException {
|
||||
RAFtime[i] = 0;
|
||||
if (rafs[i] == null)
|
||||
return;
|
||||
rafs[i].close();
|
||||
rafs[i] = null;
|
||||
/*
|
||||
* For each of the following,
|
||||
* caller must synchronize on RAFlock[i]
|
||||
* ... except at the beginning if you're careful
|
||||
*/
|
||||
|
||||
/**
|
||||
* This must be called before using the RAF to ensure it is open
|
||||
* locking: this
|
||||
*/
|
||||
public synchronized RandomAccessFile checkRAF() throws IOException {
|
||||
if (raf != null)
|
||||
RAFtime = System.currentTimeMillis();
|
||||
else
|
||||
openRAF();
|
||||
return raf;
|
||||
}
|
||||
|
||||
/**
|
||||
* locking: this
|
||||
*/
|
||||
private synchronized void openRAF() throws IOException {
|
||||
openRAF(_probablyComplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* locking: this
|
||||
*/
|
||||
private synchronized void openRAF(boolean readonly) throws IOException {
|
||||
raf = new RandomAccessFile(RAFfile, (readonly || !RAFfile.canWrite()) ? "r" : "rw");
|
||||
RAFtime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close if last used time older than cutoff.
|
||||
* locking: this
|
||||
*/
|
||||
public synchronized void closeRAF(long cutoff) {
|
||||
if (RAFtime > 0 && RAFtime < cutoff) {
|
||||
try {
|
||||
closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called even if not open
|
||||
* locking: this
|
||||
*/
|
||||
public synchronized void closeRAF() throws IOException {
|
||||
RAFtime = 0;
|
||||
if (raf == null)
|
||||
return;
|
||||
raf.close();
|
||||
raf = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This creates a (presumably) sparse file so that reads won't fail with IOE.
|
||||
* Sets isSparse[nr] = true. balloonFile(nr) should be called later to
|
||||
* defrag the file.
|
||||
*
|
||||
* This calls openRAF(); caller must synchronize and call closeRAF().
|
||||
*/
|
||||
public synchronized void allocateFile() throws IOException {
|
||||
// caller synchronized
|
||||
openRAF(false); // RW
|
||||
raf.setLength(length);
|
||||
// don't bother ballooning later on Windows since there is no sparse file support
|
||||
// until JDK7 using the JSR-203 interface.
|
||||
// RAF seeks/writes do not create sparse files.
|
||||
// Windows will zero-fill up to the point of the write, which
|
||||
// will make the file fairly unfragmented, on average, at least until
|
||||
// near the end where it will get exponentially more fragmented.
|
||||
if (!_isWindows)
|
||||
isSparse = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This "balloons" the file with zeros to eliminate disk fragmentation.,
|
||||
* Overwrites the entire file with zeros. Sets isSparse[nr] = false.
|
||||
*
|
||||
* Caller must synchronize and call checkRAF() or openRAF().
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public synchronized void balloonFile() throws IOException
|
||||
{
|
||||
long remaining = length;
|
||||
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
raf.seek(0);
|
||||
// don't bother setting flag for small files
|
||||
if (remaining > 20*1024*1024)
|
||||
_allocateCount.incrementAndGet();
|
||||
try {
|
||||
while (remaining > 0) {
|
||||
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
|
||||
raf.write(zeros, 0, size);
|
||||
remaining -= size;
|
||||
}
|
||||
} finally {
|
||||
remaining = length;
|
||||
if (remaining > 20*1024*1024)
|
||||
_allocateCount.decrementAndGet();
|
||||
}
|
||||
isSparse = false;
|
||||
}
|
||||
|
||||
public int compareTo(TorrentFile tf) {
|
||||
return name.compareTo(tf.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return RAFfile.getAbsolutePath().hashCode(); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof TorrentFile) &&
|
||||
RAFfile.getAbsolutePath().equals(((TorrentFile)o).RAFfile.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return name; }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1225,7 +1331,7 @@ public class Storage
|
||||
File file = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
Storage storage = new Storage(util, base, announce, false, null);
|
||||
Storage storage = new Storage(util, base, announce, null, false, null);
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
file = new File(storage.getBaseName() + ".torrent");
|
||||
out = new FileOutputStream(file);
|
||||
|
@ -21,8 +21,6 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
@ -31,15 +29,16 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
@ -84,6 +83,7 @@ public class TrackerClient implements Runnable {
|
||||
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
|
||||
private final static long MIN_TRACKER_ANNOUNCE_INTERVAL = 15*60*1000;
|
||||
private final static long MIN_DHT_ANNOUNCE_INTERVAL = 10*60*1000;
|
||||
public static final int PORT = 6881;
|
||||
|
||||
private final I2PSnarkUtil _util;
|
||||
private final MetaInfo meta;
|
||||
@ -109,8 +109,9 @@ public class TrackerClient implements Runnable {
|
||||
private boolean completed;
|
||||
private volatile boolean _fastUnannounce;
|
||||
private long lastDHTAnnounce;
|
||||
private final List<Tracker> trackers;
|
||||
private final List<Tracker> backupTrackers;
|
||||
private final List<TCTracker> trackers;
|
||||
private final List<TCTracker> backupTrackers;
|
||||
private long _startedOn;
|
||||
|
||||
/**
|
||||
* Call start() to start it.
|
||||
@ -132,11 +133,11 @@ public class TrackerClient implements Runnable {
|
||||
this.coordinator = coordinator;
|
||||
this.snark = snark;
|
||||
|
||||
this.port = 6881; //(port == -1) ? 9 : port;
|
||||
this.port = PORT; //(port == -1) ? 9 : port;
|
||||
this.infoHash = urlencode(snark.getInfoHash());
|
||||
this.peerID = urlencode(snark.getID());
|
||||
this.trackers = new ArrayList(2);
|
||||
this.backupTrackers = new ArrayList(2);
|
||||
this.trackers = new ArrayList<TCTracker>(2);
|
||||
this.backupTrackers = new ArrayList<TCTracker>(2);
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
@ -270,9 +271,12 @@ public class TrackerClient implements Runnable {
|
||||
primary = meta.getAnnounce();
|
||||
else if (additionalTrackerURL != null)
|
||||
primary = additionalTrackerURL;
|
||||
Set<Hash> trackerHashes = new HashSet<Hash>(8);
|
||||
|
||||
// primary tracker
|
||||
if (primary != null) {
|
||||
if (isValidAnnounce(primary)) {
|
||||
trackers.add(new Tracker(primary, true));
|
||||
if (isNewValidTracker(trackerHashes, primary)) {
|
||||
trackers.add(new TCTracker(primary, true));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Announce: [" + primary + "] infoHash: " + infoHash);
|
||||
} else {
|
||||
@ -281,36 +285,35 @@ public class TrackerClient implements Runnable {
|
||||
}
|
||||
} else {
|
||||
_log.warn("No primary announce");
|
||||
primary = "";
|
||||
}
|
||||
|
||||
// announce list
|
||||
if (meta != null && !meta.isPrivate()) {
|
||||
List<List<String>> list = meta.getAnnounceList();
|
||||
if (list != null) {
|
||||
for (List<String> llist : list) {
|
||||
for (String url : llist) {
|
||||
if (!isNewValidTracker(trackerHashes, url))
|
||||
continue;
|
||||
trackers.add(new TCTracker(url, trackers.isEmpty()));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Additional announce (list): [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configured open trackers
|
||||
if (meta == null || !meta.isPrivate()) {
|
||||
List<String> tlist = _util.getOpenTrackers();
|
||||
for (int i = 0; i < tlist.size(); i++) {
|
||||
String url = tlist.get(i);
|
||||
if (!isValidAnnounce(url)) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
String url = tlist.get(i);
|
||||
if (!isNewValidTracker(trackerHashes, url))
|
||||
continue;
|
||||
}
|
||||
int slash = url.indexOf('/', 7);
|
||||
if (slash <= 7) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
if (primary.startsWith(url.substring(0, slash)))
|
||||
continue;
|
||||
String dest = _util.lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
|
||||
continue;
|
||||
}
|
||||
if (primary.startsWith("http://" + dest))
|
||||
continue;
|
||||
if (primary.startsWith("http://i2p/" + dest))
|
||||
continue;
|
||||
// opentrackers are primary if we don't have primary
|
||||
trackers.add(new Tracker(url, primary.equals("")));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
|
||||
// opentrackers are primary if we don't have primary
|
||||
trackers.add(new TCTracker(url, trackers.isEmpty()));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,29 +321,39 @@ public class TrackerClient implements Runnable {
|
||||
if (trackers.isEmpty() && (meta == null || !meta.isPrivate())) {
|
||||
List<String> tlist = _util.getBackupTrackers();
|
||||
for (int i = 0; i < tlist.size(); i++) {
|
||||
String url = tlist.get(i);
|
||||
if (!isValidAnnounce(url)) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
String url = tlist.get(i);
|
||||
if (!isNewValidTracker(trackerHashes, url))
|
||||
continue;
|
||||
}
|
||||
int slash = url.indexOf('/', 7);
|
||||
if (slash <= 7) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
String dest = _util.lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
|
||||
continue;
|
||||
}
|
||||
backupTrackers.add(new Tracker(url, false));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
|
||||
backupTrackers.add(new TCTracker(url, false));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
if (backupTrackers.isEmpty()) {
|
||||
backupTrackers.add(new TCTracker(DEFAULT_BACKUP_TRACKER, false));
|
||||
}
|
||||
if (backupTrackers.isEmpty())
|
||||
backupTrackers.add(new Tracker(DEFAULT_BACKUP_TRACKER, false));
|
||||
}
|
||||
this.completed = coordinator.getLeft() == 0;
|
||||
_startedOn = _util.getContext().clock().now();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param existing the ones we already know about
|
||||
* @param ann an announce URL non-null
|
||||
* @return true if ann is valid and new; adds to existing if returns true
|
||||
* @since 0.9.5
|
||||
*/
|
||||
private boolean isNewValidTracker(Set<Hash> existing, String ann) {
|
||||
Hash h = getHostHash(ann);
|
||||
if (h == null) {
|
||||
_log.error("Bad announce URL: [" + ann + ']');
|
||||
return false;
|
||||
}
|
||||
boolean rv = existing.add(h);
|
||||
if (!rv) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dup announce URL: [" + ann + ']');
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -364,15 +377,24 @@ public class TrackerClient implements Runnable {
|
||||
if (dht != null && (meta == null || !meta.isPrivate()))
|
||||
dht.announce(snark.getInfoHash());
|
||||
|
||||
int oldSeenPeers = snark.getTrackerSeenPeers();
|
||||
int maxSeenPeers = 0;
|
||||
if (!trackers.isEmpty())
|
||||
if (!trackers.isEmpty()) {
|
||||
maxSeenPeers = getPeersFromTrackers(trackers);
|
||||
// fast update for UI at startup
|
||||
if (maxSeenPeers > oldSeenPeers)
|
||||
snark.setTrackerSeenPeers(maxSeenPeers);
|
||||
}
|
||||
int p = getPeersFromPEX();
|
||||
if (p > maxSeenPeers)
|
||||
maxSeenPeers = p;
|
||||
p = getPeersFromDHT();
|
||||
if (p > maxSeenPeers)
|
||||
if (p > maxSeenPeers) {
|
||||
maxSeenPeers = p;
|
||||
// fast update for UI at startup
|
||||
if (maxSeenPeers > oldSeenPeers)
|
||||
snark.setTrackerSeenPeers(maxSeenPeers);
|
||||
}
|
||||
// backup if DHT needs bootstrapping
|
||||
if (trackers.isEmpty() && !backupTrackers.isEmpty() && dht != null && dht.size() < 16) {
|
||||
p = getPeersFromTrackers(backupTrackers);
|
||||
@ -425,32 +447,41 @@ public class TrackerClient implements Runnable {
|
||||
/**
|
||||
* @return max peers seen
|
||||
*/
|
||||
private int getPeersFromTrackers(List<Tracker> trckrs) {
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
private int getPeersFromTrackers(List<TCTracker> trckrs) {
|
||||
long left = coordinator.getLeft(); // -1 in magnet mode
|
||||
|
||||
// First time we got a complete download?
|
||||
String event;
|
||||
if (!completed && left == 0)
|
||||
{
|
||||
boolean newlyCompleted;
|
||||
if (!completed && left == 0) {
|
||||
completed = true;
|
||||
event = COMPLETED_EVENT;
|
||||
}
|
||||
else
|
||||
event = NO_EVENT;
|
||||
newlyCompleted = true;
|
||||
} else {
|
||||
newlyCompleted = false;
|
||||
}
|
||||
|
||||
// *** loop once for each tracker
|
||||
int maxSeenPeers = 0;
|
||||
for (Tracker tr : trckrs) {
|
||||
for (TCTracker tr : trckrs) {
|
||||
if ((!stop) && (!tr.stop) &&
|
||||
(completed || coordinator.needOutboundPeers() || !tr.started) &&
|
||||
(event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
|
||||
(newlyCompleted || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!tr.started)
|
||||
event = STARTED_EVENT;
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long len = snark.getTotalLength();
|
||||
if (len > 0 && downloaded > len)
|
||||
downloaded = len;
|
||||
left = coordinator.getLeft();
|
||||
String event;
|
||||
if (!tr.started) {
|
||||
event = STARTED_EVENT;
|
||||
} else if (newlyCompleted) {
|
||||
event = COMPLETED_EVENT;
|
||||
} else {
|
||||
event = NO_EVENT;
|
||||
}
|
||||
TrackerInfo info = doRequest(tr, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
@ -463,12 +494,31 @@ public class TrackerClient implements Runnable {
|
||||
consecutiveFails = 0;
|
||||
runStarted = true;
|
||||
tr.started = true;
|
||||
|
||||
Set<Peer> peers = info.getPeers();
|
||||
tr.seenPeers = info.getPeerCount();
|
||||
if (snark.getTrackerSeenPeers() < tr.seenPeers) // update rising number quickly
|
||||
snark.setTrackerSeenPeers(tr.seenPeers);
|
||||
|
||||
// auto stop
|
||||
// These are very high thresholds for now, not configurable,
|
||||
// just for update torrent
|
||||
if (completed &&
|
||||
tr.isPrimary &&
|
||||
snark.isAutoStoppable() &&
|
||||
!snark.isChecking() &&
|
||||
info.getSeedCount() > 100 &&
|
||||
coordinator.getPeerCount() <= 0 &&
|
||||
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
|
||||
snark.getTotalLength() > 0 &&
|
||||
uploaded >= 2 * snark.getTotalLength()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Auto stopping " + snark.getBaseName());
|
||||
snark.setAutoStoppable(false);
|
||||
snark.stopTorrent();
|
||||
return tr.seenPeers;
|
||||
}
|
||||
|
||||
Set<Peer> peers = info.getPeers();
|
||||
|
||||
// pass everybody over to our tracker
|
||||
DHT dht = _util.getDHT();
|
||||
if (dht != null) {
|
||||
@ -480,7 +530,7 @@ public class TrackerClient implements Runnable {
|
||||
if (coordinator.needOutboundPeers()) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List<Peer> ordered = new ArrayList(peers);
|
||||
List<Peer> ordered = new ArrayList<Peer>(peers);
|
||||
Random r = _util.getContext().random();
|
||||
Collections.shuffle(ordered, r);
|
||||
Iterator<Peer> it = ordered.iterator();
|
||||
@ -545,7 +595,7 @@ public class TrackerClient implements Runnable {
|
||||
if (!pids.isEmpty()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got " + pids.size() + " from PEX");
|
||||
List<Peer> peers = new ArrayList(pids.size());
|
||||
List<Peer> peers = new ArrayList<Peer>(pids.size());
|
||||
for (PeerID pID : pids) {
|
||||
peers.add(new Peer(pID, snark.getID(), snark.getInfoHash(), snark.getMetaInfo()));
|
||||
}
|
||||
@ -577,33 +627,29 @@ public class TrackerClient implements Runnable {
|
||||
// FIXME this needs to be in its own thread
|
||||
int rv = 0;
|
||||
DHT dht = _util.getDHT();
|
||||
if (dht != null && (meta == null || !meta.isPrivate()) && (!stop) &&
|
||||
_util.getContext().clock().now() > lastDHTAnnounce + MIN_DHT_ANNOUNCE_INTERVAL) {
|
||||
if (dht != null &&
|
||||
(meta == null || !meta.isPrivate()) &&
|
||||
(!stop) &&
|
||||
(meta == null || _util.getContext().clock().now() > lastDHTAnnounce + MIN_DHT_ANNOUNCE_INTERVAL)) {
|
||||
int numwant;
|
||||
if (!coordinator.needOutboundPeers())
|
||||
numwant = 1;
|
||||
else
|
||||
numwant = _util.getMaxConnections();
|
||||
Collection<Hash> hashes = dht.getPeers(snark.getInfoHash(), numwant, 2*60*1000);
|
||||
Collection<Hash> hashes = dht.getPeersAndAnnounce(snark.getInfoHash(), numwant, 5*60*1000, 1, 3*60*1000);
|
||||
if (!hashes.isEmpty()) {
|
||||
runStarted = true;
|
||||
lastDHTAnnounce = _util.getContext().clock().now();
|
||||
rv = hashes.size();
|
||||
} else {
|
||||
lastDHTAnnounce = 0;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got " + hashes + " from DHT");
|
||||
// announce ourselves while the token is still good
|
||||
// FIXME this needs to be in its own thread
|
||||
if (!stop) {
|
||||
// announce only to the 1 closest
|
||||
int good = dht.announce(snark.getInfoHash(), 1, 5*60*1000);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sent " + good + " good announces to DHT");
|
||||
}
|
||||
|
||||
// now try these peers
|
||||
if ((!stop) && !hashes.isEmpty()) {
|
||||
List<Peer> peers = new ArrayList(hashes.size());
|
||||
List<Peer> peers = new ArrayList<Peer>(hashes.size());
|
||||
for (Hash h : hashes) {
|
||||
try {
|
||||
PeerID pID = new PeerID(h.getData(), _util);
|
||||
@ -639,7 +685,7 @@ public class TrackerClient implements Runnable {
|
||||
if (dht != null)
|
||||
dht.unannounce(snark.getInfoHash());
|
||||
int i = 0;
|
||||
for (Tracker tr : trackers) {
|
||||
for (TCTracker tr : trackers) {
|
||||
if (_util.connected() &&
|
||||
tr.started && (!tr.stop) && tr.trackerProblems == null) {
|
||||
try {
|
||||
@ -659,9 +705,9 @@ public class TrackerClient implements Runnable {
|
||||
* @since 0.9.1
|
||||
*/
|
||||
private class Unannouncer implements Runnable {
|
||||
private final Tracker tr;
|
||||
private final TCTracker tr;
|
||||
|
||||
public Unannouncer(Tracker tr) {
|
||||
public Unannouncer(TCTracker tr) {
|
||||
this.tr = tr;
|
||||
}
|
||||
|
||||
@ -670,6 +716,9 @@ public class TrackerClient implements Runnable {
|
||||
_log.debug("Running unannounce " + _threadName + " to " + tr.announce);
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long len = snark.getTotalLength();
|
||||
if (len > 0 && downloaded > len)
|
||||
downloaded = len;
|
||||
long left = coordinator.getLeft();
|
||||
try
|
||||
{
|
||||
@ -685,7 +734,7 @@ public class TrackerClient implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private TrackerInfo doRequest(Tracker tr, String infoHash,
|
||||
private TrackerInfo doRequest(TCTracker tr, String infoHash,
|
||||
String peerID, long uploaded,
|
||||
long downloaded, long left, String event)
|
||||
throws IOException
|
||||
@ -775,6 +824,7 @@ public class TrackerClient implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ann an announce URL
|
||||
* @return true for i2p hosts only
|
||||
* @since 0.7.12
|
||||
*/
|
||||
@ -790,10 +840,38 @@ public class TrackerClient implements Runnable {
|
||||
url.getPort() < 0;
|
||||
}
|
||||
|
||||
private static class Tracker
|
||||
/**
|
||||
* @param ann an announce URL non-null
|
||||
* @return a Hash for i2p hosts only, null otherwise
|
||||
* @since 0.9.5
|
||||
*/
|
||||
private static Hash getHostHash(String ann) {
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(ann);
|
||||
} catch (MalformedURLException mue) {
|
||||
return null;
|
||||
}
|
||||
if (url.getPort() >= 0 || !url.getProtocol().equals("http"))
|
||||
return null;
|
||||
String host = url.getHost();
|
||||
if (host.endsWith(".i2p"))
|
||||
return ConvertToHash.getHash(host);
|
||||
if (host.equals("i2p")) {
|
||||
String path = url.getPath();
|
||||
if (path == null || path.length() < 517 ||
|
||||
!path.startsWith("/"))
|
||||
return null;
|
||||
String[] parts = path.substring(1).split("/?&;", 2);
|
||||
return ConvertToHash.getHash(parts[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class TCTracker
|
||||
{
|
||||
String announce;
|
||||
boolean isPrimary;
|
||||
final String announce;
|
||||
final boolean isPrimary;
|
||||
long interval;
|
||||
long lastRequestTime;
|
||||
String trackerProblems;
|
||||
@ -803,7 +881,7 @@ public class TrackerClient implements Runnable {
|
||||
int consecutiveFails;
|
||||
int seenPeers;
|
||||
|
||||
public Tracker(String a, boolean p)
|
||||
public TCTracker(String a, boolean p)
|
||||
{
|
||||
announce = a;
|
||||
isPrimary = p;
|
||||
|
@ -59,7 +59,7 @@ class TrackerInfo
|
||||
this(be.bdecodeMap().getMap(), my_id, infohash, metainfo, util);
|
||||
}
|
||||
|
||||
private TrackerInfo(Map m, byte[] my_id, byte[] infohash, MetaInfo metainfo, I2PSnarkUtil util)
|
||||
private TrackerInfo(Map<String, BEValue> m, byte[] my_id, byte[] infohash, MetaInfo metainfo, I2PSnarkUtil util)
|
||||
throws IOException
|
||||
{
|
||||
BEValue reason = (BEValue)m.get("failure reason");
|
||||
@ -80,7 +80,7 @@ class TrackerInfo
|
||||
|
||||
BEValue bePeers = (BEValue)m.get("peers");
|
||||
if (bePeers == null) {
|
||||
peers = Collections.EMPTY_SET;
|
||||
peers = Collections.emptySet();
|
||||
} else {
|
||||
Set<Peer> p;
|
||||
try {
|
||||
@ -127,7 +127,7 @@ class TrackerInfo
|
||||
private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, byte[] infohash, MetaInfo metainfo, I2PSnarkUtil util)
|
||||
throws IOException
|
||||
{
|
||||
Set<Peer> peers = new HashSet(l.size());
|
||||
Set<Peer> peers = new HashSet<Peer>(l.size());
|
||||
|
||||
for (BEValue bev : l) {
|
||||
PeerID peerID;
|
||||
@ -161,7 +161,7 @@ class TrackerInfo
|
||||
throws IOException
|
||||
{
|
||||
int count = l.length / HASH_LENGTH;
|
||||
Set<Peer> peers = new HashSet(count);
|
||||
Set<Peer> peers = new HashSet<Peer>(count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
PeerID peerID;
|
||||
@ -190,6 +190,12 @@ class TrackerInfo
|
||||
return Math.max(pc, complete + incomplete - 1);
|
||||
}
|
||||
|
||||
/** @since 0.9.9 */
|
||||
public int getSeedCount()
|
||||
{
|
||||
return complete;
|
||||
}
|
||||
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
|
@ -42,10 +42,10 @@ class UpdateHandler implements Updater {
|
||||
*/
|
||||
public UpdateTask update(UpdateType type, UpdateMethod method, List<URI> updateSources,
|
||||
String id, String newVersion, long maxTime) {
|
||||
if (type != UpdateType.ROUTER_SIGNED ||
|
||||
if ((type != UpdateType.ROUTER_SIGNED && type != UpdateType.ROUTER_SIGNED_SU3) ||
|
||||
method != UpdateMethod.TORRENT || updateSources.isEmpty())
|
||||
return null;
|
||||
UpdateRunner update = new UpdateRunner(_context, _umgr, _smgr, updateSources, newVersion);
|
||||
UpdateRunner update = new UpdateRunner(_context, _umgr, _smgr, type, updateSources, newVersion);
|
||||
_umgr.notifyProgress(update, "<b>" + _smgr.util().getString("Updating") + "</b>");
|
||||
return update;
|
||||
}
|
||||
|
@ -6,11 +6,9 @@ import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* The downloader for router signed updates.
|
||||
@ -22,6 +20,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
private final Log _log;
|
||||
private final UpdateManager _umgr;
|
||||
private final SnarkManager _smgr;
|
||||
private final UpdateType _type;
|
||||
private final List<URI> _urls;
|
||||
private volatile boolean _isRunning;
|
||||
private volatile boolean _hasMetaInfo;
|
||||
@ -36,11 +35,12 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
private static final long CHECK_INTERVAL = 3*60*1000;
|
||||
|
||||
public UpdateRunner(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr,
|
||||
List<URI> uris, String newVersion) {
|
||||
UpdateType type, List<URI> uris, String newVersion) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
_umgr = umgr;
|
||||
_smgr = smgr;
|
||||
_type = type;
|
||||
_urls = uris;
|
||||
_newVersion = newVersion;
|
||||
}
|
||||
@ -56,7 +56,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateType getType() { return UpdateType.ROUTER_SIGNED; }
|
||||
public UpdateType getType() { return _type; }
|
||||
|
||||
public UpdateMethod getMethod() { return UpdateMethod.TORRENT; }
|
||||
|
||||
@ -111,7 +111,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
}
|
||||
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
|
||||
if (_snark != null) {
|
||||
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", updateURL) + "</b>");
|
||||
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>");
|
||||
new Timeout();
|
||||
break;
|
||||
}
|
||||
@ -264,6 +264,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
}
|
||||
_hasMetaInfo = true;
|
||||
notifyProgress();
|
||||
snark.setAutoStoppable(true);
|
||||
return _smgr.gotMetaInfo(snark);
|
||||
}
|
||||
|
||||
@ -291,6 +292,12 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
|
||||
//////// end CompleteListener methods
|
||||
|
||||
private static String linkify(String url) {
|
||||
String durl = url.length() <= 28 ? url :
|
||||
url.substring(0, 25) + "…";
|
||||
return "<a target=\"_blank\" href=\"" + url + "\"/>" + durl + "</a>";
|
||||
}
|
||||
|
||||
private void updateStatus(String s) {
|
||||
_umgr.notifyProgress(this, s);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class BDecoder
|
||||
private int indicator = 0;
|
||||
|
||||
// Used for ugly hack to get SHA hash over the metainfo info map
|
||||
private final String special_map = "info";
|
||||
private static final String special_map = "info";
|
||||
private boolean in_special_map = false;
|
||||
/** creation deferred until we encounter the special map, to make processing of announce replies more efficient */
|
||||
private MessageDigest sha_digest;
|
||||
@ -221,46 +221,51 @@ public class BDecoder
|
||||
{
|
||||
c = read();
|
||||
if (c == 'e')
|
||||
return new BEValue(BigInteger.ZERO);
|
||||
return new BEValue(Integer.valueOf(0));
|
||||
else
|
||||
throw new InvalidBEncodingException("'e' expected after zero,"
|
||||
+ " not '" + (char)c + "'");
|
||||
}
|
||||
|
||||
// XXX - We don't support more the 255 char big integers
|
||||
char[] chars = new char[256];
|
||||
int off = 0;
|
||||
StringBuilder chars = new StringBuilder(16);
|
||||
|
||||
if (c == '-')
|
||||
{
|
||||
c = read();
|
||||
if (c == '0')
|
||||
throw new InvalidBEncodingException("Negative zero not allowed");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
chars.append((char)c);
|
||||
}
|
||||
|
||||
if (c < '1' || c > '9')
|
||||
throw new InvalidBEncodingException("Invalid Integer start '"
|
||||
+ (char)c + "'");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
chars.append((char)c);
|
||||
|
||||
c = read();
|
||||
int i = c - '0';
|
||||
while(i >= 0 && i <= 9)
|
||||
while(c >= '0' && c <= '9')
|
||||
{
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
chars.append((char)c);
|
||||
c = read();
|
||||
i = c - '0';
|
||||
}
|
||||
|
||||
if (c != 'e')
|
||||
throw new InvalidBEncodingException("Integer should end with 'e'");
|
||||
|
||||
String s = new String(chars, 0, off);
|
||||
return new BEValue(new BigInteger(s));
|
||||
String s = chars.toString();
|
||||
int len = s.length();
|
||||
// save a little space if we're sure it will fit
|
||||
Number num;
|
||||
if (len < 10)
|
||||
num = Integer.valueOf(s);
|
||||
else if (len < 19)
|
||||
num = Long.valueOf(s);
|
||||
else if (len > 256)
|
||||
throw new InvalidBEncodingException("Too many digits: " + len);
|
||||
else
|
||||
num = new BigInteger(s);
|
||||
return new BEValue(num);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,16 +36,18 @@ public interface DHT {
|
||||
public void ping(Destination dest, int port);
|
||||
|
||||
/**
|
||||
* Get peers for a torrent.
|
||||
* Get peers for a torrent, and announce to the closest node we find.
|
||||
* Blocking!
|
||||
* Caller should run in a thread.
|
||||
*
|
||||
* @param ih the Info Hash (torrent)
|
||||
* @param max maximum number of peers to return
|
||||
* @param maxWait the maximum time to wait (ms) must be > 0
|
||||
* @param annMax the number of peers to announce to
|
||||
* @param annMaxWait the maximum total time to wait for announces, may be 0 to return immediately without waiting for acks
|
||||
* @return possibly empty (never null)
|
||||
*/
|
||||
public Collection<Hash> getPeers(byte[] ih, int max, long maxWait);
|
||||
public Collection<Hash> getPeersAndAnnounce(byte[] ih, int max, long maxWait, int annMax, long annMaxWait);
|
||||
|
||||
/**
|
||||
* Announce to ourselves.
|
||||
|
@ -5,11 +5,8 @@ package org.klomp.snark.dht;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@ -53,8 +50,8 @@ class DHTNodes {
|
||||
_context = ctx;
|
||||
_expireTime = MAX_EXPIRE_TIME;
|
||||
_log = _context.logManager().getLog(DHTNodes.class);
|
||||
_nodeMap = new ConcurrentHashMap();
|
||||
_kad = new KBucketSet(ctx, me, KAD_K, KAD_B, new KBTrimmer(ctx, KAD_K));
|
||||
_nodeMap = new ConcurrentHashMap<NID, NodeInfo>();
|
||||
_kad = new KBucketSet<NID>(ctx, me, KAD_K, KAD_B, new KBTrimmer(ctx, KAD_K));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@ -120,7 +117,7 @@ class DHTNodes {
|
||||
else
|
||||
key = new NID(h.getData());
|
||||
List<NID> keys = _kad.getClosest(key, numWant);
|
||||
List<NodeInfo> rv = new ArrayList(keys.size());
|
||||
List<NodeInfo> rv = new ArrayList<NodeInfo>(keys.size());
|
||||
for (NID nid : keys) {
|
||||
NodeInfo ninfo = _nodeMap.get(nid);
|
||||
if (ninfo != null)
|
||||
|
@ -104,10 +104,10 @@ class DHTTracker {
|
||||
List<Hash> getPeers(InfoHash ih, int max) {
|
||||
Peers peers = _torrents.get(ih);
|
||||
if (peers == null)
|
||||
return Collections.EMPTY_LIST;
|
||||
return Collections.emptyList();
|
||||
|
||||
int size = peers.size();
|
||||
List<Hash> rv = new ArrayList(peers.values());
|
||||
List<Hash> rv = new ArrayList<Hash>(peers.values());
|
||||
if (max < size) {
|
||||
Collections.shuffle(rv, _context.random());
|
||||
rv = rv.subList(0, max);
|
||||
|
@ -10,7 +10,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@ -24,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionMuxedListener;
|
||||
@ -32,17 +30,16 @@ import net.i2p.client.SendMessageOptions;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
import net.i2p.client.datagram.I2PInvalidDatagramException;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.TrackerClient;
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEncoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
@ -114,6 +111,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
/** signed dgrams */
|
||||
private final int _qPort;
|
||||
private final File _dhtFile;
|
||||
private final File _backupDhtFile;
|
||||
private volatile boolean _isRunning;
|
||||
private volatile boolean _hasBootstrapped;
|
||||
/** stats */
|
||||
@ -148,30 +146,34 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private static final long MAX_MSGID_AGE = 2*60*1000;
|
||||
/** how long since sent do we wait for a reply */
|
||||
private static final long DEFAULT_QUERY_TIMEOUT = 75*1000;
|
||||
private static final long DEST_LOOKUP_TIMEOUT = 10*1000;
|
||||
/** stagger with other cleaners */
|
||||
private static final long CLEAN_TIME = 63*1000;
|
||||
private static final long EXPLORE_TIME = 877*1000;
|
||||
private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
|
||||
private static final String DHT_FILE = "i2psnark.dht.dat";
|
||||
private static final String DHT_FILE_SUFFIX = ".dht.dat";
|
||||
|
||||
private static final int SEND_CRYPTO_TAGS = 8;
|
||||
private static final int LOW_CRYPTO_TAGS = 4;
|
||||
|
||||
public KRPC (I2PAppContext ctx, I2PSession session) {
|
||||
/**
|
||||
* @param baseName generally "i2psnark"
|
||||
*/
|
||||
public KRPC(I2PAppContext ctx, String baseName, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(KRPC.class);
|
||||
_tracker = new DHTTracker(ctx);
|
||||
|
||||
_sentQueries = new ConcurrentHashMap();
|
||||
_outgoingTokens = new ConcurrentHashMap();
|
||||
_incomingTokens = new ConcurrentHashMap();
|
||||
_blacklist = new ConcurrentHashSet();
|
||||
_sentQueries = new ConcurrentHashMap<MsgID, ReplyWaiter>();
|
||||
_outgoingTokens = new ConcurrentHashMap<Token, NodeInfo>();
|
||||
_incomingTokens = new ConcurrentHashMap<NID, Token>();
|
||||
_blacklist = new ConcurrentHashSet<NID>();
|
||||
|
||||
// Construct my NodeInfo
|
||||
// Pick ports over a big range to marginally increase security
|
||||
// If we add a search DHT, adjust to stay out of each other's way
|
||||
_qPort = 2555 + ctx.random().nextInt(61111);
|
||||
_qPort = TrackerClient.PORT + 10 + ctx.random().nextInt(65535 - 20 - TrackerClient.PORT);
|
||||
_rPort = _qPort + 1;
|
||||
if (SECURE_NID) {
|
||||
_myNID = NodeInfo.generateNID(session.getMyDestination().calculateHash(), _qPort, _context.random());
|
||||
@ -182,7 +184,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_myNID = new NID(_myID);
|
||||
}
|
||||
_myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
|
||||
_dhtFile = new File(ctx.getConfigDir(), DHT_FILE);
|
||||
_dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
|
||||
_backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
|
||||
_knownNodes = new DHTNodes(ctx, _myNID);
|
||||
|
||||
start();
|
||||
@ -239,9 +242,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_log.info("DHT is empty, cannot explore");
|
||||
return;
|
||||
}
|
||||
SortedSet<NodeInfo> toTry = new TreeSet(new NodeInfoComparator(target));
|
||||
SortedSet<NodeInfo> toTry = new TreeSet<NodeInfo>(new NodeInfoComparator(target));
|
||||
toTry.addAll(nodes);
|
||||
Set<NodeInfo> tried = new HashSet();
|
||||
Set<NodeInfo> tried = new HashSet<NodeInfo>();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting explore of " + target);
|
||||
@ -302,7 +305,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get peers for a torrent.
|
||||
* Get peers for a torrent, and announce to the closest node we find.
|
||||
* This is an iterative lookup in the DHT.
|
||||
* Blocking!
|
||||
* Caller should run in a thread.
|
||||
@ -310,30 +313,38 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param ih the Info Hash (torrent)
|
||||
* @param max maximum number of peers to return
|
||||
* @param maxWait the maximum time to wait (ms) must be > 0
|
||||
* @param annMax the number of peers to announce to
|
||||
* @param annMaxWait the maximum total time to wait for announces, may be 0 to return immediately without waiting for acks
|
||||
* @return possibly empty (never null)
|
||||
*/
|
||||
public Collection<Hash> getPeers(byte[] ih, int max, long maxWait) {
|
||||
public Collection<Hash> getPeersAndAnnounce(byte[] ih, int max, long maxWait, int annMax, long annMaxWait) {
|
||||
// check local tracker first
|
||||
InfoHash iHash = new InfoHash(ih);
|
||||
Collection<Hash> rv = _tracker.getPeers(iHash, max);
|
||||
rv.remove(_myNodeInfo.getHash());
|
||||
if (rv.size() >= max)
|
||||
return rv;
|
||||
rv = new HashSet(rv);
|
||||
rv = new HashSet<Hash>(rv);
|
||||
long endTime = _context.clock().now() + maxWait;
|
||||
|
||||
// needs to be much higher than log(size) since many lookups will fail
|
||||
// at first and we will give up too early
|
||||
int maxNodes = 30;
|
||||
// Initial set to try, will get added to as we go
|
||||
int maxNodes = 12;
|
||||
List<NodeInfo> nodes = _knownNodes.findClosest(iHash, maxNodes);
|
||||
SortedSet<NodeInfo> toTry = new TreeSet(new NodeInfoComparator(iHash));
|
||||
NodeInfoComparator comp = new NodeInfoComparator(iHash);
|
||||
SortedSet<NodeInfo> toTry = new TreeSet<NodeInfo>(comp);
|
||||
SortedSet<NodeInfo> heardFrom = new TreeSet<NodeInfo>(comp);
|
||||
toTry.addAll(nodes);
|
||||
Set<NodeInfo> tried = new HashSet();
|
||||
SortedSet<NodeInfo> tried = new TreeSet<NodeInfo>(comp);
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting getPeers for " + iHash + " with " + nodes.size() + " to try");
|
||||
_log.info("Starting getPeers for " + iHash + " (b64: " + new NID(ih) + ") " + " with " + nodes.size() + " to try");
|
||||
for (int i = 0; i < maxNodes; i++) {
|
||||
if (!_isRunning)
|
||||
break;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Now to try: " + toTry);
|
||||
NodeInfo nInfo;
|
||||
try {
|
||||
nInfo = toTry.first();
|
||||
@ -342,13 +353,15 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
}
|
||||
toTry.remove(nInfo);
|
||||
tried.add(nInfo);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Try " + i + ": " + nInfo);
|
||||
|
||||
ReplyWaiter waiter = sendGetPeers(nInfo, iHash);
|
||||
if (waiter == null)
|
||||
continue;
|
||||
synchronized(waiter) {
|
||||
try {
|
||||
waiter.wait(Math.max(20*1000, (Math.min(40*1000, endTime - _context.clock().now()))));
|
||||
waiter.wait(Math.max(30*1000, (Math.min(45*1000, endTime - _context.clock().now()))));
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
@ -360,18 +373,24 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got pong");
|
||||
} else if (replyType == REPLY_PEERS) {
|
||||
heardFrom.add(waiter.getSentTo());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got peers");
|
||||
List<Hash> reply = (List<Hash>) waiter.getReplyObject();
|
||||
// shouldn't send us an empty peers list but through
|
||||
// 0.9.8.1 it will
|
||||
if (!reply.isEmpty()) {
|
||||
for (int j = 0; j < reply.size() && rv.size() < max; j++) {
|
||||
rv.add(reply.get(j));
|
||||
Hash h = reply.get(j);
|
||||
if (!h.equals(_myNodeInfo.getHash()))
|
||||
rv.add(h);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Finished get Peers, got " + rv.size() + " from DHT, returning " + reply.size());
|
||||
return rv;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Finished get Peers, got " + reply.size() + " from DHT, returning " + rv.size());
|
||||
break;
|
||||
} else if (replyType == REPLY_NODES) {
|
||||
heardFrom.add(waiter.getSentTo());
|
||||
List<NodeInfo> reply = (List<NodeInfo>) waiter.getReplyObject();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got " + reply.size() + " nodes");
|
||||
@ -387,9 +406,45 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
}
|
||||
if (_context.clock().now() > endTime)
|
||||
break;
|
||||
if (!toTry.isEmpty() && !heardFrom.isEmpty() &&
|
||||
comp.compare(toTry.first(), heardFrom.first()) >= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Finished get Peers, nothing closer to try after " + (i+1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// now announce
|
||||
if (!heardFrom.isEmpty()) {
|
||||
announce(ih);
|
||||
// announce to the closest we've heard from
|
||||
int annCnt = 0;
|
||||
long start = _context.clock().now();
|
||||
for (Iterator<NodeInfo> iter = heardFrom.iterator(); iter.hasNext() && annCnt < annMax && _isRunning; ) {
|
||||
NodeInfo annTo = iter.next();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Announcing to closest from get peers: " + annTo);
|
||||
long toWait = annMaxWait > 0 ? Math.min(annMaxWait, 60*1000) : 0;
|
||||
if (announce(ih, annTo, toWait))
|
||||
annCnt++;
|
||||
if (annMaxWait > 0) {
|
||||
annMaxWait -= _context.clock().now() - start;
|
||||
if (annMaxWait < 1000)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// spray it, but unlikely to work, we just went through the kbuckets,
|
||||
// so this is essentially just a retry
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Announcing to closest in kbuckets after get peers failed");
|
||||
announce(ih, annMax, annMaxWait);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Finished get Peers, returning " + rv.size());
|
||||
_log.info("Tried: " + tried);
|
||||
_log.info("Heard from: " + heardFrom);
|
||||
_log.info("Not tried: " + toTry);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Finished get Peers, " + rv.size() + " from local and none from DHT");
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -431,13 +486,16 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
}
|
||||
|
||||
/**
|
||||
* Not recommended - use getPeersAndAnnounce().
|
||||
*
|
||||
* Announce to the closest peers in the local DHT.
|
||||
* This is NOT iterative - call getPeers() first to get the closest
|
||||
* peers into the local DHT.
|
||||
* Blocking unless maxWait <= 0
|
||||
* Caller should run in a thread.
|
||||
* This also automatically announces ourself to our local tracker.
|
||||
* For best results do a getPeers() first so we have tokens.
|
||||
* For best results do a getPeersAndAnnounce() instead, as this announces to
|
||||
* the closest in the kbuckets, it does NOT sort through the known nodes hashmap.
|
||||
*
|
||||
* @param ih the Info Hash (torrent)
|
||||
* @param max maximum number of peers to announce to
|
||||
@ -547,7 +605,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_session.addMuxedSessionListener(this, I2PSession.PROTO_DATAGRAM, _qPort);
|
||||
_knownNodes.start();
|
||||
_tracker.start();
|
||||
PersistDHT.loadDHT(this, _dhtFile);
|
||||
PersistDHT.loadDHT(this, _dhtFile, _backupDhtFile);
|
||||
// start the explore thread
|
||||
_isRunning = true;
|
||||
// no need to keep ref, it will eventually stop
|
||||
@ -635,9 +693,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private ReplyWaiter sendPing(NodeInfo nInfo) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending ping to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("q", "ping");
|
||||
Map<String, Object> args = new HashMap();
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
map.put("a", args);
|
||||
return sendQuery(nInfo, map, true);
|
||||
}
|
||||
@ -652,9 +710,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private ReplyWaiter sendFindNode(NodeInfo nInfo, NID tID) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending find node of " + tID + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("q", "find_node");
|
||||
Map<String, Object> args = new HashMap();
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("target", tID.getData());
|
||||
map.put("a", args);
|
||||
return sendQuery(nInfo, map, true);
|
||||
@ -669,9 +727,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private ReplyWaiter sendGetPeers(NodeInfo nInfo, InfoHash ih) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending get peers of " + ih + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("q", "get_peers");
|
||||
Map<String, Object> args = new HashMap();
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("info_hash", ih.getData());
|
||||
map.put("a", args);
|
||||
ReplyWaiter rv = sendQuery(nInfo, map, true);
|
||||
@ -690,12 +748,12 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private ReplyWaiter sendAnnouncePeer(NodeInfo nInfo, InfoHash ih, Token token) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending announce of " + ih + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("q", "announce_peer");
|
||||
Map<String, Object> args = new HashMap();
|
||||
Map<String, Object> args = new HashMap<String, Object>();
|
||||
args.put("info_hash", ih.getData());
|
||||
// port ignored
|
||||
args.put("port", Integer.valueOf(6881));
|
||||
args.put("port", Integer.valueOf(TrackerClient.PORT));
|
||||
args.put("token", token.getData());
|
||||
map.put("a", args);
|
||||
// an announce need not be signed, we have a token
|
||||
@ -713,8 +771,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private boolean sendPong(NodeInfo nInfo, MsgID msgID) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending pong to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> resps = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
Map<String, Object> resps = new HashMap<String, Object>();
|
||||
map.put("r", resps);
|
||||
return sendResponse(nInfo, msgID, map);
|
||||
}
|
||||
@ -732,8 +790,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private boolean sendNodes(NodeInfo nInfo, MsgID msgID, Token token, byte[] ids) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending nodes to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> resps = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
Map<String, Object> resps = new HashMap<String, Object>();
|
||||
map.put("r", resps);
|
||||
if (token != null)
|
||||
resps.put("token", token.getData());
|
||||
@ -745,8 +803,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private boolean sendPeers(NodeInfo nInfo, MsgID msgID, Token token, List<byte[]> peers) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending peers to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> resps = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
Map<String, Object> resps = new HashMap<String, Object>();
|
||||
map.put("r", resps);
|
||||
resps.put("token", token.getData());
|
||||
resps.put("values", peers);
|
||||
@ -762,8 +820,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private boolean sendError(NodeInfo nInfo, MsgID msgID, int err, String msg) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending error " + msg + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap();
|
||||
Map<String, Object> resps = new HashMap();
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
Map<String, Object> resps = new HashMap<String, Object>();
|
||||
map.put("r", resps);
|
||||
return sendResponse(nInfo, msgID, map);
|
||||
}
|
||||
@ -795,8 +853,8 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
// Lookup the dest for the hash
|
||||
// TODO spin off into thread or queue? We really don't want to block here
|
||||
if (!lookupDest(nInfo)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping repliable query, no dest for " + nInfo);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Dropping repliable query, no dest for " + nInfo);
|
||||
timeout(nInfo);
|
||||
return null;
|
||||
}
|
||||
@ -886,7 +944,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_log.info("looking up dest for " + nInfo);
|
||||
try {
|
||||
// use a short timeout for now
|
||||
Destination dest = _session.lookupDest(nInfo.getHash(), 5*1000);
|
||||
Destination dest = _session.lookupDest(nInfo.getHash(), DEST_LOOKUP_TIMEOUT);
|
||||
if (dest != null) {
|
||||
nInfo.setDestination(dest);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@ -1185,6 +1243,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_log.info("Stored new OB token: " + token + " for: " + nInfo);
|
||||
|
||||
List<Hash> peers = _tracker.getPeers(ih, MAX_WANT);
|
||||
peers.remove(nInfo.getHash()); // him
|
||||
if (peers.isEmpty()) {
|
||||
// similar to find node, but with token
|
||||
// get closest from DHT
|
||||
@ -1197,11 +1256,9 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
}
|
||||
sendNodes(nInfo, msgID, token, nodeArray);
|
||||
} else {
|
||||
List<byte[]> hashes = new ArrayList(peers.size());
|
||||
Hash him = nInfo.getHash();
|
||||
List<byte[]> hashes = new ArrayList<byte[]>(peers.size());
|
||||
for (Hash peer : peers) {
|
||||
if (!peer.equals(him))
|
||||
hashes.add(peer.getData());
|
||||
hashes.add(peer.getData());
|
||||
}
|
||||
sendPeers(nInfo, msgID, token, hashes);
|
||||
}
|
||||
@ -1285,7 +1342,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
*/
|
||||
private List<NodeInfo> receiveNodes(NodeInfo nInfo, byte[] ids) throws InvalidBEncodingException {
|
||||
int max = Math.min(K, ids.length / NodeInfo.LENGTH);
|
||||
List<NodeInfo> rv = new ArrayList(max);
|
||||
List<NodeInfo> rv = new ArrayList<NodeInfo>(max);
|
||||
for (int off = 0; off < ids.length && rv.size() < max; off += NodeInfo.LENGTH) {
|
||||
NodeInfo nInf = new NodeInfo(ids, off);
|
||||
if (_blacklist.contains(nInf.getNID())) {
|
||||
@ -1309,7 +1366,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Rcvd peers from: " + nInfo);
|
||||
int max = Math.min(MAX_WANT, peers.size());
|
||||
List<Hash> rv = new ArrayList(max);
|
||||
List<Hash> rv = new ArrayList<Hash>(max);
|
||||
for (BEValue bev : peers) {
|
||||
byte[] b = bev.getBytes();
|
||||
//Hash h = new Hash(b);
|
||||
@ -1319,7 +1376,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
break;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Rcvd peers from: " + nInfo + ": " + DataHelper.toString(rv));
|
||||
_log.info("Rcvd " + peers.size() + " peers from: " + nInfo + ": " + DataHelper.toString(rv));
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,17 @@ abstract class PersistDHT {
|
||||
|
||||
private static final long MAX_AGE = 60*60*1000;
|
||||
|
||||
/**
|
||||
* @param backupFile may be null
|
||||
* @since 0.9.6
|
||||
*/
|
||||
public static synchronized void loadDHT(KRPC krpc, File file, File backupFile) {
|
||||
if (file.exists())
|
||||
loadDHT(krpc, file);
|
||||
else if (backupFile != null)
|
||||
loadDHT(krpc, backupFile);
|
||||
}
|
||||
|
||||
public static synchronized void loadDHT(KRPC krpc, File file) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(PersistDHT.class);
|
||||
int count = 0;
|
||||
|
@ -7,7 +7,6 @@ import java.util.Date;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Used for Both outgoing and incoming tokens
|
||||
|
603
apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java
Normal file
603
apps/i2psnark/java/src/org/klomp/snark/web/BasicServlet.java
Normal file
@ -0,0 +1,603 @@
|
||||
// ========================================================================
|
||||
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Based on DefaultServlet from Jetty 6.1.26, heavily simplified
|
||||
* and modified to remove all dependencies on Jetty libs.
|
||||
*
|
||||
* Supports HEAD and GET only, for resources from the .war and local files.
|
||||
* Supports files and resource only.
|
||||
* Supports MIME types with local overrides and additions.
|
||||
* Supports Last-Modified.
|
||||
* Supports single request ranges.
|
||||
*
|
||||
* Does not support directories or "welcome files".
|
||||
* Does not support gzip.
|
||||
* Does not support multiple request ranges.
|
||||
* Does not cache.
|
||||
*
|
||||
* POST returns 405.
|
||||
* Directories return 403.
|
||||
* Jar resources are sent with a long cache directive.
|
||||
*
|
||||
* ------------------------------------------------------------
|
||||
*
|
||||
* The default servlet.
|
||||
* This servlet, normally mapped to /, provides the handling for static
|
||||
* content, OPTION and TRACE methods for the context.
|
||||
* The following initParameters are supported, these can be set
|
||||
* on the servlet itself:
|
||||
* <PRE>
|
||||
*
|
||||
* resourceBase Set to replace the context resource base
|
||||
|
||||
* warBase Path allowed for resource in war
|
||||
*
|
||||
* </PRE>
|
||||
*
|
||||
*
|
||||
* @author Greg Wilkins (gregw)
|
||||
* @author Nigel Canonizado
|
||||
*
|
||||
* @since Jetty 7
|
||||
*/
|
||||
class BasicServlet extends HttpServlet
|
||||
{
|
||||
protected final I2PAppContext _context;
|
||||
protected final Log _log;
|
||||
protected File _resourceBase;
|
||||
private String _warBase;
|
||||
|
||||
private final MimeTypes _mimeTypes;
|
||||
|
||||
/** same as PeerState.PARTSIZE */
|
||||
private static final int BUFSIZE = 16*1024;
|
||||
private ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
||||
|
||||
private static final int WAR_CACHE_CONTROL_SECS = 24*60*60;
|
||||
private static final int FILE_CACHE_CONTROL_SECS = 24*60*60;
|
||||
|
||||
public BasicServlet() {
|
||||
super();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
_mimeTypes = new MimeTypes();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void init(ServletConfig cfg) throws ServletException {
|
||||
super.init(cfg);
|
||||
String rb=getInitParameter("resourceBase");
|
||||
if (rb!=null)
|
||||
{
|
||||
File f = new File(rb);
|
||||
setResourceBase(f);
|
||||
}
|
||||
String wb = getInitParameter("warBase");
|
||||
if (wb != null)
|
||||
setWarBase(wb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Files are served from here
|
||||
*/
|
||||
protected void setResourceBase(File base) throws UnavailableException {
|
||||
if (!base.isDirectory())
|
||||
throw new UnavailableException("Resource base does not exist: " + base);
|
||||
_resourceBase = base;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Resource base is " + _resourceBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only paths starting with this in the path are served
|
||||
*/
|
||||
protected void setWarBase(String base) {
|
||||
if (!base.startsWith("/"))
|
||||
base = '/' + base;
|
||||
if (!base.endsWith("/"))
|
||||
base = base + '/';
|
||||
_warBase = base;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("War base is " + _warBase);
|
||||
}
|
||||
|
||||
/** get Resource to serve.
|
||||
* Map a path to a resource. The default implementation calls
|
||||
* HttpContext.getResource but derived servlets may provide
|
||||
* their own mapping.
|
||||
* @param pathInContext The path to find a resource for.
|
||||
* @return The resource to serve or null if not existing
|
||||
*/
|
||||
public File getResource(String pathInContext)
|
||||
{
|
||||
if (_resourceBase==null)
|
||||
return null;
|
||||
File r = null;
|
||||
if (!pathInContext.contains("..") &&
|
||||
!pathInContext.endsWith("/")) {
|
||||
File f = new File(_resourceBase, pathInContext);
|
||||
if (f.exists())
|
||||
r = f;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/** get Resource to serve.
|
||||
* Map a path to a resource. The default implementation calls
|
||||
* HttpContext.getResource but derived servlets may provide
|
||||
* their own mapping.
|
||||
* @param pathInContext The path to find a resource for.
|
||||
* @return The resource to serve or null. Returns null for directories
|
||||
*/
|
||||
public HttpContent getContent(String pathInContext)
|
||||
{
|
||||
if (_resourceBase==null)
|
||||
return null;
|
||||
HttpContent r = null;
|
||||
if (_warBase != null && pathInContext.startsWith(_warBase)) {
|
||||
r = new JarContent(pathInContext);
|
||||
} else if (!pathInContext.contains("..") &&
|
||||
!pathInContext.endsWith("/")) {
|
||||
File f = new File(_resourceBase, pathInContext);
|
||||
// exists && !directory
|
||||
if (f.isFile())
|
||||
r = new FileContent(f);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
// always starts with a '/'
|
||||
String servletpath = request.getServletPath();
|
||||
String pathInfo=request.getPathInfo();
|
||||
// ??? right??
|
||||
String pathInContext = addPaths(servletpath, pathInfo);
|
||||
|
||||
// Find the resource and content
|
||||
try {
|
||||
HttpContent content = getContent(pathInContext);
|
||||
|
||||
// Handle resource
|
||||
if (content == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Not found: " + pathInContext);
|
||||
response.sendError(404);
|
||||
} else {
|
||||
if (passConditionalHeaders(request, response, content)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending: " + content);
|
||||
sendData(request, response, content);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Not modified: " + content);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error sending " + pathInContext, e);
|
||||
if(!response.isCommitted())
|
||||
response.sendError(500, e.getMessage());
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
// typical browser abort
|
||||
//_log.warn("Error sending", e);
|
||||
_log.warn("Error sending " + pathInContext + ": " + e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
response.sendError(405);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
response.sendError(405);
|
||||
}
|
||||
|
||||
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
response.sendError(405);
|
||||
}
|
||||
|
||||
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException
|
||||
{
|
||||
response.sendError(405);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Check modification date headers.
|
||||
* @return true to keep going, false if handled here
|
||||
*/
|
||||
protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
|
||||
throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!request.getMethod().equals("HEAD") ) {
|
||||
long ifmsl=request.getDateHeader("If-Modified-Since");
|
||||
if (ifmsl!=-1)
|
||||
{
|
||||
if (content.getLastModified()/1000 <= ifmsl/1000)
|
||||
{
|
||||
response.reset();
|
||||
response.setStatus(304);
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
if(!response.isCommitted())
|
||||
response.sendError(400, iae.getMessage());
|
||||
throw iae;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void sendData(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
HttpContent content)
|
||||
throws IOException
|
||||
{
|
||||
InputStream in =null;
|
||||
try {
|
||||
in = content.getInputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Not found: " + content);
|
||||
response.sendError(404);
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream out =null;
|
||||
try {
|
||||
out = response.getOutputStream();
|
||||
} catch (IllegalStateException e) {
|
||||
out = new WriterOutputStream(response.getWriter());
|
||||
}
|
||||
|
||||
long content_length = content.getContentLength();
|
||||
|
||||
// see if there are any range headers
|
||||
Enumeration reqRanges = request.getHeaders("Range");
|
||||
|
||||
if (reqRanges == null || !reqRanges.hasMoreElements()) {
|
||||
// if there were no ranges, send entire entity
|
||||
// Write content normally
|
||||
writeHeaders(response,content,content_length);
|
||||
if (content_length >= 0 && request.getMethod().equals("HEAD")) {
|
||||
// if we know the content length, don't send it to be counted
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HEAD: " + content);
|
||||
} else {
|
||||
// GET or unknown size for HEAD
|
||||
copy(in, out);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Parse the satisfiable ranges
|
||||
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges(reqRanges, content_length);
|
||||
|
||||
// if there are no satisfiable ranges, send 416 response
|
||||
// Completely punt on multiple ranges (unlike Default)
|
||||
if (ranges == null || ranges.size() != 1) {
|
||||
writeHeaders(response, content, content_length);
|
||||
response.setStatus(416);
|
||||
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is only a single valid range (must be satisfiable
|
||||
// since were here now), send that range with a 216 response
|
||||
InclusiveByteRange singleSatisfiableRange = ranges.get(0);
|
||||
long singleLength = singleSatisfiableRange.getSize(content_length);
|
||||
writeHeaders(response, content, singleLength);
|
||||
response.setStatus(206);
|
||||
response.setHeader("Content-Range", singleSatisfiableRange.toHeaderRangeString(content_length));
|
||||
copy(in, singleSatisfiableRange.getFirst(content_length), out, singleLength);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
|
||||
throws IOException
|
||||
{
|
||||
if (content.getContentType()!=null && response.getContentType()==null)
|
||||
response.setContentType(content.getContentType());
|
||||
|
||||
long lml = content.getLastModified();
|
||||
if (lml > 0)
|
||||
response.setDateHeader("Last-Modified",lml);
|
||||
|
||||
if (count != -1)
|
||||
{
|
||||
if (count<Integer.MAX_VALUE)
|
||||
response.setContentLength((int)count);
|
||||
else
|
||||
response.setHeader("Content-Length", Long.toString(count));
|
||||
}
|
||||
|
||||
long ct = content.getCacheTime();
|
||||
if (ct>=0)
|
||||
response.setHeader("Cache-Control", "public, max-age=" + ct);
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* I2P additions below here */
|
||||
|
||||
/** from Jetty HttpContent.java */
|
||||
public interface HttpContent
|
||||
{
|
||||
String getContentType();
|
||||
long getLastModified();
|
||||
/** in seconds */
|
||||
int getCacheTime();
|
||||
long getContentLength();
|
||||
InputStream getInputStream() throws IOException;
|
||||
}
|
||||
|
||||
private class FileContent implements HttpContent
|
||||
{
|
||||
private final File _file;
|
||||
|
||||
public FileContent(File file)
|
||||
{
|
||||
_file = file;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getContentType()
|
||||
{
|
||||
//return _mimeTypes.getMimeByExtension(_file.toString());
|
||||
return getMimeType(_file.toString());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getLastModified()
|
||||
{
|
||||
return _file.lastModified();
|
||||
}
|
||||
|
||||
public int getCacheTime()
|
||||
{
|
||||
return FILE_CACHE_CONTROL_SECS;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getContentLength()
|
||||
{
|
||||
return _file.length();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
return new BufferedInputStream(new FileInputStream(_file));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return "File \"" + _file + '"'; }
|
||||
}
|
||||
|
||||
private class JarContent implements HttpContent
|
||||
{
|
||||
private final String _path;
|
||||
|
||||
public JarContent(String path)
|
||||
{
|
||||
_path = path;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String getContentType()
|
||||
{
|
||||
return getMimeType(_path);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getLastModified()
|
||||
{
|
||||
String cpath = getServletContext().getContextPath();
|
||||
// this won't work if we aren't at top level
|
||||
String cname = "".equals(cpath) ? "i2psnark" : cpath.substring(1).replace("/", "_");
|
||||
return (new File(_context.getBaseDir(), "webapps/" + cname + ".war")).lastModified();
|
||||
}
|
||||
|
||||
public int getCacheTime()
|
||||
{
|
||||
return WAR_CACHE_CONTROL_SECS;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getContentLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
InputStream rv = getServletContext().getResourceAsStream(_path);
|
||||
if (rv == null)
|
||||
throw new IOException("Not found");
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return "Jar resource \"" + _path + '"'; }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param resourcePath in the classpath, without ".properties" extension
|
||||
*/
|
||||
protected void loadMimeMap(String resourcePath) {
|
||||
_mimeTypes.loadMimeMap(resourcePath);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the MIME type by filename extension.
|
||||
* @param filename A file name
|
||||
* @return MIME type matching the longest dot extension of the
|
||||
* file name.
|
||||
*/
|
||||
protected String getMimeType(String filename) {
|
||||
String rv = _mimeTypes.getMimeByExtension(filename);
|
||||
if (rv != null)
|
||||
return rv;
|
||||
return getServletContext().getMimeType(filename);
|
||||
}
|
||||
|
||||
protected void addMimeMapping(String extension, String type) {
|
||||
_mimeTypes.addMimeMapping(extension, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple version of URIUtil.addPaths()
|
||||
* @param path may be null
|
||||
*/
|
||||
protected static String addPaths(String base, String path) {
|
||||
if (path == null)
|
||||
return base;
|
||||
String rv = (new File(base, path)).toString();
|
||||
if (SystemVersion.isWindows())
|
||||
rv = rv.replace("\\", "/");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple version of URIUtil.decodePath()
|
||||
*/
|
||||
protected static String decodePath(String path) throws MalformedURLException {
|
||||
if (!path.contains("%"))
|
||||
return path;
|
||||
try {
|
||||
URI uri = new URI(path);
|
||||
return uri.getPath();
|
||||
} catch (URISyntaxException use) {
|
||||
// for ease of use, since a USE is not an IOE but a MUE is...
|
||||
throw new MalformedURLException(use.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple version of URIUtil.encodePath()
|
||||
*/
|
||||
protected static String encodePath(String path) throws MalformedURLException {
|
||||
try {
|
||||
URI uri = new URI(null, null, path, null);
|
||||
return uri.toString();
|
||||
} catch (URISyntaxException use) {
|
||||
// for ease of use, since a USE is not an IOE but a MUE is...
|
||||
throw new MalformedURLException(use.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write from in to out
|
||||
*/
|
||||
private void copy(InputStream in, OutputStream out) throws IOException {
|
||||
copy(in, 0, out, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write from in to out
|
||||
*/
|
||||
private void copy(InputStream in, long skip, OutputStream out, final long len) throws IOException {
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte[] buf = ba.getData();
|
||||
try {
|
||||
if (skip > 0)
|
||||
DataHelper.skip(in, skip);
|
||||
int read = 0;
|
||||
long tot = 0;
|
||||
boolean done = false;
|
||||
while ( (read = in.read(buf)) != -1 && !done) {
|
||||
if (len >= 0) {
|
||||
tot += read;
|
||||
if (tot >= len) {
|
||||
read -= (int) (tot - len);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
_cache.release(ba, false);
|
||||
if (in != null)
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null)
|
||||
try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -160,8 +160,8 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
return;
|
||||
}
|
||||
|
||||
name = Storage.filterName(name);
|
||||
name = name + ".torrent";
|
||||
String originalName = Storage.filterName(name);
|
||||
name = originalName + ".torrent";
|
||||
File torrentFile = new File(_mgr.getDataDir(), name);
|
||||
|
||||
String canonical = torrentFile.getCanonicalPath();
|
||||
@ -174,6 +174,8 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
} else {
|
||||
// This may take a LONG time to create the storage.
|
||||
_mgr.copyAndAddTorrent(file, canonical);
|
||||
snark = _mgr.getTorrentByBaseName(originalName);
|
||||
snark.startTorrent();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_mgr.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
|
||||
@ -196,7 +198,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
//_total = -1;
|
||||
_transferred = 0;
|
||||
_failCause = null;
|
||||
_started = _ctx.clock().now();
|
||||
_started = _util.getContext().clock().now();
|
||||
_isRunning = true;
|
||||
_active = false;
|
||||
_thread = new I2PAppThread(this, "Torrent File EepGet", true);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,218 @@
|
||||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Byte range inclusive of end points.
|
||||
* <PRE>
|
||||
*
|
||||
* parses the following types of byte ranges:
|
||||
*
|
||||
* bytes=100-499
|
||||
* bytes=-300
|
||||
* bytes=100-
|
||||
* bytes=1-2,2-3,6-,-2
|
||||
*
|
||||
* given an entity length, converts range to string
|
||||
*
|
||||
* bytes 100-499/500
|
||||
*
|
||||
* </PRE>
|
||||
*
|
||||
* Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
|
||||
* @version $version$
|
||||
*
|
||||
*/
|
||||
public class InclusiveByteRange
|
||||
{
|
||||
long first = 0;
|
||||
long last = 0;
|
||||
|
||||
public InclusiveByteRange(long first, long last)
|
||||
{
|
||||
this.first = first;
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public long getFirst()
|
||||
{
|
||||
return first;
|
||||
}
|
||||
|
||||
public long getLast()
|
||||
{
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param headers Enumeration of Range header fields.
|
||||
* @param size Size of the resource.
|
||||
* @return List of satisfiable ranges
|
||||
*/
|
||||
public static List<InclusiveByteRange> satisfiableRanges(Enumeration headers, long size)
|
||||
{
|
||||
List<InclusiveByteRange> satRanges = null;
|
||||
|
||||
// walk through all Range headers
|
||||
headers:
|
||||
while (headers.hasMoreElements())
|
||||
{
|
||||
String header = (String) headers.nextElement();
|
||||
StringTokenizer tok = new StringTokenizer(header,"=,",false);
|
||||
String t=null;
|
||||
try
|
||||
{
|
||||
// read all byte ranges for this header
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
try
|
||||
{
|
||||
t = tok.nextToken().trim();
|
||||
|
||||
long first = -1;
|
||||
long last = -1;
|
||||
int d = t.indexOf('-');
|
||||
if (d < 0 || t.indexOf("-",d + 1) >= 0)
|
||||
{
|
||||
if ("bytes".equals(t))
|
||||
continue;
|
||||
continue headers;
|
||||
}
|
||||
else if (d == 0)
|
||||
{
|
||||
if (d + 1 < t.length())
|
||||
last = Long.parseLong(t.substring(d + 1).trim());
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (d + 1 < t.length())
|
||||
{
|
||||
first = Long.parseLong(t.substring(0,d).trim());
|
||||
last = Long.parseLong(t.substring(d + 1).trim());
|
||||
}
|
||||
else
|
||||
first = Long.parseLong(t.substring(0,d).trim());
|
||||
|
||||
if (first == -1 && last == -1)
|
||||
continue headers;
|
||||
|
||||
if (first != -1 && last != -1 && (first > last))
|
||||
continue headers;
|
||||
|
||||
if (first < size)
|
||||
{
|
||||
if (satRanges == null)
|
||||
satRanges = new ArrayList(4);
|
||||
InclusiveByteRange range = new InclusiveByteRange(first,last);
|
||||
satRanges.add(range);
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
}
|
||||
}
|
||||
return satRanges;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getFirst(long size)
|
||||
{
|
||||
if (first<0)
|
||||
{
|
||||
long tf=size-last;
|
||||
if (tf<0)
|
||||
tf=0;
|
||||
return tf;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getLast(long size)
|
||||
{
|
||||
if (first<0)
|
||||
return size-1;
|
||||
|
||||
if (last<0 ||last>=size)
|
||||
return size-1;
|
||||
return last;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long getSize(long size)
|
||||
{
|
||||
return getLast(size)-getFirst(size)+1;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String toHeaderRangeString(long size)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append("bytes ");
|
||||
sb.append(getFirst(size));
|
||||
sb.append('-');
|
||||
sb.append(getLast(size));
|
||||
sb.append("/");
|
||||
sb.append(size);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static String to416HeaderRangeString(long size)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append("bytes */");
|
||||
sb.append(size);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(60);
|
||||
sb.append(Long.toString(first));
|
||||
sb.append(":");
|
||||
sb.append(Long.toString(last));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
131
apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java
Normal file
131
apps/i2psnark/java/src/org/klomp/snark/web/MimeTypes.java
Normal file
@ -0,0 +1,131 @@
|
||||
// ========================================================================
|
||||
// Copyright 2000-2005 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Based on MimeTypes from Jetty 6.1.26, heavily simplified
|
||||
* and modified to remove all dependencies on Jetty libs.
|
||||
*
|
||||
* Supports mime types only, not encodings.
|
||||
* Does not support a default "*" mapping.
|
||||
*
|
||||
* This is only for local mappings.
|
||||
* Caller should use getServletContext().getMimeType() if this returns null.
|
||||
*
|
||||
*
|
||||
* ------------------------------------------------------------
|
||||
*
|
||||
* @author Greg Wilkins
|
||||
*
|
||||
* @since Jetty 7
|
||||
*/
|
||||
class MimeTypes
|
||||
{
|
||||
|
||||
private final Map<String, String> _mimeMap;
|
||||
|
||||
public MimeTypes() {
|
||||
_mimeMap = new ConcurrentHashMap();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param resourcePath A Map of file extension to mime-type.
|
||||
*/
|
||||
public void loadMimeMap(String resourcePath) {
|
||||
loadMimeMap(_mimeMap, resourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries both webapp and system class loader, since Jetty blocks
|
||||
* its classes from the webapp class loader.
|
||||
*/
|
||||
private static void loadMimeMap(Map<String, String> map, String resourcePath) {
|
||||
try
|
||||
{
|
||||
ResourceBundle mime;
|
||||
try {
|
||||
mime = ResourceBundle.getBundle(resourcePath);
|
||||
} catch(MissingResourceException e) {
|
||||
// Jetty 7 webapp classloader blocks jetty classes
|
||||
// http://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading
|
||||
//System.out.println("No mime types loaded from " + resourcePath + ", trying system classloader");
|
||||
mime = ResourceBundle.getBundle(resourcePath, Locale.getDefault(), ClassLoader.getSystemClassLoader());
|
||||
}
|
||||
Enumeration<String> i = mime.getKeys();
|
||||
while(i.hasMoreElements())
|
||||
{
|
||||
String ext = i.nextElement();
|
||||
String m = mime.getString(ext);
|
||||
map.put(ext.toLowerCase(Locale.US), m);
|
||||
}
|
||||
//System.out.println("Loaded " + map.size() + " mime types from " + resourcePath);
|
||||
} catch(MissingResourceException e) {
|
||||
//System.out.println("No mime types loaded from " + resourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the MIME type by filename extension.
|
||||
*
|
||||
* Returns ONLY local mappings.
|
||||
* Caller should use getServletContext().getMimeType() if this returns null.
|
||||
*
|
||||
* @param filename A file name
|
||||
* @return MIME type matching the longest dot extension of the
|
||||
* file name.
|
||||
*/
|
||||
public String getMimeByExtension(String filename)
|
||||
{
|
||||
String type=null;
|
||||
|
||||
if (filename!=null)
|
||||
{
|
||||
int i=-1;
|
||||
while(type==null)
|
||||
{
|
||||
i=filename.indexOf(".",i+1);
|
||||
|
||||
if (i<0 || i>=filename.length())
|
||||
break;
|
||||
|
||||
String ext=filename.substring(i+1).toLowerCase(Locale.US);
|
||||
type = _mimeMap.get(ext);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set a mime mapping
|
||||
* @param extension
|
||||
* @param type
|
||||
*/
|
||||
public void addMimeMapping(String extension, String type)
|
||||
{
|
||||
_mimeMap.put(extension.toLowerCase(Locale.US), type);
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import java.io.File;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import org.mortbay.jetty.Server;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
||||
public class RunStandalone {
|
||||
static {
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* Treat a writer as an output stream. Quick 'n dirty, none
|
||||
* of that "intarnasheeonaleyzayshun" stuff. So we can treat
|
||||
* the jsp's PrintWriter as an OutputStream
|
||||
*
|
||||
* @since Jetty 7 copied from routerconsole
|
||||
*/
|
||||
class WriterOutputStream extends OutputStream {
|
||||
private final Writer _writer;
|
||||
|
||||
public WriterOutputStream(Writer writer) { _writer = writer; }
|
||||
public void write(int b) throws IOException { _writer.write(b); }
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1197
apps/i2psnark/locale/messages_nb.po
Normal file
1197
apps/i2psnark/locale/messages_nb.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1257
apps/i2psnark/locale/messages_ro.po
Normal file
1257
apps/i2psnark/locale/messages_ro.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
31
apps/i2psnark/mime.properties
Normal file
31
apps/i2psnark/mime.properties
Normal file
@ -0,0 +1,31 @@
|
||||
3gp = video/3gpp
|
||||
3gpp = video/3gpp
|
||||
7z = application/x-7z-compressed
|
||||
ape = audio/x-monkeys-audio
|
||||
bz2 = application/x-bzip2
|
||||
dmg = application/apple-diskimage
|
||||
epub = application/epub+zip
|
||||
flac = audio/flac
|
||||
flv = video/x-flv
|
||||
iso = application/x-iso9660-image
|
||||
m4a = audio/mp4a-latm
|
||||
m4v = video/x-m4v
|
||||
mkv = video/x-matroska
|
||||
mobi = application/x-mobipocket-ebook
|
||||
mp4 = video/mp4
|
||||
mpc = audio/x-musepack
|
||||
nfo = text/plain
|
||||
ogm = video/ogg
|
||||
ogv = video/ogg
|
||||
oga = audio/ogg
|
||||
rar = application/x-rar-compressed
|
||||
sfv = text/x-sfv
|
||||
su2 = application/zip
|
||||
su3 = application/zip
|
||||
sud = application/zip
|
||||
tbz = application/x-bzip2
|
||||
txt = text/plain
|
||||
war = application/java-archive
|
||||
webm = video/webm
|
||||
wma = audio/x-ms-wma
|
||||
wmv = video/x-ms-wmv
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user