Compare commits
1104 Commits
i2p-0.7.12
...
i2p-0.8.2
| Author | SHA1 | Date | |
|---|---|---|---|
| f02a0d96dd | |||
| eefa732815 | |||
| 299c1bd67b | |||
| 1f22ae6a0c | |||
| a3644ccaa9 | |||
| 5e7bad2af0 | |||
| de112fd63c | |||
|
|
c3f43bc63b | ||
|
|
f1fe29e4ba | ||
|
|
086004879d | ||
|
|
8ded0392bf | ||
|
|
58cacd88e4 | ||
| 360d96d085 | |||
|
|
da8661526f | ||
|
|
0d20d95ccd | ||
|
|
5781880707 | ||
| 59e5ec7426 | |||
| 7944065e3f | |||
|
|
d3498138ac | ||
|
|
2302545a03 | ||
|
|
5531b4be51 | ||
|
|
3b4007f8fe | ||
|
|
5a567705ca | ||
|
|
0a5818e1c3 | ||
|
|
6c6609c937 | ||
|
|
7e3c347ac3 | ||
|
|
c09664c57a | ||
|
|
73f5eed45c | ||
|
|
112f3736fb | ||
|
|
c55778ea1a | ||
|
|
8a1fab0c2f | ||
|
|
65c623a9cc | ||
|
|
4e4165e30d | ||
|
|
ef340e6b3e | ||
|
|
cab63efacf | ||
|
|
6bab0e7251 | ||
|
|
cb7e6cfb38 | ||
|
|
aca0ae2103 | ||
|
|
ed9b0bbdd5 | ||
|
|
97bb51c67b | ||
|
|
a85f931c1c | ||
| 46b8befda5 | |||
| 2082feeaa9 | |||
| f9c2624b24 | |||
| 8bcfdc3c6e | |||
|
|
1a41334e34 | ||
|
|
311f295f22 | ||
|
|
194abc7916 | ||
|
|
30145ff298 | ||
|
|
f8bf32e05c | ||
|
|
35feab3dad | ||
|
|
7f3650650d | ||
|
|
57edfbbc15 | ||
|
|
7e1a5d5cb5 | ||
|
|
af8751a3a4 | ||
| 7c63866c51 | |||
|
|
d4b0bfda7e | ||
|
|
c3597cfd99 | ||
|
|
5f91be1b47 | ||
|
|
50caf108cc | ||
|
|
dea69fee76 | ||
| 0be2ca8beb | |||
|
|
eec96ff670 | ||
|
|
8c4298167b | ||
| 2ca8fc6e62 | |||
| 89b3e3bcb4 | |||
| b55b552e06 | |||
| 3098d6ef8e | |||
|
|
036e36f611 | ||
|
|
11de315834 | ||
| 638e04beb8 | |||
| 10f674a782 | |||
| 8d54dbac9a | |||
|
|
198280eea5 | ||
|
|
6644bb0f53 | ||
|
|
99ebcb5e17 | ||
|
|
624aff645d | ||
|
|
b0ac5d3bdd | ||
|
|
3887b0c4e4 | ||
|
|
1abf447f85 | ||
|
|
587b7861cd | ||
|
|
4833b68fc0 | ||
|
|
a292770415 | ||
|
|
b77bcd4692 | ||
|
|
ec9238c99c | ||
|
|
a9f1f1bc1f | ||
|
|
ceb45dd17b | ||
|
|
b0564a8aeb | ||
|
|
b7d89a9796 | ||
|
|
49a03bdde9 | ||
|
|
173e03a050 | ||
|
|
651e258de0 | ||
|
|
4e7b013ce6 | ||
|
|
60b60f20c9 | ||
|
|
f196ec8854 | ||
|
|
507dc44b1d | ||
|
|
36305db762 | ||
|
|
ea3f886597 | ||
|
|
6983380668 | ||
|
|
59343b5899 | ||
|
|
6df27b273d | ||
|
|
1a3076a162 | ||
|
|
e46d5c6658 | ||
|
|
0fdb10940e | ||
|
|
29b8788fc2 | ||
|
|
6996c60a41 | ||
|
|
7d5cfecb64 | ||
|
|
6fe4477ccb | ||
|
|
e349e13efe | ||
|
|
020da9d0be | ||
|
|
022e77d58d | ||
|
|
7280110c7b | ||
|
|
03ff26acc7 | ||
|
|
26356ce35f | ||
|
|
1ddf19c7f9 | ||
|
|
f9e05f5e56 | ||
|
|
001e5a0816 | ||
|
|
7e00d830a9 | ||
|
|
c1f992f21c | ||
|
|
510b4e295b | ||
|
|
eeb7669c2e | ||
|
|
2486ebbbd1 | ||
|
|
a1698b8eab | ||
|
|
274272111f | ||
|
|
a723bce18a | ||
|
|
5a0fcc7791 | ||
|
|
b4d2eda02c | ||
|
|
6360fba8f8 | ||
|
|
e7426f727e | ||
|
|
51ba6c16fc | ||
|
|
6749931450 | ||
|
|
43e09b00b6 | ||
|
|
bd11011d01 | ||
|
|
a31e3a2a1f | ||
|
|
6ae3d8ed01 | ||
|
|
e6c4d23402 | ||
|
|
f054663b17 | ||
|
|
78b990880c | ||
|
|
6a11c472ed | ||
|
|
c8d9dee46e | ||
|
|
e4417c3582 | ||
|
|
55d1bf353d | ||
|
|
3d0394e63b | ||
|
|
a00845ce4a | ||
|
|
57963c9c10 | ||
|
|
c13d2c2dfc | ||
|
|
26fda3944b | ||
|
|
bbad4dd5fa | ||
|
|
b411de7bf8 | ||
|
|
9664ac2a8b | ||
|
|
0bab0ae217 | ||
|
|
b5be73a15f | ||
|
|
1531fde198 | ||
| c94fa6ef17 | |||
| 3872cad2fb | |||
|
|
9c9d91c5d3 | ||
|
|
44d5dd65ba | ||
|
|
e577379519 | ||
|
|
5b0a9fd287 | ||
|
|
aa7e1cf72f | ||
|
|
bf9ce6e82e | ||
|
|
2403d82a7b | ||
|
|
225cd17cf5 | ||
|
|
6efec491c6 | ||
|
|
589f4ba29d | ||
|
|
11f0259b36 | ||
|
|
3d5a42658f | ||
|
|
8f104223df | ||
|
|
c713ff6ac0 | ||
|
|
2b11267b45 | ||
|
|
9fbeca08e1 | ||
|
|
b23abfb8fc | ||
|
|
e88a1d2a4d | ||
|
|
62be1bf1ce | ||
|
|
ab29e1e560 | ||
|
|
176f54023a | ||
|
|
c36d2409a7 | ||
|
|
333e015a53 | ||
|
|
d0ac53fa5e | ||
|
|
d2a1a6d113 | ||
|
|
d236a3c72c | ||
|
|
5474646fb2 | ||
|
|
8b75b3c773 | ||
|
|
36a7fa1b64 | ||
|
|
46dcba12ed | ||
|
|
6d3b09a7a2 | ||
|
|
e9aca5dacb | ||
|
|
6b8f420ad0 | ||
|
|
42753be69b | ||
|
|
1054080cf2 | ||
|
|
a6946803e4 | ||
|
|
3f3c44d432 | ||
|
|
f23b1880f5 | ||
|
|
1330930867 | ||
|
|
5c3e5cf1e6 | ||
|
|
d60da1bf63 | ||
|
|
26d423ff6b | ||
|
|
92254f4295 | ||
|
|
e59797e660 | ||
|
|
c7f6e72807 | ||
|
|
f4ceb163bd | ||
|
|
e3f2673919 | ||
|
|
4a1235a03f | ||
|
|
33dde2b44e | ||
|
|
3f63633b45 | ||
|
|
2b87eb86ef | ||
|
|
61f6ac56e0 | ||
|
|
cfc69c22b5 | ||
|
|
a20ed8aa18 | ||
|
|
b4fce55aee | ||
|
|
19fb2877d3 | ||
|
|
9906fc4bdf | ||
|
|
94620d6acb | ||
|
|
1442fd68f3 | ||
|
|
90e87046aa | ||
|
|
f088302b05 | ||
|
|
24839d9b04 | ||
|
|
ef028005bf | ||
|
|
240642803a | ||
|
|
efd11d1950 | ||
|
|
2f49575adb | ||
|
|
e7dc90907d | ||
|
|
56fbb54580 | ||
|
|
cf236deec0 | ||
|
|
594765dd4e | ||
|
|
17526f435c | ||
|
|
b649d8424a | ||
|
|
faf3d08164 | ||
|
|
e4281cfbab | ||
|
|
1b36b3efe1 | ||
|
|
336f576499 | ||
|
|
3e0da23b4d | ||
| acf09bb3d0 | |||
| 5ba101063a | |||
| 8f8fb0e5cb | |||
|
|
c1e56cd05c | ||
| 2126b5156e | |||
| 3d6a5bd9e7 | |||
| 2c8421d8ad | |||
| d226d6047f | |||
| 9a6a66d70f | |||
|
|
3c51725916 | ||
|
|
6eee69835d | ||
|
|
e3bb912d08 | ||
|
|
28ee1d1f1f | ||
|
|
5fa17238e0 | ||
|
|
2f044f1345 | ||
|
|
17afef63f2 | ||
|
|
8d62632945 | ||
|
|
35a72e8a97 | ||
|
|
9f3bcc20f9 | ||
|
|
a2a406fb7c | ||
| 293eea9e38 | |||
|
|
6de6fb1b56 | ||
| e04252f2e7 | |||
| 578d656f9d | |||
| c97e72d050 | |||
| ad54822383 | |||
| 243bd412e1 | |||
| 45b8d8b6b5 | |||
|
|
f7ed341263 | ||
| 9147fddb8e | |||
|
|
db23534e36 | ||
|
|
4efeecdaba | ||
|
|
59b53eb6f5 | ||
|
|
2980794645 | ||
|
|
f12556714a | ||
|
|
0f288ed720 | ||
|
|
e243f90b35 | ||
| 601abdce6d | |||
| b5f652ef04 | |||
| 671f48e77e | |||
| 174c222662 | |||
| d31113255e | |||
| a86fef2a21 | |||
|
|
2138599567 | ||
|
|
8d2ea460c8 | ||
|
|
054eae8718 | ||
|
|
c23116bca8 | ||
|
|
47ce7b24fa | ||
|
|
4a94d48ef7 | ||
|
|
d2afaa4586 | ||
|
|
140d893364 | ||
|
|
693945a471 | ||
|
|
f3fc28ff74 | ||
|
|
3480f8e827 | ||
|
|
de85a8d3f9 | ||
|
|
77221de703 | ||
|
|
26d5390e85 | ||
|
|
1ddea5c134 | ||
|
|
48adefca22 | ||
|
|
609a17f438 | ||
|
|
e37f831ce6 | ||
| b9413d540a | |||
| a52fb65c64 | |||
| 9ba86e86aa | |||
| 612d06bd53 | |||
|
|
a59e52bff5 | ||
|
|
5b5459f6e8 | ||
|
|
86bbb8578d | ||
|
|
0e19f45862 | ||
|
|
8f3d4e8e6b | ||
|
|
bcf1999347 | ||
|
|
ab80fafa67 | ||
|
|
0d3f85a2c4 | ||
|
|
6a9ea5a131 | ||
|
|
48419588c3 | ||
|
|
8aaa2ebb3c | ||
|
|
0ea55cbcb8 | ||
|
|
9158ce9ae2 | ||
| fdf2b5f7d2 | |||
|
|
ce332a407e | ||
| 8e98f58f6d | |||
|
|
fdb19bb671 | ||
|
|
b61f564d3b | ||
|
|
b805bc7a56 | ||
| 3c45b038c6 | |||
|
|
8c5fd29233 | ||
| ff828e6417 | |||
| 28b4239d08 | |||
| 5b951b5b4b | |||
| df4f40f6f4 | |||
| 595b562461 | |||
| daa4ff6308 | |||
| 2d0e8b6ec8 | |||
|
|
f7c85a4746 | ||
|
|
bcf27dbe12 | ||
|
|
8eef3808a4 | ||
|
|
49a946d0f1 | ||
|
|
64999e7f01 | ||
|
|
be7609baed | ||
|
|
d57988fbbc | ||
|
|
a034a8c4f5 | ||
|
|
cb99f4191f | ||
|
|
56cf29c626 | ||
|
|
f6ad9be8e4 | ||
|
|
f56992e8e8 | ||
|
|
6aa4baa2b4 | ||
|
|
f0f6aeaea1 | ||
|
|
31f3159991 | ||
|
|
bf0af85714 | ||
|
|
c4424b4235 | ||
|
|
17f0627262 | ||
|
|
eac4613cec | ||
|
|
c16ea7b05b | ||
|
|
4690ce4533 | ||
|
|
14bb8bf37d | ||
|
|
537ef93eb1 | ||
|
|
2160608a21 | ||
|
|
ccecd72dc0 | ||
|
|
25b9ce1076 | ||
|
|
0e7385a77a | ||
|
|
c79f0caa67 | ||
|
|
bbfb8583c7 | ||
|
|
b54598e9ba | ||
| 08372be34f | |||
| df55494c58 | |||
| b902656dd4 | |||
| a0a3622f16 | |||
| 78a588af0e | |||
|
|
558d0284e9 | ||
| e1e6db2b3c | |||
|
|
f66fbfd0fd | ||
|
|
d7128b4db2 | ||
|
|
15382478fa | ||
|
|
b21f7f7a89 | ||
|
|
b9567f1e54 | ||
|
|
3c256bbd30 | ||
|
|
a82c50fa59 | ||
|
|
505d2cd469 | ||
|
|
8b3c072c3c | ||
|
|
66e0a6d79d | ||
|
|
bf21c28ecd | ||
|
|
5642529cc4 | ||
|
|
06400a56ae | ||
|
|
d49c4f4658 | ||
| 7f1ace4dbe | |||
| d37944e081 | |||
| 358846ab04 | |||
| c3a2982154 | |||
| 1197a5c8c9 | |||
| 9e250bc07d | |||
| ec51ea6513 | |||
|
|
ab57b55e64 | ||
|
|
949f933f04 | ||
|
|
8e996cd09a | ||
|
|
bab97bfbe4 | ||
|
|
35db17fa50 | ||
|
|
c66b787006 | ||
|
|
00aa884a72 | ||
|
|
fc7b1ea150 | ||
|
|
0a5ffe6651 | ||
|
|
e7272fce53 | ||
|
|
b9c36e436d | ||
|
|
a8a608c5c5 | ||
|
|
ef92123e00 | ||
|
|
58da5d7942 | ||
| 838da762f8 | |||
| 9c96c07f3e | |||
| 9053a86eb0 | |||
| d5b079faa8 | |||
| 8228365d4b | |||
| a8b602bc54 | |||
| 0b59af6551 | |||
| 90490cb65d | |||
| af519732c4 | |||
| 868f5b1c38 | |||
| 55db8bf3f6 | |||
| 18a90516b3 | |||
|
|
5f3834d398 | ||
| 4dfac0846b | |||
| ffee32535e | |||
| 1b59135b4a | |||
|
|
8b5c0a2db1 | ||
|
|
dec1a9d77d | ||
|
|
b39d1c7322 | ||
|
|
19696e1ec1 | ||
|
|
d6a6836d90 | ||
|
|
53efb4e046 | ||
|
|
fa4379aef1 | ||
|
|
591e531ab6 | ||
|
|
c41a0c49b3 | ||
|
|
7c37590800 | ||
| e4e0697ea8 | |||
| ee831106b7 | |||
| 502f247d08 | |||
| ad96c8498d | |||
|
|
705598d66a | ||
| 3e52d6959b | |||
|
|
76bc6f5aee | ||
| 6c19e7e399 | |||
| b5ae626425 | |||
| d710da5c11 | |||
| 883fb2cb4a | |||
| 2a34ea8356 | |||
| 9e8af7367e | |||
| 106af9967a | |||
|
|
c8cad6ab79 | ||
|
|
d8139cb19e | ||
|
|
7a469b048e | ||
|
|
b23d6c9dbe | ||
| d23fdd6b4c | |||
| 49325d491d | |||
| 635b53c329 | |||
| 72d2137e9b | |||
| c06198491e | |||
| 9b69dad06b | |||
|
|
1a8406e0f7 | ||
|
|
509befc912 | ||
|
|
e73439d876 | ||
| 729aedee5f | |||
|
|
765d4b8563 | ||
| 6b0c931200 | |||
| dd39f3f244 | |||
| af4a285e5b | |||
| 166f378f2f | |||
|
|
9d037911d0 | ||
|
|
73baec8539 | ||
|
|
118872ab69 | ||
| f0ac96cab1 | |||
| 4545a98968 | |||
|
|
0ba6299655 | ||
|
|
e32e316146 | ||
| e940f51599 | |||
| 50d9080e26 | |||
|
|
6836b548af | ||
|
|
457e1d293a | ||
|
|
11807df8b3 | ||
|
|
96ff36c159 | ||
|
|
da782c07a4 | ||
|
|
b32399ac60 | ||
|
|
f1e36f7fd0 | ||
| 1cad02c461 | |||
|
|
b56563deee | ||
| 81d885c5a4 | |||
|
|
ad3039390d | ||
|
|
365e0f093d | ||
| 138be42aa5 | |||
| 8a385ffc32 | |||
| 995c736a71 | |||
| a9801766e5 | |||
| 6544e135b2 | |||
| a71b379ff8 | |||
| 2f880f7b5b | |||
| f698ef93e8 | |||
| bf0275ddcb | |||
| e68a3fb856 | |||
|
|
b9a6dfbcda | ||
|
|
28f790bbe7 | ||
|
|
1becd42695 | ||
|
|
d6f80a7b77 | ||
| bdbbe30c2c | |||
| b0ae907a86 | |||
| 4078c70caa | |||
|
|
fb6560db40 | ||
| 10aed35b08 | |||
|
|
cbf0239c23 | ||
|
|
a7c50fcfd9 | ||
|
|
a598d9019c | ||
|
|
a91d9bc68f | ||
| 355ca7b2f7 | |||
|
|
e963c3d3a2 | ||
|
|
3b4371ad4b | ||
|
|
2121b04f31 | ||
|
|
2420373389 | ||
|
|
235f6e0383 | ||
|
|
3f7d432f91 | ||
|
|
894e649be9 | ||
|
|
388767258a | ||
| f3307d6508 | |||
| c29a275969 | |||
| c890f61d0b | |||
| 1e0e24826e | |||
|
|
d6ea9cb0a4 | ||
| dc6fc0185c | |||
| 1d627371ce | |||
| d3b05f44d5 | |||
| 581b915748 | |||
| e293b25bb7 | |||
| 23005a82b1 | |||
| d47dcddb9b | |||
| cd621f2b4b | |||
| 7cbf74d3f2 | |||
| 7967653dd1 | |||
| ad060c5d5d | |||
|
|
9af33974eb | ||
|
|
fdbfa00d96 | ||
|
|
e844cf25c2 | ||
|
|
4df05f69b1 | ||
|
|
c52693d2ac | ||
|
|
8d2a75bc01 | ||
|
|
5fe654e7e8 | ||
|
|
cd741439d9 | ||
|
|
bdff919d3f | ||
| f4b49f7425 | |||
| db7e4a273b | |||
|
|
42f6b9e24b | ||
|
|
ad3ae84083 | ||
|
|
a4c9397db0 | ||
|
|
5380879aba | ||
|
|
eda1f8d640 | ||
|
|
88e98f0f67 | ||
|
|
2faa60ee59 | ||
|
|
b614d14037 | ||
|
|
d9bf826baf | ||
|
|
2152c5f6c9 | ||
|
|
0d23e37124 | ||
|
|
fddf32a6ca | ||
|
|
a07339e1ff | ||
|
|
c5a6c5d412 | ||
|
|
7e17ac989b | ||
|
|
7b5e331038 | ||
|
|
ae101f6cad | ||
|
|
98f559c9c0 | ||
|
|
d368bb8ae0 | ||
|
|
33932eb373 | ||
|
|
7d6e237183 | ||
|
|
056fb5ea88 | ||
|
|
92d013752a | ||
|
|
a9daf8fc8f | ||
|
|
c3aa84f961 | ||
|
|
b4524c67d5 | ||
|
|
11b69ee121 | ||
|
|
be3330d84f | ||
|
|
9439477799 | ||
|
|
826efdf767 | ||
|
|
7ef35e0284 | ||
|
|
0324bc4eec | ||
|
|
f157471ac1 | ||
|
|
416e7825a8 | ||
|
|
6b12d26388 | ||
|
|
0adac224fb | ||
|
|
6935d7361a | ||
|
|
05409bae6e | ||
|
|
283e915514 | ||
|
|
676d84a081 | ||
|
|
9f6e6cd54d | ||
|
|
dc51d694db | ||
|
|
0f63158f50 | ||
|
|
903d27ec0d | ||
|
|
5d9ed45cbd | ||
|
|
7c0ef0ab80 | ||
|
|
bda00e18fe | ||
|
|
49fb6c59d1 | ||
|
|
224aa5fd9c | ||
|
|
25e21ffb1e | ||
|
|
0165c6068a | ||
|
|
585339e0d4 | ||
|
|
83ae568d38 | ||
|
|
b323408cee | ||
|
|
71707bf0c0 | ||
|
|
7db5340159 | ||
|
|
dee2f2431c | ||
|
|
0b0fa04210 | ||
|
|
18374fe426 | ||
|
|
ab432e14ee | ||
|
|
5d9a7b9452 | ||
|
|
e9af7406c6 | ||
| 20e2e20212 | |||
|
|
7897df5544 | ||
|
|
0e9f0a741e | ||
| 31ff9b2747 | |||
| dcd915457b | |||
| 454a5c5286 | |||
|
|
168a4ca6f9 | ||
|
|
6e48ecb9ce | ||
| 959e57e755 | |||
| 0e53445e91 | |||
| 3ee85fed30 | |||
| 010a1fde3f | |||
|
|
c2349662e7 | ||
|
|
43c7cc0893 | ||
|
|
d64a2b0306 | ||
| 1bc563832e | |||
| 1f48c6c03d | |||
|
|
50aca88438 | ||
|
|
be5bd43194 | ||
|
|
8894aa7d38 | ||
|
|
092d29fe56 | ||
|
|
8593931171 | ||
|
|
77e0cb94d3 | ||
| 2b2c3cf118 | |||
| be308a0444 | |||
|
|
9b39f02ce5 | ||
|
|
7109061ee0 | ||
|
|
f71dd25b3c | ||
|
|
012fbe3a45 | ||
|
|
853f941d88 | ||
|
|
c03abb50d3 | ||
|
|
94bc3c3503 | ||
|
|
252473d7cf | ||
|
|
6eb8cbfacc | ||
| ddc86b54c7 | |||
| 3678aa157e | |||
|
|
4d7a77d318 | ||
| 8d13bcbac0 | |||
| 2f54ec61bd | |||
|
|
af541662f3 | ||
|
|
3e2c530281 | ||
|
|
ff5b7950f1 | ||
|
|
148ce25af7 | ||
|
|
56ef384595 | ||
|
|
ea24f3ba6d | ||
| ba4f6608e4 | |||
| 07aa07981d | |||
| 0afabbd609 | |||
| 2ea3f9b9bb | |||
|
|
35a8c703a7 | ||
|
|
d0855ee892 | ||
|
|
e95b41511a | ||
| 30a5c4907b | |||
| f170baab3f | |||
| 643687472a | |||
| c76058efc3 | |||
| 502cf72653 | |||
| 9baa6e7bc8 | |||
| 7efb0fa7ed | |||
| 571ad83e03 | |||
| 983e7683fd | |||
| b9af4a8cf0 | |||
|
|
f239d4f350 | ||
|
|
4d77f62e38 | ||
| ac3e6e27dc | |||
| 4f9c442d55 | |||
| adab0cc3d3 | |||
| b1f1725506 | |||
| 4bb902a8b9 | |||
| ed399a07d8 | |||
|
|
4db38b9ba5 | ||
|
|
22934545eb | ||
|
|
7fe6b35359 | ||
|
|
cfd2ad9a1c | ||
|
|
74a30aeee4 | ||
|
|
1bff62e3c7 | ||
|
|
31032cd794 | ||
|
|
8ccad29353 | ||
|
|
7ff873bbc9 | ||
|
|
a55a464694 | ||
|
|
c14760c294 | ||
|
|
e6bf1af982 | ||
|
|
3998ce311f | ||
|
|
e6c45ae5f8 | ||
|
|
e8abe14395 | ||
| 466128c179 | |||
| 8c7a39f00a | |||
|
|
1400c4d4d0 | ||
| 24dd78394b | |||
| 9afff4f80a | |||
| 1aba324481 | |||
|
|
3daa6b964d | ||
|
|
8cda5104e3 | ||
|
|
8db45ffaa1 | ||
|
|
b41e714a1b | ||
|
|
6cd645b34b | ||
| 772c1d4fb8 | |||
|
|
5a782cca4d | ||
| 647b8f7fa1 | |||
| 798bdf32c1 | |||
| fbc20da606 | |||
|
|
0820b2c13f | ||
|
|
5f2361fe7c | ||
|
|
6e6142a91f | ||
| 500f6cf896 | |||
| a23ea5e5f1 | |||
| 86a7d68f08 | |||
| 373fce2988 | |||
| 8ac5d5d5fc | |||
| 3841e92d53 | |||
|
|
e5f53ed5e9 | ||
| 5ef9d46d0b | |||
| 5389ee056a | |||
| e2b7f93d11 | |||
| 09d1eb17d4 | |||
| 895c9a33a9 | |||
| ab91d35331 | |||
| 2d601099f3 | |||
| 48ccf85e97 | |||
| 6cf7bc7985 | |||
| 3d9b6061ce | |||
| 042cde2952 | |||
| 3b2aa946af | |||
| a687180d98 | |||
| b1fd835f56 | |||
| 53847dc3ad | |||
| ec0c678cc9 | |||
| b83184e895 | |||
| f0f1a6f529 | |||
| 333f80680e | |||
| 3489512a54 | |||
| 6100c799b7 | |||
| 4a96e88118 | |||
| ed4c09b456 | |||
| 939dcee537 | |||
| 7424fdd623 | |||
| 4456048e79 | |||
| 4c31c70298 | |||
| c10a4f51ba | |||
| 53dd0c7655 | |||
| 6f449aa4f6 | |||
| 171e3abe34 | |||
| 2bffeea7eb | |||
|
|
90288202e5 | ||
|
|
a4d24c61ba | ||
|
|
3075593767 | ||
|
|
8ab134ffe5 | ||
|
|
4800e73a4a | ||
|
|
3bd97646a9 | ||
|
|
059e4176a1 | ||
|
|
57b627fb71 | ||
|
|
5281862932 | ||
|
|
0fe2313754 | ||
|
|
f62dfb0abf | ||
|
|
7507282886 | ||
|
|
9db5dd36b9 | ||
|
|
9ce54d803f | ||
|
|
7e7d36f0d6 | ||
|
|
82323cd806 | ||
|
|
511182f148 | ||
|
|
e9b1db7ac7 | ||
|
|
9795334f12 | ||
|
|
321d88e795 | ||
|
|
99d2e2d0d0 | ||
|
|
3fb1fbe1b3 | ||
|
|
d4f3304397 | ||
|
|
4865373b4f | ||
|
|
5378b0ad56 | ||
|
|
7de357df98 | ||
|
|
27808012d0 | ||
|
|
dc22949b47 | ||
|
|
3d7ad215d9 | ||
|
|
67994d7e99 | ||
|
|
deab6b40e0 | ||
|
|
0205fa6385 | ||
|
|
69b3343f45 | ||
|
|
9c5b8419a5 | ||
|
|
fedf6d7537 | ||
|
|
21306dbf5d | ||
|
|
9b69f2266a | ||
| 161f86b6bb | |||
|
|
7f24dc5f03 | ||
|
|
6423c92b84 | ||
|
|
f7ea958961 | ||
|
|
8262048edc | ||
|
|
73d956462f | ||
|
|
db0bc1a618 | ||
| 91bcf947df | |||
|
|
c035ef6eb7 | ||
|
|
4f31691c8a | ||
| 2244142bd8 | |||
| 4323036992 | |||
|
|
8eeabe4409 | ||
|
|
6add722a25 | ||
|
|
87abc1d6b4 | ||
|
|
6ddac9a478 | ||
|
|
38169b6d70 | ||
| 8cc561775b | |||
| 0634154b28 | |||
|
|
c08f79f71e | ||
|
|
7532276a00 | ||
|
|
ee29074a30 | ||
| 870ace55e2 | |||
| 05ac2594b6 | |||
| 8353b623da | |||
| c19af4dbcf | |||
|
|
ad7447f8ae | ||
|
|
4f827a5b1d | ||
| be75455b84 | |||
|
|
96d3f67436 | ||
|
|
906bce637a | ||
|
|
3c0d0dfeee | ||
|
|
2ca5802e4d | ||
|
|
f4b06e586e | ||
| 525806d776 | |||
| 01ef6baa53 | |||
| ed04747517 | |||
|
|
e13d336f2f | ||
| 5accdd24fc | |||
|
|
5c61c28772 | ||
| 3a767d84df | |||
| d04ce7a2b7 | |||
| a1524241cb | |||
|
|
b312fdeac1 | ||
|
|
30c8cf7b96 | ||
| 50bda941ad | |||
| fc6306575d | |||
|
|
c43ca7de87 | ||
|
|
826951536b | ||
| 5f52edf831 | |||
| 29bc53d618 | |||
| 378c855902 | |||
|
|
546a588aa5 | ||
|
|
6e517c4a19 | ||
|
|
30d3f52f30 | ||
| 5dee6cb3d5 | |||
| 2a96c9a145 | |||
| 6435514e0d | |||
| cd7a41924d | |||
| b9452546c5 | |||
| 4fa89d5e86 | |||
| 63ece7e1aa | |||
|
|
4808055054 | ||
| 115016e75e | |||
|
|
ee09bfac66 | ||
| 530a3fcd10 | |||
| 0010229363 | |||
| d241afcbd8 | |||
| 615257831c | |||
| b9b737f4ce | |||
|
|
36a032d249 | ||
| 726079e0bb | |||
| 66421858e7 | |||
| df7b3dd861 | |||
| 22ea79a4ff | |||
| 2025fe7c20 | |||
| a11c529557 | |||
| edaa2fba16 | |||
|
|
110f01a55b | ||
|
|
ada39a970e | ||
| 8c2641703c | |||
| 9fcb07250d | |||
| 47f39d0766 | |||
| bcba5af8a9 | |||
| aec1b3aeef | |||
| a979ed770d | |||
| 5485568764 | |||
| 6f3597cc83 | |||
| 1202d09966 | |||
| 266eb8307c | |||
| 8843cc2948 | |||
|
|
2c4acce0f3 | ||
|
|
87beb2ea1a | ||
|
|
9c5b7760ba | ||
|
|
6964786552 | ||
|
|
d755756ee5 | ||
|
|
fbc970e4a7 | ||
| 364b905790 | |||
| 34a1085604 | |||
| c460ac8ade | |||
| 49a09f61a2 | |||
| 08b4563f49 | |||
|
|
026f62f183 | ||
|
|
2307ac5a22 | ||
|
|
2b186421a7 | ||
|
|
1dc471e07e | ||
|
|
db1fb7ccf7 | ||
|
|
e5071a3b7c | ||
|
|
e6bfe0c10b | ||
|
|
919a97d4c8 | ||
|
|
61216b638d | ||
|
|
e065d2b01e | ||
| 746bad3c30 | |||
| 5bbd61b75c | |||
| 27eb7e46d0 | |||
| c20bef3731 | |||
|
|
d5aaff7f06 | ||
|
|
fc60768a66 | ||
| 8ef1dac95b | |||
| 2024fb1b65 | |||
| 617ca79b8f | |||
|
|
5081755d0b | ||
| 7bfb5b1bf4 | |||
|
|
8d73529fa4 | ||
|
|
a19d04d3ba | ||
|
|
a9c7748a52 | ||
| 41e4e952b7 | |||
| c0b0b5e4c5 | |||
|
|
e424479e7e | ||
|
|
a8804f3093 | ||
|
|
6479a24bb7 | ||
|
|
8b372ad306 | ||
|
|
86791a2f1b | ||
| 7cf0aad388 | |||
| c5ea51beec | |||
| 7cc8e51d73 | |||
| 75ba58d68c | |||
| cd35b219db | |||
| 4a863f8ce7 | |||
| 24264548a6 | |||
| f9e4b1a56b | |||
| 13b54b864e | |||
|
|
05d45fe945 | ||
|
|
2781f6035a | ||
| dc3378d084 | |||
| 9132e94143 | |||
|
|
b61e2aa73c | ||
|
|
7fdbae3b0f | ||
|
|
4dc6fc3b5d | ||
|
|
618275b1f9 | ||
|
|
7a1111d845 | ||
| 3af356840e | |||
| 911a278926 | |||
|
|
014063700f | ||
|
|
f7c0db0454 | ||
| a534d25d82 | |||
| bcf3e4a2d3 | |||
|
|
0cdfbd9803 | ||
|
|
a3e5654d86 | ||
|
|
2f9364db2b | ||
| 5d7c9ebf82 | |||
| 48da98d0e4 | |||
|
|
55e994ac3c | ||
|
|
6d46a21f9f | ||
|
|
fdc83484fd | ||
| 6786817fff | |||
| b77cd0db15 | |||
| 20bef76878 | |||
| 7a30490482 | |||
| 3bc2e469cc | |||
| d770d3c6da | |||
| 339a001592 | |||
| ace57a96a9 | |||
| 2c26b8d422 | |||
| e1eafa2394 | |||
| 39cb51c9eb | |||
| fa5016ab04 | |||
| b134ef1a74 | |||
| 234dff888d | |||
| a08c15a3ee | |||
| cfa894e7b6 | |||
| d6c8e64575 | |||
| dc91580e30 | |||
| 7ec1dd7a98 | |||
| 82f3f7506c | |||
| e26df1c26b | |||
| aea77cf225 | |||
| a1e3ef9c5c | |||
| 7aece71342 | |||
| bdbde54f04 | |||
| 157e035710 | |||
| 97d9a3a4e5 | |||
| cb7f111ade | |||
| 35f670706a | |||
| 3fac888fe5 | |||
| d843646b4f | |||
| c2b73d9fb5 | |||
|
|
9da95b8165 | ||
| 5bcd8efe14 | |||
|
|
027a1d748d | ||
|
|
6d6e012c19 | ||
| a8db6b007f | |||
| f3576e54c6 | |||
| 0325f6c4d2 | |||
| 8225ce063a | |||
| c2c379c994 | |||
| 7344c2af47 | |||
| f484ea8c64 | |||
| ac790492eb | |||
| 9ac5fb4890 | |||
| 2baee7413c | |||
|
|
16bec08f09 | ||
|
|
afb3c76922 | ||
|
|
2f526b35e8 | ||
|
|
2dc32aa310 | ||
| 10e669165a | |||
| b6cb90d731 | |||
| 949a8901fb | |||
|
|
d608f450af | ||
|
|
e0a1341901 | ||
|
|
2cfb03f17d | ||
|
|
4dd0f51da4 | ||
|
|
d65a3e54a2 | ||
|
|
c212eacf19 | ||
| 46f341d782 | |||
| ab4ff5548d | |||
| d4713e1e6c | |||
| 8a3a1466c9 | |||
| a5af9dc973 | |||
| 049a083e42 | |||
| 9683a110d6 | |||
| c44698f61a | |||
| 106bccda0e | |||
| b1aafa5aaf | |||
| e2e43cd534 | |||
| 43b4fe8300 | |||
| 7c3e4fd947 | |||
| 9916ef4d3d | |||
| ad4da54bc4 | |||
|
|
2415c5a38b | ||
|
|
ecbc0a2a2d | ||
| 10d37a9be5 | |||
| 590d2e4639 | |||
| 806a07acc5 | |||
| 8258cdd6cf | |||
|
|
2fcee6e87a | ||
| 04efbc8819 | |||
| 1fc288917a | |||
| 27587e83c8 | |||
| a0d6741ff5 | |||
| 63562ddd48 | |||
| aac96b15b0 | |||
| 0f502b4229 | |||
| a916f970b1 | |||
| 7f2d0acc3b | |||
| 8b6751f419 | |||
| 70e9cf5838 | |||
| 24020302fd | |||
| d7e2f39d25 | |||
| 89d0d7b266 | |||
| e3c222b5c1 | |||
|
|
a199015bc9 | ||
| 23617f7b30 | |||
| f5f02236df | |||
| ad76bc378c | |||
| 570d8d15af | |||
| e254c5f31a | |||
| 2a92be5946 | |||
| caab860351 | |||
|
|
32861b7ce9 | ||
|
|
a08802c4b6 | ||
|
|
6b51be6fae | ||
| 5b5c975884 | |||
| 605dfec5e7 | |||
| 71aa0cfba7 | |||
| 55e45c4274 | |||
| 8c880b2518 | |||
| c43b16cfbb | |||
| 394903a8f0 | |||
| e31c0636ab | |||
| e9fe80f8e5 | |||
| 7671550a9f | |||
| 83d24fa90d | |||
| 3e2956da3f | |||
| cf3fd01012 | |||
| 319071c73b | |||
| ab8d9bb79b | |||
| 25eaf8cad7 | |||
| c8f97d9c73 | |||
| d3f1fe1c30 | |||
| 5fb01a01af | |||
| 617d1cd648 | |||
| f672193fcf | |||
| d3c490e9d7 | |||
| 2e8fd23f2b | |||
| 3eef403b04 | |||
| f3b78fc82f | |||
| 80654b2732 | |||
| 05597ae914 | |||
| 0f1eb464e8 | |||
| 8745ffd42f | |||
| db99e98658 | |||
| 9f1a663f63 | |||
| db0b3da446 | |||
| 5d22d41201 | |||
| b397de1d54 | |||
|
|
4bda79b263 | ||
|
|
697a9dbd06 | ||
|
|
accaabcfde | ||
| 16a14d4ebd | |||
| c151352910 | |||
| b80f70fc54 | |||
| 939cdb019b | |||
| fde36fe238 | |||
| 40e820cabb | |||
| d79387bd92 | |||
| 05f2a62cbb | |||
| 78a965dc90 | |||
| 5b603d6627 | |||
| e93d2046d3 | |||
| 995871db8a | |||
| 501535f196 | |||
| 91e854e99c | |||
| 9b05d8e774 | |||
| e70793c3bc | |||
| abb2603bea |
@@ -27,6 +27,7 @@ run I2P for the first time.
|
||||
To run I2P explicitly:
|
||||
(*nix): sh i2prouter start
|
||||
(win*): I2P.exe
|
||||
(Platforms unsupported by the wrapper - PPC, ARM, etc): sh runplain.sh
|
||||
|
||||
To stop the router (gracefully):
|
||||
lynx http://localhost:7657/configservice.jsp ("Shutdown gracefully")
|
||||
|
||||
@@ -2,6 +2,7 @@ I2P source installation instructions
|
||||
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
|
||||
The SDK must have Pack200 support (java.util.jar.Pack200)
|
||||
Apache Ant 1.7.0 or higher
|
||||
Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
|
||||
13
LICENSE.txt
@@ -64,6 +64,13 @@ Public domain except as listed below:
|
||||
Copyright 2006 Gregory Rubin grrubin@gmail.com
|
||||
See licenses/LICENSE-HashCash.txt
|
||||
|
||||
GettextResource from gettext v0.18:
|
||||
Copyright (C) 2001, 2007 Free Software Foundation, Inc.
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
SSLEepGet:
|
||||
Contains some code Copyright 2006 Sun Microsystems, Inc.
|
||||
See licenses/LICENSE-InstallCert.txt
|
||||
|
||||
|
||||
Router:
|
||||
@@ -139,6 +146,7 @@ Applications:
|
||||
I2PSnark:
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
Silk icons: See licenses/LICENSE-SilkIcons.txt
|
||||
|
||||
I2PTunnel:
|
||||
(c) 2003 - 2004 mihi
|
||||
@@ -176,6 +184,7 @@ Applications:
|
||||
Router console:
|
||||
Public domain.
|
||||
Flag icons: public domain, courtesy mjames@gmail.com http://www.famfamfam.com/
|
||||
Silk icons: See licenses/LICENSE-SilkIcons.txt
|
||||
|
||||
GeoIP Data:
|
||||
Copyright (c) 2003 Direct Information Pvt. Ltd. All Rights Reserved.
|
||||
@@ -231,3 +240,7 @@ distributions. See the source package for the additional license information.
|
||||
|
||||
SAM Python Library:
|
||||
Public domain.
|
||||
|
||||
I2PSnark/Console themes:
|
||||
"Man with hat over face" & related images licensed under a Creative Commons 2.0 license.
|
||||
Original photos by Florian Kuhlmann. http://www.flickr.com/photos/floriankuhlmann/3117758155
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
|
||||
The SDK must have Pack200 support (java.util.jar.Pack200)
|
||||
Apache Ant 1.7.0 or higher
|
||||
Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
|
||||
@@ -21,7 +21,7 @@ CWD=$(pwd)
|
||||
TMP=/tmp
|
||||
PKG=/$TMP/package-base-i2p
|
||||
NAME=i2p-base
|
||||
VERSION=0.0.2
|
||||
VERSION=0.0.3
|
||||
BUILD=1sponge
|
||||
ARCH=noarch
|
||||
INSTALL_DIR=opt
|
||||
|
||||
@@ -2,15 +2,32 @@
|
||||
# Start/stop i2p service.
|
||||
|
||||
i2p_start() {
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory start )"
|
||||
# Check if router is up first!
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory status )" > /dev/null
|
||||
if [ $? -eq 0 ] ; then {
|
||||
# I2p is already running, so tell the user.
|
||||
echo "I2P is already running..."
|
||||
i2p_status
|
||||
}
|
||||
else
|
||||
{
|
||||
# Just in-case there are leftover junk in /tmp...
|
||||
rm -Rf `grep /tmp/hsperfdata_root/* -le i2p` /tmp/i2p-*.tmp /tmp/router.ping
|
||||
# Now that all junk is cleaned up, start.
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory start )"
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
i2p_stop() {
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory stop )"
|
||||
rm -Rf `grep /tmp/hsperfdata_root/* -le i2p` /tmp/i2p-*.tmp /tmp/router.ping
|
||||
}
|
||||
|
||||
i2p_restart() {
|
||||
/bin/su - -c "( export PATH=\"$PATH:/usr/lib/java/bin:/usr/lib/java/jre/bin\"; directory restart)"
|
||||
# We want a FULL cycle here, not the wrappers idea of this!
|
||||
i2p_stop
|
||||
i2p_start
|
||||
}
|
||||
|
||||
i2p_status() {
|
||||
|
||||
@@ -85,23 +85,26 @@ cp -a ../i2p $PKG/$INSTALL_DIR/
|
||||
mkdir -p $PKG/install
|
||||
|
||||
#############################################################################
|
||||
# Preconfigureation to make package smaller
|
||||
# Preconfigureation to make package smaller, and...
|
||||
# we keep as much as reasonable in the installation directory.
|
||||
# This makes the install map fairly well to the standard installation.
|
||||
# It also makes it easier to find the log and pid files!
|
||||
#############################################################################
|
||||
cd $PKG/$INSTALL_DIR/i2p
|
||||
|
||||
# wrapper.config $INSTALL_PATH and $SYSTEM_java_io_tmpdir
|
||||
sed "s|\$INSTALL_PATH|$INSTALL_DIR/i2p|g" wrapper.config > a
|
||||
sed "s|\$SYSTEM_java_io_tmpdir|/var/tmp|g" a > wrapper.config
|
||||
sed "s|\$INSTALL_PATH|/$INSTALL_DIR/i2p|g" wrapper.config > a
|
||||
sed "s|\$SYSTEM_java_io_tmpdir|/$INSTALL_DIR/i2p|g" a > wrapper.config
|
||||
# eepget %INSTALL_PATH
|
||||
sed "s|\$INSTALL_PATH|$INSTALL_DIR/i2p|g" eepget > a
|
||||
sed "s|\$INSTALL_PATH|/$INSTALL_DIR/i2p|g" eepget > a
|
||||
rm eepget
|
||||
mv a eepget
|
||||
# runplain.sh %INSTALL_PATH and %SYSTEM_java_io_tmpdir
|
||||
sed "s|%INSTALL_PATH|$INSTALL_DIR/i2p|g" runplain.sh > a
|
||||
sed "s|%SYSTEM_java_io_tmpdir|/var/tmp|g" a > runplain.sh
|
||||
sed "s|%INSTALL_PATH|/$INSTALL_DIR/i2p|g" runplain.sh > a
|
||||
sed "s|%SYSTEM_java_io_tmpdir|/$INSTALL_DIR/i2p|g" a > runplain.sh
|
||||
# i2prouter %INSTALL_PATH and %SYSTEM_java_io_tmpdir
|
||||
sed "s|%INSTALL_PATH|$INSTALL_DIR/i2p|g" i2prouter > a
|
||||
sed "s|%SYSTEM_java_io_tmpdir|/var/tmp|g" a > i2prouter
|
||||
sed "s|%INSTALL_PATH|/$INSTALL_DIR/i2p|g" i2prouter > a
|
||||
sed "s|%SYSTEM_java_io_tmpdir|/$INSTALL_DIR/i2p|g" a > i2prouter
|
||||
|
||||
chmod 744 ./i2prouter
|
||||
chmod 744 ./osid
|
||||
|
||||
@@ -69,7 +69,7 @@ public class I2PAndroid extends Activity
|
||||
|
||||
// from routerconsole ContextHelper
|
||||
List contexts = RouterContext.listContexts();
|
||||
if ( (contexts == null) || (contexts.size() <= 0) )
|
||||
if ( (contexts == null) || (contexts.isEmpty()) )
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
RouterContext ctx = (RouterContext)contexts.get(0);
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
compile.on.save=false
|
||||
do.depend=false
|
||||
do.jar=true
|
||||
javac.debug=true
|
||||
javadoc.preview=true
|
||||
jaxbwiz.endorsed.dirs=/usr/local/netbeans-6.8/ide12/modules/ext/jaxb/api
|
||||
jaxws.endorsed.dir=/usr/local/netbeans-6.5/java2/modules/ext/jaxws21/api:/usr/local/netbeans-6.5/ide10/modules/ext/jaxb/api
|
||||
user.properties.file=/root/.netbeans/6.5/build.properties
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
||||
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
|
||||
</project-private>
|
||||
@@ -34,7 +34,9 @@ import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.Log;
|
||||
// needed only for debugging.
|
||||
// import java.util.logging.Level;
|
||||
@@ -50,7 +52,7 @@ public class DoCMDS implements Runnable {
|
||||
|
||||
// FIX ME
|
||||
// I need a better way to do versioning, but this will do for now.
|
||||
public static final String BMAJ = "00", BMIN = "00", BREV = "0B", BEXT = "";
|
||||
public static final String BMAJ = "00", BMIN = "00", BREV = "0E", BEXT = "";
|
||||
public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
|
||||
private Socket server;
|
||||
private Properties props;
|
||||
@@ -86,6 +88,7 @@ public class DoCMDS implements Runnable {
|
||||
private static final String C_inhost = "inhost";
|
||||
private static final String C_inport = "inport";
|
||||
private static final String C_list = "list";
|
||||
private static final String C_lookup = "lookup";
|
||||
private static final String C_newkeys = "newkeys";
|
||||
private static final String C_option = "option";
|
||||
private static final String C_outhost = "outhost";
|
||||
@@ -113,6 +116,7 @@ public class DoCMDS implements Runnable {
|
||||
{C_inhost, C_inhost + " hostname | IP * Set the inbound hostname or IP."},
|
||||
{C_inport, C_inport + " port_number * Set the inbound port number nickname listens on."},
|
||||
{C_list, C_list + " * List all tunnels."},
|
||||
{C_lookup, C_lookup + " * Lookup an i2p address."},
|
||||
{C_newkeys, C_newkeys + " * Generate a new keypair for the current nickname."},
|
||||
{C_option, C_option + " I2CPoption=something * Set an I2CP option. NOTE: Don't use any spaces."},
|
||||
{C_outhost, C_outhost + " hostname | IP * Set the outbound hostname or IP."},
|
||||
@@ -138,6 +142,7 @@ public class DoCMDS implements Runnable {
|
||||
C_inhost + " " +
|
||||
C_inport + " " +
|
||||
C_list + " " +
|
||||
C_lookup + " " +
|
||||
C_newkeys + " " +
|
||||
C_option + " " +
|
||||
C_outhost + " " +
|
||||
@@ -446,6 +451,25 @@ public class DoCMDS implements Runnable {
|
||||
} else if (Command.equals(C_visit)) {
|
||||
visitAllThreads();
|
||||
out.println("OK ");
|
||||
} else if (Command.equals(C_lookup)) {
|
||||
Destination dest = null;
|
||||
String reply = null;
|
||||
if (Arg.endsWith(".i2p")) {
|
||||
try {
|
||||
try {
|
||||
dest = I2PTunnel.destFromName(Arg);
|
||||
} catch (DataFormatException ex) {
|
||||
}
|
||||
reply = dest.toBase64();
|
||||
} catch (NullPointerException npe) {
|
||||
// Could not find the destination!?
|
||||
}
|
||||
}
|
||||
if (reply == null) {
|
||||
out.println("ERROR Address Not found.");
|
||||
} else {
|
||||
out.println("OK " + reply);
|
||||
}
|
||||
} else if (Command.equals(C_getdest)) {
|
||||
if (ns) {
|
||||
if (dk) {
|
||||
|
||||
@@ -311,6 +311,19 @@ public class MUXlisten implements Runnable {
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
|
||||
// Hopefully nuke stuff here...
|
||||
{
|
||||
String boner = tg.getName();
|
||||
try {
|
||||
_log.warn("destroySocketManager " + boner);
|
||||
socketManager.destroySocketManager();
|
||||
_log.warn("destroySocketManager Successful" + boner);
|
||||
} catch (Exception e) {
|
||||
// nop
|
||||
_log.warn("destroySocketManager Failed" + boner);
|
||||
_log.warn(e.toString());
|
||||
}
|
||||
}
|
||||
// zero out everything.
|
||||
try {
|
||||
wlock();
|
||||
|
||||
@@ -95,10 +95,14 @@ public class TCPio implements Runnable {
|
||||
if (b > 0) {
|
||||
Aout.write(a, 0, b);
|
||||
} else if (b == 0) {
|
||||
Thread.yield(); // this should act like a mini sleep.
|
||||
if (Ain.available() == 0) {
|
||||
Thread.sleep(10);
|
||||
// Will this die? We'll see.
|
||||
while(Ain.available() == 0) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
// Thread.yield(); // this should act like a mini sleep.
|
||||
// if (Ain.available() == 0) {
|
||||
// Thread.sleep(10);
|
||||
// }
|
||||
} else {
|
||||
/* according to the specs:
|
||||
*
|
||||
|
||||
@@ -38,7 +38,7 @@ import net.i2p.util.EepGet;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
class AddressBook {
|
||||
|
||||
private String location;
|
||||
|
||||
@@ -88,6 +88,8 @@ public class AddressBook {
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
* Set a maximum size of the remote book to make it a little harder for a malicious book-sender.
|
||||
*
|
||||
* Yes, the EepGet fetch() is done in this constructor.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
* @param proxyHost hostname of proxy
|
||||
@@ -102,6 +104,7 @@ public class AddressBook {
|
||||
if (get.fetch()) {
|
||||
subscription.setEtag(get.getETag());
|
||||
subscription.setLastModified(get.getLastModified());
|
||||
subscription.setLastFetched(I2PAppContext.getGlobalContext().clock().now());
|
||||
}
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(tmp);
|
||||
@@ -196,6 +199,8 @@ public class AddressBook {
|
||||
// null cert ends with AAAA but other zero-length certs would be AA
|
||||
((dest.length() == MIN_DEST_LENGTH && dest.endsWith("AA")) ||
|
||||
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
|
||||
// B64 comes in groups of 2, 3, or 4 chars, but never 1
|
||||
((dest.length() % 4) != 1) &&
|
||||
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
||||
;
|
||||
}
|
||||
@@ -253,6 +258,7 @@ public class AddressBook {
|
||||
try {
|
||||
ConfigParser.write(this.addresses, file);
|
||||
} catch (IOException exp) {
|
||||
System.err.println("Error writing addressbook " + file.getAbsolutePath() + " : " + exp.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringReader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -35,6 +35,9 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
* format, and subscription file format.
|
||||
@@ -44,7 +47,9 @@ import java.util.Map;
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class ConfigParser {
|
||||
class ConfigParser {
|
||||
|
||||
private static final boolean isWindows = System.getProperty("os.name").startsWith("Win");
|
||||
|
||||
/**
|
||||
* Strip the comments from a String. Lines that begin with '#' and ';' are
|
||||
@@ -140,7 +145,7 @@ public class ConfigParser {
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param map
|
||||
* A Map to use as the default, if file fails.
|
||||
* A Map containing values to use as defaults.
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
@@ -148,6 +153,11 @@ public class ConfigParser {
|
||||
Map result;
|
||||
try {
|
||||
result = ConfigParser.parse(file);
|
||||
for (Iterator iter = map.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String) iter.next();
|
||||
if (!result.containsKey(key))
|
||||
result.put(key, map.get(key));
|
||||
}
|
||||
} catch (IOException exp) {
|
||||
result = map;
|
||||
try {
|
||||
@@ -267,7 +277,10 @@ public class ConfigParser {
|
||||
/**
|
||||
* Write contents of Map map to the File file. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* Write to a temp file in the same directory and then rename, to not corrupt
|
||||
* simultaneous accesses by the router. Except on Windows where renameTo()
|
||||
* will fail if the target exists.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to file.
|
||||
* @param file
|
||||
@@ -276,8 +289,22 @@ public class ConfigParser {
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, File file) throws IOException {
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new FileWriter(file, false)));
|
||||
boolean success = false;
|
||||
if (!isWindows) {
|
||||
File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile());
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8")));
|
||||
success = tmp.renameTo(file);
|
||||
if (!success) {
|
||||
tmp.delete();
|
||||
//System.out.println("Warning: addressbook rename fail from " + tmp + " to " + file);
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
// hmm, that didn't work, try it the old way
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,7 +343,7 @@ public class ConfigParser {
|
||||
public static void writeSubscriptions(List list, File file)
|
||||
throws IOException {
|
||||
ConfigParser.writeSubscriptions(list, new BufferedWriter(
|
||||
new FileWriter(file, false)));
|
||||
new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
@@ -37,8 +38,9 @@ import net.i2p.I2PAppContext;
|
||||
*
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
public static final String VERSION = "2.0.4";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
private boolean _running;
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
@@ -64,6 +66,7 @@ public class Daemon {
|
||||
router.merge(master, true, null);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
// yes, the EepGet fetch() is done in next()
|
||||
router.merge((AddressBook) iter.next(), false, log);
|
||||
}
|
||||
router.write();
|
||||
@@ -95,6 +98,15 @@ public class Daemon {
|
||||
File etagsFile = new File(home, (String) settings.get("etags"));
|
||||
File lastModifiedFile = new File(home, (String) settings
|
||||
.get("last_modified"));
|
||||
File lastFetchedFile = new File(home, (String) settings
|
||||
.get("last_fetched"));
|
||||
long delay;
|
||||
try {
|
||||
delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
} catch (NumberFormatException nfe) {
|
||||
delay = 12;
|
||||
}
|
||||
delay *= 60 * 60 * 1000;
|
||||
|
||||
AddressBook master = new AddressBook(masterFile);
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
@@ -104,7 +116,7 @@ public class Daemon {
|
||||
defaultSubs.add("http://www.i2p2.i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
etagsFile, lastModifiedFile, lastFetchedFile, delay, defaultSubs, (String) settings
|
||||
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
@@ -126,14 +138,15 @@ public class Daemon {
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
_running = true;
|
||||
String settingsLocation = "config.txt";
|
||||
File homeFile;
|
||||
if (args.length > 0) {
|
||||
homeFile = new File(args[0]);
|
||||
homeFile = new SecureDirectory(args[0]);
|
||||
if (!homeFile.isAbsolute())
|
||||
homeFile = new File(I2PAppContext.getGlobalContext().getRouterDir(), args[0]);
|
||||
homeFile = new SecureDirectory(I2PAppContext.getGlobalContext().getRouterDir(), args[0]);
|
||||
} else {
|
||||
homeFile = new File(System.getProperty("user.dir"));
|
||||
homeFile = new SecureDirectory(System.getProperty("user.dir"));
|
||||
}
|
||||
|
||||
Map defaultSettings = new HashMap();
|
||||
@@ -147,6 +160,7 @@ public class Daemon {
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("last_fetched", "last_fetched");
|
||||
defaultSettings.put("update_delay", "12");
|
||||
|
||||
if (!homeFile.exists()) {
|
||||
@@ -162,11 +176,11 @@ public class Daemon {
|
||||
Map settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
// wait
|
||||
try {
|
||||
Thread.sleep(5*60*1000);
|
||||
Thread.sleep(5*60*1000 + I2PAppContext.getGlobalContext().random().nextLong(5*60*1000));
|
||||
// Static method, and redundent Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
while (_running) {
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
@@ -179,6 +193,8 @@ public class Daemon {
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
if (!_running)
|
||||
break;
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
@@ -192,4 +208,9 @@ public class Daemon {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
_instance._running = false;
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ package net.i2p.addressbook;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread {
|
||||
class DaemonThread extends Thread {
|
||||
|
||||
private String[] args;
|
||||
|
||||
@@ -51,4 +51,9 @@ public class DaemonThread extends Thread {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() {
|
||||
Daemon.stop();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import java.util.Date;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Log {
|
||||
class Log {
|
||||
|
||||
private File file;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
*
|
||||
*/
|
||||
public class Servlet extends HttpServlet {
|
||||
private Thread thread;
|
||||
private DaemonThread thread;
|
||||
private String nonce;
|
||||
private static final String PROP_NONCE = "addressbook.nonce";
|
||||
|
||||
@@ -88,4 +88,9 @@ public class Servlet extends HttpServlet {
|
||||
//System.out.println("INFO: config root under " + args[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.thread.halt();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,14 @@ package net.i2p.addressbook;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Subscription {
|
||||
class Subscription {
|
||||
|
||||
private String location;
|
||||
|
||||
private String etag;
|
||||
|
||||
private String lastModified;
|
||||
private long lastFetched;
|
||||
|
||||
/**
|
||||
* Construct a Subscription pointing to the address book at location, that
|
||||
@@ -47,11 +48,17 @@ public class Subscription {
|
||||
* @param lastModified
|
||||
* the last-modified header we recieved the last time we read
|
||||
* this subscription.
|
||||
* @param lastFetched when the subscription was last fetched (Java time, as a String)
|
||||
*/
|
||||
public Subscription(String location, String etag, String lastModified) {
|
||||
public Subscription(String location, String etag, String lastModified, String lastFetched) {
|
||||
this.location = location;
|
||||
this.etag = etag;
|
||||
this.lastModified = lastModified;
|
||||
if (lastFetched != null) {
|
||||
try {
|
||||
this.lastFetched = Long.parseLong(lastFetched);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,4 +109,14 @@ public class Subscription {
|
||||
public void setLastModified(String lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
public long getLastFetched() {
|
||||
return this.lastFetched;
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
public void setLastFetched(long t) {
|
||||
this.lastFetched = t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,31 +21,39 @@
|
||||
|
||||
package net.i2p.addressbook;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper; // debug
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
* returns AddressBook objects, and not Subscription objects.
|
||||
* Yes, the EepGet fetch() is done in here in next().
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
private final long delay;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
*
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
* @param delay the minimum delay since last fetched for the iterator to actually fetch
|
||||
* @param proxyHost proxy hostname
|
||||
* @param proxyPort proxt port number
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
|
||||
public SubscriptionIterator(List subscriptions, long delay, String proxyHost, int proxyPort) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
this.delay = delay;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
@@ -58,12 +66,24 @@ public class SubscriptionIterator implements Iterator {
|
||||
return this.subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
/**
|
||||
* Yes, the EepGet fetch() is done in here in next().
|
||||
*
|
||||
* see java.util.Iterator#next()
|
||||
* @return an AddressBook (empty if the minimum delay has not been met)
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) this.subIterator.next();
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
|
||||
//System.err.println("Fetching addressbook from " + sub.getLocation());
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
} else {
|
||||
//System.err.println("Addressbook " + sub.getLocation() + " was last fetched " +
|
||||
// DataHelper.formatDuration(I2PAppContext.getGlobalContext().clock().now() - sub.getLastFetched()) +
|
||||
// " ago but the minimum delay is " +
|
||||
// DataHelper.formatDuration(this.delay));
|
||||
return new AddressBook(Collections.EMPTY_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -72,4 +92,4 @@ public class SubscriptionIterator implements Iterator {
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,15 @@ import java.util.Map;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class SubscriptionList {
|
||||
class SubscriptionList {
|
||||
|
||||
private List subscriptions;
|
||||
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
private File lastFetchedFile;
|
||||
private final long delay;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
@@ -60,20 +62,24 @@ public class SubscriptionList {
|
||||
* @param lastModifiedFile
|
||||
* A file containg the last-modified headers used for conditional
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
* @param delay the minimum delay since last fetched for the iterator to actually fetch
|
||||
* @param defaultSubs default subscription file
|
||||
* @param proxyHost proxy hostname
|
||||
* @param proxyPort proxy port number
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs, String proxyHost,
|
||||
File lastModifiedFile, File lastFetchedFile, long delay, List defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
this.lastFetchedFile = lastFetchedFile;
|
||||
this.delay = delay;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
Map lastFetched;
|
||||
String location;
|
||||
List locations = ConfigParser.parseSubscriptions(locationsFile,
|
||||
defaultSubs);
|
||||
@@ -87,11 +93,17 @@ public class SubscriptionList {
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap();
|
||||
}
|
||||
try {
|
||||
lastFetched = ConfigParser.parse(lastFetchedFile);
|
||||
} catch (IOException exp) {
|
||||
lastFetched = new HashMap();
|
||||
}
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
this.subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
this.subscriptions.add(new Subscription(location, (String) etags.get(location),
|
||||
(String) lastModified.get(location),
|
||||
(String) lastFetched.get(location)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,18 +114,22 @@ public class SubscriptionList {
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
|
||||
return new SubscriptionIterator(this.subscriptions, this.delay, this.proxyHost,
|
||||
this.proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the etag and last-modified headers for each Subscription to files.
|
||||
* Write the etag and last-modified headers,
|
||||
* and the last-fetched time, for each Subscription to files.
|
||||
* BUG - If the subscription URL is a cgi containing an '=' the files
|
||||
* won't be read back correctly; the '=' should be escaped.
|
||||
*/
|
||||
public void write() {
|
||||
Iterator iter = this.subscriptions.iterator();
|
||||
Subscription sub;
|
||||
Map etags = new HashMap();
|
||||
Map lastModified = new HashMap();
|
||||
Map lastFetched = new HashMap();
|
||||
while (iter.hasNext()) {
|
||||
sub = (Subscription) iter.next();
|
||||
if (sub.getEtag() != null) {
|
||||
@@ -122,11 +138,13 @@ public class SubscriptionList {
|
||||
if (sub.getLastModified() != null) {
|
||||
lastModified.put(sub.getLocation(), sub.getLastModified());
|
||||
}
|
||||
lastFetched.put(sub.getLocation(), "" + sub.getLastFetched());
|
||||
}
|
||||
try {
|
||||
ConfigParser.write(etags, this.etagsFile);
|
||||
ConfigParser.write(lastModified, this.lastModifiedFile);
|
||||
ConfigParser.write(lastFetched, this.lastFetchedFile);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
apps/i2psnark/_icons/application.png
Normal file
|
After Width: | Height: | Size: 464 B |
BIN
apps/i2psnark/_icons/cancel.png
Normal file
|
After Width: | Height: | Size: 587 B |
BIN
apps/i2psnark/_icons/cd.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
apps/i2psnark/_icons/clock.png
Normal file
|
After Width: | Height: | Size: 882 B |
BIN
apps/i2psnark/_icons/clock_red.png
Normal file
|
After Width: | Height: | Size: 889 B |
BIN
apps/i2psnark/_icons/compress.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
apps/i2psnark/_icons/film.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
apps/i2psnark/_icons/folder.png
Normal file
|
After Width: | Height: | Size: 537 B |
BIN
apps/i2psnark/_icons/html.png
Normal file
|
After Width: | Height: | Size: 578 B |
BIN
apps/i2psnark/_icons/music.png
Normal file
|
After Width: | Height: | Size: 385 B |
BIN
apps/i2psnark/_icons/package.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
apps/i2psnark/_icons/page.png
Normal file
|
After Width: | Height: | Size: 635 B |
BIN
apps/i2psnark/_icons/page_white.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
apps/i2psnark/_icons/page_white_acrobat.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
apps/i2psnark/_icons/photo.png
Normal file
|
After Width: | Height: | Size: 589 B |
BIN
apps/i2psnark/_icons/plugin.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
apps/i2psnark/_icons/tick.png
Normal file
|
After Width: | Height: | Size: 537 B |
@@ -3,9 +3,7 @@
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar, war" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<ant dir="../../streaming/java/" target="build" />
|
||||
<!-- streaming will build ministreaming and core -->
|
||||
<!-- run from top level build.xml to get dependencies built -->
|
||||
</target>
|
||||
<condition property="depend.available">
|
||||
<typefound name="depend" />
|
||||
@@ -52,7 +50,7 @@
|
||||
<classes dir="./build/obj" includes="**/I2PSnarkServlet*.class" />
|
||||
-->
|
||||
<target name="war" depends="jar, bundle">
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml">
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml" basedir="../" includes="_icons/*" >
|
||||
<!-- 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" />
|
||||
</war>
|
||||
@@ -73,7 +71,7 @@
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="poupdate" depends="compile">
|
||||
<target name="poupdate" depends="builddep, compile">
|
||||
<!-- Update the messages_*.po files. -->
|
||||
<exec executable="sh" osfamily="unix" failifexecutionfails="true" >
|
||||
<arg value="./bundle-messages.sh" />
|
||||
|
||||
@@ -49,7 +49,7 @@ do
|
||||
# To start a new translation, copy the header from an old translation to the new .po file,
|
||||
# then ant distclean poupdate.
|
||||
find $JPATHS -name *.java > $TMPFILE
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 \
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@@ -36,7 +37,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private Log _log = new Log(ConnectionAcceptor.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ConnectionAcceptor.class);
|
||||
private I2PServerSocket serverSocket;
|
||||
private PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
|
||||
14
apps/i2psnark/java/src/org/klomp/snark/DataLoader.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used to fetch data
|
||||
* @since 0.8.2
|
||||
*/
|
||||
interface DataLoader
|
||||
{
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls to get the data from disk
|
||||
* @return bytes or null for errors
|
||||
*/
|
||||
public byte[] loadData(int piece, int begin, int length);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.BEncoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
|
||||
/**
|
||||
* REF: BEP 10 Extension Protocol
|
||||
* @since 0.8.2
|
||||
*/
|
||||
class ExtensionHandshake {
|
||||
|
||||
private static final byte[] _payload = buildPayload();
|
||||
|
||||
/**
|
||||
* @return bencoded data
|
||||
*/
|
||||
static byte[] getPayload() {
|
||||
return _payload;
|
||||
}
|
||||
|
||||
/** just a test for now */
|
||||
private static byte[] buildPayload() {
|
||||
Map<String, Object> handshake = new HashMap();
|
||||
Map<String, Integer> m = new HashMap();
|
||||
m.put("foo", Integer.valueOf(99));
|
||||
m.put("bar", Integer.valueOf(101));
|
||||
handshake.put("m", m);
|
||||
handshake.put("p", Integer.valueOf(6881));
|
||||
handshake.put("v", "I2PSnark");
|
||||
handshake.put("reqq", Integer.valueOf(5));
|
||||
return BEncoder.bencode(handshake);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -23,9 +22,12 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.EepGet;
|
||||
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;
|
||||
@@ -45,37 +47,39 @@ public class I2PSnarkUtil {
|
||||
private int _proxyPort;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private Map _opts;
|
||||
private Map<String, String> _opts;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
private final Set _shitlist;
|
||||
private final Set<Hash> _shitlist;
|
||||
private int _maxUploaders;
|
||||
private int _maxUpBW;
|
||||
private int _maxConnections;
|
||||
private File _tmpDir;
|
||||
|
||||
private int _startupDelay;
|
||||
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
|
||||
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
|
||||
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
|
||||
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
|
||||
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
//setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_shitlist = new HashSet(64);
|
||||
_shitlist = new ConcurrentHashSet();
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
_startupDelay = DEFAULT_STARTUP_DELAY;
|
||||
// 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 File(ctx.getTempDir(), "i2psnark");
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), "i2psnark");
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
@@ -85,6 +89,7 @@ public class I2PSnarkUtil {
|
||||
* host for no proxying)
|
||||
*
|
||||
*/
|
||||
/*****
|
||||
public void setProxy(String host, int port) {
|
||||
if ( (host != null) && (port > 0) ) {
|
||||
_shouldProxy = true;
|
||||
@@ -97,6 +102,7 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
_configured = true;
|
||||
}
|
||||
******/
|
||||
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
@@ -125,22 +131,31 @@ public class I2PSnarkUtil {
|
||||
_maxConnections = limit;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setStartupDelay(int minutes) {
|
||||
_startupDelay = minutes;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public Map<String, String> getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
public int getMaxUploaders() { return _maxUploaders; }
|
||||
public int getMaxUpBW() { return _maxUpBW; }
|
||||
public int getMaxConnections() { return _maxConnections; }
|
||||
|
||||
public int getStartupDelay() { return _startupDelay; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
synchronized public boolean connect() {
|
||||
if (_manager == null) {
|
||||
// try to find why reconnecting after stop
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connecting to I2P", new Exception("I did it"));
|
||||
Properties opts = new Properties();
|
||||
if (_opts != null) {
|
||||
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
@@ -152,6 +167,10 @@ public class I2PSnarkUtil {
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("outbound.nickname") == null)
|
||||
opts.setProperty("outbound.nickname", "I2PSnark");
|
||||
// Dont do this for now, it is set in I2PSocketEepGet for announces,
|
||||
// we don't need fast handshake for peer connections.
|
||||
//if (opts.getProperty("i2p.streaming.connectDelay") == null)
|
||||
// opts.setProperty("i2p.streaming.connectDelay", "500");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
@@ -175,6 +194,7 @@ public class I2PSnarkUtil {
|
||||
*/
|
||||
public void disconnect() {
|
||||
I2PSocketManager mgr = _manager;
|
||||
// FIXME this can cause race NPEs elsewhere
|
||||
_manager = null;
|
||||
_shitlist.clear();
|
||||
mgr.destroySocketManager();
|
||||
@@ -186,19 +206,22 @@ public class I2PSnarkUtil {
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
Hash dest = peer.getAddress().calculateHash();
|
||||
synchronized (_shitlist) {
|
||||
if (_shitlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
|
||||
}
|
||||
I2PSocketManager mgr = _manager;
|
||||
if (mgr == null)
|
||||
throw new IOException("No socket manager");
|
||||
Destination addr = peer.getAddress();
|
||||
if (addr == null)
|
||||
throw new IOException("Null address");
|
||||
Hash dest = addr.calculateHash();
|
||||
if (_shitlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
|
||||
try {
|
||||
I2PSocket rv = _manager.connect(peer.getAddress());
|
||||
if (rv != null) synchronized (_shitlist) { _shitlist.remove(dest); }
|
||||
I2PSocket rv = _manager.connect(addr);
|
||||
if (rv != null)
|
||||
_shitlist.remove(dest);
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.add(dest);
|
||||
}
|
||||
_shitlist.add(dest);
|
||||
SimpleScheduler.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
@@ -207,7 +230,7 @@ public class I2PSnarkUtil {
|
||||
private class Unshitlist implements SimpleTimer.TimedEvent {
|
||||
private Hash _dest;
|
||||
public Unshitlist(Hash dest) { _dest = dest; }
|
||||
public void timeReached() { synchronized (_shitlist) { _shitlist.remove(_dest); } }
|
||||
public void timeReached() { _shitlist.remove(_dest); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,11 +240,12 @@ public class I2PSnarkUtil {
|
||||
public File get(String url, boolean rewrite) { return get(url, rewrite, 0); }
|
||||
public File get(String url, int retries) { return get(url, true, retries); }
|
||||
public File get(String url, boolean rewrite, int retries) {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
// we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms...
|
||||
out = File.createTempFile("i2psnark", null, _tmpDir);
|
||||
out = SecureFile.createTempFile("i2psnark", null, _tmpDir);
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
if (out != null)
|
||||
@@ -241,10 +265,12 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
EepGet get = new I2PSocketEepGet(_context, _manager, retries, out.getAbsolutePath(), fetchURL);
|
||||
if (get.fetch()) {
|
||||
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
|
||||
return out;
|
||||
} else {
|
||||
_log.warn("Fetch failed [" + url + "]");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Fetch failed [" + url + "]");
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
@@ -358,7 +384,7 @@ public class I2PSnarkUtil {
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
|
||||
if (rv.size() <= 0)
|
||||
if (rv.isEmpty())
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
@@ -431,4 +457,9 @@ public class I2PSnarkUtil {
|
||||
public String getString(String s, Object o, Object o2) {
|
||||
return Translate.getString(s, o, o2, _context, BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** ngettext @since 0.7.14 */
|
||||
public String getString(int n, String s, String p) {
|
||||
return Translate.getString(n, s, p, _context, BUNDLE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,23 +39,28 @@ class Message
|
||||
final static byte REQUEST = 6;
|
||||
final static byte PIECE = 7;
|
||||
final static byte CANCEL = 8;
|
||||
final static byte EXTENSION = 20;
|
||||
|
||||
// Not all fields are used for every message.
|
||||
// KEEP_ALIVE doesn't have a real wire representation
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
// low byte used for EXTENSION message
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
// Used for PIECE and BITFIELD messages
|
||||
// Used for PIECE and BITFIELD and EXTENSION messages
|
||||
byte[] data;
|
||||
int off;
|
||||
int len;
|
||||
|
||||
// Used to do deferred fetch of data
|
||||
DataLoader dataLoader;
|
||||
|
||||
SimpleTimer.TimedEvent expireEvent;
|
||||
|
||||
/** Utility method for sending a message through a DataStream. */
|
||||
@@ -68,6 +73,13 @@ class Message
|
||||
return;
|
||||
}
|
||||
|
||||
// Get deferred data
|
||||
if (data == null && dataLoader != null) {
|
||||
data = dataLoader.loadData(piece, begin, length);
|
||||
if (data == null)
|
||||
return; // hmm will get retried, but shouldn't happen
|
||||
}
|
||||
|
||||
// Calculate the total length in bytes
|
||||
|
||||
// Type is one byte.
|
||||
@@ -85,8 +97,12 @@ class Message
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// length is 1 byte
|
||||
if (type == EXTENSION)
|
||||
datalen += 1;
|
||||
|
||||
// add length of data for piece or bitfield array.
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
||||
datalen += len;
|
||||
|
||||
// Send length
|
||||
@@ -105,8 +121,11 @@ class Message
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
dos.writeInt(length);
|
||||
|
||||
if (type == EXTENSION)
|
||||
dos.writeByte((byte) piece & 0xff);
|
||||
|
||||
// Send actual data
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
if (type == BITFIELD || type == PIECE || type == EXTENSION)
|
||||
dos.write(data, off, len);
|
||||
}
|
||||
|
||||
@@ -135,6 +154,8 @@ class Message
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.Log;
|
||||
@@ -47,7 +48,7 @@ import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
*/
|
||||
public class MetaInfo
|
||||
{
|
||||
private static final Log _log = new Log(MetaInfo.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(MetaInfo.class);
|
||||
private final String announce;
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
@@ -108,7 +109,8 @@ public class MetaInfo
|
||||
*/
|
||||
public MetaInfo(Map m) throws InvalidBEncodingException
|
||||
{
|
||||
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
|
||||
BEValue val = (BEValue)m.get("announce");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing announce string");
|
||||
@@ -445,14 +447,16 @@ public class MetaInfo
|
||||
else
|
||||
buf.append(val.toString());
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(buf.toString());
|
||||
byte[] infoBytes = BEncoder.bencode(info);
|
||||
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA");
|
||||
byte hash[] = digest.digest(infoBytes);
|
||||
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
|
||||
return hash;
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
|
||||
104
apps/i2psnark/java/src/org/klomp/snark/PartialPiece.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* This is the class passed from PeerCoordinator to PeerState so
|
||||
* PeerState may start requests.
|
||||
*
|
||||
* It is also passed from PeerState to PeerCoordinator when
|
||||
* a piece is not completely downloaded, for example
|
||||
* when the Peer disconnects or chokes.
|
||||
*
|
||||
* @since 0.8.2
|
||||
*/
|
||||
class PartialPiece implements Comparable {
|
||||
|
||||
private final int piece;
|
||||
private final byte[] bs;
|
||||
private final int off;
|
||||
private final long createdTime;
|
||||
|
||||
/**
|
||||
* Used by PeerCoordinator.
|
||||
* Creates a new PartialPiece, with no chunks yet downloaded.
|
||||
* Allocates the data.
|
||||
*
|
||||
* @param piece Piece number requested.
|
||||
* @param len must be equal to the piece length
|
||||
*/
|
||||
public PartialPiece (int piece, int len) throws OutOfMemoryError {
|
||||
this.piece = piece;
|
||||
this.bs = new byte[len];
|
||||
this.off = 0;
|
||||
this.createdTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by PeerState.
|
||||
* Creates a new PartialPiece, with chunks up to but not including
|
||||
* firstOutstandingRequest already downloaded and stored in the Request byte array.
|
||||
*
|
||||
* Note that this cannot handle gaps; chunks after a missing chunk cannot be saved.
|
||||
* That would be harder.
|
||||
*
|
||||
* @param firstOutstandingRequest the first request not fulfilled for the piece
|
||||
*/
|
||||
public PartialPiece (Request firstOutstandingRequest) {
|
||||
this.piece = firstOutstandingRequest.piece;
|
||||
this.bs = firstOutstandingRequest.bs;
|
||||
this.off = firstOutstandingRequest.off;
|
||||
this.createdTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this PartialPiece to a request for the next chunk.
|
||||
* Used by PeerState only.
|
||||
*/
|
||||
|
||||
public Request getRequest() {
|
||||
return new Request(this.piece, this.bs, this.off, Math.min(this.bs.length - this.off, PeerState.PARTSIZE));
|
||||
}
|
||||
|
||||
/** piece number */
|
||||
public int getPiece() {
|
||||
return this.piece;
|
||||
}
|
||||
|
||||
/** how many bytes are good */
|
||||
public int getDownloaded() {
|
||||
return this.off;
|
||||
}
|
||||
|
||||
public long getCreated() {
|
||||
return this.createdTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest downloaded first
|
||||
*/
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
return ((PartialPiece)o).off - this.off; // reverse
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return piece * 7777;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this simple so PeerCoordinator can keep a List.
|
||||
* Warning - compares piece number only!
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof PartialPiece) {
|
||||
PartialPiece pp = (PartialPiece)o;
|
||||
return pp.piece == this.piece;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Partial(" + piece + ',' + off + ',' + bs.length + ')';
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.util.Log;
|
||||
@@ -56,10 +57,16 @@ public class Peer implements Comparable
|
||||
private long _id;
|
||||
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
|
||||
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// bytes per bt spec: 0011223344556677
|
||||
static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
static final long OPTION_FAST = 0x0000000000000004l;
|
||||
private long options;
|
||||
|
||||
/**
|
||||
* Outgoing connection.
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
* relevant MetaInfo.
|
||||
*/
|
||||
@@ -74,6 +81,7 @@ public class Peer implements Comparable
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming connection.
|
||||
* Creates a unconnected peer from the input and output stream got
|
||||
* from the socket. Note that the complete handshake (which can take
|
||||
* some time or block indefinitely) is done in the calling Thread to
|
||||
@@ -92,7 +100,8 @@ public class Peer implements Comparable
|
||||
byte[] id = handshake(in, out);
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
_id = ++__id;
|
||||
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,10 +125,15 @@ public class Peer implements Comparable
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns socket (for debug printing)
|
||||
* @return socket debug string (for debug printing)
|
||||
*/
|
||||
public String getSocket()
|
||||
{
|
||||
if (state != null) {
|
||||
String r = state.getRequests();
|
||||
if (r != null)
|
||||
return sock.toString() + "<br>Requests: " + r;
|
||||
}
|
||||
return sock.toString();
|
||||
}
|
||||
|
||||
@@ -129,7 +143,7 @@ public class Peer implements Comparable
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return peerID.hashCode() ^ (2 << _id);
|
||||
return peerID.hashCode() ^ (7777 * (int)_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,6 +164,7 @@ public class Peer implements Comparable
|
||||
|
||||
/**
|
||||
* Compares the PeerIDs.
|
||||
* @deprecated unused?
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
@@ -181,14 +196,17 @@ public class Peer implements Comparable
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
|
||||
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
|
||||
try
|
||||
{
|
||||
// Do we need to handshake?
|
||||
if (din == null)
|
||||
{
|
||||
// Outgoing connection
|
||||
sock = util.connect(peerID);
|
||||
_log.debug("Connected to " + peerID + ": " + sock);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Connected to " + peerID + ": " + sock);
|
||||
if ((sock == null) || (sock.isClosed())) {
|
||||
throw new IOException("Unable to reach " + peerID);
|
||||
}
|
||||
@@ -207,20 +225,33 @@ public class Peer implements Comparable
|
||||
// = new BufferedOutputStream(sock.getOutputStream());
|
||||
byte [] id = handshake(in, out); //handshake(bis, bos);
|
||||
byte [] expected_id = peerID.getID();
|
||||
if (!Arrays.equals(expected_id, id))
|
||||
throw new IOException("Unexpected peerID '"
|
||||
if (expected_id == null) {
|
||||
peerID.setID(id);
|
||||
} else if (Arrays.equals(expected_id, id)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handshake got matching IDs with " + toString());
|
||||
} else {
|
||||
throw new IOException("Unexpected peerID '"
|
||||
+ PeerID.idencode(id)
|
||||
+ "' expected '"
|
||||
+ PeerID.idencode(expected_id) + "'");
|
||||
_log.debug("Handshake got matching IDs with " + toString());
|
||||
}
|
||||
} else {
|
||||
_log.debug("Already have din [" + sock + "] with " + toString());
|
||||
// Incoming connection
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Already have din [" + sock + "] with " + toString());
|
||||
}
|
||||
|
||||
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||
PeerConnectionOut out = new PeerConnectionOut(this, dout);
|
||||
PeerState s = new PeerState(this, listener, metainfo, in, out);
|
||||
|
||||
if ((options & OPTION_EXTENSION) != 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports extensions, sending test message");
|
||||
out.sendExtension(0, ExtensionHandshake.getPayload());
|
||||
}
|
||||
|
||||
// Send our bitmap
|
||||
if (bitfield != null)
|
||||
s.out.sendBitfield(bitfield);
|
||||
@@ -229,7 +260,8 @@ public class Peer implements Comparable
|
||||
state = s;
|
||||
listener.connected(this);
|
||||
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
@@ -269,9 +301,8 @@ public class Peer implements Comparable
|
||||
// Handshake write - header
|
||||
dout.write(19);
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - zeros
|
||||
byte[] zeros = new byte[8];
|
||||
dout.write(zeros);
|
||||
// Handshake write - options
|
||||
dout.writeLong(OPTION_EXTENSION);
|
||||
// Handshake write - metainfo hash
|
||||
byte[] shared_hash = metainfo.getInfoHash();
|
||||
dout.write(shared_hash);
|
||||
@@ -279,7 +310,8 @@ public class Peer implements Comparable
|
||||
dout.write(my_id);
|
||||
dout.flush();
|
||||
|
||||
_log.debug("Wrote my shared hash and ID to " + toString());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote my shared hash and ID to " + toString());
|
||||
|
||||
// Handshake read - header
|
||||
byte b = din.readByte();
|
||||
@@ -295,8 +327,8 @@ public class Peer implements Comparable
|
||||
+ "'Bittorrent protocol', got '"
|
||||
+ bittorrentProtocol + "'");
|
||||
|
||||
// Handshake read - zeros
|
||||
din.readFully(zeros);
|
||||
// Handshake read - options
|
||||
options = din.readLong();
|
||||
|
||||
// Handshake read - metainfo hash
|
||||
bs = new byte[20];
|
||||
@@ -306,7 +338,15 @@ public class Peer implements Comparable
|
||||
|
||||
// Handshake read - peer id
|
||||
din.readFully(bs);
|
||||
_log.debug("Read the remote side's hash and peerID fully from " + toString());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the remote side's hash and peerID fully from " + toString());
|
||||
|
||||
if (options != 0) {
|
||||
// send them something
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
@@ -337,8 +377,11 @@ public class Peer implements Comparable
|
||||
if (this.deregister) {
|
||||
PeerListener p = s.listener;
|
||||
if (p != null) {
|
||||
p.savePeerPartial(s);
|
||||
p.markUnrequested(this);
|
||||
List<PartialPiece> pcs = s.returnPartialPieces();
|
||||
if (!pcs.isEmpty())
|
||||
p.savePartialPieces(this, pcs);
|
||||
// now covered by savePartialPieces
|
||||
//p.markUnrequested(this);
|
||||
}
|
||||
}
|
||||
state = null;
|
||||
@@ -374,6 +417,37 @@ public class Peer implements Comparable
|
||||
s.havePiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the other side that we are no longer interested in any of
|
||||
* the outstanding requests (if any) for this piece.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void cancel(int piece) {
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.cancelPiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently requesting the piece?
|
||||
* @since 0.8.1
|
||||
*/
|
||||
boolean isRequesting(int p) {
|
||||
PeerState s = state;
|
||||
return s != null && s.isRequesting(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the request queue.
|
||||
* Call after adding wanted pieces.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void request() {
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.addRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer is interested in pieces we have. Returns
|
||||
* false if not connected.
|
||||
@@ -388,6 +462,7 @@ public class Peer implements Comparable
|
||||
* Sets whether or not we are interested in pieces from this peer.
|
||||
* Defaults to false. When interest is true and this peer unchokes
|
||||
* us then we start downloading from it. Has no effect when not connected.
|
||||
* @deprecated unused
|
||||
*/
|
||||
public void setInteresting(boolean interest)
|
||||
{
|
||||
@@ -531,17 +606,8 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
PeerCoordinator.setRate(up, uploaded_old);
|
||||
PeerCoordinator.setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,28 +615,11 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
return PeerCoordinator.getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
return PeerCoordinator.getRate(downloaded_old);
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++){
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ 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;
|
||||
import net.i2p.data.DataHelper;
|
||||
@@ -41,7 +42,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
{
|
||||
private static final Log _log = new Log(PeerAcceptor.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerAcceptor.class);
|
||||
private final PeerCoordinator coordinator;
|
||||
final PeerCoordinatorSet coordinators;
|
||||
|
||||
@@ -75,10 +76,12 @@ public class PeerAcceptor
|
||||
// is this working right?
|
||||
try {
|
||||
peerInfoHash = readHash(in);
|
||||
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
|
||||
+ ": " + Base64.encode(peerInfoHash));
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
|
||||
+ ": " + Base64.encode(peerInfoHash));
|
||||
} catch (IOException ioe) {
|
||||
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
throw ioe;
|
||||
}
|
||||
in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in);
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* TimerTask that checks for good/bad up/downloader. Works together
|
||||
* with the PeerCoordinator to select which Peers get (un)choked.
|
||||
@@ -43,14 +45,12 @@ class PeerCheckerTask extends TimerTask
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
private Random random = new Random();
|
||||
private static final Random random = I2PAppContext.getGlobalContext().random();
|
||||
|
||||
public void run()
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
if ((!it.hasNext()) || coordinator.halted()) {
|
||||
List<Peer> peerList = coordinator.peerList();
|
||||
if (peerList.isEmpty() || coordinator.halted()) {
|
||||
coordinator.peerCount = 0;
|
||||
coordinator.interestedAndChoking = 0;
|
||||
coordinator.setRateHistory(0, 0);
|
||||
@@ -74,19 +74,18 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List removed = new ArrayList();
|
||||
List<Peer> removed = new ArrayList();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
for (Peer peer : peerList) {
|
||||
|
||||
// Remove dying peers
|
||||
if (!peer.isConnected())
|
||||
{
|
||||
it.remove();
|
||||
coordinator.removePeerFromPieces(peer);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
// This was just a failsafe, right?
|
||||
//it.remove();
|
||||
//coordinator.removePeerFromPieces(peer);
|
||||
//coordinator.peerCount = coordinator.peers.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -113,10 +112,11 @@ class PeerCheckerTask extends TimerTask
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
|
||||
// Choke half of them rather than all so it isn't so drastic...
|
||||
// Choke a percentage of them rather than all so it isn't so drastic...
|
||||
// unless this torrent is over the limit all by itself.
|
||||
// choke 5/8 of the time when seeding and 3/8 when leeching
|
||||
boolean overBWLimitChoke = upload > 0 &&
|
||||
((overBWLimit && random.nextBoolean()) ||
|
||||
((overBWLimit && (random.nextInt(8) > (coordinator.completed() ? 2 : 4))) ||
|
||||
(coordinator.overUpBWLimit(uploaded)));
|
||||
|
||||
// If we are at our max uploaders and we have lots of other
|
||||
@@ -138,7 +138,6 @@ class PeerCheckerTask extends TimerTask
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (overBWLimitChoke)
|
||||
@@ -151,7 +150,6 @@ class PeerCheckerTask extends TimerTask
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list for fairness, even though we won't be unchoking this time
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting() && peer.isChoked())
|
||||
@@ -164,7 +162,6 @@ class PeerCheckerTask extends TimerTask
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isInteresting() && !coordinator.completed())
|
||||
@@ -177,7 +174,6 @@ class PeerCheckerTask extends TimerTask
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting()
|
||||
@@ -193,7 +189,6 @@ class PeerCheckerTask extends TimerTask
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting() && !peer.isChoked() &&
|
||||
@@ -232,8 +227,6 @@ class PeerCheckerTask extends TimerTask
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
removed.add(worstDownloader);
|
||||
}
|
||||
|
||||
@@ -242,8 +235,12 @@ class PeerCheckerTask extends TimerTask
|
||||
coordinator.unchokePeer();
|
||||
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
synchronized (coordinator.peers) {
|
||||
for(Peer peer : removed) {
|
||||
if (coordinator.peers.remove(peer))
|
||||
coordinator.peers.add(peer);
|
||||
}
|
||||
}
|
||||
coordinator.interestedAndChoking += removedCount;
|
||||
|
||||
// store the rates
|
||||
@@ -253,6 +250,5 @@ class PeerCheckerTask extends TimerTask
|
||||
if (random.nextInt(4) == 0)
|
||||
coordinator.getStorage().cleanRAFs();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ package org.klomp.snark;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerConnectionIn implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionIn.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerConnectionIn.class);
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
@@ -129,7 +130,7 @@ class PeerConnectionIn implements Runnable
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) + ": " + ps.bitfield);
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
@@ -170,6 +171,14 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 20: // Extension message
|
||||
int id = din.readUnsignedByte();
|
||||
byte[] payload = new byte[i-2];
|
||||
din.readFully(payload);
|
||||
ps.extensionMessage(id, payload);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received extension message from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
@@ -33,7 +34,7 @@ import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionOut.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerConnectionOut.class);
|
||||
private final Peer peer;
|
||||
private final DataOutputStream dout;
|
||||
|
||||
@@ -41,7 +42,7 @@ class PeerConnectionOut implements Runnable
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private final List sendQueue = new ArrayList();
|
||||
private final List<Message> sendQueue = new ArrayList();
|
||||
|
||||
private static long __id = 0;
|
||||
private long _id;
|
||||
@@ -141,7 +142,7 @@ class PeerConnectionOut implements Runnable
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && sendQueue.size() > 0) {
|
||||
if (m == null && !sendQueue.isEmpty()) {
|
||||
m = (Message)sendQueue.remove(0);
|
||||
SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
@@ -163,10 +164,22 @@ class PeerConnectionOut implements Runnable
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
// XXX - Should also register overhead...
|
||||
if (m.type == Message.PIECE)
|
||||
state.uploaded(m.len);
|
||||
// Don't let other clients requesting big chunks get an advantage
|
||||
// when we are seeding;
|
||||
// only count the rest of the upload after sendMessage().
|
||||
int remainder = 0;
|
||||
if (m.type == Message.PIECE) {
|
||||
if (m.len <= PeerState.PARTSIZE) {
|
||||
state.uploaded(m.len);
|
||||
} else {
|
||||
state.uploaded(PeerState.PARTSIZE);
|
||||
remainder = m.len - PeerState.PARTSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
m.sendMessage(dout);
|
||||
if (remainder > 0)
|
||||
state.uploaded(remainder);
|
||||
m = null;
|
||||
}
|
||||
}
|
||||
@@ -216,12 +229,9 @@ class PeerConnectionOut implements Runnable
|
||||
/**
|
||||
* Adds a message to the sendQueue and notifies the method waiting
|
||||
* on the sendQueue to change.
|
||||
* If a PIECE message only, add a timeout.
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
if (m.type == Message.PIECE)
|
||||
SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
@@ -417,6 +427,42 @@ class PeerConnectionOut implements Runnable
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a piece message with a callback to load the data
|
||||
* from disk when required.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void sendPiece(int piece, int begin, int length, DataLoader loader)
|
||||
{
|
||||
boolean sendNow = false;
|
||||
// are there any cases where we should?
|
||||
|
||||
if (sendNow) {
|
||||
// queue the real thing
|
||||
byte[] bytes = loader.loadData(piece, begin, length);
|
||||
if (bytes != null)
|
||||
sendPiece(piece, begin, length, bytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// queue a fake message... set everything up,
|
||||
// except save the PeerState instead of the bytes.
|
||||
Message m = new Message();
|
||||
m.type = Message.PIECE;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.dataLoader = loader;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a piece message with the data already loaded from disk
|
||||
* Also add a timeout.
|
||||
* We don't use this anymore.
|
||||
*/
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
{
|
||||
Message m = new Message();
|
||||
@@ -427,6 +473,8 @@ class PeerConnectionOut implements Runnable
|
||||
m.data = bytes;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
// since we have the data already loaded, queue a timeout to remove it
|
||||
SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@@ -456,6 +504,19 @@ class PeerConnectionOut implements Runnable
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all Request messages from the queue
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void cancelRequestMessages() {
|
||||
synchronized(sendQueue) {
|
||||
for (Iterator<Message> it = sendQueue.iterator(); it.hasNext(); ) {
|
||||
if (it.next().type == Message.REQUEST)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the PeerState when the other side doesn't want this
|
||||
// request to be handled anymore. Removes any pending Piece Message
|
||||
// from out send queue.
|
||||
@@ -475,4 +536,15 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
void sendExtension(int id, byte[] bytes) {
|
||||
Message m = new Message();
|
||||
m.type = Message.EXTENSION;
|
||||
m.piece = id;
|
||||
m.data = bytes;
|
||||
m.off = 0;
|
||||
m.len = bytes.length;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,22 +22,26 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* Coordinates what peer does what.
|
||||
*/
|
||||
public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = new Log(PeerCoordinator.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
|
||||
final MetaInfo metainfo;
|
||||
final Storage storage;
|
||||
final Snark snark;
|
||||
@@ -56,27 +60,34 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
// This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking
|
||||
final Queue<Peer> peers;
|
||||
/** estimate of the peers, without requiring any synchronization */
|
||||
volatile int peerCount;
|
||||
|
||||
/** Timer to handle all periodical tasks. */
|
||||
private final Timer timer = new Timer(true);
|
||||
private final CheckEvent timer;
|
||||
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private List wantedPieces;
|
||||
/** The wanted pieces. We could use a TreeSet but we'd have to clear and re-add everything
|
||||
* when priorities change.
|
||||
*/
|
||||
private final List<Piece> wantedPieces;
|
||||
|
||||
/** partial pieces - lock by synching on wantedPieces */
|
||||
private final List<PartialPiece> partialPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
public I2PSnarkUtil _util;
|
||||
private static final Random _random = I2PAppContext.getGlobalContext().random();
|
||||
|
||||
public String trackerProblems = null;
|
||||
public int trackerSeenPeers = 0;
|
||||
@@ -91,37 +102,63 @@ public class PeerCoordinator implements PeerListener
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
wantedPieces = new ArrayList();
|
||||
setWantedPieces();
|
||||
partialPieces = new ArrayList(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue();
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
// this will help the behavior with global limits
|
||||
Random r = new Random();
|
||||
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
timer = new CheckEvent(new PeerCheckerTask(_util, this));
|
||||
timer.schedule((CHECK_PERIOD / 2) + _random.nextInt((int) CHECK_PERIOD));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the PeerCheckerTask via the SimpleTimer2 executors
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private static class CheckEvent extends SimpleTimer2.TimedEvent {
|
||||
private final PeerCheckerTask _task;
|
||||
public CheckEvent(PeerCheckerTask task) {
|
||||
super(SimpleTimer2.getInstance());
|
||||
_task = task;
|
||||
}
|
||||
public void timeReached() {
|
||||
_task.run();
|
||||
schedule(CHECK_PERIOD);
|
||||
}
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
synchronized(wantedPieces) {
|
||||
wantedPieces.clear();
|
||||
BitField bitfield = storage.getBitField();
|
||||
int[] pri = storage.getPiecePriorities();
|
||||
for (int i = 0; i < metainfo.getPieces(); i++) {
|
||||
// only add if we don't have and the priority is >= 0
|
||||
if ((!bitfield.get(i)) &&
|
||||
(pri == null || pri[i] >= 0)) {
|
||||
Piece p = new Piece(i);
|
||||
if (pri != null)
|
||||
p.setPriority(pri[i]);
|
||||
wantedPieces.add(p);
|
||||
}
|
||||
}
|
||||
Collections.shuffle(wantedPieces, _random);
|
||||
}
|
||||
}
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
// for web page detailed stats
|
||||
public List peerList()
|
||||
public List<Peer> peerList()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return new ArrayList(peers);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
@@ -134,16 +171,15 @@ public class PeerCoordinator implements PeerListener
|
||||
return storage.complete();
|
||||
}
|
||||
|
||||
/** might be wrong */
|
||||
public int getPeerCount() { return peerCount; }
|
||||
|
||||
/** should be right */
|
||||
public int getPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
int rv = peers.size();
|
||||
peerCount = rv;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +216,7 @@ public class PeerCoordinator implements PeerListener
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private static void setRate(long val, long array[])
|
||||
static void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
@@ -211,20 +247,23 @@ public class PeerCoordinator implements PeerListener
|
||||
return (r * 1000) / CHECK_PERIOD;
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
static long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
int factor = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++) {
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
int f = RATE_DEPTH - i;
|
||||
rate += array[i] * f;
|
||||
factor += f;
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
return rate / (factor * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
@@ -234,21 +273,21 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
public boolean needPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return !halted && peers.size() < getMaxConnections();
|
||||
}
|
||||
}
|
||||
|
||||
/** reduce max if huge pieces to keep from ooming */
|
||||
/**
|
||||
* Reduce max if huge pieces to keep from ooming when leeching
|
||||
* @return 512K: 16; 1M: 11; 2M: 6
|
||||
*/
|
||||
private int getMaxConnections() {
|
||||
int size = metainfo.getPieceLength(0);
|
||||
int max = _util.getMaxConnections();
|
||||
if (size <= 1024*1024)
|
||||
if (size <= 512*1024 || completed())
|
||||
return max;
|
||||
if (size <= 2*1024*1024)
|
||||
return (max + 1) / 2;
|
||||
return (max + 3) / 4;
|
||||
if (size <= 1024*1024)
|
||||
return (max + max + 2) / 3;
|
||||
return (max + 2) / 3;
|
||||
}
|
||||
|
||||
public boolean halted() { return halted; }
|
||||
@@ -256,7 +295,7 @@ public class PeerCoordinator implements PeerListener
|
||||
public void halt()
|
||||
{
|
||||
halted = true;
|
||||
List removed = new ArrayList();
|
||||
List<Peer> removed = new ArrayList();
|
||||
synchronized(peers)
|
||||
{
|
||||
// Stop peer checker task.
|
||||
@@ -268,13 +307,15 @@ public class PeerCoordinator implements PeerListener
|
||||
peerCount = 0;
|
||||
}
|
||||
|
||||
while (removed.size() > 0) {
|
||||
Peer peer = (Peer)removed.remove(0);
|
||||
while (!removed.isEmpty()) {
|
||||
Peer peer = removed.remove(0);
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
// delete any saved orphan partial piece
|
||||
savedRequest = null;
|
||||
synchronized (partialPieces) {
|
||||
partialPieces.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
@@ -319,7 +360,10 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
// Add it to the beginning of the list.
|
||||
// And try to optimistically make it a uploader.
|
||||
peers.add(0, peer);
|
||||
// Can't add to beginning since we converted from a List to a Queue
|
||||
// We can do this in Java 6 with a Deque
|
||||
//peers.add(0, peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
@@ -333,12 +377,14 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
// caller must synchronize on peers
|
||||
private static Peer peerIDInList(PeerID pid, List peers)
|
||||
/**
|
||||
* @return peer if peer id is in the collection, else null
|
||||
*/
|
||||
private static Peer peerIDInList(PeerID pid, Collection<Peer> peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
Iterator<Peer> it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
Peer cur = it.next();
|
||||
if (pid.sameID(cur.getPeerID()))
|
||||
return cur;
|
||||
}
|
||||
@@ -369,7 +415,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
if (need_more)
|
||||
{
|
||||
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Adding a peer " + peer.getPeerID().toString() + " for " + metainfo.getName(), new Exception("add/run"));
|
||||
|
||||
// Run the peer with us as listener and the current bitfield.
|
||||
final PeerListener listener = this;
|
||||
@@ -402,15 +449,14 @@ public 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 interested = new LinkedList();
|
||||
synchronized (peers) {
|
||||
List<Peer> interested = new LinkedList();
|
||||
int count = 0;
|
||||
int unchokedCount = 0;
|
||||
int maxUploaders = allowedUploaders();
|
||||
Iterator it = peers.iterator();
|
||||
Iterator<Peer> it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
Peer peer = it.next();
|
||||
if (peer.isChoking() && peer.isInterested())
|
||||
{
|
||||
count++;
|
||||
@@ -424,9 +470,9 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < maxUploaders && interested.size() > 0)
|
||||
while (uploaders < maxUploaders && !interested.isEmpty())
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
Peer peer = interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
@@ -438,7 +484,6 @@ public class PeerCoordinator implements PeerListener
|
||||
peerCount = peers.size();
|
||||
}
|
||||
interestedAndChoking = count;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getBitMap()
|
||||
@@ -447,7 +492,7 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we don't have the given piece yet.
|
||||
* @return true if we still want the given piece
|
||||
*/
|
||||
public boolean gotHave(Peer peer, int piece)
|
||||
{
|
||||
@@ -471,10 +516,10 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
Iterator<Piece> it = wantedPieces.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
Piece p = it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i)) {
|
||||
p.addPeer(peer);
|
||||
@@ -485,27 +530,56 @@ public class PeerCoordinator implements PeerListener
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be somewhat less than the max conns per torrent,
|
||||
* but not too much less, so a torrent doesn't get stuck near the end.
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static final int END_GAME_THRESHOLD = 8;
|
||||
|
||||
/**
|
||||
* Max number of peers to get a piece from when in end game
|
||||
* @since 0.8.1
|
||||
*/
|
||||
private static final int MAX_PARALLEL_REQUESTS = 4;
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
*/
|
||||
public int wantPiece(Peer peer, BitField havePieces)
|
||||
{
|
||||
public int wantPiece(Peer peer, BitField havePieces) {
|
||||
return wantPiece(peer, havePieces, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
*
|
||||
* @param record if true, actually record in our data structures that we gave the
|
||||
* request to this peer. If false, do not update the data structures.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private int wantPiece(Peer peer, BitField havePieces, boolean record) {
|
||||
if (halted) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Piece piece = null;
|
||||
List<Piece> requested = new ArrayList();
|
||||
int wantedSize = END_GAME_THRESHOLD + 1;
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece piece = null;
|
||||
Collections.sort(wantedPieces); // Sort in order of rarest first.
|
||||
List requested = new ArrayList();
|
||||
Iterator it = wantedPieces.iterator();
|
||||
if (record)
|
||||
Collections.sort(wantedPieces); // Sort in order of rarest first.
|
||||
Iterator<Piece> it = wantedPieces.iterator();
|
||||
while (piece == null && it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
Piece p = it.next();
|
||||
// sorted by priority, so when we hit a disabled piece we are done
|
||||
if (p.isDisabled())
|
||||
break;
|
||||
if (havePieces.get(p.getId()) && !p.isRequested())
|
||||
{
|
||||
piece = p;
|
||||
@@ -515,19 +589,46 @@ public class PeerCoordinator implements PeerListener
|
||||
requested.add(p);
|
||||
}
|
||||
}
|
||||
if (piece == null)
|
||||
wantedSize = wantedPieces.size();
|
||||
} // synch
|
||||
|
||||
// Don't sync the following, deadlock from calling each Peer's isRequesting()
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
// AND if there are almost no wanted pieces left (real end game).
|
||||
// If we do end game all the time, we generate lots of extra traffic
|
||||
// when the seeder is super-slow and all the peers are "caught up"
|
||||
if (wantedSize > END_GAME_THRESHOLD)
|
||||
return -1; // nothing to request and not in end game
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
Iterator it2 = requested.iterator();
|
||||
// Even better would be to sort by number of requests
|
||||
if (record)
|
||||
Collections.shuffle(requested, _random);
|
||||
Iterator<Piece> it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it2.next();
|
||||
if (havePieces.get(p.getId()))
|
||||
{
|
||||
Piece p = it2.next();
|
||||
if (havePieces.get(p.getId())) {
|
||||
// limit number of parallel requests
|
||||
int requestedCount = 0;
|
||||
for (Peer pr : peers) {
|
||||
// deadlock if synced on wantedPieces
|
||||
if (pr.isRequesting(p.getId())) {
|
||||
if (pr.equals(peer)) {
|
||||
// don't give it to him again
|
||||
requestedCount = MAX_PARALLEL_REQUESTS;
|
||||
break;
|
||||
}
|
||||
if (++requestedCount >= MAX_PARALLEL_REQUESTS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (requestedCount >= MAX_PARALLEL_REQUESTS)
|
||||
continue;
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -536,16 +637,83 @@ public class PeerCoordinator implements PeerListener
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here - limit # of parallel attempts and
|
||||
// Should be a lot smarter here -
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
// This is where the flaws of the snark data model are really exposed.
|
||||
// Could also randomize within the duplicate set rather than strict rarest-first
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
}
|
||||
}
|
||||
piece.setRequested(true);
|
||||
if (record) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(peer + " is now requesting: piece " + piece + " priority " + piece.getPriority());
|
||||
piece.setRequested(true);
|
||||
}
|
||||
return piece.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps file priorities to piece priorities.
|
||||
* Call after updating file priorities Storage.setPriority()
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void updatePiecePriorities() {
|
||||
int[] pri = storage.getPiecePriorities();
|
||||
if (pri == null) {
|
||||
_log.debug("Updated piece priorities called but no priorities to set?");
|
||||
return;
|
||||
}
|
||||
synchronized(wantedPieces) {
|
||||
// Add incomplete and previously unwanted pieces to the list
|
||||
// Temp to avoid O(n**2)
|
||||
BitField want = new BitField(pri.length);
|
||||
for (Piece p : wantedPieces) {
|
||||
want.set(p.getId());
|
||||
}
|
||||
BitField bitfield = storage.getBitField();
|
||||
for (int i = 0; i < pri.length; i++) {
|
||||
if (pri[i] >= 0 && !bitfield.get(i)) {
|
||||
if (!want.get(i)) {
|
||||
Piece piece = new Piece(i);
|
||||
wantedPieces.add(piece);
|
||||
// As connections are already up, new Pieces will
|
||||
// not have their PeerID list populated, so do that.
|
||||
for (Peer p : peers) {
|
||||
PeerState s = p.state;
|
||||
if (s != null) {
|
||||
BitField bf = s.bitfield;
|
||||
if (bf != null && bf.get(i))
|
||||
piece.addPeer(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// now set the new priorities and remove newly unwanted pieces
|
||||
for (Iterator<Piece> iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece p = iter.next();
|
||||
int priority = pri[p.getId()];
|
||||
if (priority >= 0) {
|
||||
p.setPriority(priority);
|
||||
} else {
|
||||
iter.remove();
|
||||
// cancel all peers
|
||||
for (Peer peer : peers) {
|
||||
peer.cancel(p.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Updated piece priorities, now wanted: " + wantedPieces);
|
||||
// if we added pieces, they will be in-order unless we shuffle
|
||||
Collections.shuffle(wantedPieces, _random);
|
||||
|
||||
// update request queues, in case we added wanted pieces
|
||||
// and we were previously uninterested
|
||||
for (Peer peer : peers) {
|
||||
peer.request();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,14 +781,18 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
// No need to announce have piece to peers.
|
||||
// Assume we got a good piece, we don't really care anymore.
|
||||
return true;
|
||||
// Well, this could be caused by a change in priorities, so
|
||||
// only return true if we already have it, otherwise might as well keep it.
|
||||
if (storage.getBitField().get(piece))
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
{
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -639,15 +811,16 @@ public class PeerCoordinator implements PeerListener
|
||||
wantedPieces.remove(p);
|
||||
}
|
||||
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
|
||||
// Announce to the world we have it!
|
||||
// Disconnect from other seeders when we get the last piece
|
||||
synchronized(peers)
|
||||
{
|
||||
List toDisconnect = new ArrayList();
|
||||
Iterator it = peers.iterator();
|
||||
List<Peer> toDisconnect = new ArrayList();
|
||||
Iterator<Peer> it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
Peer p = it.next();
|
||||
if (p.isConnected())
|
||||
{
|
||||
if (completed() && p.isCompleted())
|
||||
@@ -659,14 +832,14 @@ public class PeerCoordinator implements PeerListener
|
||||
it = toDisconnect.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
Peer p = it.next();
|
||||
p.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** this does nothing but logging */
|
||||
public void gotChoke(Peer peer, boolean choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -680,8 +853,6 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
if (interest)
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < allowedUploaders())
|
||||
{
|
||||
if(peer.isChoking())
|
||||
@@ -692,7 +863,6 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.info("Unchoke: " + peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
@@ -725,77 +895,162 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void removePeerFromPieces(Peer peer) {
|
||||
synchronized(wantedPieces) {
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
for(Iterator<Piece> iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = iter.next();
|
||||
piece.removePeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simple method to save a partial piece on peer disconnection
|
||||
/**
|
||||
* Save partial pieces on peer disconnection
|
||||
* and hopefully restart it later.
|
||||
* Only one partial piece is saved at a time.
|
||||
* Replace it if a new one is bigger or the old one is too old.
|
||||
* Replace a partial piece in the List if the new one is bigger.
|
||||
* Storage method is private so we can expand to save multiple partials
|
||||
* if we wish.
|
||||
*
|
||||
* Also mark the piece unrequested if this peer was the only one.
|
||||
*
|
||||
* @param peer partials, must include the zero-offset (empty) ones too
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private Request savedRequest = null;
|
||||
private long savedRequestTime = 0;
|
||||
public void savePeerPartial(PeerState state)
|
||||
public void savePartialPieces(Peer peer, List<PartialPiece> partials)
|
||||
{
|
||||
if (halted)
|
||||
return;
|
||||
Request req = state.getPartialRequest();
|
||||
if (req == null)
|
||||
return;
|
||||
if (savedRequest == null ||
|
||||
req.off > savedRequest.off ||
|
||||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
|
||||
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(" Saving orphaned partial piece " + req);
|
||||
if (savedRequest != null)
|
||||
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
|
||||
}
|
||||
if (halted)
|
||||
return;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Partials received from " + peer + ": " + partials);
|
||||
synchronized(wantedPieces) {
|
||||
for (PartialPiece pp : partials) {
|
||||
if (pp.getDownloaded() > 0) {
|
||||
// PartialPiece.equals() only compares piece number, which is what we want
|
||||
int idx = partialPieces.indexOf(pp);
|
||||
if (idx < 0) {
|
||||
partialPieces.add(pp);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Saving orphaned partial piece (new) " + pp);
|
||||
} else if (idx >= 0 && pp.getDownloaded() > partialPieces.get(idx).getDownloaded()) {
|
||||
// replace what's there now
|
||||
partialPieces.set(idx, pp);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Saving orphaned partial piece (bigger) " + pp);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Discarding partial piece (not bigger)" + pp);
|
||||
}
|
||||
int max = getMaxConnections();
|
||||
if (partialPieces.size() > max) {
|
||||
// sorts by remaining bytes, least first
|
||||
Collections.sort(partialPieces);
|
||||
PartialPiece gone = partialPieces.remove(max);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Discarding orphaned partial piece (list full)" + gone);
|
||||
}
|
||||
} // else drop the empty partial piece
|
||||
// synchs on wantedPieces...
|
||||
markUnrequestedIfOnlyOne(peer, pp.getPiece());
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Partial list size now: " + partialPieces.size());
|
||||
}
|
||||
savedRequest = req;
|
||||
savedRequestTime = System.currentTimeMillis();
|
||||
} else {
|
||||
if (req.piece != savedRequest.piece)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(" Discarding orphaned partial piece " + req);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return partial piece if it's still wanted and peer has it.
|
||||
/**
|
||||
* Return partial piece to the PeerState if it's still wanted and peer has it.
|
||||
* @param havePieces pieces the peer has, the rv will be one of these
|
||||
*
|
||||
* @return PartialPiece or null
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public Request getPeerPartial(BitField havePieces) {
|
||||
if (savedRequest == null)
|
||||
return null;
|
||||
if (! havePieces.get(savedRequest.piece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
|
||||
return null;
|
||||
}
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
if (piece.getId() == savedRequest.piece) {
|
||||
Request req = savedRequest;
|
||||
piece.setRequested(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Restoring orphaned partial piece " + req);
|
||||
savedRequest = null;
|
||||
return req;
|
||||
public PartialPiece getPartialPiece(Peer peer, BitField havePieces) {
|
||||
synchronized(wantedPieces) {
|
||||
// sorts by remaining bytes, least first
|
||||
Collections.sort(partialPieces);
|
||||
for (Iterator<PartialPiece> iter = partialPieces.iterator(); iter.hasNext(); ) {
|
||||
PartialPiece pp = iter.next();
|
||||
int savedPiece = pp.getPiece();
|
||||
if (havePieces.get(savedPiece)) {
|
||||
iter.remove();
|
||||
// this is just a double-check, it should be in there
|
||||
for(Piece piece : wantedPieces) {
|
||||
if (piece.getId() == savedPiece) {
|
||||
piece.setRequested(true);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Restoring orphaned partial piece " + pp +
|
||||
" Partial list size now: " + partialPieces.size());
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN) && !partialPieces.isEmpty())
|
||||
_log.warn("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...
|
||||
int piece = wantPiece(peer, havePieces);
|
||||
if (piece >= 0) {
|
||||
try {
|
||||
return new PartialPiece(piece, metainfo.getPieceLength(piece));
|
||||
} catch (OutOfMemoryError oom) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("OOM creating new partial piece");
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We have no partial piece to return");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we are downloading from the peer and may need to ask for
|
||||
* a new piece. Returns true if wantPiece() or getPartialPiece() would return a piece.
|
||||
*
|
||||
* @param peer the Peer that will be asked to provide the piece.
|
||||
* @param havePieces a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return if we want any of what the peer has
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public boolean needPiece(Peer peer, BitField havePieces) {
|
||||
synchronized(wantedPieces) {
|
||||
for (PartialPiece pp : partialPieces) {
|
||||
int savedPiece = pp.getPiece();
|
||||
if (havePieces.get(savedPiece)) {
|
||||
// this is just a double-check, it should be in there
|
||||
for(Piece piece : wantedPieces) {
|
||||
if (piece.getId() == savedPiece) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("We could restore orphaned partial piece " + pp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return wantPiece(peer, havePieces, false) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove saved state for this piece.
|
||||
* Unless we are in the end game there shouldnt be anything in there.
|
||||
* Do not call with wantedPieces lock held (deadlock)
|
||||
*/
|
||||
private void removePartialPiece(int piece) {
|
||||
synchronized(wantedPieces) {
|
||||
for (Iterator<PartialPiece> iter = partialPieces.iterator(); iter.hasNext(); ) {
|
||||
PartialPiece pp = iter.next();
|
||||
if (pp.getPiece() == piece) {
|
||||
iter.remove();
|
||||
// there should be only one but keep going to be sure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We no longer want orphaned piece " + savedRequest);
|
||||
savedRequest = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Clear the requested flag for a piece if the peer
|
||||
@@ -804,33 +1059,25 @@ public class PeerCoordinator implements PeerListener
|
||||
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
|
||||
{
|
||||
// see if anybody else is requesting
|
||||
synchronized (peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer p = (Peer)it.next();
|
||||
for (Peer p : peers) {
|
||||
if (p.equals(peer))
|
||||
continue;
|
||||
if (p.state == null)
|
||||
continue;
|
||||
int[] arr = p.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
if(arr[i] == piece) {
|
||||
// FIXME don't go into the state
|
||||
if (p.state.getRequestedPieces().contains(Integer.valueOf(piece))) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Another peer is requesting piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nobody is, so mark unrequested
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext()) {
|
||||
Piece p = (Piece)it.next();
|
||||
if (p.getId() == piece) {
|
||||
p.setRequested(false);
|
||||
for (Piece pc : wantedPieces) {
|
||||
if (pc.getId() == piece) {
|
||||
pc.setRequested(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing from request list piece " + piece);
|
||||
return;
|
||||
@@ -839,20 +1086,6 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
** Once for each piece
|
||||
** This is enough trouble, maybe would be easier just to regenerate
|
||||
** the requested list from scratch instead.
|
||||
*/
|
||||
public void markUnrequested(Peer peer)
|
||||
{
|
||||
if (halted || peer.state == null)
|
||||
return;
|
||||
int[] arr = peer.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
markUnrequestedIfOnlyOne(peer, arr[i]);
|
||||
}
|
||||
|
||||
/** Return number of allowed uploaders for this torrent.
|
||||
** Check with Snark to see if we are over the total upload limit.
|
||||
*/
|
||||
|
||||
@@ -24,19 +24,33 @@ 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;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
|
||||
/**
|
||||
* Store the address information about a peer.
|
||||
* Prior to 0.8.1, an instantiation required a peer ID, and full Destination address.
|
||||
* Starting with 0.8.1, to support compact tracker responses,
|
||||
* a PeerID can be instantiated with a Destination Hash alone.
|
||||
* The full destination lookup is deferred until getAddress() is called,
|
||||
* and the PeerID is not required.
|
||||
* Equality is now determined solely by the dest hash.
|
||||
*/
|
||||
public class PeerID implements Comparable
|
||||
{
|
||||
private final byte[] id;
|
||||
private final Destination address;
|
||||
private byte[] id;
|
||||
private Destination address;
|
||||
private final int port;
|
||||
|
||||
private byte[] destHash;
|
||||
/** whether we have tried to get the dest from the hash - only do once */
|
||||
private boolean triedDestLookup;
|
||||
private final int hash;
|
||||
|
||||
public PeerID(byte[] id, Destination address)
|
||||
@@ -44,7 +58,7 @@ public class PeerID implements Comparable
|
||||
this.id = id;
|
||||
this.address = address;
|
||||
this.port = 6881;
|
||||
|
||||
this.destHash = address.calculateHash().getData();
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
@@ -77,17 +91,49 @@ public class PeerID implements Comparable
|
||||
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||
|
||||
port = 6881;
|
||||
|
||||
this.destHash = address.calculateHash().getData();
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PeerID from a destHash
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public PeerID(byte[] dest_hash) throws InvalidBEncodingException
|
||||
{
|
||||
// id and address remain null
|
||||
port = 6881;
|
||||
if (dest_hash.length != 32)
|
||||
throw new InvalidBEncodingException("bad hash length");
|
||||
destHash = dest_hash;
|
||||
hash = DataHelper.hashCode(dest_hash);
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Destination getAddress()
|
||||
/** for connecting out to peer based on desthash @since 0.8.1 */
|
||||
public void setID(byte[] xid)
|
||||
{
|
||||
id = xid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination.
|
||||
* If this PeerId was instantiated with a destHash,
|
||||
* and we have not yet done so, lookup the full destination, which may take
|
||||
* up to 10 seconds.
|
||||
* @return Dest or null if unknown
|
||||
*/
|
||||
public synchronized Destination getAddress()
|
||||
{
|
||||
if (address == null && destHash != null && !triedDestLookup) {
|
||||
String b32 = Base32.encode(destHash) + ".b32.i2p";
|
||||
address = I2PAppContext.getGlobalContext().namingService().lookup(b32);
|
||||
triedDestLookup = true;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -96,16 +142,19 @@ public class PeerID implements Comparable
|
||||
return port;
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public byte[] getDestHash()
|
||||
{
|
||||
return destHash;
|
||||
}
|
||||
|
||||
private int calculateHash()
|
||||
{
|
||||
int b = 0;
|
||||
for (int i = 0; i < id.length; i++)
|
||||
b ^= id[i];
|
||||
return (b ^ address.hashCode()) ^ port;
|
||||
return DataHelper.hashCode(destHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a PeerID is the exclusive or of all id bytes.
|
||||
* The hash code of a PeerID is the hashcode of the desthash
|
||||
*/
|
||||
@Override
|
||||
public int hashCode()
|
||||
@@ -115,18 +164,15 @@ public class PeerID implements Comparable
|
||||
|
||||
/**
|
||||
* Returns true if and only if this peerID and the given peerID have
|
||||
* the same 20 bytes as ID.
|
||||
* the same destination hash
|
||||
*/
|
||||
public boolean sameID(PeerID pid)
|
||||
{
|
||||
boolean equal = true;
|
||||
for (int i = 0; equal && i < id.length; i++)
|
||||
equal = id[i] == pid.id[i];
|
||||
return equal;
|
||||
return DataHelper.eq(destHash, pid.getDestHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* Two PeerIDs are equal when they have the same id, address and port.
|
||||
* Two PeerIDs are equal when they have the same dest hash
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
@@ -135,9 +181,7 @@ public class PeerID implements Comparable
|
||||
{
|
||||
PeerID pid = (PeerID)o;
|
||||
|
||||
return port == pid.port
|
||||
&& address.equals(pid.address)
|
||||
&& sameID(pid);
|
||||
return sameID(pid);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
@@ -145,6 +189,7 @@ public class PeerID implements Comparable
|
||||
|
||||
/**
|
||||
* Compares port, address and id.
|
||||
* @deprecated unused? and will NPE now that address can be null?
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
@@ -176,6 +221,8 @@ public class PeerID implements Comparable
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
if (id == null || address == null)
|
||||
return "unkn@" + Base64.encode(destHash).substring(0, 6);
|
||||
int nonZero = 0;
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] != 0) {
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Listener for Peer events.
|
||||
*/
|
||||
public interface PeerListener
|
||||
interface PeerListener
|
||||
{
|
||||
/**
|
||||
* Called when the connection to the peer has started and the
|
||||
@@ -145,13 +147,27 @@ public interface PeerListener
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when we are downloading from the peer and may need to ask for
|
||||
* a new piece. Returns true if wantPiece() or getPartialPiece() would return a piece.
|
||||
*
|
||||
* @param peer the Peer that will be asked to provide the piece.
|
||||
* @param bitfield a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return if we want any of what the peer has
|
||||
* @since 0.8.2
|
||||
*/
|
||||
boolean needPiece(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when the peer has disconnected and the peer task may have a partially
|
||||
* downloaded piece that the PeerCoordinator can save
|
||||
*
|
||||
* @param state the PeerState for the peer
|
||||
* @param peer the peer
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void savePeerPartial(PeerState state); /* FIXME Exporting non-public type through public API FIXME */
|
||||
void savePartialPieces(Peer peer, List<PartialPiece> pcs);
|
||||
|
||||
/**
|
||||
* Called when a peer has connected and there may be a partially
|
||||
@@ -160,13 +176,7 @@ public interface PeerListener
|
||||
* @param havePieces the have-pieces bitmask for the peer
|
||||
*
|
||||
* @return request (contains the partial data and valid length)
|
||||
* @since 0.8.2
|
||||
*/
|
||||
Request getPeerPartial(BitField havePieces); /* FIXME Exporting non-public type through public API FIXME */
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
* This prevents premature end game
|
||||
*
|
||||
* @param peer the peer that is disconnecting
|
||||
*/
|
||||
void markUnrequested(Peer peer);
|
||||
PartialPiece getPartialPiece(Peer peer, BitField havePieces);
|
||||
}
|
||||
|
||||
@@ -20,18 +20,27 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerState
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
|
||||
class PeerState implements DataLoader
|
||||
{
|
||||
private Log _log = new Log(PeerState.class);
|
||||
final Peer peer;
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class);
|
||||
private final Peer peer;
|
||||
final PeerListener listener;
|
||||
final MetaInfo metainfo;
|
||||
private final MetaInfo metainfo;
|
||||
|
||||
// Interesting and choking describes whether we are interested in or
|
||||
// are choking the other side.
|
||||
@@ -47,6 +56,7 @@ class PeerState
|
||||
long downloaded;
|
||||
long uploaded;
|
||||
|
||||
/** the pieces the peer has */
|
||||
BitField bitfield;
|
||||
|
||||
// Package local for use by Peer.
|
||||
@@ -54,12 +64,11 @@ class PeerState
|
||||
final PeerConnectionOut out;
|
||||
|
||||
// Outstanding request
|
||||
private final List outstandingRequests = new ArrayList();
|
||||
private final List<Request> outstandingRequests = new ArrayList();
|
||||
/** the tail (NOT the head) of the request queue */
|
||||
private Request lastRequest = null;
|
||||
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
// FIXME if piece size < PARTSIZE, pipeline could be bigger
|
||||
private final static int MAX_PIPELINE = 5; // this is for outbound requests
|
||||
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
|
||||
public final static int PARTSIZE = 16*1024; // outbound request
|
||||
@@ -90,14 +99,27 @@ class PeerState
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
|
||||
|
||||
boolean resend = choked && !choke;
|
||||
choked = choke;
|
||||
if (choked)
|
||||
resend = true;
|
||||
|
||||
listener.gotChoke(peer, choke);
|
||||
|
||||
if (!choked && interesting)
|
||||
request();
|
||||
if (interesting && !choked)
|
||||
request(resend);
|
||||
|
||||
if (choked) {
|
||||
out.cancelRequestMessages();
|
||||
// old Roberts thrash us here, choke+unchoke right together
|
||||
// The only problem with returning the partials to the coordinator
|
||||
// is that chunks above a missing request are lost.
|
||||
// Future enhancements to PartialPiece could keep track of the holes.
|
||||
List<PartialPiece> pcs = returnPartialPieces();
|
||||
if (!pcs.isEmpty()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " got choked, returning partial pieces to the PeerCoordinator: " + pcs);
|
||||
listener.savePartialPieces(this.peer, pcs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void interestedMessage(boolean interest)
|
||||
@@ -152,7 +174,16 @@ class PeerState
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
}
|
||||
setInteresting(listener.gotBitField(peer, bitfield));
|
||||
boolean interest = listener.gotBitField(peer, bitfield);
|
||||
setInteresting(interest);
|
||||
if (bitfield.complete() && !interest) {
|
||||
// They are seeding and we are seeding,
|
||||
// why did they contact us? (robert)
|
||||
// Dump them quick before we send our whole bitmap
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Disconnecting seed that connects to seeds: " + peer);
|
||||
peer.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
void requestMessage(int piece, int begin, int length)
|
||||
@@ -186,6 +217,7 @@ class PeerState
|
||||
|
||||
// Limit total pipelined requests to MAX_PIPELINE bytes
|
||||
// to conserve memory and prevent DOS
|
||||
// Todo: limit number of requests also? (robert 64 x 4KB)
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -193,13 +225,28 @@ class PeerState
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Queueing (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
|
||||
// don't load the data into mem now, let PeerConnectionOut do it
|
||||
out.sendPiece(piece, begin, length, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls
|
||||
*
|
||||
* @return bytes or null for errors
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public byte[] loadData(int piece, int begin, int length) {
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
@@ -211,13 +258,13 @@ class PeerState
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
out.sendPiece(piece, begin, length, pieceBytes);
|
||||
return pieceBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,11 +279,16 @@ class PeerState
|
||||
|
||||
// This is used to flag that we have to back up from the firstOutstandingRequest
|
||||
// when calculating how far we've gotten
|
||||
Request pendingRequest = null;
|
||||
private Request pendingRequest;
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* Called when a full chunk (i.e. a piece message) has been received by
|
||||
* PeerConnectionIn.
|
||||
*
|
||||
* This may block quite a while if it is the last chunk for a piece,
|
||||
* as it calls the listener, who stores the piece and then calls
|
||||
* havePiece for every peer on the torrent (including us).
|
||||
*
|
||||
*/
|
||||
void pieceMessage(Request req)
|
||||
{
|
||||
@@ -244,11 +296,15 @@ class PeerState
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
pendingRequest = null;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("got end of Chunk("
|
||||
+ req.piece + "," + req.off + "," + req.len + ") from "
|
||||
+ peer);
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
// warning - may block here for a while
|
||||
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -259,30 +315,40 @@ class PeerState
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.piece + " from " + peer);
|
||||
// XXX ARGH What now !?!
|
||||
// FIXME Why would we set downloaded to 0?
|
||||
downloaded = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ok done with this one
|
||||
synchronized(this) {
|
||||
pendingRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index in outstandingRequests or -1
|
||||
*/
|
||||
synchronized private int getFirstOutstandingRequest(int piece)
|
||||
{
|
||||
{
|
||||
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||
if (((Request)outstandingRequests.get(i)).piece == piece)
|
||||
if (outstandingRequests.get(i).piece == piece)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a piece message is being processed by the incoming
|
||||
* connection. Returns null when there was no such request. It also
|
||||
* connection. That is, when the header of the piece message was received.
|
||||
* Returns null when there was no such request. It also
|
||||
* requeues/sends requests when it thinks that they must have been
|
||||
* lost.
|
||||
*/
|
||||
Request getOutstandingRequest(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getChunk("
|
||||
+ piece + "," + begin + "," + length + ") "
|
||||
_log.debug("got start of Chunk("
|
||||
+ piece + "," + begin + "," + length + ") from "
|
||||
+ peer);
|
||||
|
||||
int r = getFirstOutstandingRequest(piece);
|
||||
@@ -302,12 +368,12 @@ class PeerState
|
||||
Request req;
|
||||
synchronized(this)
|
||||
{
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
req = outstandingRequests.get(r);
|
||||
while (req.piece == piece && req.off != begin
|
||||
&& r < outstandingRequests.size() - 1)
|
||||
{
|
||||
r++;
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
req = outstandingRequests.get(r);
|
||||
}
|
||||
|
||||
// Something wrong?
|
||||
@@ -322,6 +388,9 @@ class PeerState
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
|
||||
// note that this request is being read
|
||||
pendingRequest = req;
|
||||
|
||||
// Report missing requests.
|
||||
if (r != 0)
|
||||
@@ -331,7 +400,7 @@ class PeerState
|
||||
+ ", wanted for peer: " + peer);
|
||||
for (int i = 0; i < r; i++)
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
Request dropReq = outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
@@ -345,56 +414,64 @@ class PeerState
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
pendingRequest = req;
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
// get longest partial piece
|
||||
Request getPartialRequest()
|
||||
{
|
||||
Request req = null;
|
||||
for (int i = 0; i < outstandingRequests.size(); i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
int j = getFirstOutstandingRequest(r1.piece);
|
||||
if (j == -1)
|
||||
continue;
|
||||
Request r2 = (Request)outstandingRequests.get(j);
|
||||
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
|
||||
req = r2;
|
||||
}
|
||||
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
|
||||
if (pendingRequest.off != 0)
|
||||
req = pendingRequest;
|
||||
else
|
||||
req = null;
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
// return array of pieces terminated by -1
|
||||
// remove most duplicates
|
||||
// but still could be some duplicates, not guaranteed
|
||||
int[] getRequestedPieces()
|
||||
{
|
||||
int size = outstandingRequests.size();
|
||||
int[] arr = new int[size+2];
|
||||
int pc = -1;
|
||||
int pos = 0;
|
||||
if (pendingRequest != null) {
|
||||
pc = pendingRequest.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
Request req = null;
|
||||
for (int i = 0; i < size; i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
if (pc != r1.piece) {
|
||||
pc = r1.piece;
|
||||
arr[pos++] = pc;
|
||||
/**
|
||||
* @return lowest offset of any request for the piece
|
||||
* @since 0.8.2
|
||||
*/
|
||||
synchronized private Request getLowestOutstandingRequest(int piece) {
|
||||
Request rv = null;
|
||||
int lowest = Integer.MAX_VALUE;
|
||||
for (Request r : outstandingRequests) {
|
||||
if (r.piece == piece && r.off < lowest) {
|
||||
lowest = r.off;
|
||||
rv = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
arr[pos] = -1;
|
||||
return(arr);
|
||||
if (pendingRequest != null &&
|
||||
pendingRequest.piece == piece && pendingRequest.off < lowest)
|
||||
rv = pendingRequest;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " lowest for " + piece + " is " + rv + " out of " + pendingRequest + " and " + outstandingRequests);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get partial pieces, give them back to PeerCoordinator.
|
||||
* Clears the request queue.
|
||||
* @return List of PartialPieces, even those with an offset == 0, or empty list
|
||||
* @since 0.8.2
|
||||
*/
|
||||
synchronized List<PartialPiece> returnPartialPieces()
|
||||
{
|
||||
Set<Integer> pcs = getRequestedPieces();
|
||||
List<PartialPiece> rv = new ArrayList(pcs.size());
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null)
|
||||
rv.add(new PartialPiece(req));
|
||||
}
|
||||
outstandingRequests.clear();
|
||||
pendingRequest = null;
|
||||
lastRequest = null;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all pieces we are currently requesting, or empty Set
|
||||
*/
|
||||
synchronized Set<Integer> getRequestedPieces() {
|
||||
Set<Integer> rv = new HashSet(outstandingRequests.size() + 1);
|
||||
for (Request req : outstandingRequests) {
|
||||
rv.add(Integer.valueOf(req.piece));
|
||||
if (pendingRequest != null)
|
||||
rv.add(Integer.valueOf(pendingRequest.piece));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
@@ -405,6 +482,27 @@ class PeerState
|
||||
out.cancelRequest(piece, begin, length);
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
void extensionMessage(int id, byte[] bs)
|
||||
{
|
||||
if (id == 0) {
|
||||
InputStream is = new ByteArrayInputStream(bs);
|
||||
try {
|
||||
BDecoder dec = new BDecoder(is);
|
||||
BEValue bev = dec.bdecodeMap();
|
||||
Map map = bev.getMap();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got extension handshake message " + bev.toString());
|
||||
} catch (Exception e) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Failed extension decode", e);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got extended message type: " + id + " length: " + bs.length);
|
||||
}
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -412,22 +510,48 @@ class PeerState
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* We now have this piece.
|
||||
* Tell the peer and cancel any requests for the piece.
|
||||
*/
|
||||
void havePiece(int piece)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Tell the other side that we are no longer interested in any of
|
||||
// the outstanding requests for this piece.
|
||||
cancelPiece(piece);
|
||||
|
||||
// Tell the other side that we really have this piece.
|
||||
out.sendHave(piece);
|
||||
|
||||
// Request something else if necessary.
|
||||
addRequest();
|
||||
|
||||
/**** taken care of in addRequest()
|
||||
synchronized(this)
|
||||
{
|
||||
// Is the peer still interesting?
|
||||
if (lastRequest == null)
|
||||
setInteresting(false);
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the other side that we are no longer interested in any of
|
||||
* the outstanding requests (if any) for this piece.
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized void cancelPiece(int piece) {
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator it = outstandingRequests.iterator();
|
||||
Iterator<Request> it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
Request req = it.next();
|
||||
if (req.piece == piece)
|
||||
{
|
||||
it.remove();
|
||||
@@ -436,32 +560,38 @@ class PeerState
|
||||
out.sendCancel(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the other side that we really have this piece.
|
||||
out.sendHave(piece);
|
||||
|
||||
// Request something else if necessary.
|
||||
addRequest();
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Is the peer still interesting?
|
||||
if (lastRequest == null)
|
||||
setInteresting(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Starts or resumes requesting pieces.
|
||||
private void request()
|
||||
/**
|
||||
* Are we currently requesting the piece?
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized boolean isRequesting(int piece) {
|
||||
if (pendingRequest != null && pendingRequest.piece == piece)
|
||||
return true;
|
||||
for (Request req : outstandingRequests) {
|
||||
if (req.piece == piece)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or resumes requesting pieces.
|
||||
* @param resend should we resend outstanding requests?
|
||||
*/
|
||||
private void request(boolean resend)
|
||||
{
|
||||
// Are there outstanding requests that have to be resend?
|
||||
if (resend)
|
||||
{
|
||||
synchronized (this) {
|
||||
out.sendRequests(outstandingRequests);
|
||||
if (!outstandingRequests.isEmpty()) {
|
||||
out.sendRequests(outstandingRequests);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resending requests to " + peer + outstandingRequests);
|
||||
}
|
||||
}
|
||||
resend = false;
|
||||
}
|
||||
|
||||
// Add/Send some more requests if necessary.
|
||||
@@ -470,17 +600,53 @@ class PeerState
|
||||
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
* Then send interested if we weren't.
|
||||
* Then send new requests if not choked.
|
||||
* If nothing to request, send not interested if we were.
|
||||
*
|
||||
* This is called from several places:
|
||||
*<pre>
|
||||
* By getOustandingRequest() when the first part of a chunk comes in
|
||||
* By havePiece() when somebody got a new piece completed
|
||||
* By chokeMessage() when we receive an unchoke
|
||||
* By setInteresting() when we are now interested
|
||||
* By PeerCoordinator.updatePiecePriorities()
|
||||
*</pre>
|
||||
*/
|
||||
synchronized private void addRequest()
|
||||
synchronized void addRequest()
|
||||
{
|
||||
// no bitfield yet? nothing to request then.
|
||||
if (bitfield == null)
|
||||
return;
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
if (more_pieces && lastRequest == null) {
|
||||
// we have nothing in the queue right now
|
||||
if (!interesting) {
|
||||
// If we need something, set interesting but delay pulling
|
||||
// a request from the PeerCoordinator until unchoked.
|
||||
if (listener.needPiece(this.peer, bitfield)) {
|
||||
setInteresting(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " addRequest() we need something, setting interesting, delaying requestNextPiece()");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " addRequest() needs nothing");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (choked) {
|
||||
// If choked, delay pulling
|
||||
// a request from the PeerCoordinator until unchoked.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " addRequest() we are choked, delaying requestNextPiece()");
|
||||
return;
|
||||
}
|
||||
more_pieces = requestNextPiece();
|
||||
else if (more_pieces) // We want something
|
||||
} else if (more_pieces) // We want something
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
@@ -508,43 +674,55 @@ class PeerState
|
||||
}
|
||||
}
|
||||
|
||||
// failsafe
|
||||
if (interesting && lastRequest == null && outstandingRequests.isEmpty())
|
||||
setInteresting(false);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " requests " + outstandingRequests);
|
||||
}
|
||||
|
||||
// Starts requesting first chunk of next piece. Returns true if
|
||||
// something has been added to the requests, false otherwise.
|
||||
/**
|
||||
* Starts requesting first chunk of next piece. Returns true if
|
||||
* something has been added to the requests, false otherwise.
|
||||
* Caller should synchronize.
|
||||
*/
|
||||
private boolean requestNextPiece()
|
||||
{
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
if (bitfield != null) {
|
||||
// Check for adopting an orphaned partial piece
|
||||
Request r = listener.getPeerPartial(bitfield);
|
||||
if (r != null) {
|
||||
// Check that r not already in outstandingRequests
|
||||
int[] arr = getRequestedPieces();
|
||||
boolean found = false;
|
||||
for (int i = 0; arr[i] >= 0; i++) {
|
||||
if (arr[i] == r.piece) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
PartialPiece pp = listener.getPartialPiece(peer, bitfield);
|
||||
if (pp != null) {
|
||||
// Double-check that r not already in outstandingRequests
|
||||
if (!getRequestedPieces().contains(Integer.valueOf(pp.getPiece()))) {
|
||||
Request r = pp.getRequest();
|
||||
outstandingRequests.add(r);
|
||||
if (!choked)
|
||||
out.sendRequest(r);
|
||||
lastRequest = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/******* getPartialPiece() does it all now
|
||||
// Note that in addition to the bitfield, PeerCoordinator uses
|
||||
// its request tracking and isRequesting() to determine
|
||||
// what piece to give us next.
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
// Fail safe to make sure we are interested
|
||||
// When we transition into the end game we may not be interested...
|
||||
if (!interesting) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " transition to end game, setting interesting");
|
||||
interesting = true;
|
||||
out.sendInterest(true);
|
||||
}
|
||||
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
@@ -562,34 +740,49 @@ class PeerState
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " no more pieces to request");
|
||||
}
|
||||
*******/
|
||||
}
|
||||
|
||||
// failsafe
|
||||
if (outstandingRequests.isEmpty())
|
||||
lastRequest = null;
|
||||
|
||||
// If we are not in the end game, we may run out of things to request
|
||||
// because we are asking other peers. Set not-interesting now rather than
|
||||
// wait for those other requests to be satisfied via havePiece()
|
||||
if (interesting && lastRequest == null) {
|
||||
interesting = false;
|
||||
out.sendInterest(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " nothing more to request, now uninteresting");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized void setInteresting(boolean interest)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setInteresting(" + interest + ")");
|
||||
|
||||
if (interest != interesting)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setInteresting(" + interest + ")");
|
||||
interesting = interest;
|
||||
out.sendInterest(interest);
|
||||
|
||||
if (interesting && !choked)
|
||||
request();
|
||||
request(true); // we shouldnt have any pending requests, but if we do, resend them
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void setChoking(boolean choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setChoking(" + choke + ")");
|
||||
|
||||
if (choking != choke)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setChoking(" + choke + ")");
|
||||
choking = choke;
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
@@ -605,4 +798,16 @@ class PeerState
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* debug
|
||||
* @return string or null
|
||||
* @since 0.8.1
|
||||
*/
|
||||
synchronized String getRequests() {
|
||||
if (outstandingRequests.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return outstandingRequests.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
/**
|
||||
* This class is used solely by PeerCoordinator.
|
||||
*/
|
||||
class Piece implements Comparable {
|
||||
|
||||
private int id;
|
||||
private Set peers;
|
||||
private Set<PeerID> peers;
|
||||
private boolean requested;
|
||||
/** @since 0.8.1 */
|
||||
private int priority;
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = Collections.synchronizedSet(new HashSet());
|
||||
this.requested = false;
|
||||
this.peers = new ConcurrentHashSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest priority first,
|
||||
* then rarest first
|
||||
*/
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
int pdiff = ((Piece)o).priority - this.priority; // reverse
|
||||
if (pdiff != 0)
|
||||
return pdiff;
|
||||
return this.peers.size() - ((Piece)o).peers.size();
|
||||
}
|
||||
|
||||
@@ -37,12 +49,25 @@ public class Piece implements Comparable {
|
||||
}
|
||||
|
||||
public int getId() { return this.id; }
|
||||
public Set getPeers() { return this.peers; }
|
||||
/** @deprecated unused */
|
||||
public Set<PeerID> getPeers() { return this.peers; }
|
||||
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
|
||||
/** @return default 0 @since 0.8.1 */
|
||||
public int getPriority() { return this.priority; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void setPriority(int p) { this.priority = p; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public boolean isDisabled() { return this.priority < 0; }
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void setDisabled() { this.priority = -1; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(id);
|
||||
|
||||
@@ -22,6 +22,7 @@ package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Holds all information needed for a partial piece request.
|
||||
* This class should be used only by PeerState, PeerConnectionIn, and PeerConnectionOut.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
|
||||
@@ -321,7 +321,7 @@ public class Snark
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
id = new byte[20];
|
||||
Random random = new Random();
|
||||
Random random = I2PAppContext.getGlobalContext().random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
id[i] = 0;
|
||||
@@ -437,7 +437,7 @@ public class Snark
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not create storage", ioe);
|
||||
fatal("Could not check or create storage", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,14 +618,14 @@ public class Snark
|
||||
command_interpreter = false;
|
||||
i++;
|
||||
}
|
||||
else if (args[i].equals("--eepproxy"))
|
||||
{
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
if (!configured)
|
||||
util.setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
//else if (args[i].equals("--eepproxy"))
|
||||
// {
|
||||
// String proxyHost = args[i+1];
|
||||
// String proxyPort = args[i+2];
|
||||
// if (!configured)
|
||||
// util.setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
// i += 3;
|
||||
// }
|
||||
else if (args[i].equals("--i2cp"))
|
||||
{
|
||||
String i2cpHost = args[i+1];
|
||||
@@ -734,7 +734,7 @@ public class Snark
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
stopTorrent();
|
||||
throw new RuntimeException("die bart die");
|
||||
throw new RuntimeException(s + (t == null ? "" : ": " + t));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -745,7 +745,7 @@ public class Snark
|
||||
_util.debug(s, level, null);
|
||||
}
|
||||
|
||||
/** coordinatorListener */
|
||||
/** CoordinatorListener - this does nothing */
|
||||
public void peerChange(PeerCoordinator coordinator, Peer peer)
|
||||
{
|
||||
// System.out.println(peer.toString());
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
@@ -42,6 +43,8 @@ public class Storage
|
||||
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;
|
||||
|
||||
private final StorageListener listener;
|
||||
private I2PSnarkUtil _util;
|
||||
@@ -228,6 +231,8 @@ public class Storage
|
||||
RAFlock = new Object[size];
|
||||
RAFtime = new long[size];
|
||||
RAFfile = new File[size];
|
||||
priorities = new int[size];
|
||||
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
@@ -286,6 +291,146 @@ public class Storage
|
||||
return needed == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* @return number of bytes remaining; -1 if unknown file
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public long remaining(String file) {
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
String canonical = null;
|
||||
if (f != null) {
|
||||
try {
|
||||
canonical = f.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
f = null;
|
||||
}
|
||||
}
|
||||
if (f != null && canonical.equals(file)) {
|
||||
if (complete())
|
||||
return 0;
|
||||
int psz = metainfo.getPieceLength(0);
|
||||
long start = bytes;
|
||||
long end = start + lengths[i];
|
||||
int pc = (int) (bytes / psz);
|
||||
long rv = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rv = Math.min(psz - (start % psz), lengths[i]);
|
||||
int pieces = metainfo.getPieces();
|
||||
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*psz < end)
|
||||
rv += psz;
|
||||
else
|
||||
rv += end - (((long)j) * psz);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
bytes += lengths[i];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int getPriority(String file) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// 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];
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must call setPiecePriorities() after calling this
|
||||
* @param file canonical path (non-directory)
|
||||
* @param pri default 0; <0 to disable
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void setPriority(String file, int pri) {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// 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;
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file priorities array.
|
||||
* @return null on error, if complete, or if only one file
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getFilePriorities() {
|
||||
return priorities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file priorities array.
|
||||
* Only call this when stopped, but after check()
|
||||
* @param p may be null
|
||||
* @since 0.8.1
|
||||
*/
|
||||
void setFilePriorities(int[] p) {
|
||||
priorities = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setPriority() for all changed files first,
|
||||
* then call this.
|
||||
* Set the piece priority to the highest priority
|
||||
* of all files spanning the piece.
|
||||
* Caller must pass array to the PeerCoordinator.
|
||||
* @return null on error, if complete, or if only one file
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int[] getPiecePriorities() {
|
||||
if (complete() || metainfo.getFiles() == null || priorities == null)
|
||||
return null;
|
||||
int[] rv = new int[metainfo.getPieces()];
|
||||
int file = 0;
|
||||
long pcEnd = -1;
|
||||
long fileEnd = lengths[0] - 1;
|
||||
int psz = metainfo.getPieceLength(0);
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
pcEnd += psz;
|
||||
int pri = priorities[file];
|
||||
while (fileEnd <= pcEnd && file < lengths.length - 1) {
|
||||
file++;
|
||||
long oldFileEnd = fileEnd;
|
||||
fileEnd += lengths[file];
|
||||
if (priorities[file] > pri && oldFileEnd < pcEnd)
|
||||
pri = priorities[file];
|
||||
}
|
||||
rv[i] = pri;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
@@ -295,6 +440,18 @@ public class Storage
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base file or directory name of the data,
|
||||
* as specified in the .torrent file, but filtered to remove
|
||||
* illegal characters. This is where the data actually is,
|
||||
* relative to the snark base dir.
|
||||
*
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public String getBaseName() {
|
||||
return filterName(metainfo.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
@@ -306,7 +463,7 @@ public class Storage
|
||||
/** use a saved bitfield and timestamp from a config file */
|
||||
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
File base = new SecureFile(rootDir, filterName(metainfo.getName()));
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
@@ -357,7 +514,7 @@ public class Storage
|
||||
RAFfile[i] = f;
|
||||
total += lengths[i];
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
long lm = f.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
@@ -380,10 +537,14 @@ public class Storage
|
||||
changed = true;
|
||||
checkCreateFiles();
|
||||
}
|
||||
if (complete())
|
||||
if (complete()) {
|
||||
_util.debug("Torrent is complete", Snark.NOTICE);
|
||||
else
|
||||
} else {
|
||||
// fixme saved priorities
|
||||
if (files != null)
|
||||
priorities = new int[files.size()];
|
||||
_util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,7 +624,7 @@ public class Storage
|
||||
else
|
||||
{
|
||||
// The final element (file) in the hierarchy.
|
||||
f = new File(base, name);
|
||||
f = new SecureFile(base, name);
|
||||
if (!f.createNewFile() && !f.exists())
|
||||
throw new IOException("Could not create file " + f);
|
||||
}
|
||||
@@ -509,6 +670,10 @@ public class Storage
|
||||
changed = true;
|
||||
synchronized(RAFlock[i]) {
|
||||
allocateFile(i);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_util.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
|
||||
@@ -517,8 +682,10 @@ public class Storage
|
||||
synchronized(RAFlock[i]) {
|
||||
checkRAF(i);
|
||||
rafs[i].setLength(lengths[i]);
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
// will be closed below
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,10 +694,25 @@ public class Storage
|
||||
{
|
||||
pieces = metainfo.getPieces();
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
int file = 0;
|
||||
long fileEnd = lengths[0];
|
||||
long pieceEnd = 0;
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
pieceEnd += length;
|
||||
while (fileEnd <= pieceEnd) {
|
||||
synchronized(RAFlock[file]) {
|
||||
try {
|
||||
closeRAF(file);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (++file >= rafs.length)
|
||||
break;
|
||||
fileEnd += lengths[file];
|
||||
}
|
||||
if (correctHash)
|
||||
{
|
||||
bitfield.set(i);
|
||||
@@ -545,13 +727,14 @@ public class Storage
|
||||
_probablyComplete = complete();
|
||||
// close all the files so we don't end up with a zillion open ones;
|
||||
// we will reopen as needed
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
synchronized(RAFlock[i]) {
|
||||
try {
|
||||
closeRAF(i);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
// Now closed above to avoid running out of file descriptors
|
||||
//for (int i = 0; i < rafs.length; i++) {
|
||||
// synchronized(RAFlock[i]) {
|
||||
// try {
|
||||
// closeRAF(i);
|
||||
// } catch (IOException ioe) {}
|
||||
// }
|
||||
//}
|
||||
|
||||
if (listener != null) {
|
||||
listener.storageAllChecked(this);
|
||||
@@ -560,6 +743,7 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
/** this calls openRAF(); caller must synnchronize and call closeRAF() */
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// caller synchronized
|
||||
@@ -568,7 +752,12 @@ public class Storage
|
||||
// the whole file?
|
||||
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
byte[] zeros;
|
||||
try {
|
||||
zeros = new byte[ZEROBLOCKSIZE];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
throw new IOException(oom.toString());
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
|
||||
{
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -44,7 +45,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class TrackerClient extends I2PAppThread
|
||||
{
|
||||
private static final Log _log = new Log(TrackerClient.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(TrackerClient.class);
|
||||
private static final String NO_EVENT = "";
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
@@ -150,7 +151,7 @@ public class TrackerClient extends I2PAppThread
|
||||
continue;
|
||||
String dest = _util.lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url + "]");
|
||||
_log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
|
||||
continue;
|
||||
}
|
||||
if (primary.startsWith("http://" + dest))
|
||||
@@ -162,7 +163,7 @@ public class TrackerClient extends I2PAppThread
|
||||
}
|
||||
}
|
||||
|
||||
if (tlist.size() <= 0) {
|
||||
if (tlist.isEmpty()) {
|
||||
// FIXME really need to get this message to the gui
|
||||
stop = true;
|
||||
_log.error("No valid trackers for infoHash: " + infoHash);
|
||||
@@ -182,7 +183,7 @@ public class TrackerClient extends I2PAppThread
|
||||
boolean runStarted = false;
|
||||
boolean firstTime = true;
|
||||
int consecutiveFails = 0;
|
||||
Random r = new Random();
|
||||
Random r = I2PAppContext.getGlobalContext().random();
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
@@ -258,17 +259,18 @@ public class TrackerClient extends I2PAppThread
|
||||
tr.started = true;
|
||||
|
||||
Set peers = info.getPeers();
|
||||
tr.seenPeers = peers.size();
|
||||
tr.seenPeers = info.getPeerCount();
|
||||
if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
|
||||
coordinator.trackerSeenPeers = tr.seenPeers;
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List ordered = new ArrayList(peers);
|
||||
Collections.shuffle(ordered);
|
||||
Collections.shuffle(ordered, r);
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
while ((!stop) && it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
// FIXME if id == us || dest == us continue;
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
@@ -355,7 +357,12 @@ public class TrackerClient extends I2PAppThread
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ "&compact=1" // NOTE: opentracker will return 400 for &compact alone
|
||||
+ ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
|
||||
if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
s += "&numwant=0";
|
||||
else
|
||||
s += "&numwant=" + _util.getMaxConnections();
|
||||
_util.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
tr.lastRequestTime = System.currentTimeMillis();
|
||||
@@ -430,7 +437,7 @@ public class TrackerClient extends I2PAppThread
|
||||
url.getPort() < 0;
|
||||
}
|
||||
|
||||
private class Tracker
|
||||
private static class Tracker
|
||||
{
|
||||
String announce;
|
||||
boolean isPrimary;
|
||||
|
||||
@@ -23,7 +23,7 @@ package org.klomp.snark;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -32,11 +32,19 @@ import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEValue;
|
||||
import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
|
||||
/**
|
||||
* The data structure for the tracker response.
|
||||
* Handles both traditional and compact formats.
|
||||
* Compact format 1 - a list of hashes - early format for testing
|
||||
* Compact format 2 - One big string of concatenated hashes - official format
|
||||
*/
|
||||
public class TrackerInfo
|
||||
{
|
||||
private final String failure_reason;
|
||||
private final int interval;
|
||||
private final Set peers;
|
||||
private final Set<Peer> peers;
|
||||
private int complete;
|
||||
private int incomplete;
|
||||
|
||||
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
@@ -68,40 +76,100 @@ public class TrackerInfo
|
||||
throw new InvalidBEncodingException("No interval given");
|
||||
else
|
||||
interval = beInterval.getInt();
|
||||
|
||||
BEValue bePeers = (BEValue)m.get("peers");
|
||||
if (bePeers == null)
|
||||
throw new InvalidBEncodingException("No peer list");
|
||||
else
|
||||
peers = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
if (bePeers == null) {
|
||||
peers = Collections.EMPTY_SET;
|
||||
} else {
|
||||
Set<Peer> p;
|
||||
try {
|
||||
// One big string (the official compact format)
|
||||
p = getPeers(bePeers.getBytes(), my_id, metainfo);
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// List of Dictionaries or List of Strings
|
||||
p = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
}
|
||||
peers = p;
|
||||
}
|
||||
|
||||
BEValue bev = (BEValue)m.get("complete");
|
||||
if (bev != null) try {
|
||||
complete = bev.getInt();
|
||||
if (complete < 0)
|
||||
complete = 0;
|
||||
} catch (InvalidBEncodingException ibe) {}
|
||||
|
||||
bev = (BEValue)m.get("incomplete");
|
||||
if (bev != null) try {
|
||||
incomplete = bev.getInt();
|
||||
if (incomplete < 0)
|
||||
incomplete = 0;
|
||||
} catch (InvalidBEncodingException ibe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
/******
|
||||
public static Set<Peer> getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(new BDecoder(in), my_id, metainfo);
|
||||
}
|
||||
|
||||
public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
public static Set<Peer> getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(be.bdecodeList().getList(), my_id, metainfo);
|
||||
}
|
||||
******/
|
||||
|
||||
public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
|
||||
/** List of Dictionaries or List of Strings */
|
||||
private static Set<Peer> getPeers(List<BEValue> l, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
Set peers = new HashSet(l.size());
|
||||
Set<Peer> peers = new HashSet(l.size());
|
||||
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
for (BEValue bev : l) {
|
||||
PeerID peerID;
|
||||
try {
|
||||
peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
// Case 1 - non-compact - A list of dictionaries (maps)
|
||||
peerID = new PeerID(bev.getMap());
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// don't let one bad entry spoil the whole list
|
||||
//Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
|
||||
try {
|
||||
// Case 2 - compact - A list of 32-byte binary strings (hashes)
|
||||
// This was just for testing and is not the official format
|
||||
peerID = new PeerID(bev.getBytes());
|
||||
} catch (InvalidBEncodingException ibe2) {
|
||||
// don't let one bad entry spoil the whole list
|
||||
//Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
private static final int HASH_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* One big string of concatenated 32-byte hashes
|
||||
* @since 0.8.1
|
||||
*/
|
||||
private static Set<Peer> getPeers(byte[] l, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
int count = l.length / HASH_LENGTH;
|
||||
Set<Peer> peers = new HashSet(count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
PeerID peerID;
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
System.arraycopy(l, i * HASH_LENGTH, hash, 0, HASH_LENGTH);
|
||||
try {
|
||||
peerID = new PeerID(hash);
|
||||
} catch (InvalidBEncodingException ibe) {
|
||||
// won't happen
|
||||
continue;
|
||||
}
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
@@ -110,11 +178,17 @@ public class TrackerInfo
|
||||
return peers;
|
||||
}
|
||||
|
||||
public Set getPeers()
|
||||
public Set<Peer> getPeers()
|
||||
{
|
||||
return peers;
|
||||
}
|
||||
|
||||
public int getPeerCount()
|
||||
{
|
||||
int pc = peers == null ? 0 : peers.size();
|
||||
return Math.max(pc, complete + incomplete - 1);
|
||||
}
|
||||
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
@@ -132,6 +206,8 @@ public class TrackerInfo
|
||||
return "TrackerInfo[FAILED: " + failure_reason + "]";
|
||||
else
|
||||
return "TrackerInfo[interval=" + interval
|
||||
+ (complete > 0 ? (", complete=" + complete) : "" )
|
||||
+ (incomplete > 0 ? (", incomplete=" + incomplete) : "" )
|
||||
+ ", peers=" + peers + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class BEValue
|
||||
* succeeds when the BEValue is actually a List, otherwise it will
|
||||
* throw a InvalidBEncodingException.
|
||||
*/
|
||||
public List getList() throws InvalidBEncodingException
|
||||
public List<BEValue> getList() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -157,7 +157,7 @@ public class BEValue
|
||||
* values. This operation only succeeds when the BEValue is actually
|
||||
* a Map, otherwise it will throw a InvalidBEncodingException.
|
||||
*/
|
||||
public Map getMap() throws InvalidBEncodingException
|
||||
public Map<BEValue, BEValue> getMap() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
862
apps/i2psnark/locale/messages_es.po
Normal file
@@ -0,0 +1,862 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the i2psnark package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-12-19 03:16+0000\n"
|
||||
"PO-Revision-Date: 2010-12-19 04:49+0100\n"
|
||||
"Last-Translator: mixxy <m1xxy@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:94
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "Se añaden los torrents en {0} minutos ..."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:296
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr "Límite del número total de subidores cambiado a {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:298
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr "El límite mínimo de subidores es {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:310
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "Ancho de banda para la subida ha sido cambiado a {0} kbyte/s."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:312
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "El límite mínimo de ancho de banda para la subida está en {0} kbyte/s."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:324
|
||||
#, java-format
|
||||
msgid "Startup delay limit changed to {0} minutes"
|
||||
msgstr "Demora del arranque cambiado a {0} minutos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:371
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr "Cambios de I2CP y del túnel tomarán efecto despues de detener todos los torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:378
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "Desconectando anterior Destino I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:382
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "Preferencias de I2CP cambiadas a {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:386
|
||||
msgid "Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr "Conectarse no fue posíble con las nuevas preferencias I2CP, utilizaré las anteriores."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:390
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "Conectarse usando las preferencias anteriores no fue posible!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:392
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "Conectado con la nueva Destino I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:403
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "Conexión I2CP reestablecida para \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:414
|
||||
msgid "Enabled autostart"
|
||||
msgstr "Arranque automático activado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:416
|
||||
msgid "Disabled autostart"
|
||||
msgstr "Arranque automático desactivado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:422
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Rastreadores abiertos activados - Para aplicar ello es necesario que reinicies los torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:424
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Rastreadores abiertos desactivados - Para aplicar ello es necesario que reinicies los torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:431
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "Lista de rastreadores abiertos cambiada - Para aplicar ello es necesario que reinicies los torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:438
|
||||
#, java-format
|
||||
msgid "{0} theme loaded, return to main i2psnark page to view."
|
||||
msgstr "Tema {0} cargado. ¡Vuelve a la página principal de i2psnark para verlo!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:445
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "Configuración no cambiada."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "No se pudo guardar la configuración en {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:494
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "Conectando a I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:497
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "Error al conectar a I2P - Compruebe su configuración I2CP!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:506
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "Error: No se ha podido añadir el torrent {0}."
|
||||
|
||||
#. catch this here so we don't try do delete it below
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:528
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "No se puede abrir \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:541
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only"
|
||||
msgstr "Advertencia - Se ignora rastreado no I2P en \"{0}\", anunciando sólo a los rastreadorse abiertos de I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:543
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr "Advertencia - Se ignora rastreado no I2P en \"{0}\", rastreadores abiertos están desactivados. ¡Tienes que activarlos antes de iniciar el torrent!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:563
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "El archivo .torrent en \"{0}\" no es válido."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:578
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "Torrent añadido e iniciado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:580
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "Torrent añadido: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:734
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "Hay demasiados archivos en \"{0}\", se borrará ({1}). "
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:736
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr "Archivo de datos del torrent \"{0}\" no puede terminar en \".torrent' y será borrado."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:738
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "No hay partes en \"{0}\", se borrará."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:740
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "Hay demasiadas partes en \"{0}\" y el límite es {1}. Se borrarán."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:742
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "Partes en \"{0}\" son demasiado grandes ({1}B). Se borrarán."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:743
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "El límite es de \"{0}\"Bytes"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:751
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr "Torrents más grandes que \"{0}\"Bytes aún no funcionan, se borrará \"{1}\"."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:767
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "Error: No se pudo quitar el torrent \"{0}\"."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:794
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "Torrent detenido: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:814
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "Torrent quitado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:852
|
||||
#, java-format
|
||||
msgid "Download finished: {0}"
|
||||
msgstr "Terminada la descarga de \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:880
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "Imposible de conectarse con I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:886
|
||||
#, java-format
|
||||
msgid "Unable to add {0}"
|
||||
msgstr "Imposible de añadir {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:185
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark - Cliente de BitTorrent Anónimo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:198
|
||||
msgid "Torrents"
|
||||
msgstr "Torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:201
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:208
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:977
|
||||
msgid "I2PSnark"
|
||||
msgstr "I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:205
|
||||
msgid "Refresh page"
|
||||
msgstr "Actualizar página"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:210
|
||||
msgid "Forum"
|
||||
msgstr "Foro"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:264
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1483
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:270
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:272
|
||||
msgid "Hide Peers"
|
||||
msgstr "ocultar pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:277
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:279
|
||||
msgid "Show Peers"
|
||||
msgstr "mostrar pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:286
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1464
|
||||
msgid "Torrent"
|
||||
msgstr "Torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:290
|
||||
msgid "Estimated time remaining"
|
||||
msgstr "Tiempo restante para completar la descarga"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:293
|
||||
msgid "ETA"
|
||||
msgstr "Completado en"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:297
|
||||
msgid "Downloaded"
|
||||
msgstr "Descargado"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:300
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:314
|
||||
msgid "RX"
|
||||
msgstr "Bajado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:304
|
||||
msgid "Uploaded"
|
||||
msgstr "Subido"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:307
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:324
|
||||
msgid "TX"
|
||||
msgstr "Subido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:312
|
||||
msgid "Down Rate"
|
||||
msgstr "Tasa de descarga"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:317
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
msgid "Rate"
|
||||
msgstr "Tasa"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:322
|
||||
msgid "Up Rate"
|
||||
msgstr "Tasa de subida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:340
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "Detener todos los torrents y el túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:342
|
||||
msgid "Stop All"
|
||||
msgstr "Detener todos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:351
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "Iniciar todos los torrents y el túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:353
|
||||
msgid "Start All"
|
||||
msgstr "Arrancar todos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:372
|
||||
msgid "No torrents loaded."
|
||||
msgstr "No cargado ningún torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:377
|
||||
msgid "Totals"
|
||||
msgstr "Total"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:379
|
||||
#, java-format
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "1 torrent"
|
||||
msgstr[1] "{0} torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:382
|
||||
#, java-format
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "1 par conectado"
|
||||
msgstr[1] "{0} pares conectados"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:454
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "Recogiendo {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:458
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "Dirección no válida - tiene que comenzar con http://"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:489
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "Iniciando el torrent {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:527
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "Borrado archivo torrent: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:533
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:543
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "Borrado el archivo de datos: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:535
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:545
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "No se pudo borrar el archivo de datos: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:554
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "Ha sido borrada la carpeta de datos: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:587
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "Error al crear el torrents - Tienes que elegir un rastreador."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:602
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "Torrent creado para \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:605
|
||||
#, java-format
|
||||
msgid "Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\""
|
||||
msgstr "Muchos rastreadores en I2P requieren que te registres, antes de que puedas subir el torrent. Por favor, ¡hazlo antes de iniciar \"{0}\"!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:607
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "Error al crear el torrent \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:610
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "No se puede crear un torrent para datos inexistentes: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:613
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr "Error al crear el torrent - Tienes que especificar un archivo o una carpeta."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:616
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "Deteniendo todos los torrents y cerrando el túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:627
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "Túnel I2P cerrado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:630
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "Abriendo el túnel I2P e iniciando los torrents ..."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:759
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:764
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:770
|
||||
msgid "Tracker Error"
|
||||
msgstr "Error del rastrador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:762
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:778
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:782
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:790
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:794
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:799
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:803
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "1 par"
|
||||
msgstr[1] "{0} pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:775
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:780
|
||||
msgid "Seeding"
|
||||
msgstr "sembrando"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:784
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1533
|
||||
msgid "Complete"
|
||||
msgstr "completo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:787
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:792
|
||||
msgid "OK"
|
||||
msgstr "Bien"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:796
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:801
|
||||
msgid "Stalled"
|
||||
msgstr "estancado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:805
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:808
|
||||
msgid "No Peers"
|
||||
msgstr "sin pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:810
|
||||
msgid "Stopped"
|
||||
msgstr "detenido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:837
|
||||
#, java-format
|
||||
msgid "Details at {0} tracker"
|
||||
msgstr "Detalles en el rastreador {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:838
|
||||
msgid "Info"
|
||||
msgstr "Info"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:853
|
||||
msgid "View files"
|
||||
msgstr "mostrar archivos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:855
|
||||
msgid "Open file"
|
||||
msgstr "abrir archivo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:865
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1569
|
||||
msgid "Open"
|
||||
msgstr "abrir"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:909
|
||||
msgid "Stop the torrent"
|
||||
msgstr "Detener el torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:911
|
||||
msgid "Stop"
|
||||
msgstr "Detener"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:921
|
||||
msgid "Start the torrent"
|
||||
msgstr "Iniciar el torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:923
|
||||
msgid "Start"
|
||||
msgstr "Iniciar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:933
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr "Quita el torrent de la lista de los torrents activos borrando el archivo .torrent"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:938
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?"
|
||||
msgstr "¿Estás seguro de que quieres borrar el archivo \\''{0}.torrent\\''? (Datos bajados no se borrarán.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:941
|
||||
msgid "Remove"
|
||||
msgstr "Quitar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:950
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "Borrar el archivo torrent y el/los archivo(s) de datos pertenecientes"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:955
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?"
|
||||
msgstr "¿Estás seguro de que quieres borrar el archivo torrent \\''{0}\\'' y todos los datos descargados de este torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:958
|
||||
msgid "Delete"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:991
|
||||
msgid "Unknown"
|
||||
msgstr "desconocido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1001
|
||||
msgid "Seed"
|
||||
msgstr "Sembrador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1019
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "no interesante (El par no tiene partes que nos interesen.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1021
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "moderado (De momento el par no nos permite solicitar más partes)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1035
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "desinteresado (No tenemos las partes que el par quiere.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1037
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "Moderando (De momento no se le permite al par solicitar más partes)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
msgid "Add Torrent"
|
||||
msgstr "Añadir un torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1094
|
||||
msgid "From URL"
|
||||
msgstr "URL fuente"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1097
|
||||
msgid "Torrent file must originate from an I2P-based tracker"
|
||||
msgstr "El archivo torrent debe incluir un rastreador I2P."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1102
|
||||
msgid "Add torrent"
|
||||
msgstr "Añadir torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1105
|
||||
#, java-format
|
||||
msgid "You can also copy .torrent files to: {0}."
|
||||
msgstr "También puedes copiar archivos torrent a {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1107
|
||||
msgid "Removing a .torrent will cause it to stop."
|
||||
msgstr "Quitar un archivo torrent resultará en que se detenga el torrent perteneciente."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1131
|
||||
msgid "Create Torrent"
|
||||
msgstr "Crear un torrent"
|
||||
|
||||
#. out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1134
|
||||
msgid "Data to seed"
|
||||
msgstr "Datos para sembrar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1138
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr "Archivo o carpeta para sembrar (tiene que estár en la carpeta especificada)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1140
|
||||
msgid "Tracker"
|
||||
msgstr "Rastreador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1142
|
||||
msgid "Select a tracker"
|
||||
msgstr "Selecciona un rastreador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1155
|
||||
msgid "or"
|
||||
msgstr "o"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1158
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "¡Especifica una URL para anunciar al rastreador!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1161
|
||||
msgid "Create torrent"
|
||||
msgstr "Crear torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1180
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1317
|
||||
msgid "Configuration"
|
||||
msgstr "Preferencias"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1184
|
||||
msgid "Data directory"
|
||||
msgstr "Carpeta de datos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1186
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "Para cambiar, ¡modifica el archivo i2psnark.config y reinicia!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1190
|
||||
msgid "Auto start"
|
||||
msgstr "Arranque automático"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1194
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "Si marcado, los torrents añadidos se iniciarán de forma automática."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1198
|
||||
msgid "Theme"
|
||||
msgstr "tema"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1211
|
||||
msgid "Startup delay"
|
||||
msgstr "Demora del arranque"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1213
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1237
|
||||
msgid "Total uploader limit"
|
||||
msgstr "Límite global de subidores"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1240
|
||||
msgid "peers"
|
||||
msgstr "pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1244
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "Límite de ancho de banda para la subida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1247
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "Se recomienda la mitad del ancho de banda disponible."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1249
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "Mostrar y cambiar preferencias del ancho de banda del enrutador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1253
|
||||
msgid "Use open trackers also"
|
||||
msgstr "Usar también rastreadores abiertos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1257
|
||||
msgid "If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"
|
||||
msgstr "Si está marcado, el torrent se anunciará a los rastreadores abiertos, además de a los rastreadores especificados."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1261
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "URL(s) para anunciar a rastreadores abiertos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1273
|
||||
msgid "Inbound Settings"
|
||||
msgstr "Preferencias de entrada"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1279
|
||||
msgid "Outbound Settings"
|
||||
msgstr "Preferencias de salida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1286
|
||||
msgid "I2CP host"
|
||||
msgstr "Huésped I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1291
|
||||
msgid "I2CP port"
|
||||
msgstr "Puerto I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1303
|
||||
msgid "I2CP options"
|
||||
msgstr "Opciones I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1308
|
||||
msgid "Save configuration"
|
||||
msgstr "Guardar ajustes"
|
||||
|
||||
#. * dummies for translation
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1325
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "1 salto"
|
||||
msgstr[1] "{0} saltos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1326
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "1 túnel"
|
||||
msgstr[1] "{0} túneles"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1480
|
||||
msgid "Size"
|
||||
msgstr "Tamaño"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1487
|
||||
msgid "Priority"
|
||||
msgstr "Prioridad"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1492
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "Subir una herarquía"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1517
|
||||
msgid "Directory"
|
||||
msgstr "Carpeta"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1522
|
||||
msgid "Torrent not found?"
|
||||
msgstr "¿No se encotró el archivo torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1530
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "¿Archivo no encontrado en el torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1543
|
||||
msgid "complete"
|
||||
msgstr "completo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1544
|
||||
msgid "bytes remaining"
|
||||
msgstr "Bytes faltando"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1595
|
||||
msgid "High"
|
||||
msgstr "alta"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1600
|
||||
msgid "Normal"
|
||||
msgstr "normal"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1605
|
||||
msgid "Skip"
|
||||
msgstr "dejar de lado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1614
|
||||
msgid "Save priorities"
|
||||
msgstr "Guardar prioridades"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1726
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "Torrent obtenido desde {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1746
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "Torrent ya en marcha: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1748
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "Torrent ya encolado: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1755
|
||||
#, java-format
|
||||
msgid "Failed to copy torrent file to {0}"
|
||||
msgstr "No se pudo copiar el torrent a {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1759
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "Torrent en {0} no era válido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1764
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "Torrent no se ha podido obtener de {0}"
|
||||
|
||||
#~ msgid " theme locked and loaded."
|
||||
#~ msgstr "tema cargado"
|
||||
#~ msgid "Hide All Attached Peers [connected/total in swarm]"
|
||||
#~ msgstr "Ocultar todos los pares conectados [conectados/todos]"
|
||||
#~ msgid "Show All Attached Peers [connected/total in swarm]"
|
||||
#~ msgstr "Mostrar todos los pares conectados [conectados/todos]"
|
||||
#~ msgid "Loaded Torrents"
|
||||
#~ msgstr "Torrents"
|
||||
#~ msgid "Estimated Download Time"
|
||||
#~ msgstr "tiempo restante de descarga"
|
||||
#~ msgid "1"
|
||||
#~ msgid_plural "{0}"
|
||||
#~ msgstr[0] "{0}"
|
||||
#~ msgstr[1] "{0}"
|
||||
#~ msgid "Torrent file {0} does not exist"
|
||||
#~ msgstr "Archivo del torrent {0} no existe"
|
||||
#~ msgid "Copying torrent to {0}"
|
||||
#~ msgstr "Copiando torrent a {0}"
|
||||
#~ msgid "from {0}"
|
||||
#~ msgstr "de {0}"
|
||||
#~ msgid "Downloading"
|
||||
#~ msgstr "descargando"
|
||||
#~ msgid "File"
|
||||
#~ msgstr "Archivo"
|
||||
#~ msgid "FileSize"
|
||||
#~ msgstr "Tamaño"
|
||||
#~ msgid "Download Status"
|
||||
#~ msgstr "Estado"
|
||||
#~ msgid "size: {0}B"
|
||||
#~ msgstr "Tamaño: {0}Bytes"
|
||||
#~ msgid "Directory to store torrents and data"
|
||||
#~ msgstr "Carpeta para guardar los archivos torrent y los datos"
|
||||
#~ msgid "Do not download"
|
||||
#~ msgstr "No descargues"
|
||||
#~ msgid "Details"
|
||||
#~ msgstr "Detalles"
|
||||
#~ msgid "Cannot change the I2CP settings while torrents are active"
|
||||
#~ msgstr ""
|
||||
#~ "No se puede cammbiar los ajustes I2CP mientras estén activos los torrents"
|
||||
#~ msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
#~ msgstr ""
|
||||
#~ "Rastreador fuera de I2P en \"{0}\", borrando de la lista de rastreadores"
|
||||
#~ msgid "{0} torrents"
|
||||
#~ msgstr "{0} Torrents"
|
||||
#~ msgid "Uninteresting"
|
||||
#~ msgstr "no interesante"
|
||||
#~ msgid "Choked"
|
||||
#~ msgstr "frenado"
|
||||
#~ msgid "Uninterested"
|
||||
#~ msgstr "desinteresado"
|
||||
#~ msgid "Choking"
|
||||
#~ msgstr "frenando"
|
||||
#~ msgid "Custom tracker URL"
|
||||
#~ msgstr "URL especial del rastreador"
|
||||
#~ msgid "Configure"
|
||||
#~ msgstr "Ajustes"
|
||||
|
||||
846
apps/i2psnark/locale/messages_fr.po
Normal file
@@ -0,0 +1,846 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the i2psnark package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-11-13 08:01+0000\n"
|
||||
"PO-Revision-Date: 2010-11-22 17:49+0100\n"
|
||||
"Last-Translator: mixxy <m1xxy@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: French\n"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:90
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "Ajouter des torrents dans {0} minutes"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:261
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr "Limite agrégée des uploaders modifiée : {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:263
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr "La limite minimale agrégée des uploaders est : {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:275
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "La limite d'upload modifiée : {0} Ko/s"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:277
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "La limite minimale d'upload est {0} Ko/s"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:289
|
||||
#, java-format
|
||||
msgid "Startup delay limit changed to {0} minutes"
|
||||
msgstr "Delais de démarrage modifié : {0] minutes"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:336
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr "Les modifications I2CP et des tunnels seront prise en compte après avoir arrêté tous les torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:343
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "Déconnexion des anciennes destination I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:347
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "Les paramètres I2CP ont été changés : {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:351
|
||||
msgid "Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr "Impossible de se connecter avec les nouveaux paramètres, retour à l'ancienne configuration I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:355
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "Impossible de se reconnecter avec les anciens paramètres!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:357
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "Reconnexion sur la nouvelle destination I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:368
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "Listener I2CP redémarré pour \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:379
|
||||
msgid "Enabled autostart"
|
||||
msgstr "Le démarrage automatique est activé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:381
|
||||
msgid "Disabled autostart"
|
||||
msgstr "Le démarrage automatique est désactivé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:387
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Les open trackers sont activés - ceci a nécessité un redémarrage des torrents pour être pris en compte."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:389
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Les open trackers sont désactivés - ceci a nécessité un redémarrage des torrents pour être pris en compte."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:396
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "Liste des Open trackers modifiée - ceci nécessite un redémarrage des torrents pour être pris en compte"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:403
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "La configuration n'a pas été modifiée"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:413
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "Impossible de sauvegarder la configuration vers {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:452
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "Connexion à I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "Erreur de connexion à I2P - Vérifiez vos paramètres I2CP!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:464
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "Erreur : Impossible d'ajouter le torrent : {0}"
|
||||
|
||||
#. catch this here so we don't try do delete it below
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:486
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "Impossible d'ouvrir: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:499
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only"
|
||||
msgstr "Attention - Les trackers non-i2p dans \"{0}\" sont ignorés, seuls les open trackers I2P seront utilisés!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:501
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr "Attention - Les trackers non-i2p dans \"{0}\" sont ignorés, et les open trackers sont désactivés, vous devez activer les open trackers avant de démarrer le torrent!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:521
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "Le torrent dans \"{0}\" est invalide"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:536
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "Torrent ajouté et démarré: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:538
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "Torrent ajouté: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "Trop de fichiers dans \"{0}\" ({1}), suppression! "
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:694
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr "Le fichier torrent \"{0}\" ne peut pas se terminer par \".torrent\", suppression!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:696
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "Pas de morceaux dans \"{0}\", suppression!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:698
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "Trop de morceaux dans \"{0}\" , la limite est {1}, suppression!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:700
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "Les morceaux sont trop larges dans \"{0}\" ({1}B), suppresion."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:701
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "La limite est de \"{0}\"Octets"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:709
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr "Les torrents dont la taille est supérieure à \"{0}\"Octets ne sont pas encore supportés, suppression \"{1}\"."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:725
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "Erreur: Impossible de supprimer le torrent \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:746
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "Torrent arrêté:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:761
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "Torrent supprimé:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:799
|
||||
#, java-format
|
||||
msgid "Download finished: {0}"
|
||||
msgstr "Téléchargement terminé:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:799
|
||||
#, java-format
|
||||
msgid "size: {0}B"
|
||||
msgstr "Taille: {0}Octets"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:827
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "Impossible de se connecter à I2P!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:833
|
||||
#, java-format
|
||||
msgid "Unable to add {0}"
|
||||
msgstr "Impossible d'ajouter {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:176
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark - Client BitTorrent anonyme"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:187
|
||||
msgid "Torrents"
|
||||
msgstr "Torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:190
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:197
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:901
|
||||
msgid "I2PSnark"
|
||||
msgstr "I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:194
|
||||
msgid "Refresh page"
|
||||
msgstr "Rafraîchir la page"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:199
|
||||
msgid "Forum"
|
||||
msgstr "Forum"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:248
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1377
|
||||
msgid "Status"
|
||||
msgstr "État"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:255
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:257
|
||||
msgid "Hide Peers"
|
||||
msgstr "Cacher les pairs"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:262
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:264
|
||||
msgid "Show Peers"
|
||||
msgstr "Afficher les pairs"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:271
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:273
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1358
|
||||
msgid "Torrent"
|
||||
msgstr "Torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:276
|
||||
msgid "Estimated Download Time"
|
||||
msgstr "Temps estimé de téléchargement"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:278
|
||||
# NOTE: purposely left blank to leave more room in the table header
|
||||
#msgstr "Temps restant"
|
||||
msgid "ETA"
|
||||
msgstr " "
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:281
|
||||
msgid "Downloaded"
|
||||
msgstr "Téléchargé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:283
|
||||
msgid "RX"
|
||||
msgstr "Reçu"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:286
|
||||
msgid "Uploaded"
|
||||
msgstr "Envoyé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:288
|
||||
msgid "TX"
|
||||
msgstr "Envoyé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:291
|
||||
msgid "Down Rate"
|
||||
msgstr "Taux de téléchargement"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:293
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:298
|
||||
msgid "Rate"
|
||||
msgstr "Vitesse"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:296
|
||||
msgid "Up Rate"
|
||||
msgstr "Taux d'envoi"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:304
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:307
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "Arrêter tous les torrents et le tunnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:309
|
||||
msgid "Stop All"
|
||||
msgstr "Arrêter tout"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:315
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:318
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "Démarrer tous les torrents et le tunnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:336
|
||||
msgid "No torrents loaded."
|
||||
msgstr "Aucun torrent chargé."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:341
|
||||
msgid "Totals"
|
||||
msgstr "Totaux"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:343
|
||||
#, java-format
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "1 torrent"
|
||||
msgstr[1] "{0} torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:346
|
||||
#, java-format
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "1 pair connecté"
|
||||
msgstr[1] "{0} pairs connectés"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:375
|
||||
#, java-format
|
||||
msgid "Torrent file {0} does not exist"
|
||||
msgstr "Le fichier torrent {0} n'existe pas"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:385
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1629
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "Torrent déjà actif: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:387
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1631
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "Torrent déjà dans la queue: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:391
|
||||
#, java-format
|
||||
msgid "Copying torrent to {0}"
|
||||
msgstr "Copie du torrent vers {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:394
|
||||
#, java-format
|
||||
msgid "Unable to copy the torrent to {0}"
|
||||
msgstr "Impossible de copier le torrent vers {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:394
|
||||
#, java-format
|
||||
msgid "from {0}"
|
||||
msgstr "depuis {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:402
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "Envoi {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:406
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "URL invalide - elle doit débuter par http://"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:436
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "Démarrage du torrent {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:456
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:474
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "Fichier torrent effacé: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:480
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:490
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "Fichier de données effacé: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:482
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:492
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "Le fichier de données ne peut être effacé: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:501
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "Répertoire des données effacé: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:533
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "Erreur lors de la création du torrent - vous devez sélectionner un tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:548
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "Torrent créé pour \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:551
|
||||
#, java-format
|
||||
msgid "Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\""
|
||||
msgstr "De nombreux trackers I2P nécessitent d'enregistrer les nouveaux torrents avant de seeder - faites-le avant de démarrer \"{0}\"!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:553
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "Erreur de création du torrent pour \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:556
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "Impossible de créer un torrent pour des données inexistantes: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:559
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr "Erreur de création du torrent - vous devez saisir un fichier ou un répertoire"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:562
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "Arrêt de tous les torrents et fermeture du tunnel I2P."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:573
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "Tunnel I2P fermé."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:576
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "Ouverture du tunnel I2P and démarrage de tous les torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:698
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:915
|
||||
msgid "Unknown"
|
||||
msgstr "Inconnu"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:701
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:706
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:712
|
||||
msgid "Tracker Error"
|
||||
msgstr "Erreur du tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:704
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:708
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:720
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:724
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:732
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:736
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:741
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:745
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:749
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "1 Pair"
|
||||
msgstr[1] "{0} Pairs"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:717
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:722
|
||||
msgid "Seeding"
|
||||
msgstr "Seed en cours"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:726
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1427
|
||||
msgid "Complete"
|
||||
msgstr "Complet"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:729
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:734
|
||||
msgid "Downloading"
|
||||
msgstr "Téléchargement en cours"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:738
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:743
|
||||
msgid "Stalled"
|
||||
msgstr "Figé"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:747
|
||||
msgid "No Peers"
|
||||
msgstr "Pas de pair"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:751
|
||||
msgid "Stopped"
|
||||
msgstr "Arrêté"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
msgid "View files"
|
||||
msgstr "Voir les fichiers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:768
|
||||
msgid "Open file"
|
||||
msgstr "Ouvrir fichier"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:798
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1049
|
||||
msgid "Tracker"
|
||||
msgstr "Tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:833
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:836
|
||||
msgid "Stop the torrent"
|
||||
msgstr "Arrêter le torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:838
|
||||
msgid "Stop"
|
||||
msgstr "Arrêter"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:845
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:848
|
||||
msgid "Start the torrent"
|
||||
msgstr "Arrêter le torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:850
|
||||
msgid "Start"
|
||||
msgstr "Démarrer"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:856
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:864
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr "Enlever le torrent de la liste active, suprression du fichier .torrent"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:861
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?"
|
||||
msgstr "Etes-vous certain de vouloir supprimer le fichier \\''{0}.torrent\\'' (les données déjà téléchargées ne seront pas supprimées) ?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:866
|
||||
msgid "Remove"
|
||||
msgstr "Enlever"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:871
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:879
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "Supprimer le fichier .torrent et le(s) fichier(s) de données associé(s)"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:876
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?"
|
||||
msgstr "Etes-vous certain de vouloir supprimer le torrent \\''{0}\\'' ainsi que toutes les données téléchargées ?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:881
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:925
|
||||
msgid "Seed"
|
||||
msgstr "Seed"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:943
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "aucun intérêt (le pair n'a aucun morceau utile)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:945
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "bridé (le pair ne nous permet pas de demander un morceau)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:959
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "aucun intérêt (nous n'avons aucun morceau utile au pair)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:961
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "bridage (nous ne permettons pas au pair de demander un morceau)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1005
|
||||
msgid "Add Torrent"
|
||||
msgstr "Ajouter torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1007
|
||||
msgid "From URL"
|
||||
msgstr "Depuis l'url"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1010
|
||||
msgid "Torrent file must originate from an I2P-based tracker"
|
||||
msgstr "Le fichier torrent doit provenir d'un tracker I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1015
|
||||
msgid "Add torrent"
|
||||
msgstr "Ajouter torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1018
|
||||
#, java-format
|
||||
msgid "You can also copy .torrent files to: {0}."
|
||||
msgstr "Vous pouvez aussi copier les fichiers .torrent vers {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1020
|
||||
msgid "Removing a .torrent will cause it to stop."
|
||||
msgstr "La suppression d'un fichier .torrent entraine l'arrêt du torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1040
|
||||
msgid "Create Torrent"
|
||||
msgstr "Créer torrent"
|
||||
|
||||
#. out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1043
|
||||
msgid "Data to seed"
|
||||
msgstr "Données à seeder"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1047
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr "Fichier ou répertoire à seeder (doit être dans le chemin spécifié)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1051
|
||||
msgid "Select a tracker"
|
||||
msgstr "Sélectionner un tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1064
|
||||
msgid "or"
|
||||
msgstr "ou"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1067
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "Spécifier une URL personnalisée d'annonce de tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1070
|
||||
msgid "Create torrent"
|
||||
msgstr "Créer torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1089
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1213
|
||||
msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
msgid "Data directory"
|
||||
msgstr "Répertoire de données"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1094
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "Editez i2psnark.config et redémarrez pour prendre en compte les modifications"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1098
|
||||
msgid "Auto start"
|
||||
msgstr "Démarrage automatique"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1102
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "Si coché, les torrents démarrerons automatiquement lors de l'ajout"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1106
|
||||
msgid "Startup delay"
|
||||
msgstr "Délais de démarrage"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1108
|
||||
msgid "minutes"
|
||||
msgstr "minutes"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1132
|
||||
msgid "Total uploader limit"
|
||||
msgstr "Limite totale d'envoi"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1135
|
||||
msgid "peers"
|
||||
msgstr "pairs"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1139
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "Limite de bande passante en envoi"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1142
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "La moitié de la bande passante est recommandée."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1144
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "Consulter ou modifier la bande passante du routeur"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1148
|
||||
msgid "Use open trackers also"
|
||||
msgstr "Utiliser les open trackers aussi"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1152
|
||||
msgid "If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"
|
||||
msgstr "Si coché, les torrents seront annoncés vers les open trackers ainsi que vers les trackers indiqués dans le fichier torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1156
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "URL d'annonce open tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1168
|
||||
msgid "Inbound Settings"
|
||||
msgstr "Paramètres entrants"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1174
|
||||
msgid "Outbound Settings"
|
||||
msgstr "Paramètres sortants"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1181
|
||||
msgid "I2CP host"
|
||||
msgstr "Hôte I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1186
|
||||
msgid "I2CP port"
|
||||
msgstr "Port I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1198
|
||||
msgid "I2CP options"
|
||||
msgstr "Options I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1203
|
||||
msgid "Save configuration"
|
||||
msgstr "Sauvegarder la configuration"
|
||||
|
||||
#. * dummies for translation
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1221
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "1 saut"
|
||||
msgstr[1] "{0} sauts"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1222
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "1 tunnel"
|
||||
msgstr[1] "{0} tunnels"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1371
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1374
|
||||
msgid "FileSize"
|
||||
msgstr "Taille du fichier"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1374
|
||||
msgid "Size"
|
||||
msgstr "Taille"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1377
|
||||
msgid "Download Status"
|
||||
msgstr "État du téléchargement"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1381
|
||||
msgid "Priority"
|
||||
msgstr "Priorité"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1386
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "Vers le répertoire parent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1411
|
||||
msgid "Directory"
|
||||
msgstr "Répertoire"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1416
|
||||
msgid "Torrent not found?"
|
||||
msgstr "Torrent non trouvé?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1424
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "Fichier non trouvé dans le torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1431
|
||||
msgid "complete"
|
||||
msgstr "complet"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1432
|
||||
msgid "bytes remaining"
|
||||
msgstr "Octets restants"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1483
|
||||
msgid "High"
|
||||
msgstr "Haut"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1488
|
||||
msgid "Normal"
|
||||
msgstr "Normal"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1493
|
||||
msgid "Ignore"
|
||||
msgstr "Ignore"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1502
|
||||
msgid "Save priorities"
|
||||
msgstr "Sauvegarder les priorités"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1609
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "Torrent envoyé de {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1637
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "Le torrent {0} n'est pas valide"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1642
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "Le torrent n'a pas été reçu par {0}"
|
||||
|
||||
#~ msgid "Start All"
|
||||
#~ msgstr "Démarrer tout"
|
||||
#~ msgid "OK"
|
||||
#~ msgstr "OK"
|
||||
#~ msgid "Details"
|
||||
#~ msgstr "Détails"
|
||||
#~ msgid "Depuis l'URL"
|
||||
#~ msgstr "Quell-URL"
|
||||
#~ msgid "Directory to store torrents and data"
|
||||
#~ msgstr "Répertoire de stockage des torrents et des données"
|
||||
#~ msgid "Cannot change the I2CP settings while torrents are active"
|
||||
#~ msgstr "On ne peut changer les paramètres I2CP pendant que des torrents sont actifs"
|
||||
#~ msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
#~ msgstr "Tracker non-i2p dans \"{0}\", suppression de notre liste de trackers!"
|
||||
#~ msgid "{0} torrents"
|
||||
#~ msgstr "{0} Torrents"
|
||||
#~ msgid "Uninteresting"
|
||||
#~ msgstr "Pas intéressant"
|
||||
#~ msgid "Choked"
|
||||
#~ msgstr "Choked"
|
||||
#~ msgid "Uninterested"
|
||||
#~ msgstr "Pas interessé"
|
||||
#~ msgid "Choking"
|
||||
#~ msgstr "Choking"
|
||||
#~ msgid "Custom tracker URL"
|
||||
#~ msgstr "URL tracker spécifique"
|
||||
#~ msgid "Configure"
|
||||
#~ msgstr "Configurer"
|
||||
|
||||
838
apps/i2psnark/locale/messages_nl.po
Normal file
@@ -0,0 +1,838 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the i2psnark package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-12-17 15:04+0000\n"
|
||||
"PO-Revision-Date: 2010-06-15 09:07+0100\n"
|
||||
"Last-Translator: duck <duck@mail.i2p>\n"
|
||||
"Language-Team: duck <duck@mail.i2p>, monkeybrains <monkeybrains@mail.i2p>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Dutch\n"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:94
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "Torrents toevoegen in {0} minuten"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:296
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr "Totale uploaders limiet gewijzigd in {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:298
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr "Minimum totale uploaders limiet is {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:310
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "Up bandbreedte limiet gewijzigd in {0}KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:312
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "Minimum up bandbreedte limiet is {0}KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:324
|
||||
#, java-format
|
||||
msgid "Startup delay limit changed to {0} minutes"
|
||||
msgstr "Startup vertragings limiet gewijzigd in {0} minuten"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:371
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr ""
|
||||
"I2CP en tunnel wijzigingen hebben pas effect na het stoppen van alle torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:378
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "Oude I2CP destination wordt afgesloten"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:382
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "I2CP instellingen gewijzigd in {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:386
|
||||
msgid ""
|
||||
"Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr ""
|
||||
"Kan geen connectie maken met de nieuwe instellingen, we keren terug naar "
|
||||
"oude I2CP instellingen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:390
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "Kan niet opnieuw verbinden met de oude instellingen!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:392
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "Opnieuw verbonden met de nieuwe I2CP destination"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:403
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "I2CP listener herstart voor \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:414
|
||||
msgid "Enabled autostart"
|
||||
msgstr "Autostart ingeschakeld"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:416
|
||||
msgid "Disabled autostart"
|
||||
msgstr "Autostart uitgeschakeld"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:422
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Open Trackers ingeschakeld - torrent herstart nodig."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:424
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Open Trackers uitgeschakeld - torrent herstart nodig."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:431
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "Open Tracker lijst gewijzigd - torrent herstart nodig."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:438
|
||||
#, java-format
|
||||
msgid "{0} theme loaded, return to main i2psnark page to view."
|
||||
msgstr "{0} thema geladen, ga naar de hoofd i2psnark pagina om deze te bekijken."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:445
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "Configuratie ongewijzigd."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "Kan de configuratie niet opslaan in {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:494
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "Verbinden met I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:497
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "Fout bij verbinden met I2P - controlleer je I2CP instellingen!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:506
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "Fout: Kan de torrent {0} niet toevoegen"
|
||||
|
||||
#. catch this here so we don't try do delete it below
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:528
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "Kan \"{0}\" niet openen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:541
|
||||
#, java-format
|
||||
msgid ""
|
||||
"Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open "
|
||||
"trackers only"
|
||||
msgstr ""
|
||||
"Waarschuwing - Niet-I2P tracker in \"{0}\" wordt genegeerd, zal alleen "
|
||||
"aankondigen naar i2p open trackers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:543
|
||||
#, java-format
|
||||
msgid ""
|
||||
"Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are "
|
||||
"disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr ""
|
||||
"Waarschuwing - Niet-I2P tracker in \"{0}\" wordt genegeerd, en open trackers "
|
||||
"zijn uitgeschakeld, je moet open trackers inschakelen voor het starten van "
|
||||
"de torrent!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:563
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "Torrent in \"{0}\" is ongeldig"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:578
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "Torrent toegevoegd en gestart: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:580
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "Torrent toegevoegd: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:734
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "Te veel bestanden in \"{0}\" ({1}), wordt verwijderd!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:736
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr ""
|
||||
"Torrent bestand \"{0}\" kan niet eindigen in \".torrent\", wordt verwijderd!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:738
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "Geen stukken in \"{0}\", wordt verwijderd!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:740
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "Te veel stukken in \"{0}\", limiet is {1}, wordt verwijderd!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:742
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "Stukken zijn te groot in \"{0}\" ({1}B), wordt verwijderd."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:743
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "Limiet is {0}B"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:751
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr ""
|
||||
"Torrents groter dan {0}B worden nog niet ondersteund, verwijder \"{1}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:767
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "Fout: Kan de torrent {0} niet verwijderen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:794
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "Torrent gestopt: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:814
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "Torrent verwijderd: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:852
|
||||
#, java-format
|
||||
msgid "Download finished: {0}"
|
||||
msgstr "Download gereed: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:880
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "Kan niet verbinden met I2P!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:886
|
||||
#, java-format
|
||||
msgid "Unable to add {0}"
|
||||
msgstr "Kan {0} niet toevoegen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:185
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark - Anonieme BitTorrent Client"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:198
|
||||
msgid "Torrents"
|
||||
msgstr "Torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:201
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:208
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:977
|
||||
msgid "I2PSnark"
|
||||
msgstr "I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:205
|
||||
msgid "Refresh page"
|
||||
msgstr "Ververs pagina"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:210
|
||||
msgid "Forum"
|
||||
msgstr "Forum"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:264
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1483
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:270
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:272
|
||||
msgid "Hide Peers"
|
||||
msgstr "Verberg Peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:277
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:279
|
||||
msgid "Show Peers"
|
||||
msgstr "Toon Peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:286
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1464
|
||||
msgid "Torrent"
|
||||
msgstr "Torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:290
|
||||
msgid "Estimated time remaining"
|
||||
msgstr "Schatting resterende tijd"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:293
|
||||
msgid "ETA"
|
||||
msgstr "ETA"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:297
|
||||
msgid "Downloaded"
|
||||
msgstr "Gedownload"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:300
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:314
|
||||
msgid "RX"
|
||||
msgstr "RX"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:304
|
||||
msgid "Uploaded"
|
||||
msgstr "Geupload"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:307
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:324
|
||||
msgid "TX"
|
||||
msgstr "TX"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:312
|
||||
msgid "Down Rate"
|
||||
msgstr "Down Snelheid"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:317
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
msgid "Rate"
|
||||
msgstr "Rato"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:322
|
||||
msgid "Up Rate"
|
||||
msgstr "Up Snelheid"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:340
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "Stop alle torrents en de I2P tunnel"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:342
|
||||
msgid "Stop All"
|
||||
msgstr "Stop Alle"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:351
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "Start alle torrents en de I2P tunnel"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:353
|
||||
msgid "Start All"
|
||||
msgstr "Start Alle"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:372
|
||||
msgid "No torrents loaded."
|
||||
msgstr "Geen torrents geladen."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:377
|
||||
msgid "Totals"
|
||||
msgstr "Totalen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:379
|
||||
#, java-format
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "1 torrent"
|
||||
msgstr[1] "{0} torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:382
|
||||
#, java-format
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "1 verbonden peer"
|
||||
msgstr[1] "{0} verbonden peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:454
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "Downloaden {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:458
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "Ongeldige URL - moet beginnen met http://"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:489
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "Starten met torrent {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:527
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "Torrent bestand verwijderd: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:533
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:543
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "Data bestand verwijderd: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:535
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:545
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "Kan data bestand niet verwijderen: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:554
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "Data directory verwijderd: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:587
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "Fout bij maken van torrent - je moet een tracker selecteren"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:602
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "Torrent gemaakt voor \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:605
|
||||
#, java-format
|
||||
msgid ""
|
||||
"Many I2P trackers require you to register new torrents before seeding - "
|
||||
"please do so before starting \"{0}\""
|
||||
msgstr ""
|
||||
"Veel I2P trackers vereisen dat je de nieuwe torrent registreert voor het "
|
||||
"seeden - doe dit voordat je \"{0}\" start"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:607
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "Fout bij het maken van een torrent voor \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:610
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "Kan geen torrent maken voor niet-bestaande data: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:613
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr ""
|
||||
"Fout bij het maken van de torrent - je moet een bestand of directory invullen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:616
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "Stoppen van alle torrents en sluiten van I2P tunnel."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:627
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "I2P tunnel gesloten."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:630
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "Openen van de I2P tunnel en starten van alle torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:759
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:764
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:770
|
||||
msgid "Tracker Error"
|
||||
msgstr "Tracker Fout"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:762
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:778
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:782
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:790
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:794
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:799
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:803
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "1 peer"
|
||||
msgstr[1] "{0} peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:775
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:780
|
||||
msgid "Seeding"
|
||||
msgstr "Seeding"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:784
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1533
|
||||
msgid "Complete"
|
||||
msgstr "Voltooid"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:787
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:792
|
||||
msgid "OK"
|
||||
msgstr "OK"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:796
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:801
|
||||
msgid "Stalled"
|
||||
msgstr "Vastgelopen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:805
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:808
|
||||
msgid "No Peers"
|
||||
msgstr "Geen Peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:810
|
||||
msgid "Stopped"
|
||||
msgstr "Gestopt"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:837
|
||||
#, java-format
|
||||
msgid "Details at {0} tracker"
|
||||
msgstr "Details op de {0} tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:838
|
||||
msgid "Info"
|
||||
msgstr "Info"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:853
|
||||
msgid "View files"
|
||||
msgstr "Bekijk bestanden"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:855
|
||||
msgid "Open file"
|
||||
msgstr "Open bestand"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:865
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1569
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:909
|
||||
msgid "Stop the torrent"
|
||||
msgstr "Stop de torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:911
|
||||
msgid "Stop"
|
||||
msgstr "Stop"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:921
|
||||
msgid "Start the torrent"
|
||||
msgstr "Start de torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:923
|
||||
msgid "Start"
|
||||
msgstr "Start"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:933
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr ""
|
||||
"Verwijder de torrent van de actieve lijst, het .torrent bestand wordt "
|
||||
"verwijderd"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:938
|
||||
#, java-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded "
|
||||
"data will not be deleted) ?"
|
||||
msgstr ""
|
||||
"Weet je zeker dat je het bestand \\''{0}.torrent\\'' wilt verwijderen "
|
||||
"(gedownloade data zal niet worden verwijderd) ?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:941
|
||||
msgid "Remove"
|
||||
msgstr "Weghalen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:950
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "Verwijder het .torrent bestand en de gerelateerde data bestand(en)"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:955
|
||||
#, java-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded "
|
||||
"data?"
|
||||
msgstr ""
|
||||
"Weet je zeker dat je de torrent \\''{0}\\'' en alle gedownloade data wilt "
|
||||
"verwijderen?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:958
|
||||
msgid "Delete"
|
||||
msgstr "Verwijderen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:991
|
||||
msgid "Unknown"
|
||||
msgstr "Onbekend"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1001
|
||||
msgid "Seed"
|
||||
msgstr "Seed"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1019
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "Niet interessant (De peer heeft geen stukken die we nodig hebben)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1021
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "Verstikt (De peer laat ons niet toe om stukken op te vragen)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1035
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "Niet geïnteresseerd (We heben geen stukken die de peer nodig heeft)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1037
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "Verstikt (We laten de peer niet toe om stukken op te vragen)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
msgid "Add Torrent"
|
||||
msgstr "Torrent Toevoegen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1094
|
||||
msgid "From URL"
|
||||
msgstr "Van URL"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1097
|
||||
msgid "Torrent file must originate from an I2P-based tracker"
|
||||
msgstr "Torrent bestand moet vaan een I2P tracker komen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1102
|
||||
msgid "Add torrent"
|
||||
msgstr "Torrent toevoegen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1105
|
||||
#, java-format
|
||||
msgid "You can also copy .torrent files to: {0}."
|
||||
msgstr "Je kan ook .torrent bestanden kopieren naar: {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1107
|
||||
msgid "Removing a .torrent will cause it to stop."
|
||||
msgstr "Verwijderen van een .torrent zorgt dat deze stopt."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1131
|
||||
msgid "Create Torrent"
|
||||
msgstr "Creëer Torrent"
|
||||
|
||||
#. out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1134
|
||||
msgid "Data to seed"
|
||||
msgstr "Data om te seeden"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1138
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr ""
|
||||
"Bestand of directory om te seeden (moet binnen het gespecificeerde pad zijn)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1140
|
||||
msgid "Tracker"
|
||||
msgstr "Tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1142
|
||||
msgid "Select a tracker"
|
||||
msgstr "Selecteer een tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1155
|
||||
msgid "or"
|
||||
msgstr "of"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1158
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "Specificeer aangepaste tracker aankondigings URL"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1161
|
||||
msgid "Create torrent"
|
||||
msgstr "Creëer torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1180
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1317
|
||||
msgid "Configuration"
|
||||
msgstr "Configuratie"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1184
|
||||
msgid "Data directory"
|
||||
msgstr "Data directory"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1186
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "Bewerk i2psnark.config en herstart de wijziging"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1190
|
||||
msgid "Auto start"
|
||||
msgstr "Auto start"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1194
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "Indien aangevinkt, start toegevoegde torrents automatisch"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1198
|
||||
msgid "Theme"
|
||||
msgstr "Thema"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1211
|
||||
msgid "Startup delay"
|
||||
msgstr "Startup vertraging"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1213
|
||||
msgid "minutes"
|
||||
msgstr "minuten"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1237
|
||||
msgid "Total uploader limit"
|
||||
msgstr "Totale uploader limiet"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1240
|
||||
msgid "peers"
|
||||
msgstr "peers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1244
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "Up bandbreedte limiet"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1247
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "Helft van beschikbare bandbreedte aanbevolen."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1249
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "Bekijk of wijzig router bandbreedte"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1253
|
||||
msgid "Use open trackers also"
|
||||
msgstr "Gebruik ook open trackers"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1257
|
||||
msgid ""
|
||||
"If checked, announce torrents to open trackers as well as the tracker listed "
|
||||
"in the torrent file"
|
||||
msgstr ""
|
||||
"Indien aangevinkt, kondig torrents ook aan bij de tracker uit het torrent "
|
||||
"bestand"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1261
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "Open tracker aankondigings URLs"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1273
|
||||
msgid "Inbound Settings"
|
||||
msgstr "Inkomende Instellingen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1279
|
||||
msgid "Outbound Settings"
|
||||
msgstr "Uitgaande Instellingen"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1286
|
||||
msgid "I2CP host"
|
||||
msgstr "I2CP host"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1291
|
||||
msgid "I2CP port"
|
||||
msgstr "I2CP poort"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1303
|
||||
msgid "I2CP options"
|
||||
msgstr "I2CP opties"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1308
|
||||
msgid "Save configuration"
|
||||
msgstr "Configuratie opslaan"
|
||||
|
||||
#. * dummies for translation
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1325
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "1 hop"
|
||||
msgstr[1] "{0} hops"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1326
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "1 tunnel"
|
||||
msgstr[1] "{0} tunnels"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1480
|
||||
msgid "Size"
|
||||
msgstr "Grootte"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1487
|
||||
msgid "Priority"
|
||||
msgstr "Prioriteit"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1492
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "Naar bovenliggende directory"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1517
|
||||
msgid "Directory"
|
||||
msgstr "Directory"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1522
|
||||
msgid "Torrent not found?"
|
||||
msgstr "Torrent niet gevonden?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1530
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "Bestand niet gevonden in torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1543
|
||||
msgid "complete"
|
||||
msgstr "voltooid"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1544
|
||||
msgid "bytes remaining"
|
||||
msgstr "bytes resterend"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1595
|
||||
msgid "High"
|
||||
msgstr "Hoog"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1600
|
||||
msgid "Normal"
|
||||
msgstr "Normaal"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1605
|
||||
msgid "Skip"
|
||||
msgstr "Overslaan"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1614
|
||||
msgid "Save priorities"
|
||||
msgstr "Prioriteiten opslaan"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1726
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "Torrent gedownload van {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1746
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "Torrent draait al: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1748
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "Torrent zit al in de wachtrij: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1755
|
||||
#, java-format
|
||||
msgid "Failed to copy torrent file to {0}"
|
||||
msgstr "Kan het torrent bestand niet kopieren naar {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1759
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "Torrent op {0} was niet geldig"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1764
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "Torrent was niet ontvangen van {0}"
|
||||
864
apps/i2psnark/locale/messages_pt.po
Normal file
@@ -0,0 +1,864 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the i2psnark package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
# foo <foo@bar>, 2009.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-12-19 03:16+0000\n"
|
||||
"PO-Revision-Date: 2010-12-19 04:48+0100\n"
|
||||
"Last-Translator: mixxy <m1xxy@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:94
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "Os torrents serão adicionados em {0} minutos ..."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:296
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr "Limite total de subidores mudado a {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:298
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr "O limite mínimo de subidores é {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:310
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "Largura de banda para a subida foi mudada para {0} kbyte/s."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:312
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "O limite mínimo da largura de banda para a subida está em {0} kbyte/s."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:324
|
||||
#, java-format
|
||||
msgid "Startup delay limit changed to {0} minutes"
|
||||
msgstr "Demora do arranque mudado a {0} minutos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:371
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr "Mudanças do I2CP e do túnel terão efeito depois de parar todos os torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:378
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "Desconectando anterior Destinação I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:382
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "Preferências de I2CP mudadas a {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:386
|
||||
msgid "Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr "Conectar-se não foi posível com as novas preferências I2CP, utilizarei as anteriores."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:390
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "Impossível se conectar usando as preferências anteriores!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:392
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "Conectado com a nova Destinação I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:403
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "Conexão I2CP re-estabelecida para \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:414
|
||||
msgid "Enabled autostart"
|
||||
msgstr "Ativado o iniciar automáticamente"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:416
|
||||
msgid "Disabled autostart"
|
||||
msgstr "Desativado o iniciar automáticamente"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:422
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Uso de rastreadores abertos ativado - Para ter efeito é necesário reiniciar os torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:424
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Uso dos rastreadores abertos desativado - Para ter efeito é necesário reiniciar os torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:431
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "Listado de rastreadores abertos mudado - Para ter efeito é necesário reiniciar os torrents."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:438
|
||||
#, java-format
|
||||
msgid "{0} theme loaded, return to main i2psnark page to view."
|
||||
msgstr "Tema {0} foi carregado. Volte no menú principal para vê-lo."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:445
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "Configuração não mudada."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "Não se pode guardar a configuração em {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:494
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "Conectando com I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:497
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "Error ao se conectar com I2P - Verifique a sua configuração I2CP!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:506
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "Error: Não se pode adicionar o torrent {0}."
|
||||
|
||||
#. catch this here so we don't try do delete it below
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:528
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "Não pode se abrir \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:541
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only"
|
||||
msgstr "Aviso - Se ignorará rastreado não I2P no \"{0}\", anunciando só aos rastreadores abertos do I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:543
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr "Aviso - Se ignorará rastreador não I2P no \"{0}\", rastreadores abertos estão desativados. Tens que ativá-los antes de iniciar o torrent!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:563
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "O arquivo .torrent em \"{0}\" não é válido."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:578
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "Torrent adicionado e iniciado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:580
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "Torrent adicionado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:734
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "Ha arquivos demais no \"{0}\", se apagará ({1}). "
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:736
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr "O arquivo de dados do torrent \"{0}\" não pode terminar em \".torrent' e será apagado."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:738
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "Não ha peças no \"{0}\", se apagará."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:740
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "Ha peças demais no \"{0}\" e o limite é {1}. Se apagarão."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:742
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "Peças no \"{0}\" são grandes demais ({1}B). Se apagarão."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:743
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "O limite são \"{0}\"Bytes"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:751
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr "Torrents maiores que \"{0}\" Bytes ainda não funcionam, se apagará \"{1}\"."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:767
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "Error: Não se pode quitar o torrent \"{0}\"."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:794
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "Torrent parado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:814
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "Torrent quitado: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:852
|
||||
#, java-format
|
||||
msgid "Download finished: {0}"
|
||||
msgstr "Finalizada a descarga de \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:880
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "Impossível de se conectar com I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:886
|
||||
#, java-format
|
||||
msgid "Unable to add {0}"
|
||||
msgstr "Impossível de adicionar {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:185
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark - Cliente de BitTorrent Anônimo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:198
|
||||
msgid "Torrents"
|
||||
msgstr "Torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:201
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:208
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:977
|
||||
msgid "I2PSnark"
|
||||
msgstr "I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:205
|
||||
msgid "Refresh page"
|
||||
msgstr "Atualizar página"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:210
|
||||
msgid "Forum"
|
||||
msgstr "Foro"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:264
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1483
|
||||
msgid "Status"
|
||||
msgstr "Estado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:270
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:272
|
||||
msgid "Hide Peers"
|
||||
msgstr "ocultar pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:277
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:279
|
||||
msgid "Show Peers"
|
||||
msgstr "mostrar pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:286
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1464
|
||||
msgid "Torrent"
|
||||
msgstr "Torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:290
|
||||
msgid "Estimated time remaining"
|
||||
msgstr "Tempo que falta para completar"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:293
|
||||
msgid "ETA"
|
||||
msgstr "Completado em"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:297
|
||||
msgid "Downloaded"
|
||||
msgstr "Descarregado"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:300
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:314
|
||||
msgid "RX"
|
||||
msgstr "Baixado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:304
|
||||
msgid "Uploaded"
|
||||
msgstr "Subido"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:307
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:324
|
||||
msgid "TX"
|
||||
msgstr "Subido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:312
|
||||
msgid "Down Rate"
|
||||
msgstr "Taça de descarga"
|
||||
|
||||
#. Translators: Please keep short or translate as " "
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:317
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
msgid "Rate"
|
||||
msgstr "Tasa"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:322
|
||||
msgid "Up Rate"
|
||||
msgstr "Taça de subida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:340
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "Parar todos os torrents e o túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:342
|
||||
msgid "Stop All"
|
||||
msgstr "Parar tudos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:351
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "Iniciar todos os torrents e o túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:353
|
||||
msgid "Start All"
|
||||
msgstr "Arrancar todos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:372
|
||||
msgid "No torrents loaded."
|
||||
msgstr "Não carregado nenhum torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:377
|
||||
msgid "Totals"
|
||||
msgstr "Total"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:379
|
||||
#, java-format
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "1 torrent"
|
||||
msgstr[1] "{0} torrents"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:382
|
||||
#, java-format
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "1 par conectado"
|
||||
msgstr[1] "{0} pares conectados"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:454
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "Buscando {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:458
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "Endereço não válido - tem que começar com http://"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:489
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "Iniciando o torrent {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:527
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "Apagado o arquivo torrent: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:533
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:543
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "Apagado o arquivo de dados: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:535
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:545
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "Não se pode apagar o arquivo de dados: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:554
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "Apagada a pasta de dados: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:587
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "Error ao criar o torrent - Tens que elegir um rastreador."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:602
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "Torrent criado para \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:605
|
||||
#, java-format
|
||||
msgid "Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\""
|
||||
msgstr "Muitos rastreadores no I2P exigem que você registre novos torrents antes de poder sembrá-los. Por favor, faça isto antes de iniciar \"{0}\"!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:607
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "Error ao criar o torrent \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:610
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "Não se pode criar um torrent para dados que não existam: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:613
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr "Error ao criar o torrent - Tens que especificar um arquivo ou uma pasta."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:616
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "Parando todos os torrents e fechando o túnel I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:627
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "Túnel I2P fechado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:630
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "Abrendo o túnel I2P e iniciando os torrents ..."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:759
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:764
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:770
|
||||
msgid "Tracker Error"
|
||||
msgstr "Error do rastreador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:762
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:778
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:782
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:790
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:794
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:799
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:803
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "1 par"
|
||||
msgstr[1] "{0} pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:775
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:780
|
||||
msgid "Seeding"
|
||||
msgstr "sembrando"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:784
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1533
|
||||
msgid "Complete"
|
||||
msgstr "completo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:787
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:792
|
||||
msgid "OK"
|
||||
msgstr "Bien"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:796
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:801
|
||||
msgid "Stalled"
|
||||
msgstr "estancado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:805
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:808
|
||||
msgid "No Peers"
|
||||
msgstr "sem pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:810
|
||||
msgid "Stopped"
|
||||
msgstr "detenido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:837
|
||||
#, java-format
|
||||
msgid "Details at {0} tracker"
|
||||
msgstr "Detalhes no rastreador {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:838
|
||||
msgid "Info"
|
||||
msgstr "Info"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:853
|
||||
msgid "View files"
|
||||
msgstr "mostrar arquivos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:855
|
||||
msgid "Open file"
|
||||
msgstr "abrir arquivo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:865
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1569
|
||||
msgid "Open"
|
||||
msgstr "abrir"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:909
|
||||
msgid "Stop the torrent"
|
||||
msgstr "Parar o torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:911
|
||||
msgid "Stop"
|
||||
msgstr "Parar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:921
|
||||
msgid "Start the torrent"
|
||||
msgstr "Iniciar o torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:923
|
||||
msgid "Start"
|
||||
msgstr "Iniciar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:933
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr "Retire o torrent da lista ativa, apagando o arquivo .torrent"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:938
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?"
|
||||
msgstr "Está seguro de que quer apagar o arquivo \\''{0}.torrent\\''? (Dados baixados não se apagarão.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:941
|
||||
msgid "Remove"
|
||||
msgstr "Quitar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:950
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "Apagar o arquivo torrent e o(s) arquivo(s) de dados pertenecentes"
|
||||
|
||||
#. Can't figure out how to escape double quotes inside the onclick string.
|
||||
#. Single quotes in translate strings with parameters must be doubled.
|
||||
#. Then the remaining single quite must be escaped
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:955
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?"
|
||||
msgstr "Está seguro de que quer apagar o arquivo torrent \\''{0}\\'' e todos os dados descarregados deste torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:958
|
||||
msgid "Delete"
|
||||
msgstr "Apagar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:991
|
||||
msgid "Unknown"
|
||||
msgstr "desconhecido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1001
|
||||
msgid "Seed"
|
||||
msgstr "Sembrador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1019
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "não interessante (O par não tem peças que precisamos.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1021
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "sufocado (De momento o par não está nos permitindo pedir mais peças.c)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1035
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "desinteressado (Não temos as peças que o par quer.)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1037
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "sufocando (De momento não estamos permitindo que os pares peçam mais peças)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
msgid "Add Torrent"
|
||||
msgstr "Adicionar um torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1094
|
||||
msgid "From URL"
|
||||
msgstr "URL fonte:"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1097
|
||||
msgid "Torrent file must originate from an I2P-based tracker"
|
||||
msgstr "O arquivo torrent tem que incluir um rastreador I2P."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1102
|
||||
msgid "Add torrent"
|
||||
msgstr "Adicionar torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1105
|
||||
#, java-format
|
||||
msgid "You can also copy .torrent files to: {0}."
|
||||
msgstr "Também pode copiar arquivos torrent a {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1107
|
||||
msgid "Removing a .torrent will cause it to stop."
|
||||
msgstr "A remoção de um arquivo .torrent fará com que ele pare."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1131
|
||||
msgid "Create Torrent"
|
||||
msgstr "Criar um torrent"
|
||||
|
||||
#. out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1134
|
||||
msgid "Data to seed"
|
||||
msgstr "Dados para sembrar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1138
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr "Arquivo ou pasta para sembrar (deve estar no caminho especificado)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1140
|
||||
msgid "Tracker"
|
||||
msgstr "Rastreador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1142
|
||||
msgid "Select a tracker"
|
||||
msgstr "Selecione um rastreador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1155
|
||||
msgid "or"
|
||||
msgstr "ou"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1158
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "Especifique o URL de rastreador personalizado"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1161
|
||||
msgid "Create torrent"
|
||||
msgstr "Criar torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1180
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1317
|
||||
msgid "Configuration"
|
||||
msgstr "Preferências"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1184
|
||||
msgid "Data directory"
|
||||
msgstr "Pasta de dados"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1186
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "Para mudar, modifique o arquivo i2psnark.config e re-inície!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1190
|
||||
msgid "Auto start"
|
||||
msgstr "Iniciar automáticamente"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1194
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "se marcado, os torrents adicionados se iniciarão automaticamente"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1198
|
||||
msgid "Theme"
|
||||
msgstr "Tema"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1211
|
||||
msgid "Startup delay"
|
||||
msgstr "Demora do arranque"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1213
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1237
|
||||
msgid "Total uploader limit"
|
||||
msgstr "Limite global de subidores"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1240
|
||||
msgid "peers"
|
||||
msgstr "pares"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1244
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "Limite de largura de banda para a subida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1247
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "Se recomenda a metade da largura de banda disponível."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1249
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "mostrar e mudar as preferências da largura de banda do roteador"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1253
|
||||
msgid "Use open trackers also"
|
||||
msgstr "usar também rastreadores abertos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1257
|
||||
msgid "If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"
|
||||
msgstr "Se marcado, anunciar os torrents aos rastreadores abertos, assim como aos rastreadores listados no arquivo torrent"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1261
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "URL(s) para anunciar aos rastreadores abertos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1273
|
||||
msgid "Inbound Settings"
|
||||
msgstr "Preferências de entrada"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1279
|
||||
msgid "Outbound Settings"
|
||||
msgstr "Preferências de saida"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1286
|
||||
msgid "I2CP host"
|
||||
msgstr "Anfitrião I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1291
|
||||
msgid "I2CP port"
|
||||
msgstr "Porto I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1303
|
||||
msgid "I2CP options"
|
||||
msgstr "Opções I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1308
|
||||
msgid "Save configuration"
|
||||
msgstr "Guardar configuração"
|
||||
|
||||
#. * dummies for translation
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1325
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "1 salto"
|
||||
msgstr[1] "{0} saltos"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1326
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "1 túnel"
|
||||
msgstr[1] "{0} túneles"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1480
|
||||
msgid "Size"
|
||||
msgstr "Tamanho"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1487
|
||||
msgid "Priority"
|
||||
msgstr "Prioridade"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1492
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "Subir uma herarquia"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1517
|
||||
msgid "Directory"
|
||||
msgstr "Pasta"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1522
|
||||
msgid "Torrent not found?"
|
||||
msgstr "Não achei o arquivo torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1530
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "Arquivo não achado no torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1543
|
||||
msgid "complete"
|
||||
msgstr "completo"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1544
|
||||
msgid "bytes remaining"
|
||||
msgstr "Bytes faltando"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1595
|
||||
msgid "High"
|
||||
msgstr "alta"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1600
|
||||
msgid "Normal"
|
||||
msgstr "normal"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1605
|
||||
msgid "Skip"
|
||||
msgstr "Ignorar"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1614
|
||||
msgid "Save priorities"
|
||||
msgstr "Guardar prioridades"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1726
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "Torrent obtido de {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1746
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "Torrent já em marcha: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1748
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "Torrent já na cola: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1755
|
||||
#, java-format
|
||||
msgid "Failed to copy torrent file to {0}"
|
||||
msgstr "Não se pode copiar o torrent para {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1759
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "Torrent em {0} não foi válido"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1764
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "Não se pode obter torrent de {0}"
|
||||
|
||||
#~ msgid " theme locked and loaded."
|
||||
#~ msgstr "tema carregado"
|
||||
#~ msgid "Hide All Attached Peers [connected/total in swarm]"
|
||||
#~ msgstr "Ocultar todos os pares [conectados/total neste torrent]"
|
||||
#~ msgid "Show All Attached Peers [connected/total in swarm]"
|
||||
#~ msgstr "Mostrar todos os pares [conectados/total neste torrent]"
|
||||
#~ msgid "Loaded Torrents"
|
||||
#~ msgstr "Torrents carregados"
|
||||
#~ msgid "Estimated Download Time"
|
||||
#~ msgstr "tempo restante da descarga"
|
||||
#~ msgid "1"
|
||||
#~ msgid_plural "{0}"
|
||||
#~ msgstr[0] "1"
|
||||
#~ msgstr[1] "{0}"
|
||||
#~ msgid "Torrent file {0} does not exist"
|
||||
#~ msgstr "Arquivo do torrent {0} não existe"
|
||||
#~ msgid "Copying torrent to {0}"
|
||||
#~ msgstr "Copiando torrent para {0}"
|
||||
#~ msgid "from {0}"
|
||||
#~ msgstr "de {0}"
|
||||
#~ msgid "Downloading"
|
||||
#~ msgstr "descarregando"
|
||||
#~ msgid "File"
|
||||
#~ msgstr "Arquivo"
|
||||
#~ msgid "FileSize"
|
||||
#~ msgstr "Tamanho do arquivo"
|
||||
#~ msgid "Download Status"
|
||||
#~ msgstr "Estado"
|
||||
|
||||
#, fuzzy
|
||||
#~ msgid "size: {0}B"
|
||||
#~ msgstr "Tamaño: {0}Bytes"
|
||||
#~ msgid "Directory to store torrents and data"
|
||||
#~ msgstr "Carpeta para guardar los archivos torrent y los datos"
|
||||
#~ msgid "Do not download"
|
||||
#~ msgstr "No descargues"
|
||||
#~ msgid "Details"
|
||||
#~ msgstr "Detalles"
|
||||
#~ msgid "Cannot change the I2CP settings while torrents are active"
|
||||
#~ msgstr ""
|
||||
#~ "No se puede cammbiar los ajustes I2CP mientras estén activos los torrents"
|
||||
#~ msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
#~ msgstr ""
|
||||
#~ "Rastreador fuera de I2P en \"{0}\", borrando de la lista de rastreadores"
|
||||
#~ msgid "{0} torrents"
|
||||
#~ msgstr "{0} Torrents"
|
||||
#~ msgid "Uninteresting"
|
||||
#~ msgstr "no interesante"
|
||||
#~ msgid "Choked"
|
||||
#~ msgstr "frenado"
|
||||
#~ msgid "Uninterested"
|
||||
#~ msgstr "desinteresado"
|
||||
#~ msgid "Choking"
|
||||
#~ msgstr "frenando"
|
||||
#~ msgid "Custom tracker URL"
|
||||
#~ msgstr "URL especial del rastreador"
|
||||
#~ msgid "Configure"
|
||||
#~ msgstr "Ajustes"
|
||||
|
||||
@@ -22,4 +22,68 @@
|
||||
30
|
||||
</session-timeout>
|
||||
</session-config>
|
||||
|
||||
|
||||
<!-- mime types not in mime.properties in the jetty 5.1.15 source -->
|
||||
|
||||
<mime-mapping>
|
||||
<extension>mkv</extension>
|
||||
<mime-type>video/x-matroska</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>wmv</extension>
|
||||
<mime-type>video/x-ms-wmv</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>flv</extension>
|
||||
<mime-type>video/x-flv</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>mp4</extension>
|
||||
<mime-type>video/mp4</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>rar</extension>
|
||||
<mime-type>application/rar</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>7z</extension>
|
||||
<mime-type>application/x-7z-compressed</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>iso</extension>
|
||||
<mime-type>application/x-iso9660-image</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>ico</extension>
|
||||
<mime-type>image/x-icon</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>exe</extension>
|
||||
<mime-type>application/x-msdos-program</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>flac</extension>
|
||||
<mime-type>audio/flac</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>m4a</extension>
|
||||
<mime-type>audio/mpeg</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
<mime-mapping>
|
||||
<extension>wma</extension>
|
||||
<mime-type>audio/x-ms-wma</mime-type>
|
||||
</mime-mapping>
|
||||
|
||||
</web-app>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2ptunnel">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="build" depends="builddep, jar, war" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
<!-- run from top level build.xml to get dependencies built -->
|
||||
</target>
|
||||
<condition property="depend.available">
|
||||
<typefound name="depend" />
|
||||
@@ -34,6 +32,10 @@
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<!-- TODO: Move the web classes from the jar to the war - they are not part of the API
|
||||
- This will require sponge to rewrite some seedless stuff that uses it.
|
||||
-->
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/i2ptunnel.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
@@ -41,8 +43,6 @@
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<ant target="bundle" />
|
||||
<ant target="war" />
|
||||
</target>
|
||||
|
||||
<target name="bundle" depends="compile, precompilejsp">
|
||||
@@ -77,13 +77,13 @@
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="war" depends="precompilejsp">
|
||||
<target name="war" depends="precompilejsp, bundle">
|
||||
<war destfile="build/i2ptunnel.war" webxml="../jsp/web-out.xml"
|
||||
basedir="../jsp/" excludes="web.xml, **/*.java, *.jsp">
|
||||
basedir="../jsp/" excludes="web.xml, web-fragment.xml, web-out.xml, **/*.java, *.jsp">
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target name="precompilejsp" unless="precompilejsp.uptodate">
|
||||
<target name="precompilejsp" depends="jar" unless="precompilejsp.uptodate">
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
<delete file="../jsp/web-fragment.xml" />
|
||||
<delete file="../jsp/web-out.xml" />
|
||||
|
||||
@@ -51,7 +51,7 @@ do
|
||||
# To start a new translation, copy the header from an old translation to the new .po file,
|
||||
# then ant distclean updater.
|
||||
find $JPATHS -name *.java > $TMPFILE
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 \
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
|
||||
@@ -24,6 +24,9 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* This does the transparent gzip decompression on the client side.
|
||||
* Extended in I2PTunnelHTTPServer to do the compression on the server side.
|
||||
*
|
||||
* Simple stream for delivering an HTTP response to
|
||||
* the client, trivially filtered to make sure "Connection: close"
|
||||
* is always in the response. Perhaps add transparent handling of the
|
||||
@@ -33,29 +36,27 @@ import net.i2p.util.Log;
|
||||
*
|
||||
*/
|
||||
class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ByteCache _cache;
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
protected ByteArray _headerBuffer;
|
||||
private boolean _headerWritten;
|
||||
private byte _buf1[];
|
||||
private final byte _buf1[];
|
||||
protected boolean _gzip;
|
||||
private long _dataWritten;
|
||||
private InternalGZIPInputStream _in;
|
||||
private static final int CACHE_SIZE = 8*1024;
|
||||
private static final ByteCache _cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
// OOM DOS prevention
|
||||
private static final int MAX_HEADER_SIZE = 64*1024;
|
||||
|
||||
public HTTPResponseOutputStream(OutputStream raw) {
|
||||
super(raw);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*1000, 30*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressionRatio", "ratio of compressed size to decompressed size after transfer", "I2PTunnel", new long[] { 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpCompressed", "compressed size transferred", "I2PTunnel", new long[] { 60*60*1000 });
|
||||
_context.statManager().createRateStat("i2ptunnel.httpExpanded", "size transferred after expansion", "I2PTunnel", new long[] { 60*60*1000 });
|
||||
_log = _context.logManager().getLog(getClass());
|
||||
_cache = ByteCache.getInstance(8, CACHE_SIZE);
|
||||
_headerBuffer = _cache.acquire();
|
||||
_headerWritten = false;
|
||||
_gzip = false;
|
||||
_dataWritten = 0;
|
||||
_buf1 = new byte[1];
|
||||
}
|
||||
|
||||
@@ -96,14 +97,20 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
/** grow (and free) the buffer as necessary */
|
||||
private void ensureCapacity() {
|
||||
/**
|
||||
* grow (and free) the buffer as necessary
|
||||
* @throws IOException if the headers are too big
|
||||
*/
|
||||
private void ensureCapacity() throws IOException {
|
||||
if (_headerBuffer.getValid() >= MAX_HEADER_SIZE)
|
||||
throw new IOException("Max header size exceeded: " + MAX_HEADER_SIZE);
|
||||
if (_headerBuffer.getValid() + 1 >= _headerBuffer.getData().length) {
|
||||
int newSize = (int)(_headerBuffer.getData().length * 1.5);
|
||||
ByteArray newBuf = new ByteArray(new byte[newSize]);
|
||||
System.arraycopy(_headerBuffer.getData(), 0, newBuf.getData(), 0, _headerBuffer.getValid());
|
||||
newBuf.setValid(_headerBuffer.getValid());
|
||||
newBuf.setOffset(0);
|
||||
// if we changed the ByteArray size, don't put it back in the cache
|
||||
if (_headerBuffer.getData().length == CACHE_SIZE)
|
||||
_cache.release(_headerBuffer);
|
||||
_headerBuffer = newBuf;
|
||||
@@ -124,13 +131,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
* Tweak that first HTTP response line (HTTP 200 OK, etc)
|
||||
*
|
||||
*/
|
||||
protected String filterResponseLine(String line) {
|
||||
protected static String filterResponseLine(String line) {
|
||||
return line;
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
private static boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
/** ok, received, now munge & write it */
|
||||
private void writeHeader() throws IOException {
|
||||
@@ -172,6 +179,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else if ("Proxy-Authenticate".equalsIgnoreCase(key)) {
|
||||
// filter this hop-by-hop header; outproxy authentication must be configured in I2PTunnelHTTPClient
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
|
||||
}
|
||||
@@ -219,7 +228,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
//out.flush();
|
||||
PipedInputStream pi = new PipedInputStream();
|
||||
PipedOutputStream po = new PipedOutputStream(pi);
|
||||
new I2PAppThread(new Pusher(pi, out), "HTTP decompresser").start();
|
||||
new I2PAppThread(new Pusher(pi, out), "HTTP decompressor").start();
|
||||
out = po;
|
||||
}
|
||||
|
||||
@@ -231,13 +240,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
_out = out;
|
||||
}
|
||||
public void run() {
|
||||
OutputStream to = null;
|
||||
_in = null;
|
||||
long start = System.currentTimeMillis();
|
||||
long written = 0;
|
||||
ByteArray ba = null;
|
||||
try {
|
||||
_in = new InternalGZIPInputStream(_inRaw);
|
||||
byte buf[] = new byte[8192];
|
||||
ba = _cache.acquire();
|
||||
byte buf[] = ba.getData();
|
||||
int read = -1;
|
||||
while ( (read = _in.read(buf)) != -1) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -251,6 +260,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error decompressing: " + written + ", " + (_in != null ? _in.getTotalRead() + "/" + _in.getTotalExpanded() : ""), ioe);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.error("OOM in HTTP Decompressor", oom);
|
||||
} finally {
|
||||
if (_log.shouldLog(Log.WARN) && (_in != null))
|
||||
_log.warn("After decompression, written=" + written +
|
||||
@@ -259,23 +270,27 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
+ ", expanded=" + _in.getTotalExpanded() + ", remaining=" + _in.getRemaining()
|
||||
+ ", finished=" + _in.getFinished()
|
||||
: ""));
|
||||
if (ba != null)
|
||||
_cache.release(ba);
|
||||
if (_out != null) try {
|
||||
_out.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
|
||||
double compressed = (_in != null ? _in.getTotalRead() : 0);
|
||||
double expanded = (_in != null ? _in.getTotalExpanded() : 0);
|
||||
double ratio = 0;
|
||||
if (expanded > 0)
|
||||
ratio = compressed/expanded;
|
||||
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, end-start);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
if (compressed > 0 && expanded > 0) {
|
||||
// only update the stats if we did something
|
||||
double ratio = compressed/expanded;
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressionRatio", (int)(100d*ratio), 0);
|
||||
_context.statManager().addRateData("i2ptunnel.httpCompressed", (long)compressed, 0);
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
|
||||
/** just a wrapper to provide stats for debugging */
|
||||
private static class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
@@ -293,6 +308,12 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From Inflater javadoc:
|
||||
* Returns the total number of bytes remaining in the input buffer. This can be used to find out
|
||||
* what bytes still remain in the input buffer after decompression has finished.
|
||||
*/
|
||||
public long getRemaining() {
|
||||
try {
|
||||
return super.inf.getRemaining();
|
||||
@@ -318,6 +339,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
/*******
|
||||
public static void main(String args[]) {
|
||||
String simple = "HTTP/1.1 200 OK\n" +
|
||||
"foo: bar\n" +
|
||||
@@ -367,7 +389,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
"A:\n" +
|
||||
"\n";
|
||||
|
||||
/* */
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
test("Filtered windows", winfilter, true);
|
||||
@@ -382,7 +403,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
test("Invalid (bad headers)", invalid5, true);
|
||||
test("Invalid (bad headers2)", invalid6, false);
|
||||
test("Invalid (bad headers3)", invalid7, false);
|
||||
/* */
|
||||
}
|
||||
|
||||
private static void test(String name, String orig, boolean shouldPass) {
|
||||
@@ -401,4 +421,5 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
System.out.println("Properly fails with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
******/
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@ import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.EventDispatcherImpl;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Todo: Most events are not listened to elsewhere, so error propagation is poor
|
||||
*/
|
||||
public class I2PTunnel implements Logging, EventDispatcher {
|
||||
private Log _log;
|
||||
private EventDispatcherImpl _event;
|
||||
@@ -82,8 +85,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
|
||||
public boolean ownDest = false;
|
||||
|
||||
/** the I2CP port */
|
||||
public String port = System.getProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
/** the I2CP host */
|
||||
public String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
/** the listen-on host. Sadly the listen-on port does not have a field. */
|
||||
public String listenHost = host;
|
||||
|
||||
public long readTimeout = -1;
|
||||
@@ -163,7 +169,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
System.out.print("I2PTunnel>");
|
||||
String cmd = r.readLine();
|
||||
if (cmd == null) break;
|
||||
runCommand(cmd, this);
|
||||
if (cmd.length() <= 0) continue;
|
||||
try {
|
||||
runCommand(cmd, this);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
@@ -180,6 +191,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/** @return non-null */
|
||||
List<I2PSession> getSessions() {
|
||||
synchronized (_sessions) {
|
||||
return new ArrayList(_sessions);
|
||||
@@ -351,6 +363,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {hostname, portNumber, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runServer(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
@@ -363,7 +376,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -372,17 +385,18 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
|
||||
privKeyFile = new File(args[2]);
|
||||
if (!privKeyFile.isAbsolute())
|
||||
privKeyFile = new File(_context.getConfigDir(), args[2]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[2]);
|
||||
}
|
||||
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
@@ -400,6 +414,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
/**
|
||||
* Same args as runServer
|
||||
* (we should stop duplicating all this code...)
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runIrcServer(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
@@ -412,7 +427,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -421,17 +436,18 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
|
||||
privKeyFile = new File(args[2]);
|
||||
if (!privKeyFile.isAbsolute())
|
||||
privKeyFile = new File(_context.getConfigDir(), args[2]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[2]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[2]);
|
||||
}
|
||||
I2PTunnelServer serv = new I2PTunnelIRCServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
@@ -457,6 +473,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {hostname, portNumber, spoofedHost, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runHttpServer(String args[], Logging l) {
|
||||
if (args.length == 4) {
|
||||
@@ -469,7 +486,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -478,8 +495,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
|
||||
String spoofedHost = args[2];
|
||||
|
||||
@@ -487,10 +505,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
if (!privKeyFile.isAbsolute())
|
||||
privKeyFile = new File(_context.getConfigDir(), args[3]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[3]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[3]);
|
||||
}
|
||||
I2PTunnelHTTPServer serv = new I2PTunnelHTTPServer(serverHost, portNum, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
@@ -519,6 +537,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {hostname, portNumber, proxyPortNumber, spoofedHost, privKeyFilename}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runHttpBidirServer(String args[], Logging l) {
|
||||
if (args.length == 5) {
|
||||
@@ -532,7 +551,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -541,7 +560,6 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -550,8 +568,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[2], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
if (port2Num <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[2]);
|
||||
|
||||
String spoofedHost = args[3];
|
||||
|
||||
@@ -559,10 +580,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
if (!privKeyFile.isAbsolute())
|
||||
privKeyFile = new File(_context.getConfigDir(), args[4]);
|
||||
if (!privKeyFile.canRead()) {
|
||||
l.log("private key file does not exist");
|
||||
l.log(getPrefix() + "Private key file does not exist or is not readable: " + args[4]);
|
||||
_log.error(getPrefix() + "Private key file does not exist or is not readable: " + args[4]);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Cannot open private key file " + args[4]);
|
||||
}
|
||||
|
||||
I2PTunnelHTTPBidirServer serv = new I2PTunnelHTTPBidirServer(serverHost, portNum, port2Num, privKeyFile, args[3], spoofedHost, l, (EventDispatcher) this, this);
|
||||
@@ -585,12 +606,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
* Run the server pointing at the host and port specified using the private i2p
|
||||
* destination loaded from the given base64 stream. <p />
|
||||
*
|
||||
* Deprecated? Why run a server with a private destination?
|
||||
* Not available from the war GUI
|
||||
*
|
||||
* Sets the event "serverTaskId" = Integer(taskId) after the tunnel has been started (or -1 on error)
|
||||
* Also sets the event "openServerResult" = "ok" or "error" (displaying "Ready!" on the logger after
|
||||
* 'ok'). So, success = serverTaskId != -1 and openServerResult = ok.
|
||||
*
|
||||
* @param args {hostname, portNumber, privKeyBase64}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runTextServer(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
@@ -602,7 +627,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("unknown host");
|
||||
_log.error(getPrefix() + "Error resolving " + args[0], uhe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
throw new IllegalArgumentException(getPrefix() + "Error resolving " + args[0] + uhe.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -611,8 +636,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[1], nfe);
|
||||
notifyEvent("serverTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[1]);
|
||||
|
||||
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this, this);
|
||||
serv.setReadTimeout(readTimeout);
|
||||
@@ -638,6 +664,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {portNumber, destinationBase64 or "file:filename"[, sharedClient [, privKeyFile]]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runClient(String args[], Logging l) {
|
||||
boolean isShared = true;
|
||||
@@ -651,8 +678,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("clientTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (portNum <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
I2PTunnelTask task;
|
||||
ownDest = !isShared;
|
||||
try {
|
||||
@@ -663,9 +692,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
addtask(task);
|
||||
notifyEvent("clientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create a client [" + host + ":"+ port + "]", iae);
|
||||
l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
|
||||
String msg = "Invalid I2PTunnel configuration to create an HTTP Proxy connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("clientTaskId", Integer.valueOf(-1));
|
||||
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
|
||||
// Otherwise, the tunnel stays up even though the port is down
|
||||
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
|
||||
// so this probably leaves the tunnel open if called from the CLI
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("client <port> <pubkey>[,<pubkey>]|file:<pubkeyfile>[ <sharedClient>] [<privKeyFile>]");
|
||||
@@ -687,6 +723,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runHttpClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
@@ -697,8 +734,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("httpclientTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (clientPort <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
@@ -730,9 +768,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
addtask(task);
|
||||
notifyEvent("httpclientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ clientPort + "]", iae);
|
||||
l.log("Invalid I2PTunnel configuration [" + host + ":" + clientPort + "]");
|
||||
String msg = "Invalid I2PTunnel configuration to create an HTTP Proxy connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("httpclientTaskId", Integer.valueOf(-1));
|
||||
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
|
||||
// Otherwise, the tunnel stays up even though the port is down
|
||||
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
|
||||
// so this probably leaves the tunnel open if called from the CLI
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("httpclient <port> [<sharedClient>] [<proxy>]");
|
||||
@@ -749,6 +794,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {portNumber[, sharedClient][, proxy to be used for the WWW]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runConnectClient(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
@@ -757,8 +803,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
_port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
String proxy = "";
|
||||
boolean isShared = true;
|
||||
@@ -789,7 +836,15 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
task = new I2PTunnelConnectClient(_port, l, ownDest, proxy, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ _port + "]", iae);
|
||||
String msg = "Invalid I2PTunnel configuration to create a CONNECT client connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
|
||||
// Otherwise, the tunnel stays up even though the port is down
|
||||
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
|
||||
// so this probably leaves the tunnel open if called from the CLI
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("connectclient <port> [<sharedClient>] [<proxy>]");
|
||||
@@ -809,6 +864,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {portNumber,destinationBase64 or "file:filename" [, sharedClient [, privKeyFile]]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runIrcClient(String args[], Logging l) {
|
||||
if (args.length >= 2) {
|
||||
@@ -819,8 +875,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
boolean isShared = true;
|
||||
if (args.length > 2) {
|
||||
@@ -845,9 +902,16 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
addtask(task);
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
_log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ _port + "]", iae);
|
||||
l.log("Invalid I2PTunnel configuration [" + host + ":" + _port + "]");
|
||||
String msg = "Invalid I2PTunnel configuration to create an IRC client connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("ircclientTaskId", Integer.valueOf(-1));
|
||||
// Since nothing listens to TaskID events, use this to propagate the error to TunnelController
|
||||
// Otherwise, the tunnel stays up even though the port is down
|
||||
// This doesn't work for CLI though... and the tunnel doesn't close itself after error,
|
||||
// so this probably leaves the tunnel open if called from the CLI
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("ircclient <port> [<sharedClient> [<privKeyFile>]]");
|
||||
@@ -867,6 +931,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {portNumber [, sharedClient]}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runSOCKSTunnel(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
@@ -877,18 +942,27 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
boolean isShared = false;
|
||||
if (args.length > 1)
|
||||
isShared = "true".equalsIgnoreCase(args[1].trim());
|
||||
|
||||
ownDest = !isShared;
|
||||
I2PTunnelTask task;
|
||||
task = new I2PSOCKSTunnel(_port, l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
try {
|
||||
I2PTunnelTask task = new I2PSOCKSTunnel(_port, l, ownDest, (EventDispatcher) this, this, null);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = "Invalid I2PTunnel configuration to create a SOCKS Proxy connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("sockstunnel <port>");
|
||||
l.log(" creates a tunnel that distributes SOCKS requests.");
|
||||
@@ -899,10 +973,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
|
||||
/**
|
||||
* Run an SOCKS IRC tunnel on the given port number
|
||||
* @param args {portNumber [, sharedClient]} or (portNumber, ignored (false), privKeyFile)
|
||||
* @throws IllegalArgumentException on config problem
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public void runSOCKSIRCTunnel(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
if (args.length >= 1 && args.length <= 3) {
|
||||
int _port = -1;
|
||||
try {
|
||||
_port = Integer.parseInt(args[0]);
|
||||
@@ -910,21 +986,33 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
boolean isShared = false;
|
||||
if (args.length > 1)
|
||||
if (args.length == 2)
|
||||
isShared = "true".equalsIgnoreCase(args[1].trim());
|
||||
|
||||
ownDest = !isShared;
|
||||
I2PTunnelTask task;
|
||||
task = new I2PSOCKSIRCTunnel(_port, l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
String privateKeyFile = null;
|
||||
if (args.length == 3)
|
||||
privateKeyFile = args[2];
|
||||
try {
|
||||
I2PTunnelTask task = new I2PSOCKSIRCTunnel(_port, l, ownDest, (EventDispatcher) this, this, privateKeyFile);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = "Invalid I2PTunnel configuration to create a SOCKS IRC Proxy connecting to the router at " + host + ':'+ port +
|
||||
" and listening on " + listenHost + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("sockstunnel <port>");
|
||||
l.log(" creates a tunnel that distributes SOCKS requests.");
|
||||
l.log("socksirctunnel <port> [<sharedClient> [<privKeyFile>]]");
|
||||
l.log(" creates a tunnel for SOCKS IRC.");
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
@@ -934,6 +1022,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {targethost, targetport, destinationString}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runStreamrClient(String args[], Logging l) {
|
||||
if (args.length == 3) {
|
||||
@@ -954,13 +1043,23 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
StreamrConsumer task = new StreamrConsumer(_host, _port, args[2], l, (EventDispatcher) this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
|
||||
try {
|
||||
StreamrConsumer task = new StreamrConsumer(_host, _port, args[2], l, (EventDispatcher) this, this);
|
||||
task.startRunning();
|
||||
addtask(task);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
String msg = "Invalid I2PTunnel configuration to create a Streamr Client connecting to the router at " + host + ':'+ port +
|
||||
" and sending to " + _host + ':' + port;
|
||||
_log.error(getPrefix() + msg, iae);
|
||||
l.log(msg);
|
||||
notifyEvent("streamrtunnnelTaskId", Integer.valueOf(-1));
|
||||
throw iae;
|
||||
}
|
||||
} else {
|
||||
l.log("streamrclient <host> <port> <destination>");
|
||||
l.log(" creates a tunnel that receives streaming data.");
|
||||
@@ -973,6 +1072,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
* @param args {port, privkeyfile}
|
||||
* @param l logger to receive events and output
|
||||
* @throws IllegalArgumentException on config problem
|
||||
*/
|
||||
public void runStreamrServer(String args[], Logging l) {
|
||||
if (args.length == 2) {
|
||||
@@ -983,8 +1083,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("streamrtunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
if (_port <= 0)
|
||||
throw new IllegalArgumentException(getPrefix() + "Bad port " + args[0]);
|
||||
|
||||
File privKeyFile = new File(args[1]);
|
||||
if (!privKeyFile.isAbsolute())
|
||||
@@ -1345,7 +1446,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
for (Iterator it = tasks.iterator(); it.hasNext();) {
|
||||
I2PTunnelTask t = (I2PTunnelTask) it.next();
|
||||
int id = t.getId();
|
||||
_log.debug(getPrefix() + "closetask(): parsing task " + id + " (" + t.toString() + ")");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "closetask(): parsing task " + id + " (" + t.toString() + ")");
|
||||
if (id == num) {
|
||||
closed = closetask(t, forced, l);
|
||||
break;
|
||||
@@ -1363,9 +1465,13 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
*
|
||||
*/
|
||||
private boolean closetask(I2PTunnelTask t, boolean forced, Logging l) {
|
||||
l.log("Closing task " + t.getId() + (forced ? " forced..." : "..."));
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Closing task " + t.getId() + (forced ? " forced..." : "..."));
|
||||
//l.log("Closing task " + t.getId() + (forced ? " forced..." : "..."));
|
||||
if (t.close(forced)) {
|
||||
l.log("Task " + t.getId() + " closed.");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Task " + t.getId() + " closed.");
|
||||
//l.log("Task " + t.getId() + " closed.");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1399,6 +1505,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
/**
|
||||
* Create a new destination, storing the destination and its private keys where
|
||||
* instructed
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* @param writeTo location to store the private keys
|
||||
* @param pubDest location to store the destination
|
||||
@@ -1423,6 +1530,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
|
||||
/**
|
||||
* Read in the given destination, display it, and write it to the given location
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* @param readFrom stream to read the destination from
|
||||
* @param pubDest stream to write the destination to
|
||||
@@ -1444,6 +1552,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
|
||||
/**
|
||||
* Write out the destination to the stream
|
||||
* Deprecated - only used by CLI
|
||||
*
|
||||
* @param d Destination to write
|
||||
* @param o stream to write the destination to
|
||||
@@ -1461,6 +1570,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
* also supported, where filename is a file that either contains a
|
||||
* binary Destination structure or the Base64 encoding of that
|
||||
* structure.
|
||||
*
|
||||
* Since file:<filename> isn't really used, this method is deprecated,
|
||||
* just call context.namingService.lookup() directly.
|
||||
* @deprecated Don't use i2ptunnel for lookup! Use I2PAppContext.getGlobalContext().namingService().lookup(name) from i2p.jar
|
||||
*/
|
||||
public static Destination destFromName(String name) throws DataFormatException {
|
||||
|
||||
@@ -1525,7 +1638,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private String getPrefix() { return '[' + _tunnelId + "]: "; }
|
||||
private String getPrefix() { return "[" + _tunnelId + "]: "; }
|
||||
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -20,7 +19,7 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
private static final Log _log = new Log(I2PTunnelClient.class);
|
||||
|
||||
/** list of Destination objects that we point at */
|
||||
protected List dests;
|
||||
protected List<Destination> dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
@@ -32,7 +31,9 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
public I2PTunnelClient(int localPort, String destinations, Logging l,
|
||||
boolean ownDest, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel, String pkf) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "SynSender", tunnel, pkf);
|
||||
super(localPort, ownDest, l, notifyThis,
|
||||
"Standard client on " + tunnel.listenHost + ':' + localPort,
|
||||
tunnel, pkf);
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openClientResult", "error");
|
||||
@@ -43,21 +44,28 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String destination = tok.nextToken();
|
||||
try {
|
||||
Destination destN = I2PTunnel.destFromName(destination);
|
||||
if (destN == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(destN);
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Bad format parsing \"" + destination + "\"");
|
||||
}
|
||||
Destination destN = _context.namingService().lookup(destination);
|
||||
if (destN == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(destN);
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
l.log("No target destinations found");
|
||||
if (dests.isEmpty()) {
|
||||
l.log("No valid target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
// Nothing is listening for the above event, so it's useless
|
||||
// Maybe figure out where to put a waitEventValue("openClientResult") ??
|
||||
// In the meantime, let's do this the easy way
|
||||
// Note that b32 dests will often not be resolvable at instantiation time;
|
||||
// a delayed resolution system would be even better.
|
||||
|
||||
// Don't close() here, because it does a removeSession() and then
|
||||
// TunnelController can't acquire() it to release() it.
|
||||
//close(true);
|
||||
// Unfortunately, super() built the whole tunnel before we get here.
|
||||
throw new IllegalArgumentException("No valid target destinations found");
|
||||
//return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> " + destinations);
|
||||
@@ -78,8 +86,9 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error connecting", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
@@ -97,8 +106,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
return null;
|
||||
}
|
||||
if (size == 1) // skip the rand in the most common case
|
||||
return (Destination)dests.get(0);
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return (Destination)dests.get(index);
|
||||
return dests.get(0);
|
||||
int index = _context.random().nextInt(size);
|
||||
return dests.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,10 +128,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
configurePool(tunnel);
|
||||
|
||||
if (open && listenerReady) {
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort);
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Error listening - please see the logs!");
|
||||
l.log("Client error for " + tunnel.listenHost + ':' + localPort + ", check logs");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
while (sockMgr == null) {
|
||||
verifySocketManager();
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager (our own? " + ownDest + ")");
|
||||
_log.error("Unable to connect to router and build tunnels for " + handlerName);
|
||||
// FIXME there is a loop in buildSocketManager(), do we really need another one here?
|
||||
// no matter, buildSocketManager() now throws an IllegalArgumentException
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +191,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
l.log("Invalid I2CP configuration");
|
||||
throw new IllegalArgumentException("Socket manager could not be created");
|
||||
}
|
||||
l.log("I2P session created");
|
||||
l.log("Tunnels ready for client: " + handlerName);
|
||||
|
||||
} // else delay creating session until createI2PSocket() is called
|
||||
|
||||
@@ -212,12 +214,12 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
|
||||
if (open && listenerReady) {
|
||||
if (openNow)
|
||||
l.log("Ready! Port " + getLocalPort());
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort);
|
||||
else
|
||||
l.log("Listening on port " + getLocalPort() + ", delaying tunnel open until required");
|
||||
l.log("Client ready, listening on " + tunnel.listenHost + ':' + localPort + ", delaying tunnel open until required");
|
||||
notifyEvent("openBaseClientResult", "ok");
|
||||
} else {
|
||||
l.log("Error listening - please see the logs!");
|
||||
l.log("Client error for " + tunnel.listenHost + ':' + localPort + ", check logs");
|
||||
notifyEvent("openBaseClientResult", "error");
|
||||
}
|
||||
}
|
||||
@@ -257,6 +259,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
* Sets the this.sockMgr field if it is null, or if we want a new one
|
||||
*
|
||||
* We need a socket manager before getDefaultOptions() and most other things
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected void verifySocketManager() {
|
||||
synchronized(sockLock) {
|
||||
@@ -289,15 +293,33 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
/** this is ONLY for shared clients */
|
||||
private static I2PSocketManager socketManager;
|
||||
|
||||
/** this is ONLY for shared clients */
|
||||
|
||||
/**
|
||||
* this is ONLY for shared clients
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected synchronized I2PSocketManager getSocketManager() {
|
||||
return getSocketManager(getTunnel(), this.privKeyFile);
|
||||
}
|
||||
/** this is ONLY for shared clients */
|
||||
|
||||
/**
|
||||
* this is ONLY for shared clients
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel) {
|
||||
return getSocketManager(tunnel, null);
|
||||
}
|
||||
/** this is ONLY for shared clients */
|
||||
|
||||
/**
|
||||
* this is ONLY for shared clients
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected static synchronized I2PSocketManager getSocketManager(I2PTunnel tunnel, String pkf) {
|
||||
if (socketManager != null) {
|
||||
I2PSession s = socketManager.getSession();
|
||||
@@ -319,15 +341,43 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
return socketManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected I2PSocketManager buildSocketManager() {
|
||||
return buildSocketManager(getTunnel(), this.privKeyFile);
|
||||
return buildSocketManager(getTunnel(), this.privKeyFile, this.l);
|
||||
}
|
||||
/**
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel) {
|
||||
return buildSocketManager(tunnel, null);
|
||||
}
|
||||
|
||||
/** @param pkf absolute path or null */
|
||||
private static final int RETRY_DELAY = 20*1000;
|
||||
private static final int MAX_RETRIES = 4;
|
||||
|
||||
/**
|
||||
* @param pkf absolute path or null
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf) {
|
||||
return buildSocketManager(tunnel, pkf, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pkf absolute path or null
|
||||
* @return non-null
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
protected static I2PSocketManager buildSocketManager(I2PTunnel tunnel, String pkf, Logging log) {
|
||||
Properties props = new Properties();
|
||||
props.putAll(tunnel.getClientOptions());
|
||||
int portNum = 7654;
|
||||
@@ -340,6 +390,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
I2PSocketManager sockManager = null;
|
||||
// Todo: Can't stop a tunnel from the UI while it's in this loop (no session yet)
|
||||
int retries = 0;
|
||||
while (sockManager == null) {
|
||||
if (pkf != null) {
|
||||
// Persistent client dest
|
||||
@@ -348,8 +400,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
fis = new FileInputStream(pkf);
|
||||
sockManager = I2PSocketManagerFactory.createManager(fis, tunnel.host, portNum, props);
|
||||
} catch (IOException ioe) {
|
||||
if (log != null)
|
||||
log.log("Error opening key file " + ioe);
|
||||
_log.error("Error opening key file", ioe);
|
||||
// this is going to loop but if we break we'll get a NPE
|
||||
throw new IllegalArgumentException("Error opening key file " + ioe);
|
||||
} finally {
|
||||
if (fis != null)
|
||||
try { fis.close(); } catch (IOException ioe) {}
|
||||
@@ -359,8 +413,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
if (sockManager == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
// try to make this error sensible as it will happen... sadly we can't get to the listenPort, only the listenHost
|
||||
String msg = "Unable to connect to the router at " + tunnel.host + ':' + portNum +
|
||||
" and build tunnels for the client";
|
||||
if (++retries < MAX_RETRIES) {
|
||||
if (log != null)
|
||||
log.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
_log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
} else {
|
||||
if (log != null)
|
||||
log.log(msg + ", giving up");
|
||||
_log.log(Log.CRIT, msg + ", giving up");
|
||||
// not clear if callers can handle null
|
||||
//return null;
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sockManager.setName("Client");
|
||||
@@ -479,7 +547,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
localPort = ss.getLocalPort();
|
||||
}
|
||||
notifyEvent("clientLocalPort", new Integer(ss.getLocalPort()));
|
||||
l.log("Listening for clients on port " + localPort + " of " + getTunnel().listenHost);
|
||||
// duplicates message in constructor
|
||||
//l.log("Listening for clients on port " + localPort + " of " + getTunnel().listenHost);
|
||||
|
||||
// Notify constructor that port is ready
|
||||
synchronized (this) {
|
||||
@@ -583,6 +652,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
|
||||
public boolean close(boolean forced) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("close() called: forced = " + forced + " open = " + open + " sockMgr = " + sockMgr);
|
||||
if (!open) return true;
|
||||
// FIXME: here we might have to wait quite a long time if
|
||||
// there is a connection attempt atm. But without waiting we
|
||||
@@ -606,7 +677,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
}
|
||||
} // else the app chaining to this one closes it!
|
||||
}
|
||||
l.log("Closing client " + toString());
|
||||
l.log("Stopping client " + toString());
|
||||
open = false;
|
||||
try {
|
||||
if (ss != null) ss.close();
|
||||
@@ -614,7 +685,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
l.log("Client closed.");
|
||||
//l.log("Client closed.");
|
||||
}
|
||||
|
||||
synchronized (_waitingSockets) { _waitingSockets.notifyAll(); }
|
||||
@@ -641,7 +712,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
while (open) {
|
||||
try {
|
||||
synchronized (_waitingSockets) {
|
||||
if (_waitingSockets.size() <= 0)
|
||||
if (_waitingSockets.isEmpty())
|
||||
_waitingSockets.wait();
|
||||
else
|
||||
s = (Socket)_waitingSockets.remove(0);
|
||||
|
||||
@@ -18,6 +18,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
@@ -26,6 +27,7 @@ import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Supports the following:
|
||||
*<pre>
|
||||
* (where protocol is generally HTTP/1.1 but is ignored)
|
||||
* (where host is one of:
|
||||
* example.i2p
|
||||
@@ -39,24 +41,25 @@ import net.i2p.util.Log;
|
||||
* CONNECT host protocol
|
||||
* CONNECT host:port
|
||||
* CONNECT host:port protocol (this is the standard)
|
||||
*</pre>
|
||||
*
|
||||
* Additional lines after the CONNECT line but before the blank line are ignored and stripped.
|
||||
* The CONNECT line is removed for .i2p accesses
|
||||
* but passed along for outproxy accesses.
|
||||
*
|
||||
* Ref:
|
||||
*<pre>
|
||||
* INTERNET-DRAFT Ari Luotonen
|
||||
* Expires: September 26, 1997 Netscape Communications Corporation
|
||||
* <draft-luotonen-ssl-tunneling-03.txt> March 26, 1997
|
||||
* Tunneling SSL Through a WWW Proxy
|
||||
*</pre>
|
||||
*
|
||||
* @author zzz a stripped-down I2PTunnelHTTPClient
|
||||
*/
|
||||
public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable {
|
||||
public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelConnectClient.class);
|
||||
|
||||
private final List<String> _proxyList;
|
||||
|
||||
private final static byte[] ERR_DESTINATION_UNKNOWN =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
@@ -69,16 +72,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
"Could not find the following Destination:<BR><BR><div>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 405 Bad Method\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
@@ -98,17 +91,23 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_AUTH =
|
||||
("HTTP/1.1 407 Proxy Authentication Required\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P SSL Proxy\"\r\n" +
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
|
||||
"This proxy is configured to require authentication.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] SUCCESS_RESPONSE =
|
||||
("HTTP/1.1 200 Connection Established\r\n"+
|
||||
"Proxy-agent: I2P\r\n"+
|
||||
"\r\n")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2PTunnel does not contain
|
||||
* valid config to contact the router
|
||||
@@ -116,9 +115,8 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
public I2PTunnelConnectClient(int localPort, Logging l, boolean ownDest,
|
||||
String wwwProxy, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPS Proxy on " + tunnel.listenHost + ':' + localPort + " #" + (++__clientId), tunnel);
|
||||
|
||||
_proxyList = new ArrayList();
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openConnectClientResult", "error");
|
||||
return;
|
||||
@@ -130,25 +128,11 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
_proxyList.add(tok.nextToken().trim());
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> ConnectClient [Outproxy list: " + wwwProxy + "]");
|
||||
setName("HTTPS Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
}
|
||||
|
||||
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
private String selectProxy() {
|
||||
synchronized (_proxyList) {
|
||||
int size = _proxyList.size();
|
||||
if (size <= 0)
|
||||
return null;
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return _proxyList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
*
|
||||
@@ -168,7 +152,6 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
@@ -182,6 +165,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
String line, method = null, host = null, destination = null, restofline = null;
|
||||
StringBuilder newRequest = new StringBuilder();
|
||||
int ahelper = 0;
|
||||
String authorization = null;
|
||||
while (true) {
|
||||
// Use this rather than BufferedReader because we can't have readahead,
|
||||
// since we are passing the stream on to I2PTunnelRunner
|
||||
@@ -222,7 +206,7 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
}
|
||||
destination = currentProxy;
|
||||
usingWWWProxy = true;
|
||||
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n\r\n"); // HTTP spec
|
||||
newRequest.append("CONNECT ").append(host).append(restofline).append("\r\n"); // HTTP spec
|
||||
} else if (host.toLowerCase().equals("localhost")) {
|
||||
writeErrorMessage(ERR_LOCALHOST, out);
|
||||
s.close();
|
||||
@@ -238,7 +222,11 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
_log.debug(getPrefix(requestId) + "REST :" + restofline + ":");
|
||||
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
|
||||
}
|
||||
|
||||
} else if (line.toLowerCase().startsWith("proxy-authorization: basic ")) {
|
||||
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
|
||||
// save for auth check below
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
line = null;
|
||||
} else if (line.length() > 0) {
|
||||
// Additional lines - shouldn't be too many. Firefox sends:
|
||||
// User-Agent: blabla
|
||||
@@ -249,6 +237,23 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
// but for now just chomp them all.
|
||||
line = null;
|
||||
} else {
|
||||
// Add Proxy-Authentication header for next hop (outproxy)
|
||||
if (usingWWWProxy && Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH)).booleanValue()) {
|
||||
// specific for this proxy
|
||||
String user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER_PREFIX + currentProxy);
|
||||
String pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW_PREFIX + currentProxy);
|
||||
if (user == null || pw == null) {
|
||||
// if not, look at default user and pw
|
||||
user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER);
|
||||
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
|
||||
}
|
||||
if (user != null && pw != null) {
|
||||
newRequest.append("Proxy-Authorization: Basic ")
|
||||
.append(Base64.encode((user + ':' + pw).getBytes(), true)) // true = use standard alphabet
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
newRequest.append("\r\n"); // HTTP spec
|
||||
// do it
|
||||
break;
|
||||
}
|
||||
@@ -260,7 +265,20 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna
|
||||
return;
|
||||
}
|
||||
|
||||
Destination clientDest = I2PTunnel.destFromName(destination);
|
||||
// Authorization
|
||||
if (!authorize(s, requestId, authorization)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (authorization != null)
|
||||
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
|
||||
else
|
||||
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
|
||||
}
|
||||
writeErrorMessage(ERR_AUTH, out);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
Destination clientDest = _context.namingService().lookup(destination);
|
||||
if (clientDest == null) {
|
||||
String str;
|
||||
byte[] header;
|
||||
|
||||
@@ -27,13 +27,12 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.util.Translate;
|
||||
|
||||
/**
|
||||
@@ -61,11 +60,9 @@ import net.i2p.util.Translate;
|
||||
* and POST have been tested, though other $methods should work.
|
||||
*
|
||||
*/
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
|
||||
public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
|
||||
|
||||
protected final List proxyList = new ArrayList();
|
||||
|
||||
private HashMap addressHelpers = new HashMap();
|
||||
|
||||
/**
|
||||
@@ -153,16 +150,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
private static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
private final static byte[] ERR_AUTH =
|
||||
("HTTP/1.1 407 Proxy Authentication Required\r\n"+
|
||||
"Content-Type: text/html; charset=UTF-8\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.5\r\n" + // try to get a UTF-8-encoded response back for the password
|
||||
"Proxy-Authenticate: Basic realm=\"I2P HTTP Proxy\"\r\n" +
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: PROXY AUTHENTICATION REQUIRED</H1>"+
|
||||
"This proxy is configured to require authentication.<BR>")
|
||||
.getBytes();
|
||||
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, I2PSocketManager sockMgr, I2PTunnel tunnel, EventDispatcher notifyThis, long clientId) {
|
||||
super(localPort, l, sockMgr, tunnel, notifyThis, clientId);
|
||||
// proxyList = new ArrayList();
|
||||
|
||||
setName(getLocalPort() + " -> HTTPClient [NO PROXIES]");
|
||||
setName("HTTP Proxy on " + getTunnel().listenHost + ':' + localPort);
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
@@ -174,7 +177,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
public I2PTunnelHTTPClient(int localPort, Logging l, boolean ownDest,
|
||||
String wwwProxy, EventDispatcher notifyThis,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel);
|
||||
super(localPort, ownDest, l, notifyThis, "HTTP Proxy on " + tunnel.listenHost + ':' + localPort + " #" + (++__clientId), tunnel);
|
||||
|
||||
//proxyList = new ArrayList(); // We won't use outside of i2p
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
@@ -185,35 +188,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (wwwProxy != null) {
|
||||
StringTokenizer tok = new StringTokenizer(wwwProxy, ", ");
|
||||
while (tok.hasMoreTokens())
|
||||
proxyList.add(tok.nextToken().trim());
|
||||
_proxyList.add(tok.nextToken().trim());
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> HTTPClient [WWW outproxy list: " + wwwProxy + "]");
|
||||
setName("HTTP Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
|
||||
notifyEvent("openHTTPClientResult", "ok");
|
||||
}
|
||||
|
||||
private String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
private String selectProxy() {
|
||||
synchronized (proxyList) {
|
||||
int size = proxyList.size();
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Proxy list is empty - no outproxy available");
|
||||
l.log("Proxy list is emtpy - no outproxy available");
|
||||
return null;
|
||||
}
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
String proxy = (String)proxyList.get(index);
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
/**
|
||||
* create the default options (using the default timeout, etc)
|
||||
* unused?
|
||||
@@ -282,7 +266,6 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
public static final String PROP_VIA = "i2ptunnel.httpclient.sendVia";
|
||||
public static final String PROP_JUMP_SERVERS = "i2ptunnel.httpclient.jumpServers";
|
||||
|
||||
private static long __requestId = 0;
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
@@ -297,6 +280,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
String line, method = null, protocol = null, host = null, destination = null;
|
||||
StringBuilder newRequest = new StringBuilder();
|
||||
int ahelper = 0;
|
||||
String authorization = null;
|
||||
while ((line = reader.readLine(method)) != null) {
|
||||
line = line.trim();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -366,7 +350,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
int port = 80;
|
||||
if(posPort != -1) {
|
||||
String[] parts = host.split(":");
|
||||
try {
|
||||
host = parts[0];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
|
||||
}
|
||||
try {
|
||||
port = Integer.parseInt(parts[1]);
|
||||
} catch(Exception exc) {
|
||||
@@ -436,11 +430,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
String destB64 = null;
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(host);
|
||||
if (dest != null)
|
||||
destB64 = dest.toBase64();
|
||||
} catch (DataFormatException dfe) {}
|
||||
Destination _dest = _context.namingService().lookup(host);
|
||||
if (_dest != null)
|
||||
destB64 = _dest.toBase64();
|
||||
if (destB64 != null && !destB64.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
@@ -498,7 +490,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1")) {
|
||||
} else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1") ||
|
||||
host.startsWith("192.168.")) {
|
||||
// if somebody is trying to get to 192.168.example.com, oh well
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST));
|
||||
writeFooter(out);
|
||||
@@ -615,11 +609,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (lowercaseLine.startsWith("authorization: ntlm ")) {
|
||||
// Block Windows NTLM after 401
|
||||
line = null;
|
||||
continue;
|
||||
} else if (lowercaseLine.startsWith("proxy-authorization: ")) {
|
||||
// This should be for us. It is a
|
||||
// hop-by-hop header, and we definitely want to block Windows NTLM after a far-end 407.
|
||||
// Response to far-end shouldn't happen, as we
|
||||
// strip Proxy-Authenticate from the response in HTTPResponseOutputStream
|
||||
if (lowercaseLine.startsWith("proxy-authorization: basic "))
|
||||
// save for auth check below
|
||||
authorization = line.substring(27); // "proxy-authorization: basic ".length()
|
||||
line = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
|
||||
// No more headers, add our own and break out of the loop
|
||||
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
@@ -631,9 +639,30 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
newRequest.append("Accept-Encoding: \r\n");
|
||||
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
|
||||
}
|
||||
if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue())
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
|
||||
// let's not advertise to external sites that we are from I2P
|
||||
if (usingWWWProxy)
|
||||
newRequest.append("User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6\r\n");
|
||||
else
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
}
|
||||
// Add Proxy-Authentication header for next hop (outproxy)
|
||||
if (usingWWWProxy && Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_AUTH)).booleanValue()) {
|
||||
// specific for this proxy
|
||||
String user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER_PREFIX + currentProxy);
|
||||
String pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW_PREFIX + currentProxy);
|
||||
if (user == null || pw == null) {
|
||||
// if not, look at default user and pw
|
||||
user = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_USER);
|
||||
pw = getTunnel().getClientOptions().getProperty(PROP_OUTPROXY_PW);
|
||||
}
|
||||
if (user != null && pw != null) {
|
||||
newRequest.append("Proxy-Authorization: Basic ")
|
||||
.append(Base64.encode((user + ':' + pw).getBytes(), true)) // true = use standard alphabet
|
||||
.append("\r\n");
|
||||
}
|
||||
}
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
@@ -661,12 +690,26 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Serve local proxy files (images, css linked from error pages)
|
||||
// Ignore all the headers
|
||||
// Allow without authorization
|
||||
if (usingInternalServer) {
|
||||
serveLocalFile(out, method, targetRequest);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Authorization
|
||||
if (!authorize(s, requestId, authorization)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (authorization != null)
|
||||
_log.warn(getPrefix(requestId) + "Auth failed, sending 407 again");
|
||||
else
|
||||
_log.warn(getPrefix(requestId) + "Auth required, sending 407");
|
||||
}
|
||||
out.write(getErrorPage("auth", ERR_AUTH));
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the host is "i2p", the getHostName() lookup failed, don't try to
|
||||
// look it up again as the naming service does not do negative caching
|
||||
// so it will be slow.
|
||||
@@ -675,7 +718,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if ("i2p".equals(host))
|
||||
clientDest = null;
|
||||
else
|
||||
clientDest = I2PTunnel.destFromName(destination);
|
||||
clientDest = _context.namingService().lookup(destination);
|
||||
|
||||
if (clientDest == null) {
|
||||
//l.log("Could not resolve " + destination + ".");
|
||||
@@ -711,24 +754,27 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (IOException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
IOException ex = new IOException("OOM");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
_log.error("getPrefix(requestId) + Error trying to connect", oom);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
}
|
||||
@@ -740,6 +786,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
* We can't use BufferedReader for POST because we can't have readahead,
|
||||
* since we are passing the stream on to I2PTunnelRunner for the POST data.
|
||||
*
|
||||
* Warning - BufferedReader removes \r, DataHelper does not
|
||||
* Warning - DataHelper limits line length, BufferedReader does not
|
||||
* Todo: Limit line length for buffered reads, or go back to unbuffered for all
|
||||
*/
|
||||
private static class InputReader {
|
||||
BufferedReader _br;
|
||||
@@ -749,11 +798,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_s = s;
|
||||
}
|
||||
String readLine(String method) throws IOException {
|
||||
if (method == null || "POST".equals(method))
|
||||
// Use unbuffered until we can find a BufferedReader that limits line length
|
||||
//if (method == null || "POST".equals(method))
|
||||
return DataHelper.readLine(_s);
|
||||
if (_br == null)
|
||||
_br = new BufferedReader(new InputStreamReader(_s, "ISO-8859-1"));
|
||||
return _br.readLine();
|
||||
//if (_br == null)
|
||||
// _br = new BufferedReader(new InputStreamReader(_s, "ISO-8859-1"));
|
||||
//return _br.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,17 +811,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
* @return b32hash.b32.i2p, or "i2p" on lookup failure.
|
||||
* Prior to 0.7.12, returned b64 key
|
||||
*/
|
||||
private final static String getHostName(String host) {
|
||||
private final String getHostName(String host) {
|
||||
if (host == null) return null;
|
||||
if (host.length() == 60 && host.toLowerCase().endsWith(".b32.i2p"))
|
||||
return host;
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(host);
|
||||
if (dest == null) return "i2p";
|
||||
return Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
|
||||
} catch (DataFormatException dfe) {
|
||||
return "i2p";
|
||||
}
|
||||
Destination dest = _context.namingService().lookup(host);
|
||||
if (dest == null) return "i2p";
|
||||
return Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,7 +831,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
* @return non-null
|
||||
*/
|
||||
private byte[] getErrorPage(String base, byte[] backup) {
|
||||
return getErrorPage(getTunnel().getContext(), base, backup);
|
||||
return getErrorPage(_context, base, backup);
|
||||
}
|
||||
|
||||
private static byte[] getErrorPage(I2PAppContext ctx, String base, byte[] backup) {
|
||||
@@ -894,12 +940,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
// Skip jump servers we don't know
|
||||
String jumphost = jurl.substring(7); // "http://"
|
||||
jumphost = jumphost.substring(0, jumphost.indexOf('/'));
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(jumphost);
|
||||
if (dest == null) continue;
|
||||
} catch (DataFormatException dfe) {
|
||||
continue;
|
||||
}
|
||||
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost);
|
||||
if (dest == null) continue;
|
||||
|
||||
out.write("<br><a href=\"".getBytes());
|
||||
out.write(jurl.getBytes());
|
||||
@@ -961,7 +1003,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
if (!found) {
|
||||
try {
|
||||
Destination d = I2PTunnel.destFromName(host);
|
||||
Destination d = _context.namingService().lookup(host);
|
||||
if (d == null) return false;
|
||||
} catch (DataFormatException dfe) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/* I2PTunnel is GPL'ed (with the exception mentioned in I2PTunnel.java)
|
||||
* (c) 2003 - 2004 mihi
|
||||
*/
|
||||
package net.i2p.i2ptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.InternalSocket;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Common things for HTTPClient and ConnectClient
|
||||
* Retrofit over them in 0.8.2
|
||||
*
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implements Runnable {
|
||||
private static final Log _log = new Log(I2PTunnelHTTPClientBase.class);
|
||||
protected final List<String> _proxyList;
|
||||
|
||||
protected final static byte[] ERR_NO_OUTPROXY =
|
||||
("HTTP/1.1 503 Service Unavailable\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: No outproxy found</H1>"+
|
||||
"Your request was for a site outside of I2P, but you have no "+
|
||||
"HTTP outproxy configured. Please configure an outproxy in I2PTunnel")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
protected static volatile long __clientId = 0;
|
||||
|
||||
protected static final File _errorDir = new File(I2PAppContext.getGlobalContext().getBaseDir(), "docs");
|
||||
|
||||
protected String getPrefix(long requestId) { return "Client[" + _clientId + "/" + requestId + "]: "; }
|
||||
|
||||
protected String selectProxy() {
|
||||
synchronized (_proxyList) {
|
||||
int size = _proxyList.size();
|
||||
if (size <= 0)
|
||||
return null;
|
||||
int index = _context.random().nextInt(size);
|
||||
return _proxyList.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
protected static final int DEFAULT_READ_TIMEOUT = 60*1000;
|
||||
|
||||
protected static long __requestId = 0;
|
||||
|
||||
public I2PTunnelHTTPClientBase(int localPort, boolean ownDest, Logging l,
|
||||
EventDispatcher notifyThis, String handlerName,
|
||||
I2PTunnel tunnel) throws IllegalArgumentException {
|
||||
super(localPort, ownDest, l, notifyThis, handlerName, tunnel);
|
||||
_proxyList = new ArrayList(4);
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPClientBase(int localPort, Logging l, I2PSocketManager sktMgr,
|
||||
I2PTunnel tunnel, EventDispatcher notifyThis, long clientId )
|
||||
throws IllegalArgumentException {
|
||||
super(localPort, l, sktMgr, tunnel, notifyThis, clientId);
|
||||
_proxyList = new ArrayList(4);
|
||||
}
|
||||
|
||||
/** all auth @since 0.8.2 */
|
||||
public static final String PROP_AUTH = "proxyAuth";
|
||||
public static final String PROP_USER = "proxyUsername";
|
||||
public static final String PROP_PW = "proxyPassword";
|
||||
/** additional users may be added with proxyPassword.user=pw */
|
||||
public static final String PROP_PW_PREFIX = PROP_PW + '.';
|
||||
public static final String PROP_OUTPROXY_AUTH = "outproxyAuth";
|
||||
public static final String PROP_OUTPROXY_USER = "outproxyUsername";
|
||||
public static final String PROP_OUTPROXY_PW = "outproxyPassword";
|
||||
/** passwords for specific outproxies may be added with outproxyUsername.fooproxy.i2p=user and outproxyPassword.fooproxy.i2p=pw */
|
||||
public static final String PROP_OUTPROXY_USER_PREFIX = PROP_OUTPROXY_USER + '.';
|
||||
public static final String PROP_OUTPROXY_PW_PREFIX = PROP_OUTPROXY_PW + '.';
|
||||
|
||||
/**
|
||||
* @param authorization may be null
|
||||
* @return success
|
||||
*/
|
||||
protected boolean authorize(Socket s, long requestId, String authorization) {
|
||||
// Authorization
|
||||
// Ref: RFC 2617
|
||||
// If the socket is an InternalSocket, no auth required.
|
||||
String authRequired = getTunnel().getClientOptions().getProperty(PROP_AUTH);
|
||||
if (authRequired != null && (authRequired.equalsIgnoreCase("true") || authRequired.equalsIgnoreCase("basic"))) {
|
||||
if (s instanceof InternalSocket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Internal access, no auth required");
|
||||
return true;
|
||||
} else if (authorization != null) {
|
||||
// hmm safeDecode(foo, true) to use standard alphabet is private in Base64
|
||||
byte[] decoded = Base64.decode(authorization.replace("/", "~").replace("+", "="));
|
||||
if (decoded != null) {
|
||||
// We send Accept-Charset: UTF-8 in the 407 so hopefully it comes back that way inside the B64 ?
|
||||
try {
|
||||
String dec = new String(decoded, "UTF-8");
|
||||
String[] parts = dec.split(":");
|
||||
String user = parts[0];
|
||||
String pw = parts[1];
|
||||
// first try pw for that user
|
||||
String configPW = getTunnel().getClientOptions().getProperty(PROP_PW_PREFIX + user);
|
||||
if (configPW == null) {
|
||||
// if not, look at default user and pw
|
||||
String configUser = getTunnel().getClientOptions().getProperty(PROP_USER);
|
||||
if (user.equals(configUser))
|
||||
configPW = getTunnel().getClientOptions().getProperty(PROP_PW);
|
||||
}
|
||||
if (configPW != null) {
|
||||
if (pw.equals(configPW)) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Good auth - user: " + user + " pw: " + pw);
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth, pw mismatch - user: " + user + " pw: " + pw + " expected: " + configPW);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth, no stored pw for user: " + user + " pw: " + pw);
|
||||
}
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
_log.error(getPrefix(requestId) + "No UTF-8 support? B64: " + authorization, uee);
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
// no ':' in response
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization, aioobe);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Bad auth B64: " + authorization);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.util.Properties;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@@ -36,6 +37,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
private static final String HASH_HEADER = "X-I2P-DestHash";
|
||||
private static final String DEST64_HEADER = "X-I2P-DestB64";
|
||||
private static final String DEST32_HEADER = "X-I2P-DestB32";
|
||||
private static final String[] CLIENT_SKIPHEADERS = {HASH_HEADER, DEST64_HEADER, DEST32_HEADER};
|
||||
private static final String SERVER_HEADER = "Server";
|
||||
private static final String[] SERVER_SKIPHEADERS = {SERVER_HEADER};
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, String privData, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
@@ -75,7 +79,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
InputStream in = socket.getInputStream();
|
||||
|
||||
StringBuilder command = new StringBuilder(128);
|
||||
Properties headers = readHeaders(in, command);
|
||||
Properties headers = readHeaders(in, command,
|
||||
CLIENT_SKIPHEADERS, getTunnel().getContext());
|
||||
headers.setProperty(HASH_HEADER, socket.getPeerDestination().calculateHash().toBase64());
|
||||
headers.setProperty(DEST32_HEADER, Base32.encode(socket.getPeerDestination().calculateHash().getData()) + ".b32.i2p" );
|
||||
headers.setProperty(DEST64_HEADER, socket.getPeerDestination().toBase64());
|
||||
@@ -118,7 +123,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
useGZIP = true;
|
||||
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PAppThread req = new I2PAppThread(new CompressedRequestor(s, socket, modifiedHeader), Thread.currentThread().getName()+".hc");
|
||||
I2PAppThread req = new I2PAppThread(
|
||||
new CompressedRequestor(s, socket, modifiedHeader, getTunnel().getContext()),
|
||||
Thread.currentThread().getName()+".hc");
|
||||
req.start();
|
||||
} else {
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
@@ -151,14 +158,16 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
_log.warn("Took a while to handle the request [" + timeToHandle + ", socket create: " + (afterSocket-afterAccept) + "]");
|
||||
}
|
||||
|
||||
private class CompressedRequestor implements Runnable {
|
||||
private static class CompressedRequestor implements Runnable {
|
||||
private Socket _webserver;
|
||||
private I2PSocket _browser;
|
||||
private String _headers;
|
||||
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers) {
|
||||
private I2PAppContext _ctx;
|
||||
public CompressedRequestor(Socket webserver, I2PSocket browser, String headers, I2PAppContext ctx) {
|
||||
_webserver = webserver;
|
||||
_browser = browser;
|
||||
_headers = headers;
|
||||
_ctx = ctx;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -192,11 +201,19 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
// at java.lang.Thread.run(Thread.java:619)
|
||||
// at net.i2p.util.I2PThread.run(I2PThread.java:71)
|
||||
try {
|
||||
serverin = _webserver.getInputStream();
|
||||
serverin = _webserver.getInputStream();
|
||||
} catch (NullPointerException npe) {
|
||||
throw new IOException("getInputStream NPE");
|
||||
}
|
||||
CompressedResponseOutputStream compressedOut = new CompressedResponseOutputStream(browserout);
|
||||
|
||||
//Change headers to protect server identity
|
||||
StringBuilder command = new StringBuilder(128);
|
||||
Properties headers = readHeaders(serverin, command,
|
||||
SERVER_SKIPHEADERS, _ctx);
|
||||
String modifiedHeaders = formatHeaders(headers, command);
|
||||
compressedOut.write(modifiedHeaders.getBytes());
|
||||
|
||||
Sender s = new Sender(compressedOut, serverin, "server: server to browser");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Before pumping the compressed response");
|
||||
@@ -214,7 +231,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
private class Sender implements Runnable {
|
||||
|
||||
private static class Sender implements Runnable {
|
||||
private OutputStream _out;
|
||||
private InputStream _in;
|
||||
private String _name;
|
||||
@@ -248,7 +266,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
private class CompressedResponseOutputStream extends HTTPResponseOutputStream {
|
||||
|
||||
private static class CompressedResponseOutputStream extends HTTPResponseOutputStream {
|
||||
private InternalGZIPOutputStream _gzipOut;
|
||||
public CompressedResponseOutputStream(OutputStream o) {
|
||||
super(o);
|
||||
@@ -287,7 +306,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
private class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
|
||||
/** just a wrapper to provide stats for debugging */
|
||||
private static class InternalGZIPOutputStream extends GZIPOutputStream {
|
||||
public InternalGZIPOutputStream(OutputStream target) throws IOException {
|
||||
super(target);
|
||||
}
|
||||
@@ -309,7 +330,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuilder command) {
|
||||
private static String formatHeaders(Properties headers, StringBuilder command) {
|
||||
StringBuilder buf = new StringBuilder(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString().trim()).append("\r\n");
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
@@ -321,7 +342,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private Properties readHeaders(InputStream in, StringBuilder command) throws IOException {
|
||||
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
|
||||
private static final int MAX_HEADERS = 60;
|
||||
|
||||
private static Properties readHeaders(InputStream in, StringBuilder command, String[] skipHeaders, I2PAppContext ctx) throws IOException {
|
||||
Properties headers = new Properties();
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
|
||||
@@ -331,6 +355,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
// FIXME we probably don't need or want this in the outgoing direction
|
||||
int trimmed = 0;
|
||||
if (command.length() > 0) {
|
||||
for (int i = 0; i < command.length(); i++) {
|
||||
@@ -342,9 +367,12 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
}
|
||||
}
|
||||
if (trimmed > 0)
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed, 0);
|
||||
ctx.statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed, 0);
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
if (++i > MAX_HEADERS)
|
||||
throw new IOException("Too many header lines - max " + MAX_HEADERS);
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
@@ -361,16 +389,25 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
value = buf.substring(split+1).trim(); // ":"
|
||||
else
|
||||
value = "";
|
||||
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
else if ("X-Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "X-Accept-encoding";
|
||||
else if (HASH_HEADER.equalsIgnoreCase(name))
|
||||
continue; // Prevent spoofing
|
||||
else if (DEST64_HEADER.equalsIgnoreCase(name))
|
||||
continue; // Prevent spoofing
|
||||
else if (DEST32_HEADER.equalsIgnoreCase(name))
|
||||
continue; // Prevent spoofing
|
||||
|
||||
// For incoming, we remove certain headers to prevent spoofing.
|
||||
// For outgoing, we remove certain headers to improve anonymity.
|
||||
boolean skip = false;
|
||||
for (String skipHeader: skipHeaders) {
|
||||
if (skipHeader.equalsIgnoreCase(name)) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
|
||||
@@ -9,14 +9,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Todo: Can we extend I2PTunnelClient instead and remove some duplicated code?
|
||||
*/
|
||||
public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable {
|
||||
|
||||
private static final Log _log = new Log(I2PTunnelIRCClient.class);
|
||||
@@ -25,7 +26,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
/** list of Destination objects that we point at */
|
||||
protected List dests;
|
||||
protected List<Destination> dests;
|
||||
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
|
||||
protected long readTimeout = DEFAULT_READ_TIMEOUT;
|
||||
|
||||
@@ -44,30 +45,37 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
ownDest,
|
||||
l,
|
||||
notifyThis,
|
||||
"IRCHandler " + (++__clientId), tunnel, pkf);
|
||||
"IRC Client on " + tunnel.listenHost + ':' + localPort + " #" + (++__clientId), tunnel, pkf);
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ", ");
|
||||
dests = new ArrayList(1);
|
||||
dests = new ArrayList(2);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String destination = tok.nextToken();
|
||||
try {
|
||||
Destination destN = I2PTunnel.destFromName(destination);
|
||||
if (destN == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(destN);
|
||||
} catch (DataFormatException dfe) {
|
||||
l.log("Bad format parsing \"" + destination + "\"");
|
||||
}
|
||||
Destination destN = _context.namingService().lookup(destination);
|
||||
if (destN == null)
|
||||
l.log("Could not resolve " + destination);
|
||||
else
|
||||
dests.add(destN);
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
if (dests.isEmpty()) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
// Nothing is listening for the above event, so it's useless
|
||||
// Maybe figure out where to put a waitEventValue("openClientResult") ??
|
||||
// In the meantime, let's do this the easy way
|
||||
// Note that b32 dests will often not be resolvable at instantiation time;
|
||||
// a delayed resolution system would be even better.
|
||||
|
||||
// Don't close() here, because it does a removeSession() and then
|
||||
// TunnelController can't acquire() it to release() it.
|
||||
//close(true);
|
||||
// Unfortunately, super() built the whole tunnel before we get here.
|
||||
throw new IllegalArgumentException("No valid target destinations found");
|
||||
//return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> IRCClient");
|
||||
setName("IRC Client on " + tunnel.listenHost + ':' + localPort);
|
||||
|
||||
startRunning();
|
||||
|
||||
@@ -90,7 +98,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
@@ -109,9 +117,9 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return null;
|
||||
}
|
||||
if (size == 1) // skip the rand in the most common case
|
||||
return (Destination)dests.get(0);
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
return (Destination)dests.get(index);
|
||||
return dests.get(0);
|
||||
int index = _context.random().nextInt(size);
|
||||
return dests.get(index);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
@@ -130,6 +138,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
||||
BufferedReader in;
|
||||
OutputStream output;
|
||||
try {
|
||||
@@ -167,6 +176,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
// probably doesn't do much but can't hurt
|
||||
output.flush();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("inbound BLOCKED: "+inmsg);
|
||||
@@ -204,6 +215,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// Todo: Don't use BufferedReader - IRC spec limits line length to 512 but...
|
||||
BufferedReader in;
|
||||
OutputStream output;
|
||||
try {
|
||||
@@ -241,6 +253,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
// save 250 ms in streaming
|
||||
output.flush();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||
@@ -371,7 +385,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
// "QUIT", // replace with a filtered QUIT to hide client quit messages
|
||||
"SILENCE",
|
||||
"MAP", // seems safe enough, the ircd should protect themselves though
|
||||
"PART",
|
||||
// "PART", // replace with filtered PART to hide client part messages
|
||||
"OPER",
|
||||
// "PONG", // replaced with a filtered PING/PONG since some clients send the server IP (thanks aardvax!)
|
||||
// "PING",
|
||||
@@ -475,6 +489,11 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ("PART".equals(command)) {
|
||||
// hide client message
|
||||
return "PART " + field[1] + " :leaving";
|
||||
}
|
||||
|
||||
if ("QUIT".equals(command)) {
|
||||
return "QUIT :leaving";
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* There are three options for mangling the desthash. Put the option in the
|
||||
* "custom options" section of i2ptunnel.
|
||||
* - ircserver.method unset: Defaults to user.
|
||||
* - ircserver.method=user: Use method described above.
|
||||
* - ircserver.method=webirc: Use the WEBIRC protocol.
|
||||
* - ircserver.cloakKey unset: Cloak with a random value that is persistent for
|
||||
* the life of this tunnel. This is the default.
|
||||
* - ircserver.cloakKey=somepassphrase: Cloak with the hash of the passphrase. Use this to
|
||||
@@ -39,6 +42,8 @@ import net.i2p.util.Log;
|
||||
* be able to track users even when they switch servers.
|
||||
* Note: don't quote or put spaces in the passphrase,
|
||||
* the i2ptunnel gui can't handle it.
|
||||
* - ircserver.webircPassword=password The password to use for the WEBIRC protocol.
|
||||
* - ircserver.webircSpoofIP=IP The IP
|
||||
* - ircserver.fakeHostname=%f.b32.i2p: Set the fake hostname sent by I2PTunnel,
|
||||
* %f is the full B32 destination hash
|
||||
* %c is the cloaked hash.
|
||||
@@ -48,7 +53,12 @@ import net.i2p.util.Log;
|
||||
* @author zzz
|
||||
*/
|
||||
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
public static final String PROP_METHOD="ircserver.method";
|
||||
public static final String PROP_METHOD_DEFAULT="user";
|
||||
public static final String PROP_CLOAK="ircserver.cloakKey";
|
||||
public static final String PROP_WEBIRC_PASSWORD="ircserver.webircPassword";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
||||
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
|
||||
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
|
||||
|
||||
@@ -67,7 +77,20 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
|
||||
/** generate a random 32 bytes, or the hash of the passphrase */
|
||||
private void initCloak(I2PTunnel tunnel) {
|
||||
// get the properties of this server-tunnel
|
||||
Properties opts = tunnel.getClientOptions();
|
||||
|
||||
// get method of host faking
|
||||
this.method = opts.getProperty(PROP_METHOD, PROP_METHOD_DEFAULT);
|
||||
assert this.method != null;
|
||||
|
||||
// get the password for the webirc method
|
||||
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
|
||||
|
||||
// get the spoof IP for the webirc method
|
||||
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
||||
|
||||
// get the cloaking passphrase
|
||||
String passphrase = opts.getProperty(PROP_CLOAK);
|
||||
if (passphrase == null) {
|
||||
this.cloakKey = new byte[Hash.HASH_LENGTH];
|
||||
@@ -76,17 +99,30 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
this.cloakKey = SHA256Generator.getInstance().calculateHash(passphrase.trim().getBytes()).getData();
|
||||
}
|
||||
|
||||
// get the fake hostmask to use
|
||||
this.hostname = opts.getProperty(PROP_HOSTNAME, PROP_HOSTNAME_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
try {
|
||||
// give them 15 seconds to send in the request
|
||||
socket.setReadTimeout(15*1000);
|
||||
InputStream in = socket.getInputStream();
|
||||
String modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
String modifiedRegistration;
|
||||
if(!this.method.equals("webirc")) {
|
||||
// give them 15 seconds to send in the request
|
||||
socket.setReadTimeout(15*1000);
|
||||
InputStream in = socket.getInputStream();
|
||||
modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
} else {
|
||||
StringBuffer buf = new StringBuffer("WEBIRC ");
|
||||
buf.append(this.webircPassword);
|
||||
buf.append(" cgiirc ");
|
||||
buf.append(cloakDest(socket.getPeerDestination()));
|
||||
buf.append(' ');
|
||||
buf.append(this.webircSpoofIP);
|
||||
buf.append("\r\n");
|
||||
modifiedRegistration = buf.toString();
|
||||
}
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
@@ -134,7 +170,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
}
|
||||
|
||||
/** keep reading until we see USER or SERVER */
|
||||
private String filterRegistration(InputStream in, String newHostname) throws IOException {
|
||||
private static String filterRegistration(InputStream in, String newHostname) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
int lineCount = 0;
|
||||
|
||||
@@ -185,4 +221,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
|
||||
private byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||
private String hostname;
|
||||
private String method;
|
||||
private String webircPassword;
|
||||
private String webircSpoofIP;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,16 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
// this does not increment totalSent
|
||||
i2pout.write(initialI2PData);
|
||||
// do NOT flush here, it will block and then onTimeout.run() won't happen on fail.
|
||||
//i2pout.flush();
|
||||
// But if we don't flush, then we have to wait for the connectDelay timer to fire
|
||||
// in i2p socket? To be researched and/or fixed.
|
||||
//
|
||||
// AS OF 0.8.1, MessageOutputStream.flush() is fixed to only wait for accept,
|
||||
// not for "completion" (i.e. an ACK from the far end).
|
||||
// So we now get a fast return from flush(), and can do it here to save 250 ms.
|
||||
// To make sure we are under the initial window size and don't hang waiting for accept,
|
||||
// only flush if it fits in one message.
|
||||
if (initialI2PData.length <= 1730) // ConnectionOptions.DEFAULT_MAX_MESSAGE_SIZE
|
||||
i2pout.flush();
|
||||
}
|
||||
}
|
||||
if (initialSocketData != null) {
|
||||
|
||||
@@ -61,16 +61,24 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
private int DEFAULT_LOCALPORT = 4488;
|
||||
protected int localPort = DEFAULT_LOCALPORT;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privData, notifyThis, tunnel);
|
||||
super("Server at " + host + ':' + port, notifyThis, tunnel);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
|
||||
SetUsePool(tunnel);
|
||||
init(host, port, bais, privData, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
public I2PTunnelServer(InetAddress host, int port, File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
super("Server at " + host + ':' + port, notifyThis, tunnel);
|
||||
SetUsePool(tunnel);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@@ -85,8 +93,12 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host + ":" + port + " <- " + privkeyname, notifyThis, tunnel);
|
||||
super("Server at " + host + ':' + port, notifyThis, tunnel);
|
||||
SetUsePool(tunnel);
|
||||
init(host, port, privData, privkeyname, l);
|
||||
}
|
||||
@@ -100,6 +112,13 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
_usePool = DEFAULT_USE_POOL;
|
||||
}
|
||||
|
||||
private static final int RETRY_DELAY = 20*1000;
|
||||
private static final int MAX_RETRIES = 4;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
* badly that we cant create a socketManager
|
||||
*/
|
||||
private void init(InetAddress host, int port, InputStream privData, String privkeyname, Logging l) {
|
||||
this.l = l;
|
||||
this.remoteHost = host;
|
||||
@@ -111,7 +130,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
try {
|
||||
portNum = Integer.parseInt(getTunnel().port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.log(Log.CRIT, "Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
_log.error("Invalid port specified [" + getTunnel().port + "], reverting to " + portNum);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +144,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
|
||||
// Todo: Can't stop a tunnel from the UI while it's in this loop (no session yet)
|
||||
int retries = 0;
|
||||
while (sockMgr == null) {
|
||||
synchronized (slock) {
|
||||
sockMgr = I2PSocketManagerFactory.createManager(privDataCopy, getTunnel().host, portNum,
|
||||
@@ -132,15 +152,25 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
|
||||
}
|
||||
if (sockMgr == null) {
|
||||
_log.log(Log.CRIT, "Unable to create socket manager");
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
// try to make this error sensible as it will happen...
|
||||
String msg = "Unable to connect to the router at " + getTunnel().host + ':' + portNum +
|
||||
" and build tunnels for the server at " + getTunnel().listenHost + ':' + port;
|
||||
if (++retries < MAX_RETRIES) {
|
||||
this.l.log(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
_log.error(msg + ", retrying in " + (RETRY_DELAY / 1000) + " seconds");
|
||||
} else {
|
||||
this.l.log(msg + ", giving up");
|
||||
_log.log(Log.CRIT, msg + ", giving up");
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
try { Thread.sleep(RETRY_DELAY); } catch (InterruptedException ie) {}
|
||||
privDataCopy.reset();
|
||||
}
|
||||
}
|
||||
|
||||
sockMgr.setName("Server");
|
||||
getTunnel().addSession(sockMgr.getSession());
|
||||
l.log("Ready!");
|
||||
l.log("Tunnels ready for server at " + getTunnel().listenHost + ':' + port);
|
||||
notifyEvent("openServerResult", "ok");
|
||||
open = true;
|
||||
}
|
||||
@@ -206,16 +236,16 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
l.log("Shutting down server " + toString());
|
||||
l.log("Stopping tunnels for server at " + getTunnel().listenHost + ':' + this.remotePort);
|
||||
try {
|
||||
if (i2pss != null) i2pss.close();
|
||||
getTunnel().removeSession(sockMgr.getSession());
|
||||
sockMgr.getSession().destroySession();
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error destroying the session", ex);
|
||||
System.exit(1);
|
||||
//System.exit(1);
|
||||
}
|
||||
l.log("Server shut down.");
|
||||
//l.log("Server shut down.");
|
||||
open = false;
|
||||
return true;
|
||||
}
|
||||
@@ -294,6 +324,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Incoming connection to '" + toString() + "' from: " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -200,7 +201,7 @@ public class I2Ping extends I2PTunnelTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(destination);
|
||||
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(destination);
|
||||
if (dest == null) {
|
||||
synchronized (lock) { // Logger is not thread safe
|
||||
l.log("Unresolvable: " + destination + "");
|
||||
|
||||
@@ -39,7 +39,7 @@ class InternalSocketRunner implements Runnable {
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (this.open) {
|
||||
_log.error("Error listening for internal connections on " + this.port, ex);
|
||||
_log.error("Error listening for internal connections on port " + this.port, ex);
|
||||
}
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
/**
|
||||
* Coordinate the runtime operation and configuration of a tunnel.
|
||||
@@ -29,8 +31,8 @@ public class TunnelController implements Logging {
|
||||
private Log _log;
|
||||
private Properties _config;
|
||||
private I2PTunnel _tunnel;
|
||||
private List _messages;
|
||||
private List _sessions;
|
||||
private List<String> _messages;
|
||||
private List<I2PSession> _sessions;
|
||||
private boolean _running;
|
||||
private boolean _starting;
|
||||
|
||||
@@ -46,6 +48,7 @@ public class TunnelController implements Logging {
|
||||
public TunnelController(Properties config, String prefix) {
|
||||
this(config, prefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param createKey for servers, whether we want to create a brand new destination
|
||||
@@ -58,17 +61,21 @@ public class TunnelController implements Logging {
|
||||
setConfig(config, prefix);
|
||||
_messages = new ArrayList(4);
|
||||
_running = false;
|
||||
boolean keyOK = true;
|
||||
if (createKey && (getType().endsWith("server") || getPersistentClientKey()))
|
||||
createPrivateKey();
|
||||
_starting = getStartOnLoad();
|
||||
keyOK = createPrivateKey();
|
||||
_starting = keyOK && getStartOnLoad();
|
||||
}
|
||||
|
||||
private void createPrivateKey() {
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
private boolean createPrivateKey() {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
String filename = getPrivKeyFile();
|
||||
if ( (filename == null) || (filename.trim().length() <= 0) ) {
|
||||
log("No filename specified for the private key");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
File keyFile = new File(getPrivKeyFile());
|
||||
@@ -76,7 +83,7 @@ public class TunnelController implements Logging {
|
||||
keyFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), getPrivKeyFile());
|
||||
if (keyFile.exists()) {
|
||||
//log("Not overwriting existing private keys in " + keyFile.getAbsolutePath());
|
||||
return;
|
||||
return true;
|
||||
} else {
|
||||
File parent = keyFile.getParentFile();
|
||||
if ( (parent != null) && (!parent.exists()) )
|
||||
@@ -84,7 +91,7 @@ public class TunnelController implements Logging {
|
||||
}
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(keyFile);
|
||||
fos = new SecureFileOutputStream(keyFile);
|
||||
Destination dest = client.createDestination(fos);
|
||||
String destStr = dest.toBase64();
|
||||
log("Private key created and saved in " + keyFile.getAbsolutePath());
|
||||
@@ -94,13 +101,16 @@ public class TunnelController implements Logging {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error creating new destination", ie);
|
||||
log("Error creating new destination: " + ie.getMessage());
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error creating writing the destination to " + keyFile.getAbsolutePath(), ioe);
|
||||
log("Error writing the keys to " + keyFile.getAbsolutePath());
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void startTunnelBackground() {
|
||||
@@ -118,11 +128,18 @@ public class TunnelController implements Logging {
|
||||
try {
|
||||
doStartTunnel();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error starting up the tunnel", e);
|
||||
log("Error starting up the tunnel - " + e.getMessage());
|
||||
_log.error("Error starting the tunnel " + getName(), e);
|
||||
log("Error starting the tunnel " + getName() + ": " + e.getMessage());
|
||||
// if we don't acquire() then the release() in stopTunnel() won't work
|
||||
acquire();
|
||||
stopTunnel();
|
||||
}
|
||||
_starting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException via methods in I2PTunnel
|
||||
*/
|
||||
private void doStartTunnel() {
|
||||
if (_running) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
@@ -210,6 +227,13 @@ public class TunnelController implements Logging {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String sharedClient = getSharedClient();
|
||||
String proxyList = getProxyList();
|
||||
if (proxyList != null) {
|
||||
// set the outproxy property the socks tunnel wants
|
||||
Properties props = _tunnel.getClientOptions();
|
||||
if (!props.containsKey(I2PSOCKSTunnel.PROP_PROXY_DEFAULT))
|
||||
props.setProperty(I2PSOCKSTunnel.PROP_PROXY_DEFAULT, proxyList);
|
||||
}
|
||||
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
}
|
||||
|
||||
@@ -218,7 +242,19 @@ public class TunnelController implements Logging {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runSOCKSIRCTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
String proxyList = getProxyList();
|
||||
if (proxyList != null) {
|
||||
// set the outproxy property the socks tunnel wants
|
||||
Properties props = _tunnel.getClientOptions();
|
||||
if (!props.containsKey(I2PSOCKSTunnel.PROP_PROXY_DEFAULT))
|
||||
props.setProperty(I2PSOCKSTunnel.PROP_PROXY_DEFAULT, proxyList);
|
||||
}
|
||||
if (getPersistentClientKey()) {
|
||||
String privKeyFile = getPrivKeyFile();
|
||||
_tunnel.runSOCKSIRCTunnel(new String[] { listenPort, "false", privKeyFile }, this);
|
||||
} else {
|
||||
_tunnel.runSOCKSIRCTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -251,15 +287,18 @@ public class TunnelController implements Logging {
|
||||
* closed by some other tunnels
|
||||
*/
|
||||
private void acquire() {
|
||||
List sessions = _tunnel.getSessions();
|
||||
if (sessions != null) {
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
if (!sessions.isEmpty()) {
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
I2PSession session = sessions.get(i);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Acquiring session " + session);
|
||||
TunnelControllerGroup.getInstance().acquire(this, session);
|
||||
}
|
||||
_sessions = sessions;
|
||||
} else {
|
||||
_log.error("No sessions to acquire?");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No sessions to acquire? for " + getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,13 +307,16 @@ public class TunnelController implements Logging {
|
||||
* no other tunnels are using them, close them.
|
||||
*/
|
||||
private void release() {
|
||||
if (_sessions != null) {
|
||||
if (_sessions != null && !_sessions.isEmpty()) {
|
||||
for (int i = 0; i < _sessions.size(); i++) {
|
||||
I2PSession s = (I2PSession)_sessions.get(i);
|
||||
I2PSession s = _sessions.get(i);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Releasing session " + s);
|
||||
TunnelControllerGroup.getInstance().release(this, s);
|
||||
}
|
||||
} else {
|
||||
_log.error("No sessions to release?");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("No sessions to release? for " + getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +407,7 @@ public class TunnelController implements Logging {
|
||||
_tunnel.port = "7654";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void stopTunnel() {
|
||||
_tunnel.runClose(new String[] { "forced", "all" }, this);
|
||||
release();
|
||||
@@ -427,14 +469,16 @@ public class TunnelController implements Logging {
|
||||
public String getListenPort() { return _config.getProperty("listenPort"); }
|
||||
public String getTargetDestination() { return _config.getProperty("targetDestination"); }
|
||||
public String getProxyList() { return _config.getProperty("proxyList"); }
|
||||
/** default true */
|
||||
public String getSharedClient() { return _config.getProperty("sharedClient", "true"); }
|
||||
/** default true */
|
||||
public boolean getStartOnLoad() { return "true".equalsIgnoreCase(_config.getProperty("startOnLoad", "true")); }
|
||||
public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); }
|
||||
public String getMyDestination() {
|
||||
if (_tunnel != null) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
I2PSession session = sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
@@ -445,9 +489,9 @@ public class TunnelController implements Logging {
|
||||
|
||||
public String getMyDestHashBase32() {
|
||||
if (_tunnel != null) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
I2PSession session = sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null)
|
||||
return Base32.encode(dest.calculateHash().getData());
|
||||
@@ -549,9 +593,9 @@ public class TunnelController implements Logging {
|
||||
if ( (opts != null) && (opts.length() > 0) )
|
||||
buf.append("Network options: ").append(opts).append("<br />\n");
|
||||
if (_running) {
|
||||
List sessions = _tunnel.getSessions();
|
||||
List<I2PSession> sessions = _tunnel.getSessions();
|
||||
for (int i = 0; i < sessions.size(); i++) {
|
||||
I2PSession session = (I2PSession)sessions.get(i);
|
||||
I2PSession session = sessions.get(i);
|
||||
Destination dest = session.getMyDestination();
|
||||
if (dest != null) {
|
||||
buf.append("Destination hash: ").append(dest.calculateHash().toBase64()).append("<br />\n");
|
||||
@@ -590,7 +634,7 @@ public class TunnelController implements Logging {
|
||||
*
|
||||
* @return list of messages pulled off (each is a String, earliest first)
|
||||
*/
|
||||
public List clearMessages() {
|
||||
public List<String> clearMessages() {
|
||||
List rv = null;
|
||||
synchronized (this) {
|
||||
rv = new ArrayList(_messages);
|
||||
|
||||
@@ -20,18 +20,20 @@ import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
/**
|
||||
* Coordinate a set of tunnels within the JVM, loading and storing their config
|
||||
* to disk, and building new ones as requested.
|
||||
*
|
||||
* Warning - this is a singleton. Todo: fix
|
||||
*/
|
||||
public class TunnelControllerGroup {
|
||||
private Log _log;
|
||||
private final Log _log;
|
||||
private static TunnelControllerGroup _instance;
|
||||
static final String DEFAULT_CONFIG_FILE = "i2ptunnel.config";
|
||||
|
||||
private List _controllers;
|
||||
private final List<TunnelController> _controllers;
|
||||
private String _configFile = DEFAULT_CONFIG_FILE;
|
||||
|
||||
/**
|
||||
@@ -40,7 +42,7 @@ public class TunnelControllerGroup {
|
||||
* no more tunnels are using it)
|
||||
*
|
||||
*/
|
||||
private final Map _sessions;
|
||||
private final Map<I2PSession, Set<TunnelController>> _sessions;
|
||||
|
||||
public static TunnelControllerGroup getInstance() {
|
||||
synchronized (TunnelControllerGroup.class) {
|
||||
@@ -104,7 +106,7 @@ public class TunnelControllerGroup {
|
||||
private class StartControllers implements Runnable {
|
||||
public void run() {
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
if (controller.getStartOnLoad())
|
||||
controller.startTunnel();
|
||||
}
|
||||
@@ -141,10 +143,10 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of messages from the controller as it is stopped
|
||||
*/
|
||||
public List removeController(TunnelController controller) {
|
||||
public List<String> removeController(TunnelController controller) {
|
||||
if (controller == null) return new ArrayList();
|
||||
controller.stopTunnel();
|
||||
List msgs = controller.clearMessages();
|
||||
List<String> msgs = controller.clearMessages();
|
||||
_controllers.remove(controller);
|
||||
msgs.add("Tunnel " + controller.getName() + " removed");
|
||||
return msgs;
|
||||
@@ -155,10 +157,10 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of messages the tunnels generate when stopped
|
||||
*/
|
||||
public List stopAllControllers() {
|
||||
List msgs = new ArrayList();
|
||||
public List<String> stopAllControllers() {
|
||||
List<String> msgs = new ArrayList();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.stopTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
@@ -172,10 +174,10 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of messages the tunnels generate when started
|
||||
*/
|
||||
public List startAllControllers() {
|
||||
List msgs = new ArrayList();
|
||||
public List<String> startAllControllers() {
|
||||
List<String> msgs = new ArrayList();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.startTunnelBackground();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
@@ -190,10 +192,10 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of messages the tunnels generate when restarted
|
||||
*/
|
||||
public List restartAllControllers() {
|
||||
List msgs = new ArrayList();
|
||||
public List<String> restartAllControllers() {
|
||||
List<String> msgs = new ArrayList();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
controller.restartTunnel();
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
@@ -207,10 +209,10 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of messages the tunnels have generated
|
||||
*/
|
||||
public List clearAllMessages() {
|
||||
List msgs = new ArrayList();
|
||||
public List<String> clearAllMessages() {
|
||||
List<String> msgs = new ArrayList();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
msgs.addAll(controller.clearMessages());
|
||||
}
|
||||
return msgs;
|
||||
@@ -240,7 +242,7 @@ public class TunnelControllerGroup {
|
||||
|
||||
TreeMap map = new TreeMap();
|
||||
for (int i = 0; i < _controllers.size(); i++) {
|
||||
TunnelController controller = (TunnelController)_controllers.get(i);
|
||||
TunnelController controller = _controllers.get(i);
|
||||
Properties cur = controller.getConfig("tunnel." + i + ".");
|
||||
map.putAll(cur);
|
||||
}
|
||||
@@ -254,7 +256,7 @@ public class TunnelControllerGroup {
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(cfgFile);
|
||||
fos = new SecureFileOutputStream(cfgFile);
|
||||
fos.write(buf.toString().getBytes("UTF-8"));
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Config written to " + cfgFile.getPath());
|
||||
@@ -296,7 +298,7 @@ public class TunnelControllerGroup {
|
||||
*
|
||||
* @return list of TunnelController objects
|
||||
*/
|
||||
public List getControllers() { return _controllers; }
|
||||
public List<TunnelController> getControllers() { return _controllers; }
|
||||
|
||||
|
||||
/**
|
||||
@@ -306,7 +308,7 @@ public class TunnelControllerGroup {
|
||||
*/
|
||||
void acquire(TunnelController controller, I2PSession session) {
|
||||
synchronized (_sessions) {
|
||||
Set owners = (Set)_sessions.get(session);
|
||||
Set<TunnelController> owners = _sessions.get(session);
|
||||
if (owners == null) {
|
||||
owners = new HashSet(1);
|
||||
_sessions.put(session, owners);
|
||||
@@ -326,10 +328,10 @@ public class TunnelControllerGroup {
|
||||
void release(TunnelController controller, I2PSession session) {
|
||||
boolean shouldClose = false;
|
||||
synchronized (_sessions) {
|
||||
Set owners = (Set)_sessions.get(session);
|
||||
Set<TunnelController> owners = _sessions.get(session);
|
||||
if (owners != null) {
|
||||
owners.remove(controller);
|
||||
if (owners.size() <= 0) {
|
||||
if (owners.isEmpty()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After releasing session " + session + " by " + controller + ", no more owners remain");
|
||||
shouldClose = true;
|
||||
|
||||
@@ -33,9 +33,10 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
||||
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(I2PSOCKSIRCTunnel.class);
|
||||
private static int __clientId = 0;
|
||||
|
||||
public I2PSOCKSIRCTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(localPort, l, ownDest, notifyThis, tunnel);
|
||||
setName(getLocalPort() + " -> SOCKSIRCTunnel");
|
||||
/** @param pkf private key file name or null for transient key */
|
||||
public I2PSOCKSIRCTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel, String pkf) {
|
||||
super(localPort, l, ownDest, notifyThis, tunnel, pkf);
|
||||
setName("SOCKS IRC Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,8 +46,8 @@ public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
||||
@Override
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
_log.error("SOCKS IRC Tunnel Start");
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
//_log.error("SOCKS IRC Tunnel Start");
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.I2PTunnelClientBase;
|
||||
@@ -33,15 +34,16 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
// I2PSOCKSTunnel(localPort, l, ownDest, (EventDispatcher)null);
|
||||
//}
|
||||
|
||||
public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(localPort, ownDest, l, notifyThis, "SOCKSHandler", tunnel);
|
||||
/** @param pkf private key file name or null for transient key */
|
||||
public I2PSOCKSTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel, String pkf) {
|
||||
super(localPort, ownDest, l, notifyThis, "SOCKS Proxy on " + tunnel.listenHost + ':' + localPort, tunnel, pkf);
|
||||
|
||||
if (waitEventValue("openBaseClientResult").equals("error")) {
|
||||
notifyEvent("openSOCKSTunnelResult", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
setName(getLocalPort() + " -> SOCKSTunnel");
|
||||
setName("SOCKS Proxy on " + tunnel.listenHost + ':' + localPort);
|
||||
parseOptions();
|
||||
startRunning();
|
||||
|
||||
@@ -50,7 +52,7 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s, getTunnel().getClientOptions());
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
new I2PTunnelRunner(clientSock, destSock, sockLock, null, mySockets);
|
||||
@@ -60,15 +62,19 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String PROP_PROXY = "i2ptunnel.socks.proxy.";
|
||||
/** add "default" or port number */
|
||||
public static final String PROP_PROXY_PREFIX = "i2ptunnel.socks.proxy.";
|
||||
public static final String DEFAULT = "default";
|
||||
public static final String PROP_PROXY_DEFAULT = PROP_PROXY_PREFIX + DEFAULT;
|
||||
|
||||
private void parseOptions() {
|
||||
Properties opts = getTunnel().getClientOptions();
|
||||
proxies = new HashMap(0);
|
||||
proxies = new HashMap(1);
|
||||
for (Map.Entry e : opts.entrySet()) {
|
||||
String prop = (String)e.getKey();
|
||||
if ((!prop.startsWith(PROP_PROXY)) || prop.length() <= PROP_PROXY.length())
|
||||
if ((!prop.startsWith(PROP_PROXY_PREFIX)) || prop.length() <= PROP_PROXY_PREFIX.length())
|
||||
continue;
|
||||
String port = prop.substring(PROP_PROXY.length());
|
||||
String port = prop.substring(PROP_PROXY_PREFIX.length());
|
||||
List proxyList = new ArrayList(1);
|
||||
StringTokenizer tok = new StringTokenizer((String)e.getValue(), ", \t");
|
||||
while (tok.hasMoreTokens()) {
|
||||
@@ -94,7 +100,22 @@ public class I2PSOCKSTunnel extends I2PTunnelClientBase {
|
||||
}
|
||||
|
||||
public List<String> getDefaultProxies() {
|
||||
return proxies.get("default");
|
||||
return proxies.get(DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Because getDefaultOptions() in super() is protected
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public I2PSocketOptions buildOptions(Properties overrides) {
|
||||
Properties defaultOpts = getTunnel().getClientOptions();
|
||||
defaultOpts.putAll(overrides);
|
||||
// delayed start
|
||||
verifySocketManager();
|
||||
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
|
||||
if (!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT))
|
||||
opts.setConnectTimeout(60 * 1000);
|
||||
return opts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ public class SOCKS4aServer extends SOCKSServer {
|
||||
// Let's not due a new Dest for every request, huh?
|
||||
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
|
||||
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName));
|
||||
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
|
||||
String err = "No localhost accesses allowed through the Socks Proxy";
|
||||
_log.error(err);
|
||||
@@ -224,7 +224,7 @@ public class SOCKS4aServer extends SOCKSServer {
|
||||
throw new SOCKSException(err);
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
if (proxies == null || proxies.isEmpty()) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
|
||||
_log.error(err);
|
||||
try {
|
||||
@@ -237,7 +237,7 @@ public class SOCKS4aServer extends SOCKSServer {
|
||||
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
|
||||
// this isn't going to work, these need to be socks outproxies so we need
|
||||
// to do a socks session to them?
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
|
||||
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(proxy));
|
||||
}
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
|
||||
@@ -16,12 +16,15 @@ import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.HexDump;
|
||||
import net.i2p.util.Log;
|
||||
@@ -37,8 +40,10 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
|
||||
private static final int SOCKS_VERSION_5 = 0x05;
|
||||
|
||||
private Socket clientSock = null;
|
||||
private final Socket clientSock;
|
||||
private final Properties props;
|
||||
private boolean setupCompleted = false;
|
||||
private final boolean authRequired;
|
||||
|
||||
/**
|
||||
* Create a SOCKS5 server that communicates with the client using
|
||||
@@ -49,9 +54,15 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
* client socket.
|
||||
*
|
||||
* @param clientSock client socket
|
||||
* @param props non-null
|
||||
*/
|
||||
public SOCKS5Server(Socket clientSock) {
|
||||
public SOCKS5Server(Socket clientSock, Properties props) {
|
||||
this.clientSock = clientSock;
|
||||
this.props = props;
|
||||
this.authRequired =
|
||||
Boolean.valueOf(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)).booleanValue() &&
|
||||
props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) &&
|
||||
props.containsKey(I2PTunnelHTTPClientBase.PROP_PW);
|
||||
}
|
||||
|
||||
public Socket getClientSocket() throws SOCKSException {
|
||||
@@ -73,7 +84,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
if (manageRequest(in, out) == Command.UDP_ASSOCIATE)
|
||||
handleUDP(in, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
throw new SOCKSException("Connection error: " + e);
|
||||
}
|
||||
|
||||
setupCompleted = true;
|
||||
@@ -84,31 +95,70 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
* SOCKS "VER" field has been stripped from the input stream.
|
||||
*/
|
||||
private void init(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
int nMethods = in.readByte() & 0xff;
|
||||
int nMethods = in.readUnsignedByte();
|
||||
boolean methodOk = false;
|
||||
int method = Method.NO_ACCEPTABLE_METHODS;
|
||||
|
||||
for (int i = 0; i < nMethods; ++i) {
|
||||
int meth = in.readByte() & 0xff;
|
||||
if (meth == Method.NO_AUTH_REQUIRED) {
|
||||
int meth = in.readUnsignedByte();
|
||||
if (((!authRequired) && meth == Method.NO_AUTH_REQUIRED) ||
|
||||
(authRequired && meth == Method.USERNAME_PASSWORD)) {
|
||||
// That's fine, we do support this method
|
||||
method = meth;
|
||||
}
|
||||
}
|
||||
|
||||
boolean canContinue = false;
|
||||
switch (method) {
|
||||
case Method.NO_AUTH_REQUIRED:
|
||||
case Method.USERNAME_PASSWORD:
|
||||
_log.debug("username/password authentication required");
|
||||
sendInitReply(Method.USERNAME_PASSWORD, out);
|
||||
verifyPassword(in, out);
|
||||
return;
|
||||
case Method.NO_AUTH_REQUIRED:
|
||||
_log.debug("no authentication required");
|
||||
sendInitReply(Method.NO_AUTH_REQUIRED, out);
|
||||
return;
|
||||
default:
|
||||
default:
|
||||
_log.debug("no suitable authentication methods found (" + Integer.toHexString(method) + ")");
|
||||
sendInitReply(Method.NO_ACCEPTABLE_METHODS, out);
|
||||
throw new SOCKSException("Unsupported authentication method");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the username/password message and verify or throw SOCKSException on failure
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private void verifyPassword(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
int c = in.readUnsignedByte();
|
||||
if (c != AUTH_VERSION)
|
||||
throw new SOCKSException("Unsupported authentication version");
|
||||
c = in.readUnsignedByte();
|
||||
if (c <= 0)
|
||||
throw new SOCKSException("Bad authentication");
|
||||
byte[] user = new byte[c];
|
||||
in.readFully(user);
|
||||
c = in.readUnsignedByte();
|
||||
if (c <= 0)
|
||||
throw new SOCKSException("Bad authentication");
|
||||
byte[] pw = new byte[c];
|
||||
in.readFully(pw);
|
||||
// Hopefully these are in UTF-8, since that's what our config file is in
|
||||
// these throw UnsupportedEncodingException which is an IOE
|
||||
String u = new String(user, "UTF-8");
|
||||
String p = new String(pw, "UTF-8");
|
||||
String configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_USER);
|
||||
String configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_PW);
|
||||
if ((!u.equals(configUser)) || (!p.equals(configPW))) {
|
||||
_log.error("SOCKS authorization failure");
|
||||
sendAuthReply(AUTH_FAILURE, out);
|
||||
throw new SOCKSException("SOCKS authorization failure");
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("SOCKS authorization success, user: " + u);
|
||||
sendAuthReply(AUTH_SUCCESS, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* SOCKS5 request management. This method assumes that all the
|
||||
* stuff preceding or enveloping the actual request (e.g. protocol
|
||||
@@ -116,13 +166,13 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
* has been stripped out of the input/output streams.
|
||||
*/
|
||||
private int manageRequest(DataInputStream in, DataOutputStream out) throws IOException, SOCKSException {
|
||||
int socksVer = in.readByte() & 0xff;
|
||||
int socksVer = in.readUnsignedByte();
|
||||
if (socksVer != SOCKS_VERSION_5) {
|
||||
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
|
||||
throw new SOCKSException("Invalid protocol version in request: " + socksVer);
|
||||
}
|
||||
|
||||
int command = in.readByte() & 0xff;
|
||||
int command = in.readUnsignedByte();
|
||||
switch (command) {
|
||||
case Command.CONNECT:
|
||||
break;
|
||||
@@ -143,17 +193,15 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
throw new SOCKSException("Invalid command in request");
|
||||
}
|
||||
|
||||
{
|
||||
// Reserved byte, should be 0x00
|
||||
byte rsv = in.readByte();
|
||||
}
|
||||
// Reserved byte, should be 0x00
|
||||
in.readByte();
|
||||
|
||||
int addressType = in.readByte() & 0xff;
|
||||
addressType = in.readUnsignedByte();
|
||||
switch (addressType) {
|
||||
case AddressType.IPV4:
|
||||
connHostName = new String("");
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int octet = in.readByte() & 0xff;
|
||||
int octet = in.readUnsignedByte();
|
||||
connHostName += Integer.toString(octet);
|
||||
if (i != 3) {
|
||||
connHostName += ".";
|
||||
@@ -164,7 +212,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
break;
|
||||
case AddressType.DOMAINNAME:
|
||||
{
|
||||
int addrLen = in.readByte() & 0xff;
|
||||
int addrLen = in.readUnsignedByte();
|
||||
if (addrLen == 0) {
|
||||
_log.debug("0-sized address length? wtf?");
|
||||
throw new SOCKSException("Illegal DOMAINNAME length");
|
||||
@@ -205,7 +253,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
|
||||
sendRequestReply(Reply.SUCCEEDED, AddressType.IPV4, InetAddress.getByName("127.0.0.1"), null, 1, out);
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
throw new SOCKSException("Connection error: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,17 +261,24 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
* Send the specified reply during SOCKS5 initialization
|
||||
*/
|
||||
private void sendInitReply(int replyCode, DataOutputStream out) throws IOException {
|
||||
ByteArrayOutputStream reps = new ByteArrayOutputStream();
|
||||
|
||||
reps.write(SOCKS_VERSION_5);
|
||||
reps.write(replyCode);
|
||||
|
||||
byte[] reply = reps.toByteArray();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
byte[] reply = new byte[2];
|
||||
reply[0] = SOCKS_VERSION_5;
|
||||
reply[1] = (byte) replyCode;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending init reply:\n" + HexDump.dump(reply));
|
||||
}
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the specified reply during SOCKS5 authorization
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private void sendAuthReply(int replyCode, DataOutputStream out) throws IOException {
|
||||
byte[] reply = new byte[2];
|
||||
reply[0] = AUTH_VERSION;
|
||||
reply[1] = (byte) replyCode;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending auth reply:\n" + HexDump.dump(reply));
|
||||
out.write(reply);
|
||||
}
|
||||
|
||||
@@ -291,7 +346,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
try {
|
||||
out = new DataOutputStream(clientSock.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
throw new SOCKSException("Connection error (" + e.getMessage() + ")");
|
||||
throw new SOCKSException("Connection error: " + e);
|
||||
}
|
||||
|
||||
// FIXME: here we should read our config file, select an
|
||||
@@ -305,14 +360,14 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
// Let's not due a new Dest for every request, huh?
|
||||
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
|
||||
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
|
||||
Destination dest = I2PTunnel.destFromName(connHostName);
|
||||
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(connHostName);
|
||||
if (dest == null) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Host not found");
|
||||
}
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(connHostName));
|
||||
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName));
|
||||
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
|
||||
String err = "No localhost accesses allowed through the Socks Proxy";
|
||||
_log.error(err);
|
||||
@@ -320,6 +375,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
/****
|
||||
} else if (connPort == 80) {
|
||||
// rewrite GET line to include hostname??? or add Host: line???
|
||||
// or forward to local eepProxy (but that's a Socket not an I2PSocket)
|
||||
@@ -330,9 +386,10 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
sendRequestReply(Reply.CONNECTION_NOT_ALLOWED_BY_RULESET, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException(err);
|
||||
****/
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
if (proxies == null || proxies.isEmpty()) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either";
|
||||
_log.error(err);
|
||||
try {
|
||||
@@ -342,41 +399,182 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
}
|
||||
int p = I2PAppContext.getGlobalContext().random().nextInt(proxies.size());
|
||||
String proxy = proxies.get(p);
|
||||
_log.debug("connecting to port " + connPort + " proxy " + proxy + " for " + connHostName + "...");
|
||||
// this isn't going to work, these need to be socks outproxies so we need
|
||||
// to do a socks session to them?
|
||||
destSock = t.createI2PSocket(I2PTunnel.destFromName(proxy));
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("connecting to proxy " + proxy + " for " + connHostName + " port " + connPort);
|
||||
|
||||
try {
|
||||
destSock = outproxyConnect(t, proxy);
|
||||
} catch (SOCKSException se) {
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw se;
|
||||
}
|
||||
}
|
||||
confirmConnection();
|
||||
_log.debug("connection confirmed - exchanging data...");
|
||||
} catch (DataFormatException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("socks error", e);
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error in destination format");
|
||||
} catch (SocketException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("socks error", e);
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
throw new SOCKSException("Error connecting: " + e);
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("socks error", e);
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
throw new SOCKSException("Error connecting: " + e);
|
||||
} catch (I2PException e) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("socks error", e);
|
||||
try {
|
||||
sendRequestReply(Reply.HOST_UNREACHABLE, AddressType.DOMAINNAME, null, "0.0.0.0", 0, out);
|
||||
} catch (IOException ioe) {}
|
||||
throw new SOCKSException("Error connecting ("
|
||||
+ e.getMessage() + ")");
|
||||
throw new SOCKSException("Error connecting: " + e);
|
||||
}
|
||||
|
||||
return destSock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act as a SOCKS 5 client to connect to an outproxy
|
||||
* @return open socket or throws error
|
||||
* @since 0.8.2
|
||||
*/
|
||||
private I2PSocket outproxyConnect(I2PSOCKSTunnel tun, String proxy) throws IOException, SOCKSException, DataFormatException, I2PException {
|
||||
Properties overrides = new Properties();
|
||||
overrides.setProperty("option.i2p.streaming.connectDelay", "1000");
|
||||
I2PSocketOptions proxyOpts = tun.buildOptions(overrides);
|
||||
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(proxy);
|
||||
if (dest == null)
|
||||
throw new SOCKSException("Outproxy not found");
|
||||
I2PSocket destSock = tun.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(proxy), proxyOpts);
|
||||
try {
|
||||
DataOutputStream out = new DataOutputStream(destSock.getOutputStream());
|
||||
boolean authAvail = Boolean.valueOf(props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_AUTH)).booleanValue();
|
||||
String configUser = null;
|
||||
String configPW = null;
|
||||
if (authAvail) {
|
||||
configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER_PREFIX + proxy);
|
||||
configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW_PREFIX + proxy);
|
||||
if (configUser == null || configPW == null) {
|
||||
configUser = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_USER);
|
||||
configPW = props.getProperty(I2PTunnelHTTPClientBase.PROP_OUTPROXY_PW);
|
||||
if (configUser == null || configPW == null)
|
||||
authAvail = false;
|
||||
}
|
||||
}
|
||||
|
||||
// send the init
|
||||
out.writeByte(SOCKS_VERSION_5);
|
||||
if (authAvail) {
|
||||
out.writeByte(2);
|
||||
out.writeByte(Method.NO_AUTH_REQUIRED);
|
||||
out.writeByte(Method.USERNAME_PASSWORD);
|
||||
} else {
|
||||
out.writeByte(1);
|
||||
out.writeByte(Method.NO_AUTH_REQUIRED);
|
||||
}
|
||||
out.flush();
|
||||
|
||||
// read init reply
|
||||
DataInputStream in = new DataInputStream(destSock.getInputStream());
|
||||
// is this right or should we not try to do 5-to-4 conversion?
|
||||
int hisVersion = in.readByte();
|
||||
if (hisVersion != SOCKS_VERSION_5 /* && addrtype == AddressType.DOMAINNAME */ )
|
||||
throw new SOCKSException("SOCKS Outproxy is not Version 5");
|
||||
//else if (hisVersion != 4)
|
||||
// throw new SOCKSException("Unsupported SOCKS Outproxy Version");
|
||||
|
||||
int method = in.readByte();
|
||||
if (method == Method.NO_AUTH_REQUIRED) {
|
||||
// good
|
||||
} else if (method == Method.USERNAME_PASSWORD) {
|
||||
if (authAvail) {
|
||||
// send the auth
|
||||
out.writeByte(AUTH_VERSION);
|
||||
byte[] user = configUser.getBytes("UTF-8");
|
||||
byte[] pw = configPW.getBytes("UTF-8");
|
||||
out.writeByte(user.length);
|
||||
out.write(user);
|
||||
out.writeByte(pw.length);
|
||||
out.write(pw);
|
||||
out.flush();
|
||||
// read the auth reply
|
||||
if (in.readByte() != AUTH_VERSION)
|
||||
throw new SOCKSException("Bad auth version from outproxy");
|
||||
if (in.readByte() != AUTH_SUCCESS)
|
||||
throw new SOCKSException("Outproxy authorization failure");
|
||||
} else {
|
||||
throw new SOCKSException("Outproxy requires authorization, please configure username/password");
|
||||
}
|
||||
} else {
|
||||
throw new SOCKSException("Outproxy authorization failure");
|
||||
}
|
||||
|
||||
// send the connect command
|
||||
out.writeByte(SOCKS_VERSION_5);
|
||||
out.writeByte(Command.CONNECT);
|
||||
out.writeByte(0); // reserved
|
||||
out.writeByte(addressType);
|
||||
if (addressType == AddressType.IPV4) {
|
||||
out.write(InetAddress.getByName(connHostName).getAddress());
|
||||
} else if (addressType == AddressType.DOMAINNAME) {
|
||||
byte[] d = connHostName.getBytes("ISO-8859-1");
|
||||
out.writeByte(d.length);
|
||||
out.write(d);
|
||||
} else {
|
||||
// shouldn't happen
|
||||
throw new SOCKSException("Unknown address type for outproxy?");
|
||||
}
|
||||
out.writeShort(connPort);
|
||||
out.flush();
|
||||
|
||||
// read the connect reply
|
||||
hisVersion = in.readByte();
|
||||
if (hisVersion != SOCKS_VERSION_5)
|
||||
throw new SOCKSException("Outproxy response is not Version 5");
|
||||
int reply = in.readByte();
|
||||
in.readByte(); // reserved
|
||||
int type = in.readByte();
|
||||
int count = 0;
|
||||
if (type == AddressType.IPV4) {
|
||||
count = 4;
|
||||
} else if (type == AddressType.DOMAINNAME) {
|
||||
count = in.readUnsignedByte();
|
||||
} else if (type == AddressType.IPV6) {
|
||||
count = 16;
|
||||
} else {
|
||||
throw new SOCKSException("Unsupported address type in outproxy response");
|
||||
}
|
||||
byte[] addr = new byte[count];
|
||||
in.readFully(addr); // address
|
||||
in.readUnsignedShort(); // port
|
||||
if (reply != Reply.SUCCEEDED)
|
||||
throw new SOCKSException("Outproxy rejected request, response = " + reply);
|
||||
// throw away the address in the response
|
||||
// todo pass the response through?
|
||||
} catch (IOException e) {
|
||||
try { destSock.close(); } catch (IOException ioe) {}
|
||||
throw e;
|
||||
} catch (SOCKSException e) {
|
||||
try { destSock.close(); } catch (IOException ioe) {}
|
||||
throw e;
|
||||
}
|
||||
// that's it, caller will send confirmation to our client
|
||||
return destSock;
|
||||
}
|
||||
|
||||
// This isn't really the right place for this, we can't stop the tunnel once it starts.
|
||||
static SOCKSUDPTunnel _tunnel;
|
||||
static final Object _startLock = new Object();
|
||||
@@ -435,6 +633,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
*/
|
||||
private static class Method {
|
||||
private static final int NO_AUTH_REQUIRED = 0x00;
|
||||
private static final int USERNAME_PASSWORD = 0x02;
|
||||
private static final int NO_ACCEPTABLE_METHODS = 0xff;
|
||||
}
|
||||
|
||||
@@ -461,4 +660,8 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
private static final int COMMAND_NOT_SUPPORTED = 0x07;
|
||||
private static final int ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
|
||||
}
|
||||
|
||||
private static final int AUTH_VERSION = 1;
|
||||
private static final int AUTH_SUCCESS = 0;
|
||||
private static final int AUTH_FAILURE = 1;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
|
||||
@@ -74,11 +74,8 @@ public class SOCKSHeader {
|
||||
String name = getHost();
|
||||
if (name == null)
|
||||
return null;
|
||||
try {
|
||||
// the naming service does caching (thankfully)
|
||||
return I2PTunnel.destFromName(name);
|
||||
} catch (DataFormatException dfe) {}
|
||||
return null;
|
||||
// the naming service does caching (thankfully)
|
||||
return I2PAppContext.getGlobalContext().namingService().lookup(name);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
|
||||
@@ -20,8 +20,9 @@ public abstract class SOCKSServer {
|
||||
private static final Log _log = new Log(SOCKSServer.class);
|
||||
|
||||
/* Details about the connection requested by client */
|
||||
protected String connHostName = null;
|
||||
protected int connPort = 0;
|
||||
protected String connHostName;
|
||||
protected int connPort;
|
||||
protected int addressType;
|
||||
|
||||
/**
|
||||
* Perform server initialization (expecially regarding protected
|
||||
|
||||
@@ -10,7 +10,9 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -20,7 +22,7 @@ public class SOCKSServerFactory {
|
||||
private final static Log _log = new Log(SOCKSServerFactory.class);
|
||||
|
||||
private final static String ERR_REQUEST_DENIED =
|
||||
"HTTP/1.1 403 Access Denied\r\n" +
|
||||
"HTTP/1.1 403 Access Denied - This is a SOCKS proxy, not a HTTP proxy\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Cache-control: no-cache\r\n" +
|
||||
"\r\n" +
|
||||
@@ -35,8 +37,9 @@ public class SOCKSServerFactory {
|
||||
* provided sockets's input stream.
|
||||
*
|
||||
* @param s a Socket used to choose the SOCKS server type
|
||||
* @param props non-null
|
||||
*/
|
||||
public static SOCKSServer createSOCKSServer(Socket s) throws SOCKSException {
|
||||
public static SOCKSServer createSOCKSServer(Socket s, Properties props) throws SOCKSException {
|
||||
SOCKSServer serv;
|
||||
|
||||
try {
|
||||
@@ -46,11 +49,16 @@ public class SOCKSServerFactory {
|
||||
switch (socksVer) {
|
||||
case 0x04:
|
||||
// SOCKS version 4/4a
|
||||
if (Boolean.valueOf(props.getProperty(I2PTunnelHTTPClientBase.PROP_AUTH)).booleanValue() &&
|
||||
props.containsKey(I2PTunnelHTTPClientBase.PROP_USER) &&
|
||||
props.containsKey(I2PTunnelHTTPClientBase.PROP_PW)) {
|
||||
throw new SOCKSException("SOCKS 4/4a not supported when authorization is required");
|
||||
}
|
||||
serv = new SOCKS4aServer(s);
|
||||
break;
|
||||
case 0x05:
|
||||
// SOCKS version 5
|
||||
serv = new SOCKS5Server(s);
|
||||
serv = new SOCKS5Server(s, props);
|
||||
break;
|
||||
case 'C':
|
||||
case 'G':
|
||||
|
||||