Compare commits
1164 Commits
i2p-0.9.14
...
i2p-0.9.23
Author | SHA1 | Date | |
---|---|---|---|
dfb8830802 | |||
9483e095d9 | |||
bbaa6f7f87 | |||
046ef07efd | |||
fc7939b404 | |||
dd6a3f14ec | |||
7a75e2e662 | |||
e6644236ed | |||
8a1f02aa89 | |||
a028bba997 | |||
e93e76a362 | |||
3fa2fb4c8d | |||
ffddf415c0 | |||
03a99adaab | |||
48f294024c | |||
123b4ca460 | |||
c944fcce96 | |||
077c4a073f | |||
f5bf4ec8ea | |||
c901010d96 | |||
9f0f1f5ec8 | |||
7175b1cdb9 | |||
ca4642e0f0 | |||
6bb156a436 | |||
19090343ba | |||
b15138dd67 | |||
5f50f23fe1 | |||
d5e2defb5f | |||
c1d77dfe5c | |||
eca234c187 | |||
1a6074a62b | |||
9baeedbc27 | |||
3f91e448c0 | |||
3e25ff251b | |||
f8830a759e | |||
b15ea8ba2f | |||
ef428d559e | |||
39d749ba16 | |||
a3a092a454 | |||
787921aa89 | |||
bbb6da2ac6 | |||
b7dc55e326 | |||
805979b987 | |||
c37cc7ad52 | |||
02c1417cc5 | |||
627d0d29db | |||
c595895877 | |||
6efce31eed | |||
f713a19785 | |||
abc0f4c720 | |||
71bc55b470 | |||
5f175455c7 | |||
9bddba56a0 | |||
4e6ddfcea3 | |||
3411a7c884 | |||
70921a2b09 | |||
dd36176997 | |||
fe26052189 | |||
ba1488bcce | |||
39b218b216 | |||
b43417bf77 | |||
649a63db6f | |||
6aa8ed1280 | |||
9224afb78d | |||
5e879b85a8 | |||
2c03b434e1 | |||
55a6f44651 | |||
971a2652e3 | |||
68aa8800b6 | |||
dd4d12f287 | |||
7063609f05 | |||
b32c8d5fa4 | |||
419d6a8e18 | |||
03f9df4ff0 | |||
f4a6cf2002 | |||
f93da93cf0 | |||
b068f9a262 | |||
5fa059b4a8 | |||
2f92b27446 | |||
5d345f65a3 | |||
ccc8c04782 | |||
58ccfed41d | |||
59b05d4214 | |||
f46a902256 | |||
39b810bd79 | |||
22417715e7 | |||
d21777fbc1 | |||
b22a6bc163 | |||
1c3527e1a4 | |||
4d7ad6ef7f | |||
3ea8b477d8 | |||
ea4dd12bff | |||
a13552dd8d | |||
89c14c2e9a | |||
22b9876b68 | |||
04690bed9f | |||
0faa5ba2f4 | |||
04d653a8b9 | |||
3f213cf1db | |||
53ae727935 | |||
62acfc0cae | |||
5a2f22b00f | |||
7dd438b5f0 | |||
6685b81834 | |||
c56f686d8c | |||
b81cbedd5c | |||
02a0ef3526 | |||
cfc0664756 | |||
2a3b55f3a4 | |||
287f94ad19 | |||
462c882f4e | |||
b8a909c4cc | |||
83791b2d10 | |||
ff420278c5 | |||
1a385b6dca | |||
64889b2bc2 | |||
bfc6534b20 | |||
84abfa0190 | |||
d5a0d95c61 | |||
1de840ce59 | |||
0f6176b7bf | |||
3d533a406d | |||
37597b8c7d | |||
addc9c5ca3 | |||
a2e38503fe | |||
7912d7650d | |||
6f5739b9d8 | |||
ed3e444d1e | |||
ac1a28e988 | |||
7117438b04 | |||
d5cbccf186 | |||
fd606064d9 | |||
9d05424202 | |||
157d494dee | |||
fa792a9d5e | |||
ab134261f0 | |||
de2431e9ee | |||
c4cbd7d5c4 | |||
e978bb81a0 | |||
2c6edf401f | |||
fe69d3b8f7 | |||
61edd01e3d | |||
483d7c43ee | |||
7c703953be | |||
f577a94012 | |||
b10b8581cc | |||
601376561b | |||
5a11a28a35 | |||
fde0ae8349 | |||
b5944045fb | |||
ecd0231cd0 | |||
44b35f328b | |||
f3bb20d750 | |||
20cb284f9d | |||
b4993d42b3 | |||
9b466f3261 | |||
0bf9cb3bf2 | |||
9efe60d7a8 | |||
d848a19ab0 | |||
bfde521cf9 | |||
fea6b8aec3 | |||
1681598dec | |||
809a533573 | |||
265e4b58a5 | |||
93854e93b5 | |||
f6605d05d9 | |||
c20772702a | |||
ba5af15c6f | |||
9af197e590 | |||
2f59a4b3e6 | |||
63e934f8f2 | |||
dd5f804150 | |||
35b0e99ff0 | |||
1ed1e4414b | |||
d087fd674b | |||
1f9bb046f5 | |||
914cc120ad | |||
631a0674ab | |||
17d26976d5 | |||
dc9d60e261 | |||
2c191e7bf8 | |||
817888c23c | |||
1eaf376ee7 | |||
6cb3d1d330 | |||
2681c4b42f | |||
05959d5199 | |||
113a8a52f3 | |||
98a4460bde | |||
3645c906e8 | |||
fcdd8be7a7 | |||
34f6f65104 | |||
4c516cd2af | |||
8ea6805f8d | |||
23f2261bd9 | |||
dd47389ad1 | |||
25268e7cb2 | |||
355b2a1528 | |||
975149d049 | |||
af394e13ad | |||
e3f64f6edf | |||
2fbbfa388e | |||
0b4d4ddcbc | |||
428d89a307 | |||
feff6c003b | |||
699d550992 | |||
c6896c4418 | |||
1b2d4c75eb | |||
586defc802 | |||
2499aad51d | |||
addb142ecd | |||
20c796e87a | |||
cd62d7170c | |||
acc647822f | |||
1cf544f1d4 | |||
0f4e09500c | |||
7c5dfaee20 | |||
8d9cced128 | |||
8096e4f65d | |||
036b77746b | |||
bc85543ef2 | |||
627f7076b0 | |||
863e120204 | |||
53cfba4cbd | |||
3a774b7c37 | |||
0ad34a4b00 | |||
2b9ffc1270 | |||
93c7860d2b | |||
25f6c3d9e1 | |||
b9e07bc9aa | |||
09f68e44ca | |||
013b5fd85b | |||
8962bfb6bc | |||
605602e001 | |||
f341e5566b | |||
7b84676f4a | |||
c666f8a4f9 | |||
e067761947 | |||
226bee64ef | |||
1a40e57413 | |||
f73101b014 | |||
fef65c996f | |||
cbc2f899a6 | |||
099515adff | |||
ff2ea9ac3e | |||
97aeecd865 | |||
8098d705f9 | |||
fa8c390267 | |||
e8f4e19bac | |||
9041a2c69f | |||
384e9118c6 | |||
0936a2ee23 | |||
bc6b0c12ac | |||
f6f051cfa4 | |||
fb131a040c | |||
9f2ded6073 | |||
55e36ee458 | |||
7c13fb2ba0 | |||
663ccb72d7 | |||
78e0a37fc9 | |||
09cdc00939 | |||
2590e7d4ff | |||
27f56776ca | |||
657f13af29 | |||
e2ca74963f | |||
9304cb2bbc | |||
362086994a | |||
f57e37d588 | |||
d96ddd1a0e | |||
7b711ebba0 | |||
0762715264 | |||
8a69dc0a97 | |||
39dc60cf8a | |||
09e867b194 | |||
dc9256f274 | |||
272f63dbbd | |||
06104118d0 | |||
525ec01c1e | |||
f8594c316f | |||
3c89bd4e19 | |||
1f8408f417 | |||
915b35f0c1 | |||
4521156ecb | |||
c58fd8f84e | |||
f02b401b7a | |||
4fdcb6ce29 | |||
94824e4d2b | |||
280fc05c91 | |||
89745f5002 | |||
7715e6484c | |||
c807194e93 | |||
3602f73497 | |||
4bf115b5f6 | |||
7ab85a0a20 | |||
fba0372339 | |||
03dfa6515b | |||
5e33ed1169 | |||
11ab7fc56c | |||
716bff41d7 | |||
1d8842cfc8 | |||
042b03d6b8 | |||
ab753651b9 | |||
4ea99b8a10 | |||
3d07e1a10b | |||
86525e7239 | |||
195171f9ed | |||
33c4be5b2f | |||
fea2c3c6b2 | |||
281686ba58 | |||
0b91fcb636 | |||
807f1381fb | |||
ac56a63809 | |||
e4798b9ed8 | |||
7584346c82 | |||
29330aa5d3 | |||
65ff2c0afe | |||
de4d47de95 | |||
ae41a3f316 | |||
2dc3d68418 | |||
5eb43b6ae4 | |||
0c672ecc49 | |||
3b6d98fe38 | |||
b3472cfe80 | |||
38f2b93c7a | |||
e7af87a981 | |||
d698a67660 | |||
b38f2d62a8 | |||
10556bca75 | |||
1f17d2a149 | |||
dc777c8de5 | |||
1fb9643916 | |||
081f1865a8 | |||
0e17c560b3 | |||
a3b1327934 | |||
e68ca573f0 | |||
b5455cee6e | |||
cbdc1403bf | |||
40130a8a61 | |||
ca14055976 | |||
8303016b48 | |||
287862887d | |||
f25d2a3d3f | |||
7f30f481b2 | |||
5ee6826241 | |||
68951c4c6b | |||
5dc7497802 | |||
31cfddc218 | |||
c1e70ac7d2 | |||
dd9abd3f09 | |||
2f5e64e532 | |||
b12f988390 | |||
9f3d5bf57b | |||
7f9e958e5a | |||
c4877ea092 | |||
2aafc23774 | |||
77c9a644ac | |||
abd8ca34dc | |||
31435685bf | |||
7337fd0670 | |||
f7b7a98b9d | |||
2226936737 | |||
8b293b2190 | |||
94bba8d11f | |||
5c2b5075f9 | |||
ca6820a4c0 | |||
2fafa3337f | |||
b5f75a4bb9 | |||
707bfbbf8b | |||
1eba6c5167 | |||
a14208b841 | |||
83966f9a7f | |||
d89f06015b | |||
49f786c928 | |||
e8bc0bd5d1 | |||
8d69b69357 | |||
e7b9a230e6 | |||
6385c412fd | |||
bb33b358b4 | |||
572f071cfe | |||
42cb89f525 | |||
1868d2b50f | |||
4588f1ec75 | |||
629f7f05c7 | |||
0f18686243 | |||
2a2587b13d | |||
489fdd5e4b | |||
fe680eb192 | |||
613440ff63 | |||
64121b1e92 | |||
f16927f316 | |||
cb50c1bd8b | |||
921ad86274 | |||
ac76107752 | |||
2359b1edd2 | |||
2750681d78 | |||
eaac4d3de0 | |||
f243968dfa | |||
8d9e2bdc71 | |||
6dbbb6b61b | |||
f89bf32390 | |||
ef195aa4ef | |||
843230a1cb | |||
84e63f3b38 | |||
b90816fdf8 | |||
40c4a42921 | |||
26f89391d3 | |||
aaae72cf84 | |||
3e55cff153 | |||
bd778a2204 | |||
235c196f14 | |||
e475c161cb | |||
08e96109a7 | |||
81ad33d9e3 | |||
aecc95825b | |||
37c6ac3a88 | |||
772d0beac3 | |||
64fdfd81ee | |||
1b09b9faa4 | |||
6f0ebb2d94 | |||
cbe91e3012 | |||
238501919b | |||
ae3a5f7b25 | |||
638cadc3c9 | |||
da0036581c | |||
59a58ea310 | |||
bebe5f8a4e | |||
c3af99685d | |||
e1d9e05b8d | |||
212f6b472a | |||
fdada78edf | |||
638c5429d2 | |||
b67bbd7065 | |||
1caf3e778b | |||
fd82fff07a | |||
a6ac8f8c09 | |||
19a26f8e22 | |||
46e85cf265 | |||
8f321b5427 | |||
e1f8f1a3f4 | |||
935a5b573d | |||
8c2636aa99 | |||
03ddb1075c | |||
72eb2c058c | |||
a100d2ccf9 | |||
ecfb3e94c8 | |||
c31d6b1ac1 | |||
65993e1d50 | |||
47c4c0d6bb | |||
b2872e6110 | |||
b8c8d5b447 | |||
32049d7bfc | |||
f0fdb35ba6 | |||
d8baf62966 | |||
be8f7f9676 | |||
57b641bf63 | |||
ff5d29de1a | |||
91e98ba447 | |||
6a644dd0e5 | |||
7b82393336 | |||
22993e1ea6 | |||
341bd6d7ca | |||
13d5a36cfc | |||
f3bb84f2c0 | |||
1d496404be | |||
51233371e0 | |||
bc0a7ebbbc | |||
72c78b3870 | |||
5555c52376 | |||
e1842be049 | |||
6ceb4fcf42 | |||
50b68d4e1c | |||
3f46228f0b | |||
6c954f0b68 | |||
69c2ed77a0 | |||
6f09224bdc | |||
568c90806d | |||
6e451c8d4d | |||
12fd585625 | |||
997fbb3392 | |||
089626f6b1 | |||
71d2049fe8 | |||
e5aee3001f | |||
ec62bcbf8e | |||
a8f013f3e4 | |||
037cd78dc7 | |||
b31ae4bae5 | |||
54dba980b4 | |||
dc19d2fab3 | |||
3a57310fbe | |||
749e19a1c3 | |||
de6608f6b8 | |||
cd6d9cdd94 | |||
e45413d417 | |||
11c3230150 | |||
dd99978b19 | |||
dd265bbd54 | |||
f5ba1b1b97 | |||
7825f0f84f | |||
69a0324e86 | |||
957d3545b6 | |||
466348a8c5 | |||
e5b7e97ff4 | |||
4613e5f847 | |||
44f8154f07 | |||
5486874d1a | |||
d868ca4740 | |||
780479be4b | |||
4705f01bc5 | |||
2f5f91a084 | |||
e44fe98c7e | |||
d8fbc9c170 | |||
facbe8f9a0 | |||
4d8e577ffd | |||
80eb7635c1 | |||
e3103762b6 | |||
cce710e377 | |||
013c79bc45 | |||
1e375886bd | |||
d1ac24c65d | |||
6aa1284848 | |||
bb082c35fc | |||
f7577e7de8 | |||
b5df13d8b7 | |||
9d76790cc5 | |||
706ee243a5 | |||
351a1a8d27 | |||
6916cd7977 | |||
a444c25c2c | |||
45bc533e38 | |||
03e890b01c | |||
0c90162e20 | |||
ddc3ef8db3 | |||
fcec43b7ca | |||
edb614d970 | |||
820b99e3d3 | |||
cf0453cee0 | |||
75a8d8f6d3 | |||
1ac8d99145 | |||
b7b5512e7a | |||
485acd6c8d | |||
bb68728c82 | |||
f3b2eb69d2 | |||
168d688fc9 | |||
ade93ea76d | |||
44503af88b | |||
eb7693561b | |||
3ccb03f9be | |||
f3a2af8f10 | |||
2ef615a3f7 | |||
20197fc3ec | |||
fadc624f7c | |||
22c4149358 | |||
c770c6bc6a | |||
891408191e | |||
9a8fa246a9 | |||
83c3152b5d | |||
956730c5e9 | |||
72b9c92a6e | |||
349255d252 | |||
ac902badcd | |||
9dc2ae0d7e | |||
188bd6db7b | |||
3a8ce64c84 | |||
f3d573cab0 | |||
9e18c7ea18 | |||
a975dc4427 | |||
b875e284af | |||
46fe4298b9 | |||
9790d3ba64 | |||
2d31f30a22 | |||
2fefe93922 | |||
399b068a4e | |||
dcffde6eeb | |||
78074f6a7e | |||
79dc01f7e4 | |||
0f6040ecb1 | |||
a0ab72e362 | |||
2c45378c6d | |||
44c75187f5 | |||
2609a4d124 | |||
2d58501db3 | |||
a337185820 | |||
9c0aa0c271 | |||
b2e908f094 | |||
ef32d37073 | |||
f0961a9658 | |||
876b5714be | |||
825cd7ff4c | |||
47f3476078 | |||
7b10ebc117 | |||
e5cdfd206d | |||
dd4c62b560 | |||
aae801efaf | |||
e02d44433d | |||
590a3c98e5 | |||
7f472e4ee9 | |||
a3802d4d8b | |||
59348f8dbd | |||
8742a66f2f | |||
a2f027e136 | |||
cb4359cd0a | |||
163c172823 | |||
80a2d2c1f5 | |||
486f282999 | |||
1293dccf35 | |||
91fe62eee3 | |||
d3f5596cb2 | |||
d7a88db87a | |||
0af1f67c33 | |||
8dde7b70db | |||
64faeef6c4 | |||
c79e4aeaea | |||
8b6a86e391 | |||
92daf4a8df | |||
819b07a52a | |||
b8f8c6129d | |||
25d1ae195a | |||
d22b05e114 | |||
db25eff74a | |||
c927441d66 | |||
7e4832d5f2 | |||
819b35c760 | |||
071498c413 | |||
7125ed0492 | |||
de201bdd9c | |||
4fccd258e6 | |||
56d705739b | |||
2a9d61b1ed | |||
a9f6839a04 | |||
5b555855ef | |||
76cf80a3d0 | |||
4c6aaa32b6 | |||
74ab1bff53 | |||
b5bba5e3c8 | |||
7e5bd17714 | |||
ec6207fc78 | |||
36d47a0ba9 | |||
0289cefd8d | |||
521eb2d8f8 | |||
0494266649 | |||
8fac5c064e | |||
0b6f74e646 | |||
b8b272a5b8 | |||
a570e09166 | |||
1919e36c30 | |||
812c00f11e | |||
419e27cfd1 | |||
d761c02909 | |||
c7d1d2b69a | |||
0972b6b56a | |||
6e3cf7869f | |||
f7337b4891 | |||
55161dec17 | |||
b65b53b0df | |||
49e1e1c8a4 | |||
9b73fcda40 | |||
b92e1ee9aa | |||
04ac54cd35 | |||
d47916f753 | |||
b0ea1d691a | |||
ce041dfbc1 | |||
4613da093d | |||
2d5f7aaae5 | |||
5a7a7ac83d | |||
f217af2deb | |||
6d58f9a354 | |||
29953ea5e4 | |||
bb9cef1e40 | |||
ece2f1484c | |||
a3c8a4363d | |||
2f90b5a201 | |||
d4bbdc28f3 | |||
f41df969b7 | |||
f87d006a1c | |||
f4fa9a7d8f | |||
c52047e6cd | |||
9163d41228 | |||
1be9bb29e8 | |||
522a89a045 | |||
06b9b6a7fb | |||
7f9c565cd7 | |||
201afc823e | |||
f4c79c885a | |||
656202c9db | |||
72b64072d5 | |||
b72271f9a4 | |||
b0d09d28f4 | |||
b9197e35b5 | |||
e431be2cbe | |||
761c883c1f | |||
60f86f342b | |||
1234b6b148 | |||
6f45242fc8 | |||
36c45ccb7b | |||
90cf71b5bc | |||
7165dc7860 | |||
5491287931 | |||
7256096b8a | |||
b6008b5414 | |||
1042d21278 | |||
47eff7ee86 | |||
3da850a6b9 | |||
a1358deda2 | |||
df0bbfd615 | |||
0568ac3aa5 | |||
e63a69170e | |||
711f8dedd9 | |||
09c3737a94 | |||
9384424173 | |||
4936f08212 | |||
03f4ebbe35 | |||
0671785ab2 | |||
381dbc4b4a | |||
84cf531f5f | |||
175806115b | |||
86b45ab1e5 | |||
17939036bc | |||
5bf515441e | |||
06edb9f2a6 | |||
33b58f5fab | |||
47a012a4dd | |||
e5801be43e | |||
d5a6ac591c | |||
59373f9bdf | |||
5da492b9e5 | |||
9bcc951f10 | |||
3270ba840e | |||
45b3e44cc2 | |||
7ed855b2d2 | |||
f08552c2d1 | |||
9a4c19b24b | |||
690b695373 | |||
65348b2365 | |||
285c13d900 | |||
0a938d9048 | |||
a02a265802 | |||
eeeeef81cf | |||
bcb9fe5f24 | |||
37f34d83f8 | |||
b3238079c3 | |||
ee1edb3383 | |||
7767430af2 | |||
2e5185aa99 | |||
6e847a4cc4 | |||
045f6dccf8 | |||
d7895a456a | |||
7753d05b61 | |||
043b4776c3 | |||
5db764de5f | |||
3ae846a713 | |||
927e29b8ef | |||
d271411552 | |||
31d98ac4a5 | |||
78f4cc8e30 | |||
cce30a8f42 | |||
a9e928fb46 | |||
60017f7c55 | |||
eb46f74e24 | |||
5e890bd781 | |||
20facf78d0 | |||
96db43cc8e | |||
ab4f209c10 | |||
fa51a0aef4 | |||
aa6a5e053c | |||
03df6c2ba0 | |||
501f645e60 | |||
23534b31c6 | |||
d35363cdbc | |||
ba34c90b7f | |||
94a19171ed | |||
8099591589 | |||
df6bbc59b3 | |||
05a616aa0d | |||
c84105e783 | |||
262721cc90 | |||
c24168d5cd | |||
4e529a68d3 | |||
4f3244e93b | |||
b2e17916e4 | |||
57ac344e7f | |||
98e275d908 | |||
8420b6c715 | |||
3dfcb2d5cc | |||
540720a912 | |||
f86200e3ae | |||
9e43618028 | |||
aacdba1bc7 | |||
c28d060d52 | |||
bf3fdbb1ab | |||
0a7a637d46 | |||
e842165265 | |||
2db82da910 | |||
9953bc3024 | |||
2ba4992d88 | |||
5e67008d26 | |||
e7b50c5940 | |||
78d7277298 | |||
fb641187b8 | |||
4b2715c36f | |||
f1e9f5d4fd | |||
2d43d349ab | |||
7ab6708a3c | |||
7010d9b524 | |||
947a3a2181 | |||
0ff87ef8cb | |||
ec20150ffd | |||
30876a9cd3 | |||
1773fc0e0d | |||
6d6f7fb89b | |||
449ce3176e | |||
be8832e87f | |||
5999690665 | |||
285fa6cbc9 | |||
9700f30c35 | |||
a38bd0b5cf | |||
5383f9f097 | |||
a16d17c422 | |||
5f2b620819 | |||
fd47cb88de | |||
77e7982e74 | |||
04cd1cedda | |||
3ef89f49e7 | |||
2a681608b5 | |||
a52c06a6c6 | |||
49b8a65ad9 | |||
9781cb72ac | |||
f7e83fb839 | |||
ce2a2cf684 | |||
c88fa70f82 | |||
f76744a0c0 | |||
31cc0764a9 | |||
15137d9b62 | |||
8f8adfa39e | |||
5044f3e58f | |||
279e102d7a | |||
7f72830ec8 | |||
2caaad95ec | |||
09b995aca6 | |||
a0bf8433e4 | |||
9104bd7304 | |||
2f2aa7f5a8 | |||
0773a30578 | |||
962f5efe6b | |||
6dc3cd9650 | |||
397ae536f9 | |||
f19ec4bd44 | |||
fd7e549915 | |||
7a7ae77c83 | |||
1a9fb381ed | |||
ae7bfceafb | |||
a961843aa6 | |||
43c6a4ddac | |||
ae1d5648d5 | |||
2d3e8e0c4e | |||
4691fc69d5 | |||
cb87f9f307 | |||
5f1e5bc271 | |||
1c6d5ad2db | |||
555189f123 | |||
049044b827 | |||
cfd9e2c3ab | |||
a0b457b9a1 | |||
23f24c7d39 | |||
6112cc5a38 | |||
7deb8c1bcb | |||
76e4b49d9d | |||
5ae267a8a2 | |||
f524351041 | |||
893d1bb45f | |||
945988dfb7 | |||
62698664a2 | |||
0d2892c75d | |||
ecc72e6825 | |||
531d520ceb | |||
4e72e150ad | |||
b28628b8e1 | |||
702830ad0e | |||
6ca0c54ba7 | |||
634bf5f7e3 | |||
2284c963af | |||
ad2052395f | |||
e9a1dbf8f1 | |||
703b21e89b | |||
36ea2cca6b | |||
8b2cf770a5 | |||
7d6d801943 | |||
d705b43f3a | |||
a18ed194cb | |||
a3e4293fd8 | |||
df144d8434 | |||
bab1e05235 | |||
a1fdd41b0e | |||
60d9c1651a | |||
ec1380dfa1 | |||
798275608e | |||
0f2affd414 | |||
7695b51d89 | |||
fb99122d83 | |||
6d53838e20 | |||
ec3fd9a7d7 | |||
304f2ebb7b | |||
4976e84488 | |||
2ebacb1b9b | |||
d085f9ea66 | |||
e275117569 | |||
eae277fb77 | |||
d7130c15cc | |||
937a17c5dd | |||
b6234e1d5e | |||
7955b8ae71 | |||
a36ef62358 | |||
bcbda3cd27 | |||
239fe518a9 | |||
44d6e117d5 | |||
8a12b7cb41 | |||
4d4308c486 | |||
abcdcf2e8c | |||
44b753d1e5 | |||
83b3f242a9 | |||
86c43f4734 | |||
be0cb84f97 | |||
3bea7f5ad5 | |||
266a20d55e | |||
d2c6a80d24 | |||
cd51fbc2a6 | |||
73256f6030 | |||
e081f94d9f | |||
e96cc09d75 | |||
d87178fec3 | |||
28ad95f892 | |||
8270a92a44 | |||
088290c544 | |||
6685acfef4 | |||
1d1a05ee7b | |||
bbeb429a59 | |||
55af588c2c | |||
80d0313fe5 | |||
853d309960 | |||
564400597a | |||
1c2b6fc00e | |||
7b6f32e5b2 | |||
dd4acc88a1 | |||
6e566f6e3d | |||
fff7fbe121 | |||
a50afeb5d5 | |||
49eeb99d43 | |||
bfd51097c9 | |||
a21e3cd842 | |||
0f298cf48e | |||
4b074b8dcf | |||
2c79853ffe | |||
72f57255f0 | |||
1053bc8bb0 | |||
4835e6fcb9 | |||
7ec02a1620 | |||
10993cc6ef | |||
81409369bc | |||
edb8590da8 | |||
3a7bfd28fc | |||
36fdb4ee2f | |||
b06f772647 | |||
012cc740d6 | |||
1407cff49d | |||
3331e1c152 | |||
591f48856d | |||
479b9691fd | |||
0e48557b48 | |||
3fae6f06dd | |||
7639c24bfe | |||
39fd1c3ab8 | |||
abf9dbae6d | |||
98062f830a | |||
c259347917 | |||
9c4558d891 | |||
9e7e2948e3 | |||
43430da25f | |||
eca7ac21a0 | |||
bc463f6d0b | |||
ec2708a1fd | |||
082922de01 | |||
ea02a7c70b | |||
ab7e25bd52 | |||
d4876dd25e | |||
89ee0bbab4 | |||
06ae882064 | |||
6517fe7515 | |||
d510aad2ab | |||
3db297de95 | |||
8688f26f15 | |||
85d38e7af2 | |||
0448348154 | |||
ceab4f1ffc | |||
3781b8db09 | |||
7a450c526c | |||
c1e8ea0e4a | |||
f248a33eaa | |||
67fb4e7007 | |||
0a41052f3f | |||
a7763a08dc | |||
fcfb471a8a | |||
b9e383130e | |||
cd2159b873 | |||
e492d5e0cf | |||
9a0f6490ba | |||
5183b44d8b | |||
156d86835a | |||
eab4397b0f | |||
d808b999c6 | |||
603b345405 | |||
682534f468 | |||
42eb43f713 | |||
0817b58b9d | |||
9ab766375d | |||
6c2799fe53 | |||
ef81a575cd | |||
cce0d94fbb | |||
22b5203334 | |||
d4be5abe67 | |||
9985a02efc | |||
41c2c60ab0 | |||
f285364f46 | |||
1c5e9b7fe3 | |||
12cc501e25 | |||
49118b8bf1 | |||
09dfea7dea | |||
00bd469f8e | |||
18e7e56a6c | |||
34d14a720b | |||
c4d7f9924f | |||
80d6921a66 | |||
3c95144b83 | |||
84ad155ab8 | |||
330a5ddd0f | |||
3b2f1d35c4 | |||
0f1036b0e1 | |||
86935f10a8 | |||
1078c42a14 | |||
09cf973712 | |||
5af749a226 | |||
f84b86a752 | |||
ca7873eda7 | |||
f87ebaf214 | |||
a9802eb6a7 | |||
5d5a68cb3e | |||
c6b1f5053f | |||
1d2e01c8cd | |||
0c5c18a767 | |||
6826ba05e7 | |||
053ce88743 | |||
4a216c57d4 | |||
03cec7fd5a | |||
1238001add | |||
fa1c077fdd | |||
8a7c3390f5 | |||
2302aee819 | |||
a72866ee6a | |||
0f7a3dba87 | |||
5decf18eb5 | |||
c318760398 | |||
629eff20dd | |||
f6e508ca14 | |||
588ab86abb | |||
387629372b | |||
0a01700e3e | |||
59504deb7f | |||
8ee660c238 | |||
176c106427 | |||
ed4fe56e7e | |||
310cd54aa0 | |||
3217b4ac90 | |||
7bf1949061 | |||
51f9d6d421 | |||
ddb32c65fb | |||
c5c158e983 | |||
f83007e038 | |||
2b9a368b18 | |||
6ad6974452 | |||
308923448b | |||
04ad7de2e1 | |||
54563b0b42 | |||
593779b54f | |||
34d3704680 | |||
613f90bcf7 | |||
6ff500f7cb | |||
c79e33896e | |||
68b15aadca | |||
819504f08f | |||
e65ec2a589 | |||
1bc355b8fd | |||
d76164679f | |||
efebecfc67 | |||
7b64586c87 | |||
0fe15b8e1d | |||
a1cb00b5a3 | |||
5041d819a9 | |||
02ab6eac62 | |||
d7feab116f | |||
4f9e13d0f6 | |||
d0b0e6a58e | |||
a12f898096 | |||
c921ecca05 | |||
975378b224 | |||
915e003355 | |||
51e45d128a | |||
57650ef058 | |||
dee6e16e6c | |||
c860674613 | |||
33b7f08d5c | |||
66bbe21a87 | |||
51995cc428 | |||
a3e3a305ce | |||
15facc72b3 | |||
3839c8d1c0 | |||
d5edcbc6e1 | |||
eb97ef4cb2 | |||
9c38e1e191 | |||
7c3d3b4128 | |||
367cea4b1f | |||
a63bfeaeec | |||
41672dde64 | |||
3b18cb7eca | |||
c9ce1751c1 | |||
4ba40b340a | |||
e3be1d1a04 | |||
6fa2a416be | |||
fdb54c315b | |||
22a7757461 | |||
0bacbbc553 | |||
03d8314842 | |||
fe4d98f0df | |||
b1d60122a3 | |||
c9e20c5d23 | |||
381f494754 | |||
506419964b | |||
35bb8c5348 | |||
79fe799aeb | |||
2878a6487e | |||
9655e79d26 | |||
d1a2e24f0e | |||
086381d958 | |||
89764c12e7 | |||
328d7d0008 | |||
cca5bef8c1 | |||
2cfe5e678a | |||
d48991f71f | |||
8f2dc67430 | |||
888ef37808 | |||
c987a9735d | |||
3b9549c2c1 | |||
91408cbdce | |||
97c1ba2d02 | |||
284802bfa5 | |||
48b6e0693e | |||
d867f9f36e | |||
55d92fc9f2 | |||
2e2d3c39e6 | |||
3cd01acb73 | |||
02c0ddb3d3 | |||
ce397f5858 | |||
611f991fdd | |||
47712a39ac | |||
18146daad8 | |||
a2e7fa8b7b | |||
30ccf1b334 | |||
01b153488a |
73
.tx/config
@ -12,9 +12,11 @@ trans.it = apps/i2ptunnel/locale/messages_it.po
|
||||
trans.ja = apps/i2ptunnel/locale/messages_ja.po
|
||||
trans.nb = apps/i2ptunnel/locale/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale/messages_nl.po
|
||||
trans.nn = apps/i2ptunnel/locale/messages_nn.po
|
||||
trans.pl = apps/i2ptunnel/locale/messages_pl.po
|
||||
trans.pt = apps/i2ptunnel/locale/messages_pt.po
|
||||
trans.pt_BR = apps/i2ptunnel/locale/messages_pt_BR.po
|
||||
trans.ro = apps/i2ptunnel/locale/messages_ro.po
|
||||
trans.ru_RU = apps/i2ptunnel/locale/messages_ru.po
|
||||
trans.sk = apps/i2ptunnel/locale/messages_sk.po
|
||||
trans.sv_SE = apps/i2ptunnel/locale/messages_sv.po
|
||||
@ -31,6 +33,8 @@ trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
|
||||
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
|
||||
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
|
||||
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/i2ptunnel/locale-proxy/messages_in.po
|
||||
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
|
||||
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
|
||||
@ -39,6 +43,7 @@ trans.pt = apps/i2ptunnel/locale-proxy/messages_pt.po
|
||||
trans.pt_BR = apps/i2ptunnel/locale-proxy/messages_pt_BR.po
|
||||
trans.ro = apps/i2ptunnel/locale-proxy/messages_ro.po
|
||||
trans.ru_RU = apps/i2ptunnel/locale-proxy/messages_ru.po
|
||||
trans.sk = apps/i2ptunnel/locale-proxy/messages_sk.po
|
||||
trans.sv_SE = apps/i2ptunnel/locale-proxy/messages_sv.po
|
||||
trans.uk_UA = apps/i2ptunnel/locale-proxy/messages_uk.po
|
||||
trans.vi = apps/i2ptunnel/locale-proxy/messages_vi.po
|
||||
@ -78,10 +83,16 @@ source_lang = en
|
||||
trans.ar = apps/routerconsole/locale-news/messages_ar.po
|
||||
trans.de = apps/routerconsole/locale-news/messages_de.po
|
||||
trans.es = apps/routerconsole/locale-news/messages_es.po
|
||||
trans.fi = apps/routerconsole/locale-news/messages_fi.po
|
||||
trans.fr = apps/routerconsole/locale-news/messages_fr.po
|
||||
trans.he = apps/routerconsole/locale-news/messages_he.po
|
||||
trans.ja = apps/routerconsole/locale-news/messages_ja.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/routerconsole/locale-news/messages_in.po
|
||||
trans.it = apps/routerconsole/locale-news/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-news/messages_ja.po
|
||||
trans.ko = apps/routerconsole/locale-news/messages_ko.po
|
||||
trans.mg = apps/routerconsole/locale-news/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-news/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-news/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-news/messages_pl.po
|
||||
trans.pt = apps/routerconsole/locale-news/messages_pt.po
|
||||
@ -89,14 +100,17 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-news/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-news/messages_sk.po
|
||||
trans.sq = apps/routerconsole/locale-news/messages_sq.po
|
||||
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
|
||||
trans.uk_UA = apps/routerconsole/locale-news/messages_uk.po
|
||||
trans.zh_CN = apps/routerconsole/locale-news/messages_zh.po
|
||||
|
||||
[I2P.countries]
|
||||
type = PO
|
||||
source_file = apps/routerconsole/locale-countries/messages_en.po
|
||||
source_lang = en
|
||||
trans.ca = apps/routerconsole/locale-countries/messages_ca.po
|
||||
trans.da = apps/routerconsole/locale-countries/messages_da.po
|
||||
trans.de = apps/routerconsole/locale-countries/messages_de.po
|
||||
trans.el = apps/routerconsole/locale-countries/messages_el.po
|
||||
@ -107,6 +121,7 @@ trans.fr = apps/routerconsole/locale-countries/messages_fr.po
|
||||
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
|
||||
trans.it = apps/routerconsole/locale-countries/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
|
||||
trans.mg = apps/routerconsole/locale-countries/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
|
||||
@ -115,7 +130,9 @@ trans.pt_BR = apps/routerconsole/locale-countries/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-countries/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-countries/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-countries/messages_sk.po
|
||||
trans.sq = apps/routerconsole/locale-countries/messages_sq.po
|
||||
trans.sv_SE = apps/routerconsole/locale-countries/messages_sv.po
|
||||
trans.uk_UA = apps/routerconsole/locale-countries/messages_uk.po
|
||||
trans.tr_TR = apps/routerconsole/locale-countries/messages_tr.po
|
||||
trans.vi = apps/routerconsole/locale-countries/messages_vi.po
|
||||
trans.zh_CN = apps/routerconsole/locale-countries/messages_zh.po
|
||||
@ -134,6 +151,7 @@ trans.nb = apps/i2psnark/locale/messages_nb.po
|
||||
trans.nl = apps/i2psnark/locale/messages_nl.po
|
||||
trans.pl = apps/i2psnark/locale/messages_pl.po
|
||||
trans.pt = apps/i2psnark/locale/messages_pt.po
|
||||
trans.pt_BR = apps/i2psnark/locale/messages_pt_bR.po
|
||||
trans.ro = apps/i2psnark/locale/messages_ro.po
|
||||
trans.ru_RU = apps/i2psnark/locale/messages_ru.po
|
||||
trans.sk = apps/i2psnark/locale/messages_sk.po
|
||||
@ -161,6 +179,7 @@ trans.pt_BR = apps/susidns/locale/messages_pt_BR.po
|
||||
trans.ro = apps/susidns/locale/messages_ro.po
|
||||
trans.ru_RU = apps/susidns/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susidns/locale/messages_sv.po
|
||||
trans.tr_TR = apps/susidns/locale/messages_tr.po
|
||||
trans.uk_UA = apps/susidns/locale/messages_uk.po
|
||||
trans.vi = apps/susidns/locale/messages_vi.po
|
||||
trans.zh_CN = apps/susidns/locale/messages_zh.po
|
||||
@ -194,19 +213,25 @@ trans.zh_CN = apps/desktopgui/locale/messages_zh.po
|
||||
source_file = apps/susimail/locale/messages_en.po
|
||||
source_lang = en
|
||||
trans.cs = apps/susimail/locale/messages_cs.po
|
||||
trans.da = apps/susimail/locale/messages_da.po
|
||||
trans.de = apps/susimail/locale/messages_de.po
|
||||
trans.es = apps/susimail/locale/messages_es.po
|
||||
trans.fi = apps/susimail/locale/messages_fi.po
|
||||
trans.fr = apps/susimail/locale/messages_fr.po
|
||||
trans.hu = apps/susimail/locale/messages_hu.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/susimail/locale/messages_in.po
|
||||
trans.it = apps/susimail/locale/messages_it.po
|
||||
trans.ja = apps/susimail/locale/messages_ja.po
|
||||
trans.mg = apps/susimail/locale/messages_mg.po
|
||||
trans.nl = apps/susimail/locale/messages_nl.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.pl = apps/susimail/locale/messages_pl.po
|
||||
trans.pt = apps/susimail/locale/messages_pt.po
|
||||
trans.pt_BR = apps/susimail/locale/messages_pt_BR.po
|
||||
trans.ro = apps/susimail/locale/messages_ro.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sq = apps/susimail/locale/messages_sq.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.uk_UA = apps/susimail/locale/messages_uk.po
|
||||
trans.vi = apps/susimail/locale/messages_vi.po
|
||||
trans.zh_CN = apps/susimail/locale/messages_zh.po
|
||||
@ -218,16 +243,21 @@ trans.cs = debian/po/cs.po
|
||||
trans.de = debian/po/de.po
|
||||
trans.el = debian/po/el.po
|
||||
trans.es = debian/po/es.po
|
||||
trans.fi = debian/po/fi.po
|
||||
trans.fr = debian/po/fr.po
|
||||
trans.id = debian/po/id.po
|
||||
trans.it = debian/po/it.po
|
||||
trans.hu = debian/po/hu.po
|
||||
trans.ja = debian/po/ja.po
|
||||
trans.ko = debian/po/ko.po
|
||||
trans.nl = debian/po/nl.po
|
||||
trans.pl = debian/po/pl.po
|
||||
trans.pt = debian/po/pt.po
|
||||
trans.pt_BR = debian/po/pt_BR.po
|
||||
trans.ro = debian/po/ro.po
|
||||
trans.ru_RU = debian/po/ru.po
|
||||
trans.sk = debian/po/sk.po
|
||||
trans.sq = debian/po/sq.po
|
||||
trans.sv_SE = debian/po/sv.po
|
||||
trans.uk_UA = debian/po/uk.po
|
||||
trans.tr_TR = debian/po/tr.po
|
||||
@ -236,12 +266,21 @@ trans.zh_CN = debian/po/zh.po
|
||||
[I2P.i2prouter-script]
|
||||
source_file = installer/resources/locale/po/messages_en.po
|
||||
source_lang = en
|
||||
;; currently fails check
|
||||
;;trans.ca = installer/resources/locale/po/messages_ca.po
|
||||
trans.de = installer/resources/locale/po/messages_de.po
|
||||
trans.es = installer/resources/locale/po/messages_es.po
|
||||
;; currently fails check
|
||||
;;trans.fi = installer/resources/locale/po/messages_fi.po
|
||||
trans.fr = installer/resources/locale/po/messages_fr.po
|
||||
trans.id = installer/resources/locale/po/messages_id.po
|
||||
trans.it = installer/resources/locale/po/messages_it.po
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.ja = installer/resources/locale/po/messages_ja.po
|
||||
;; currently fails check
|
||||
;;trans.ko = installer/resources/locale/po/messages_ko.po
|
||||
trans.nl = installer/resources/locale/po/messages_nl.po
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.pt = installer/resources/locale/po/messages_pt.po
|
||||
trans.pt_BR = installer/resources/locale/po/messages_pt_BR.po
|
||||
trans.ro = installer/resources/locale/po/messages_ro.po
|
||||
@ -249,6 +288,8 @@ trans.ru_RU = installer/resources/locale/po/messages_ru.po
|
||||
trans.sk = installer/resources/locale/po/messages_sk.po
|
||||
trans.sv_SE = installer/resources/locale/po/messages_sv.po
|
||||
trans.tr_TR = installer/resources/locale/po/messages_tr.po
|
||||
;; currently fails check
|
||||
;;trans.uk_UA = installer/resources/locale/po/messages_uk.po
|
||||
trans.zh_CN = installer/resources/locale/po/messages_zh.po
|
||||
|
||||
[I2P.getopt]
|
||||
@ -258,22 +299,46 @@ type = PROPERTIES
|
||||
trans.cs = core/java/src/gnu/getopt/MessagesBundle_cs.properties
|
||||
trans.de = core/java/src/gnu/getopt/MessagesBundle_de.properties
|
||||
trans.es = core/java/src/gnu/getopt/MessagesBundle_es.properties
|
||||
trans.fi = core/java/src/gnu/getopt/MessagesBundle_fi.properties
|
||||
trans.fr = core/java/src/gnu/getopt/MessagesBundle_fr.properties
|
||||
trans.hu = core/java/src/gnu/getopt/MessagesBundle_hu.properties
|
||||
;; Java converts id to in
|
||||
trans.id = core/java/src/gnu/getopt/MessagesBundle_in.properties
|
||||
trans.it = core/java/src/gnu/getopt/MessagesBundle_it.properties
|
||||
trans.ja = core/java/src/gnu/getopt/MessagesBundle_ja.properties
|
||||
trans.ko = core/java/src/gnu/getopt/MessagesBundle_ko.properties
|
||||
trans.nl = core/java/src/gnu/getopt/MessagesBundle_nl.properties
|
||||
trans.nb = core/java/src/gnu/getopt/MessagesBundle_nb.properties
|
||||
trans.pl = core/java/src/gnu/getopt/MessagesBundle_pl.properties
|
||||
trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt = core/java/src/gnu/getopt/MessagesBundle_pt.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
|
||||
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
|
||||
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
|
||||
trans.uk_UA = core/java/src/gnu/getopt/MessagesBundle_uk.properties
|
||||
trans.zh_CN = core/java/src/gnu/getopt/MessagesBundle_zh.properties
|
||||
|
||||
[I2P.streaming]
|
||||
source_file = apps/ministreaming/locale/messages_en.po
|
||||
source_lang = en
|
||||
trans.ca = apps/ministreaming/locale/messages_ca.po
|
||||
trans.de = apps/ministreaming/locale/messages_de.po
|
||||
trans.es = apps/ministreaming/locale/messages_es.po
|
||||
trans.fr = apps/ministreaming/locale/messages_fr.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/ministreaming/locale/messages_in.po
|
||||
trans.it = apps/ministreaming/locale/messages_it.po
|
||||
trans.nb = apps/ministreaming/locale/messages_nb.po
|
||||
trans.pl = apps/ministreaming/locale/messages_pl.po
|
||||
trans.ro = apps/ministreaming/locale/messages_ro.po
|
||||
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
|
||||
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
|
||||
trans.uk_UA = apps/ministreaming/locale/messages_uk.po
|
||||
trans.zh_CN = apps/ministreaming/locale/messages_zh.po
|
||||
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
14
LICENSE.txt
@ -80,6 +80,10 @@ Public domain except as listed below:
|
||||
Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
HostnameVerifier:
|
||||
From Apache HttpClient 4.4.1 and HttpCore 4.4.1
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
|
||||
Router (router.jar):
|
||||
Public domain except as listed below:
|
||||
@ -87,7 +91,7 @@ Public domain except as listed below:
|
||||
From freenet
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
UPnP subsystem (CyberLink) 2.1:
|
||||
UPnP subsystem (CyberLink) 3.0:
|
||||
Copyright (C) 2003-2010 Satoshi Konno
|
||||
See licenses/LICENSE-UPnP.txt
|
||||
|
||||
@ -137,7 +141,7 @@ Installer:
|
||||
|
||||
|
||||
|
||||
Java Service Wrapper Community Edition 32-bit 3.5.19:
|
||||
Java Service Wrapper Community Edition 32-bit 3.5.25:
|
||||
Copyright (C) 1999-2011 Tanuki Software, Ltd. All Rights Reserved.
|
||||
See licenses/LICENSE-Wrapper.txt
|
||||
|
||||
@ -182,7 +186,7 @@ Applications:
|
||||
By welterde.
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Jetty 8.1.15.v20140411:
|
||||
Jetty 8.1.17.v20150415:
|
||||
See licenses/ABOUT-Jetty.html
|
||||
See licenses/NOTICE-Jetty.html
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
@ -248,8 +252,8 @@ Applications:
|
||||
Bundles systray4j-2.4.1:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
Tomcat 6.0.41:
|
||||
Copyright 1999-2014 The Apache Software Foundation
|
||||
Tomcat 6.0.44:
|
||||
Copyright 1999-2015 The Apache Software Foundation
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Tomcat.txt
|
||||
|
||||
|
12
README.txt
@ -19,7 +19,8 @@ To build:
|
||||
|
||||
Documentation:
|
||||
https://geti2p.net/how
|
||||
API: run 'ant javadoc' then start at build/javadoc/index.html
|
||||
API: http://docs.i2p-projekt.de/javadoc/
|
||||
or run 'ant javadoc' then start at build/javadoc/index.html
|
||||
|
||||
Latest release:
|
||||
https://geti2p.net/download
|
||||
@ -34,6 +35,15 @@ Need help?
|
||||
IRC irc.freenode.net #i2p
|
||||
http://forum.i2p/
|
||||
|
||||
Bug reports:
|
||||
https://trac.i2p2.de/report/1
|
||||
|
||||
Contact information, security issues, press inquiries:
|
||||
https://geti2p.net/en/contact
|
||||
|
||||
Twitter:
|
||||
@i2p, @geti2p
|
||||
|
||||
Licenses:
|
||||
See LICENSE.txt
|
||||
|
||||
|
@ -38,7 +38,6 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.*;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@ -119,15 +118,16 @@ public class BOB implements Runnable, ClientApp {
|
||||
public final static String PROP_BOB_HOST = "BOB.host";
|
||||
public final static String PROP_CFG_VER = "BOB.CFG.VER";
|
||||
|
||||
/** unused when started via the ClientApp interface */
|
||||
private static BOB _bob;
|
||||
|
||||
private NamedDB database;
|
||||
private Properties props = new Properties();
|
||||
private AtomicBoolean spin = new AtomicBoolean(true);
|
||||
private final NamedDB database;
|
||||
private final Properties props = new Properties();
|
||||
private final AtomicBoolean spin = new AtomicBoolean(true);
|
||||
private static final String P_RUNNING = "RUNNING";
|
||||
private static final String P_STARTING = "STARTING";
|
||||
private static final String P_STOPPING = "STOPPING";
|
||||
private AtomicBoolean lock = new AtomicBoolean(false);
|
||||
private final AtomicBoolean lock = new AtomicBoolean(false);
|
||||
// no longer used.
|
||||
// private static int maxConnections = 0;
|
||||
|
||||
@ -143,8 +143,9 @@ public class BOB implements Runnable, ClientApp {
|
||||
* Stop BOB gracefully
|
||||
* @deprecated unused
|
||||
*/
|
||||
public static void stop() {
|
||||
_bob.shutdown(null);
|
||||
public synchronized static void stop() {
|
||||
if (_bob != null)
|
||||
_bob.shutdown(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,7 +190,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
*
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
public synchronized static void main(String[] args) {
|
||||
try {
|
||||
_bob = new BOB(I2PAppContext.getGlobalContext(), null, args);
|
||||
_bob.startup();
|
||||
@ -212,9 +213,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
// Re-reading the config file in each thread is pretty damn stupid.
|
||||
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
||||
// This is here just to ensure there is no interference with our threadgroups.
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
i = Y1.hashCode();
|
||||
i = Y2.hashCode();
|
||||
{
|
||||
File cfg = new File(configLocation);
|
||||
@ -339,7 +338,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
|
||||
if (g) {
|
||||
DoCMDS conn_c = new DoCMDS(spin, lock, server, props, database, _log);
|
||||
Thread t = new Thread(conn_c);
|
||||
Thread t = new I2PAppThread(conn_c);
|
||||
t.setName("BOB.DoCMDS " + i);
|
||||
t.start();
|
||||
i++;
|
||||
|
@ -25,12 +25,13 @@ import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
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.I2PAppThread;
|
||||
|
||||
// needed only for debugging.
|
||||
// import java.util.logging.Level;
|
||||
// import java.util.logging.Logger;
|
||||
@ -1307,7 +1308,7 @@ public class DoCMDS implements Runnable {
|
||||
// wait
|
||||
}
|
||||
tunnel = new MUXlisten(lock, database, nickinfo, _log);
|
||||
Thread t = new Thread(tunnel);
|
||||
Thread t = new I2PAppThread(tunnel);
|
||||
t.start();
|
||||
// try {
|
||||
// Thread.sleep(1000 * 10); // Slow down the startup.
|
||||
|
@ -18,10 +18,12 @@ package net.i2p.BOB;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Listen on I2P and connect to TCP
|
||||
@ -30,16 +32,15 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class I2Plistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PServerSocket serverSocket;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param SS
|
||||
* @param S
|
||||
* @param S unused
|
||||
* @param info
|
||||
* @param database
|
||||
* @param _log
|
||||
@ -48,7 +49,6 @@ public class I2Plistener implements Runnable {
|
||||
this.database = database;
|
||||
this.info = info;
|
||||
this._log = _log;
|
||||
this.socketManager = S;
|
||||
this.serverSocket = SS;
|
||||
this.lives = lives;
|
||||
}
|
||||
@ -79,7 +79,7 @@ public class I2Plistener implements Runnable {
|
||||
conn++;
|
||||
// toss the connection to a new thread.
|
||||
I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database, lives);
|
||||
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
|
||||
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
|
||||
t.start();
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,9 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Process I2P->TCP
|
||||
@ -111,8 +113,8 @@ public class I2PtoTCP implements Runnable {
|
||||
// setup to cross the streams
|
||||
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
|
||||
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
|
||||
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
// Fire!
|
||||
t.start();
|
||||
q.start();
|
||||
|
@ -21,11 +21,13 @@ import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@ -201,14 +203,14 @@ public class MUXlisten implements Runnable {
|
||||
// I2P -> TCP
|
||||
SS = socketManager.getServerSocket();
|
||||
I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log, lives);
|
||||
t = new Thread(tg, conn, "BOBI2Plistener " + N);
|
||||
t = new I2PAppThread(tg, conn, "BOBI2Plistener " + N);
|
||||
t.start();
|
||||
}
|
||||
|
||||
if (come_in) {
|
||||
// TCP -> I2P
|
||||
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log, lives);
|
||||
q = new Thread(tg, conn, "BOBTCPlistener " + N);
|
||||
q = new I2PAppThread(tg, conn, "BOBTCPlistener " + N);
|
||||
q.start();
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package net.i2p.BOB;
|
||||
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@ -31,12 +30,10 @@ public class Main {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// THINK THINK THINK THINK THINK THINK
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
|
||||
BOB.main(args);
|
||||
|
||||
Y2.stop();
|
||||
Y1.stop();
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class NamedDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find objects in the array, returns it's index or throws exception
|
||||
* Find objects in the array, returns its index or throws exception
|
||||
* @param key
|
||||
* @return an objects index
|
||||
* @throws ArrayIndexOutOfBoundsException when key does not exist
|
||||
|
@ -20,8 +20,10 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Listen on TCP port and connect to I2P
|
||||
@ -30,12 +32,11 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class TCPlistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private ServerSocket listener;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PSocketManager socketManager;
|
||||
private final ServerSocket listener;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -76,7 +77,7 @@ public class TCPlistener implements Runnable {
|
||||
conn++;
|
||||
// toss the connection to a new thread.
|
||||
TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info, database, lives);
|
||||
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
|
||||
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
|
||||
t.start();
|
||||
g = false;
|
||||
}
|
||||
|
@ -24,13 +24,14 @@ import java.net.NoRouteToHostException;
|
||||
import java.net.Socket;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
//import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -158,8 +159,8 @@ public class TCPtoI2P implements Runnable {
|
||||
// setup to cross the streams
|
||||
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
|
||||
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
|
||||
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
// Fire!
|
||||
t.start();
|
||||
q.start();
|
||||
|
@ -34,15 +34,17 @@ import net.i2p.util.Log;
|
||||
* The skeletal frame is here, just needs to be finished.
|
||||
*
|
||||
* @author sponge
|
||||
* @deprecated incomplete, unused
|
||||
*/
|
||||
public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
|
||||
private NamedDB info;
|
||||
private Log _log;
|
||||
private Socket socket;
|
||||
private final NamedDB info;
|
||||
private final Log _log;
|
||||
private final Socket socket;
|
||||
private DataInputStream in;
|
||||
private DataOutputStream out;
|
||||
private I2PSession _session;
|
||||
private final I2PSession _session;
|
||||
// FIXME never set
|
||||
private Destination _peerDestination;
|
||||
private boolean up;
|
||||
|
||||
@ -58,7 +60,6 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
this._log = _log;
|
||||
this.socket = socket;
|
||||
this._session = _session;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
@ -49,6 +50,26 @@ class AddressBook {
|
||||
private boolean modified;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int MIN_DEST_LENGTH = 516;
|
||||
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||
|
||||
/**
|
||||
* 5-67 chars lower/upper case
|
||||
*/
|
||||
private static final Pattern HOST_PATTERN =
|
||||
Pattern.compile("^[0-9a-zA-Z\\.-]{5,67}$");
|
||||
|
||||
/**
|
||||
* 52 chars lower/upper case
|
||||
* Always ends in 'a' or 'q'
|
||||
*/
|
||||
private static final Pattern B32_PATTERN =
|
||||
Pattern.compile("^[2-7a-zA-Z]{51}[aAqQ]$");
|
||||
|
||||
/** not a complete qualification, just a quick check */
|
||||
private static final Pattern B64_PATTERN =
|
||||
Pattern.compile("^[0-9a-zA-Z~-]{" + MIN_DEST_LENGTH + ',' + MAX_DEST_LENGTH + "}={0,2}$");
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the Map addresses.
|
||||
*
|
||||
@ -159,8 +180,13 @@ class AddressBook {
|
||||
* @since 0.8.7
|
||||
*/
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
if (this.subFile != null)
|
||||
return new ConfigIterator(this.subFile);
|
||||
if (this.subFile != null) {
|
||||
try {
|
||||
return new ConfigIterator(this.subFile);
|
||||
} catch (IOException ioe) {
|
||||
return new ConfigIterator();
|
||||
}
|
||||
}
|
||||
return this.addresses.entrySet().iterator();
|
||||
}
|
||||
|
||||
@ -201,9 +227,6 @@ class AddressBook {
|
||||
return "Map containing " + this.addresses.size() + " entries";
|
||||
}
|
||||
|
||||
private static final int MIN_DEST_LENGTH = 516;
|
||||
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||
|
||||
/**
|
||||
* Do basic validation of the hostname
|
||||
* hostname was already converted to lower case by ConfigParser.parse()
|
||||
@ -220,9 +243,10 @@ class AddressBook {
|
||||
host.indexOf("..") < 0 &&
|
||||
// IDN - basic check, not complete validation
|
||||
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
HOST_PATTERN.matcher(host).matches() &&
|
||||
// Base32 spoofing (52chars.i2p)
|
||||
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
|
||||
// We didn't do it this way, we use a .b32.i2p suffix, but let's prohibit it anyway
|
||||
(! (host.length() == 56 && B32_PATTERN.matcher(host.substring(0,52)).matches())) &&
|
||||
// ... or maybe we do Base32 this way ...
|
||||
(! host.equals("b32.i2p")) &&
|
||||
(! host.endsWith(".b32.i2p")) &&
|
||||
@ -246,7 +270,7 @@ class AddressBook {
|
||||
(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
|
||||
B64_PATTERN.matcher(dest).matches()
|
||||
;
|
||||
}
|
||||
|
||||
@ -332,4 +356,27 @@ class AddressBook {
|
||||
protected void finalize() {
|
||||
delete();
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
String[] tests = { "foo.i2p",
|
||||
"3bnipzzu67cdq2rcygyxz52xhvy6ylokn4zfrk36ywn6pixmaoza.b32.i2p",
|
||||
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAAA",
|
||||
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA==",
|
||||
"te9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
|
||||
"(*&(*&(*&(*",
|
||||
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAA",
|
||||
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA===",
|
||||
"!e9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
|
||||
"x"
|
||||
};
|
||||
for (String s : tests) {
|
||||
test(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static void test(String s) {
|
||||
System.out.println(s + " valid host? " + isValidKey(s) + " valid dest? " + isValidDest(s));
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@ -41,7 +42,7 @@ import java.util.NoSuchElementException;
|
||||
*
|
||||
* @since 0.8.7
|
||||
*/
|
||||
class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
class ConfigIterator implements Iterator<Map.Entry<String, String>>, Closeable {
|
||||
|
||||
private BufferedReader input;
|
||||
private ConfigEntry next;
|
||||
@ -54,11 +55,9 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
/**
|
||||
* An iterator over the key/value pairs in the file.
|
||||
*/
|
||||
public ConfigIterator(File file) {
|
||||
try {
|
||||
public ConfigIterator(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
input = new BufferedReader(new InputStreamReader(fileStream));
|
||||
} catch (IOException ioe) {}
|
||||
input = new BufferedReader(new InputStreamReader(fileStream, "UTF-8"));
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
|
@ -64,10 +64,11 @@ class ConfigParser {
|
||||
if (inputLine.startsWith(";")) {
|
||||
return "";
|
||||
}
|
||||
if (inputLine.split("#").length > 0) {
|
||||
return inputLine.split("#")[0];
|
||||
int hash = inputLine.indexOf('#');
|
||||
if (hash >= 0) {
|
||||
return inputLine.substring(0, hash);
|
||||
} else {
|
||||
return "";
|
||||
return inputLine;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +116,7 @@ class ConfigParser {
|
||||
public static Map<String, String> parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
fileStream, "UTF-8"));
|
||||
Map<String, String> rv = parse(input);
|
||||
try {
|
||||
fileStream.close();
|
||||
@ -204,7 +205,7 @@ class ConfigParser {
|
||||
public static List<String> parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
fileStream, "UTF-8"));
|
||||
List<String> rv = parseSubscriptions(input);
|
||||
try {
|
||||
fileStream.close();
|
||||
|
@ -25,6 +25,7 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.NamingServiceUpdater;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* A thread that waits five minutes, then runs the addressbook daemon.
|
||||
@ -32,7 +33,7 @@ import net.i2p.client.naming.NamingServiceUpdater;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread implements NamingServiceUpdater {
|
||||
public class DaemonThread extends I2PAppThread implements NamingServiceUpdater {
|
||||
|
||||
private String[] args;
|
||||
|
||||
|
@ -23,8 +23,9 @@ package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@ -56,8 +57,8 @@ class Log {
|
||||
public void append(String entry) {
|
||||
BufferedWriter bw = null;
|
||||
try {
|
||||
bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.file,
|
||||
true), "UTF-8"));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
|
@ -26,6 +26,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
@ -69,11 +70,14 @@ class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
* 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)
|
||||
* @return non-null AddressBook (empty if the minimum delay has not been met,
|
||||
* or there is no proxy tunnel, or the fetch otherwise fails)
|
||||
*/
|
||||
public AddressBook next() {
|
||||
Subscription sub = this.subIterator.next();
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now() &&
|
||||
I2PAppContext.getGlobalContext().portMapper().getPort(PortMapper.SVC_HTTP_PROXY) >= 0 &&
|
||||
!I2PAppContext.getGlobalContext().getBooleanProperty("i2p.vmCommSystem")) {
|
||||
//System.err.println("Fetching addressbook from " + sub.getLocation());
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
} else {
|
||||
|
11
apps/addressbook/java/src/net/i2p/addressbook/package.html
Normal file
@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The addressbook application, which fetches hosts.txt files from subscription URLS via
|
||||
HTTP and adds new hosts to the local database.
|
||||
While implemented as a webapp, this application contains no user interface.
|
||||
May also be packaged as a jar, as is done for Android.
|
||||
The webapp named 'addressbook' in the console is actually SusiDNS.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -28,4 +28,11 @@
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- this webapp doesn't actually use sessions or cookies -->
|
||||
<session-config>
|
||||
<session-timeout>30</session-timeout>
|
||||
<cookie-config>
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
</session-config>
|
||||
</web-app>
|
||||
|
99
apps/apparmor/home.i2p.i2prouter
Normal file
@ -0,0 +1,99 @@
|
||||
# Last Modified: Sun Apr 12 22:08:32 2015
|
||||
# vim:syntax=apparmor et ts=8 sw=4
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
$INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/fonts>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/ssl_certs>
|
||||
|
||||
capability sys_ptrace,
|
||||
network inet stream,
|
||||
network inet6 stream,
|
||||
|
||||
$INSTALL_PATH/ r,
|
||||
$INSTALL_PATH/{i2psvc,wrapper} rmix,
|
||||
owner $INSTALL_PATH/** rwklm,
|
||||
|
||||
# Needed for Java
|
||||
owner @{PROC} r,
|
||||
owner @{PROC}/[0-9]*/ r,
|
||||
owner @{PROC}/[0-9]*/status r,
|
||||
owner @{PROC}/[0-9]*/stat r,
|
||||
owner @{PROC}/[0-9]*/cmdline r,
|
||||
@{PROC}/uptime r,
|
||||
@{PROC}/sys/kernel/pid_max r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/** r,
|
||||
|
||||
/dev/random r,
|
||||
/dev/urandom r,
|
||||
|
||||
@{PROC}/1/comm r,
|
||||
|
||||
/etc/ssl/certs/java/** r,
|
||||
/etc/timezone r,
|
||||
/usr/share/javazi/** r,
|
||||
|
||||
# Debian
|
||||
/etc/java-{6,7,8}-openjdk/** r,
|
||||
/usr/lib/jvm/default-java/jre/bin/java rix,
|
||||
|
||||
# Debian, Ubuntu, openSUSE
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/java rix,
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/keytool rix,
|
||||
|
||||
# Raspbian
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/java rix,
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/keytool rix,
|
||||
|
||||
|
||||
# Fonts are needed for I2P's graphs
|
||||
/usr/share/java/java-atk-wrapper.jar r,
|
||||
|
||||
# Used by some plugins
|
||||
/usr/share/java/eclipse-ecj-*.jar r,
|
||||
|
||||
/{,var/}tmp/ rwm,
|
||||
owner /{,var/}tmp/** rwklm,
|
||||
|
||||
/{,usr/}bin/{,b,d}ash rix,
|
||||
/{,usr/}bin/cat rix,
|
||||
/{,usr/}bin/cut rix,
|
||||
/{,usr/}bin/dirname rix,
|
||||
/{,usr/}bin/expr rix,
|
||||
/{,usr/}bin/{,g,m}awk rix,
|
||||
/{,usr/}bin/grep rix,
|
||||
/{,usr/}bin/id rix,
|
||||
/{,usr/}bin/ldd rix,
|
||||
/{,usr/}bin/ls rix,
|
||||
/{,usr/}bin/mkdir rix,
|
||||
/{,usr/}bin/nohup rix,
|
||||
/{,usr/}bin/ps rix,
|
||||
/{,usr/}bin/rm rix,
|
||||
/{,usr/}bin/sed rix,
|
||||
/{,usr/}bin/sleep rix,
|
||||
/{,usr/}bin/tail rix,
|
||||
/{,usr/}bin/tr rix,
|
||||
/{,usr/}bin/uname rix,
|
||||
/{,usr/}bin/which rix,
|
||||
|
||||
@{HOME}/.java/fonts/** r,
|
||||
owner @{HOME}/.i2p/ rw,
|
||||
owner @{HOME}/.i2p/** rwk,
|
||||
|
||||
# Prevent spamming the logs
|
||||
deny owner @{HOME}/.java/ wk,
|
||||
deny @{HOME}/.fontconfig/ wk,
|
||||
deny @{HOME}/.java/fonts/** w,
|
||||
deny /dev/tty rw,
|
||||
deny /dev/pts/[0-9]* rw,
|
||||
deny @{PROC}/[0-9]*/fd/ r,
|
||||
deny /usr/local/share/fonts/ r,
|
||||
deny /var/cache/fontconfig/ wk,
|
||||
# Used by some versions of the Tanuki wrapper but never used by I2P
|
||||
deny /usr/share/java/hamcrest*.jar r,
|
||||
deny /usr/share/java/junit*.jar r,
|
||||
}
|
@ -26,12 +26,12 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
# Fast mode - update ondemond
|
||||
# set LG2 to the language you need in envrionment varibales to enable this
|
||||
# set LG2 to the language you need in environment variables to enable this
|
||||
|
||||
# add ../java/ so the refs will work in the po file
|
||||
JPATHS="src"
|
||||
@ -64,19 +64,19 @@ do
|
||||
echo "Updating the $i file from the tags..."
|
||||
# extract strings from java and jsp files, and update messages.po files
|
||||
# translate calls must be one of the forms:
|
||||
# _("foo")
|
||||
# _t("foo")
|
||||
# _x("foo")
|
||||
# intl._("foo")
|
||||
# intl._t("foo")
|
||||
# intl.title("foo")
|
||||
# handler._("foo")
|
||||
# formhandler._("foo")
|
||||
# handler._t("foo")
|
||||
# formhandler._t("foo")
|
||||
# net.i2p.router.web.Messages.getString("foo")
|
||||
# In a jsp, you must use a helper or handler that has the context set.
|
||||
# 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 --add-comments\
|
||||
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
--keyword=_t --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
--keyword=handler._ --keyword=formhandler._ \
|
||||
--keyword=net.i2p.router.web.Messages.getString \
|
||||
-o ${i}t
|
||||
|
@ -3,20 +3,22 @@
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# <b790979@klzlk.com>, 2011.
|
||||
# Translators:
|
||||
# PolishAnon <b790979@klzlk.com>, 2011
|
||||
# polacco <polacco@i2pmail.org>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2011-05-25 18:36+0000\n"
|
||||
"Last-Translator: PolishAnon <b790979@klzlk.com>\n"
|
||||
"Language-Team: Polish (http://www.transifex.net/projects/p/I2P/team/pl/)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2015-02-17 20:54+0000\n"
|
||||
"Last-Translator: polacco <polacco@i2pmail.org>\n"
|
||||
"Language-Team: Polish (http://www.transifex.com/projects/p/I2P/language/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pl\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@ -32,7 +34,7 @@ msgstr "Uruchamianie"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Uruchom Przeglądarke I2P"
|
||||
msgstr "Uruchom przeglądarkę I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
@ -46,12 +48,10 @@ msgstr "Zrestartuj I2P"
|
||||
msgid "Stop I2P"
|
||||
msgstr "Zatrzymaj I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Konfiguracja ikony zasobnika"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Czy ikona zasobnika powinna być aktywna?"
|
||||
|
||||
|
||||
|
@ -4,16 +4,17 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Denis Blank <gribua@gmail.com>, 2011
|
||||
# Denis Lysenko <gribua@gmail.com>, 2011
|
||||
# LinuxChata, 2014
|
||||
# madjong <madjong@i2pmail.org>, 2014
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2014-06-22 10:20+0000\n"
|
||||
"Last-Translator: LinuxChata\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/projects/p/I2P/language/uk_UA/)\n"
|
||||
"PO-Revision-Date: 2015-08-07 16:31+0000\n"
|
||||
"Last-Translator: Denis Lysenko <gribua@gmail.com>\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/otf/I2P/language/uk_UA/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@ -50,7 +51,7 @@ msgstr "Зупинити I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Настройка трей-іконки"
|
||||
msgstr "Налаштування трей-іконки"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
|
@ -20,7 +20,7 @@ public class ExternalTrayManager extends TrayManager {
|
||||
@Override
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
MenuItem startItem = new MenuItem(_("Start I2P"));
|
||||
MenuItem startItem = new MenuItem(_t("Start I2P"));
|
||||
startItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@ -35,7 +35,7 @@ public class ExternalTrayManager extends TrayManager {
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
trayIcon.displayMessage(_("Starting"), _("I2P is starting!"), TrayIcon.MessageType.INFO);
|
||||
trayIcon.displayMessage(_t("Starting"), _t("I2P is starting!"), TrayIcon.MessageType.INFO);
|
||||
//Hide the tray icon.
|
||||
//We cannot stop the desktopgui program entirely,
|
||||
//since that risks killing the I2P process as well.
|
||||
|
@ -23,7 +23,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
|
||||
MenuItem browserLauncher = new MenuItem(_("Launch I2P Browser"));
|
||||
MenuItem browserLauncher = new MenuItem(_t("Launch I2P Browser"));
|
||||
browserLauncher.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@ -47,7 +47,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
MenuItem desktopguiConfigurationLauncher = new MenuItem(_("Configure desktopgui"));
|
||||
MenuItem desktopguiConfigurationLauncher = new MenuItem(_t("Configure desktopgui"));
|
||||
desktopguiConfigurationLauncher.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@ -64,7 +64,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
});
|
||||
MenuItem restartItem = new MenuItem(_("Restart I2P"));
|
||||
MenuItem restartItem = new MenuItem(_t("Restart I2P"));
|
||||
restartItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@ -82,7 +82,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
});
|
||||
MenuItem stopItem = new MenuItem(_("Stop I2P"));
|
||||
MenuItem stopItem = new MenuItem(_t("Stop I2P"));
|
||||
stopItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
|
@ -78,7 +78,7 @@ public abstract class TrayManager {
|
||||
return image;
|
||||
}
|
||||
|
||||
protected static String _(String s) {
|
||||
return DesktopguiTranslator._(s);
|
||||
protected static String _t(String s) {
|
||||
return DesktopguiTranslator._t(s);
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
|
||||
cancelButton = new javax.swing.JButton();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setTitle(_("Tray icon configuration"));
|
||||
setTitle(_t("Tray icon configuration"));
|
||||
|
||||
desktopguiEnabled.setSelected(true);
|
||||
desktopguiEnabled.setText(_("Should tray icon be enabled?"));
|
||||
desktopguiEnabled.setText(_t("Should tray icon be enabled?"));
|
||||
desktopguiEnabled.setActionCommand("shouldDesktopguiBeEnabled");
|
||||
|
||||
okButton.setText("OK");
|
||||
@ -98,8 +98,8 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
|
||||
configureDesktopgui();
|
||||
}//GEN-LAST:event_okButtonMouseReleased
|
||||
|
||||
protected static String _(String s) {
|
||||
return DesktopguiTranslator._(s);
|
||||
protected static String _t(String s) {
|
||||
return DesktopguiTranslator._t(s);
|
||||
}
|
||||
|
||||
private void configureDesktopgui() {
|
||||
|
@ -16,11 +16,11 @@ public class DesktopguiTranslator {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static String _(String s) {
|
||||
public static String _t(String s) {
|
||||
return Translate.getString(s, getRouterContext(), BUNDLE_NAME);
|
||||
}
|
||||
|
||||
public static String _(String s, Object o) {
|
||||
public static String _t(String s, Object o) {
|
||||
return Translate.getString(s, o, getRouterContext(), BUNDLE_NAME);
|
||||
}
|
||||
}
|
||||
|
@ -1,340 +1 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
See ../../licenses/LICENSE-GPLv2.txt
|
||||
|
@ -1,24 +0,0 @@
|
||||
- I2PSnark:
|
||||
- add multitorrent support by checking the metainfo hash in the
|
||||
PeerAcceptor and feeding it off to the appropriate coordinator
|
||||
- add a web interface
|
||||
|
||||
- BEncode
|
||||
- Byte array length indicator can overflow.
|
||||
- Support really big BigNums (only 256 chars allowed now)
|
||||
- Better BEValue toString(). Uses stupid heuristic now for debugging.
|
||||
- Implemented bencoding.
|
||||
- Remove application level hack to calculate sha1 hash for metainfo
|
||||
(But can it be done as efficiently?)
|
||||
|
||||
- Storage
|
||||
- Check file name filter.
|
||||
|
||||
- TrackerClient
|
||||
- Support undocumented &numwant= request.
|
||||
|
||||
- PeerCoordinator
|
||||
- Disconnect from other seeds as soon as you are a seed yourself.
|
||||
|
||||
- Text UI
|
||||
- Make it completely silent.
|
@ -100,15 +100,15 @@
|
||||
<target name="war" depends="jar, bundle, warUpToDate, listChangedFiles" unless="war.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.tr" value="" />
|
||||
<copy todir="build/icons/.icons" >
|
||||
<fileset dir="../icons/" />
|
||||
<copy todir="build/resources/.resources" >
|
||||
<fileset dir="../resources/" />
|
||||
</copy>
|
||||
<!-- mime.properties must be in with the classes -->
|
||||
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml" >
|
||||
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
|
||||
<classes dir="./build/obj" includes="**/web/*" />
|
||||
<fileset dir="build/icons/" />
|
||||
<fileset dir="build/resources/" />
|
||||
<manifest>
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
@ -121,7 +121,7 @@
|
||||
|
||||
<target name="warUpToDate">
|
||||
<uptodate property="war.uptodate" targetfile="../i2psnark.war" >
|
||||
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../icons/* ../web.xml" />
|
||||
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../resources/**/* ../web.xml" />
|
||||
</uptodate>
|
||||
</target>
|
||||
|
||||
|
@ -25,12 +25,12 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
# Fast mode - update ondemond
|
||||
# set LG2 to the language you need in envrionment varibales to enable this
|
||||
# set LG2 to the language you need in environment variables to enable this
|
||||
|
||||
# add ../java/ so the refs will work in the po file
|
||||
JPATHS="../java/src"
|
||||
@ -63,13 +63,13 @@ do
|
||||
echo "Updating the $i file from the tags..."
|
||||
# extract strings from java and jsp files, and update messages.po files
|
||||
# translate calls must be one of the forms:
|
||||
# _("foo")
|
||||
# _t("foo")
|
||||
# _x("foo")
|
||||
# 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 --add-comments\
|
||||
--keyword=_ --keyword=_x \
|
||||
--keyword=_t --keyword=_x \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
@ -66,7 +68,7 @@ public class BitField
|
||||
|
||||
/**
|
||||
* This returns the actual byte array used. Changes to this array
|
||||
* effect this BitField. Note that some bits at the end of the byte
|
||||
* affect this BitField. Note that some bits at the end of the byte
|
||||
* array are supposed to be always unset if they represent bits
|
||||
* bigger then the size of the bitfield.
|
||||
*/
|
||||
@ -105,6 +107,37 @@ public class BitField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to false.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
* @since 0.9.22
|
||||
*/
|
||||
public void clear(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
synchronized(this) {
|
||||
if ((bitfield[index] & mask) != 0) {
|
||||
count--;
|
||||
bitfield[index] &= ~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all bits to true.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
public void setAll() {
|
||||
Arrays.fill(bitfield, (byte) 0xff);
|
||||
count = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bit is set or false if it is not.
|
||||
*
|
||||
|
@ -54,7 +54,15 @@ public interface CompleteListener {
|
||||
*/
|
||||
public void gotPiece(Snark snark);
|
||||
|
||||
// not really listeners but the easiest way to get back to an optional SnarkManager
|
||||
/** not really listeners but the easiest way to get back to an optional SnarkManager */
|
||||
public long getSavedTorrentTime(Snark snark);
|
||||
public BitField getSavedTorrentBitField(Snark snark);
|
||||
/**
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public boolean getSavedPreserveNamesSetting(Snark snark);
|
||||
/**
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public long getSavedUploaded(Snark snark);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@ -213,6 +214,20 @@ class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ConnectException ioe)
|
||||
{
|
||||
// This is presumed to be due to socket closing by I2PSnarkUtil.disconnect(),
|
||||
// which does not currently call our halt(), although it should
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Error while accepting", ioe);
|
||||
synchronized(this) {
|
||||
if (!stop) {
|
||||
locked_halt();
|
||||
thread = null;
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
int level = stop ? Log.WARN : Log.ERROR;
|
||||
|
@ -90,17 +90,20 @@ abstract class ExtensionHandler {
|
||||
peer.setHandshakeMap(map);
|
||||
Map<String, BEValue> msgmap = map.get("m").getMap();
|
||||
|
||||
if (msgmap.get(TYPE_PEX) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports PEX extension: " + peer);
|
||||
// peer state calls peer listener calls sendPEX()
|
||||
}
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer " + peer + " supports extensions: " + msgmap.keySet());
|
||||
|
||||
if (msgmap.get(TYPE_DHT) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports DHT extension: " + peer);
|
||||
// peer state calls peer listener calls sendDHT()
|
||||
}
|
||||
//if (msgmap.get(TYPE_PEX) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports PEX extension: " + peer);
|
||||
// // peer state calls peer listener calls sendPEX()
|
||||
//}
|
||||
|
||||
//if (msgmap.get(TYPE_DHT) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports DHT extension: " + peer);
|
||||
// // peer state calls peer listener calls sendDHT()
|
||||
//}
|
||||
|
||||
MagnetState state = peer.getMagnetState();
|
||||
|
||||
@ -204,30 +207,31 @@ abstract class ExtensionHandler {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got request for " + piece + " from: " + peer);
|
||||
byte[] pc;
|
||||
int totalSize;
|
||||
synchronized(state) {
|
||||
pc = state.getChunk(piece);
|
||||
totalSize = state.getSize();
|
||||
}
|
||||
sendPiece(peer, piece, pc);
|
||||
sendPiece(peer, piece, pc, totalSize);
|
||||
// Do this here because PeerConnectionOut only reports for PIECE messages
|
||||
peer.uploaded(pc.length);
|
||||
listener.uploaded(peer, pc.length);
|
||||
} else if (type == TYPE_DATA) {
|
||||
int size = map.get("total_size").getInt();
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
// On close reading of BEP 9, this is the total metadata size.
|
||||
// Prior to 0.9.21, we sent the piece size, so we can't count on it.
|
||||
// just ignore it. The actual length will be verified in saveChunk()
|
||||
//int size = map.get("total_size").getInt();
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
boolean done;
|
||||
int chk = -1;
|
||||
synchronized(state) {
|
||||
if (state.isComplete())
|
||||
return;
|
||||
int len = is.available();
|
||||
if (len != size) {
|
||||
// probably fatal
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("total_size " + size + " but avail data " + len);
|
||||
}
|
||||
peer.downloaded(len);
|
||||
listener.downloaded(peer, len);
|
||||
// this checks the size
|
||||
done = state.saveChunk(piece, bs, bs.length - len, len);
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Got chunk " + piece + " from " + peer);
|
||||
@ -290,11 +294,15 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data) {
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data, int totalSize) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("msg_type", Integer.valueOf(TYPE_DATA));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
map.put("total_size", Integer.valueOf(data.length));
|
||||
// BEP 9
|
||||
// "This key has the same semantics as the 'metadata_size' in the extension header"
|
||||
// which apparently means the same value. Fixed in 0.9.21.
|
||||
//map.put("total_size", Integer.valueOf(data.length));
|
||||
map.put("total_size", Integer.valueOf(totalSize));
|
||||
byte[] dict = BEncoder.bencode(map);
|
||||
byte[] payload = new byte[dict.length + data.length];
|
||||
System.arraycopy(dict, 0, payload, 0, dict.length);
|
||||
|
@ -3,14 +3,18 @@ package org.klomp.snark;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
@ -71,8 +75,6 @@ public class I2PSnarkUtil {
|
||||
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
|
||||
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 static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
|
||||
public static final boolean DEFAULT_USE_DHT = true;
|
||||
@ -95,18 +97,17 @@ public class I2PSnarkUtil {
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_banlist = new ConcurrentHashSet<Hash>();
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
_startupDelay = DEFAULT_STARTUP_DELAY;
|
||||
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
|
||||
// FIXME split if default has more than one
|
||||
_openTrackers = Collections.singletonList(DEFAULT_OPENTRACKERS);
|
||||
_openTrackers = Collections.emptyList();
|
||||
_shouldUseDHT = DEFAULT_USE_DHT;
|
||||
// This is used for both announce replies and .torrent file downloads,
|
||||
// so it must be available even if not connected to I2CP.
|
||||
// so much for multiple instances
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName + '-' + ctx.random().nextInt());
|
||||
//FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
@ -135,6 +136,7 @@ public class I2PSnarkUtil {
|
||||
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
|
||||
if (i2cpHost != null)
|
||||
_i2cpHost = i2cpHost;
|
||||
@ -255,6 +257,8 @@ public class I2PSnarkUtil {
|
||||
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
|
||||
if (opts.getProperty("i2p.streaming.answerPings") == null)
|
||||
opts.setProperty("i2p.streaming.answerPings", "false");
|
||||
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
|
||||
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
_connecting = false;
|
||||
}
|
||||
@ -328,7 +332,7 @@ public class I2PSnarkUtil {
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
_banlist.add(dest);
|
||||
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
_context.simpleTimer2().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
IOException ioe = new IOException("Unable to reach the peer " + peer);
|
||||
ioe.initCause(ie);
|
||||
throw ioe;
|
||||
@ -456,7 +460,7 @@ public class I2PSnarkUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
public String getOurIPString() {
|
||||
Destination dest = getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
@ -565,12 +569,12 @@ public class I2PSnarkUtil {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** @param ot non-null */
|
||||
/** @param ot non-null list of announce URLs */
|
||||
public void setOpenTrackers(List<String> ot) {
|
||||
_openTrackers = ot;
|
||||
}
|
||||
|
||||
/** List of open trackers to use as backups
|
||||
/** List of open tracker announce URLs to use as backups
|
||||
* @return non-null, possibly unmodifiable, empty if disabled
|
||||
*/
|
||||
public List<String> getOpenTrackers() {
|
||||
@ -580,7 +584,22 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* List of open trackers to use as backups even if disabled
|
||||
* Is this announce URL probably for an open tracker?
|
||||
*
|
||||
* @since 0.9.17
|
||||
*/
|
||||
public boolean isKnownOpenTracker(String url) {
|
||||
try {
|
||||
URL u = new URL(url);
|
||||
String host = u.getHost();
|
||||
return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
|
||||
} catch (MalformedURLException mue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of open tracker announce URLs to use as backups even if disabled
|
||||
* @return non-null
|
||||
* @since 0.9.4
|
||||
*/
|
||||
@ -642,7 +661,7 @@ public class I2PSnarkUtil {
|
||||
* The {0} will be replaced by the parameter.
|
||||
* Single quotes must be doubled, i.e. ' -> '' in the string.
|
||||
* @param o parameter, not translated.
|
||||
* To tranlslate parameter also, use _("foo {0} bar", _("baz"))
|
||||
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
|
||||
* Do not double the single quotes in the parameter.
|
||||
* Use autoboxing to call with ints, longs, floats, etc.
|
||||
*/
|
||||
|
@ -29,6 +29,9 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
private int _consec;
|
||||
private int _consecNotRunning;
|
||||
private boolean _isIdle;
|
||||
private String _lastIn = "3";
|
||||
private String _lastOut = "3";
|
||||
private final Object _lock = new Object();
|
||||
|
||||
private static final long CHECK_TIME = 63*1000;
|
||||
private static final int MAX_CONSEC_IDLE = 4;
|
||||
@ -46,16 +49,19 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
synchronized (_lock) {
|
||||
locked_timeReached();
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_timeReached() {
|
||||
if (_util.connected()) {
|
||||
boolean torrentRunning = false;
|
||||
boolean hasPeers = false;
|
||||
int peerCount = 0;
|
||||
for (PeerCoordinator pc : _pcs) {
|
||||
if (!pc.halted()) {
|
||||
torrentRunning = true;
|
||||
if (pc.getPeers() > 0) {
|
||||
hasPeers = true;
|
||||
break;
|
||||
}
|
||||
peerCount += pc.getPeers();
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,25 +72,29 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Closing tunnels on idle");
|
||||
_util.disconnect();
|
||||
_mgr.addMessage(_util.getString("I2P tunnel closed."));
|
||||
_mgr.addMessage(_util.getString("No more torrents running.") + ' ' +
|
||||
_util.getString("I2P tunnel closed."));
|
||||
schedule(3 * CHECK_TIME);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPeers) {
|
||||
if (_isIdle)
|
||||
restoreTunnels();
|
||||
if (peerCount > 0) {
|
||||
restoreTunnels(peerCount);
|
||||
} else {
|
||||
if (!_isIdle) {
|
||||
if (_consec++ >= MAX_CONSEC_IDLE)
|
||||
reduceTunnels();
|
||||
else
|
||||
restoreTunnels(1); // pretend we have one peer for now
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_isIdle = false;
|
||||
_consec = 0;
|
||||
_consecNotRunning = 0;
|
||||
_lastIn = "3";
|
||||
_lastOut = "3";
|
||||
}
|
||||
schedule(CHECK_TIME);
|
||||
}
|
||||
@ -100,12 +110,13 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore tunnel count
|
||||
* Restore or adjust tunnel count based on current peer count
|
||||
* @param peerCount greater than zero
|
||||
*/
|
||||
private void restoreTunnels() {
|
||||
_isIdle = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
private void restoreTunnels(int peerCount) {
|
||||
if (_isIdle && _log.shouldLog(Log.INFO))
|
||||
_log.info("Restoring tunnels on activity");
|
||||
_isIdle = false;
|
||||
Map<String, String> opts = _util.getI2CPOptions();
|
||||
String i = opts.get("inbound.quantity");
|
||||
if (i == null)
|
||||
@ -119,7 +130,30 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
String ob= opts.get("outbound.backupQuantity");
|
||||
if (ob == null)
|
||||
ob = "0";
|
||||
setTunnels(i, o, ib, ob);
|
||||
// we don't need more tunnels than we have peers, reduce if so
|
||||
// reduce to max(peerCount / 2, 2)
|
||||
int in, out;
|
||||
try {
|
||||
in = Integer.parseInt(i);
|
||||
} catch (NumberFormatException nfe) {
|
||||
in = 3;
|
||||
}
|
||||
try {
|
||||
out = Integer.parseInt(o);
|
||||
} catch (NumberFormatException nfe) {
|
||||
out = 3;
|
||||
}
|
||||
int target = Math.max(peerCount / 2, 2);
|
||||
if (target < in && in > 2) {
|
||||
in = target;
|
||||
i = Integer.toString(in);
|
||||
}
|
||||
if (target < out && out > 2) {
|
||||
out = target;
|
||||
o = Integer.toString(out);
|
||||
}
|
||||
if (!(_lastIn.equals(i) && _lastOut.equals(o)))
|
||||
setTunnels(i, o, ib, ob);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,12 +165,16 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
if (mgr != null) {
|
||||
I2PSession sess = mgr.getSession();
|
||||
if (sess != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob);
|
||||
Properties newProps = new Properties();
|
||||
newProps.setProperty("inbound.quantity", i);
|
||||
newProps.setProperty("outbound.quantity", o);
|
||||
newProps.setProperty("inbound.backupQuantity", ib);
|
||||
newProps.setProperty("outbound.backupQuantity", ob);
|
||||
sess.updateOptions(newProps);
|
||||
_lastIn = i;
|
||||
_lastOut = o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ public class MagnetURI {
|
||||
name = util.getString("Magnet") + ' ' + ihash;
|
||||
String dn = getParam("dn", url);
|
||||
if (dn != null)
|
||||
name += " (" + Storage.filterName(dn) + ')';
|
||||
name += " (" + dn + ')';
|
||||
} else if (url.startsWith(MAGGOT)) {
|
||||
// maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7
|
||||
ihash = url.substring(MAGGOT.length()).trim();
|
||||
@ -82,7 +82,7 @@ public class MagnetURI {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return pretty name or null
|
||||
* @return pretty name or null, NOT HTML escaped
|
||||
*/
|
||||
public String getName() {
|
||||
return _name;
|
||||
@ -175,18 +175,25 @@ public class MagnetURI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode %xx encoding, convert to UTF-8 if necessary
|
||||
* Copied from i2ptunnel LocalHTTPServer
|
||||
* Decode %xx encoding, convert to UTF-8 if necessary.
|
||||
* Copied from i2ptunnel LocalHTTPServer.
|
||||
* Also converts '+' to ' ' so the dn parameter comes out right
|
||||
* These are coming in via a application/x-www-form-urlencoded form so
|
||||
* the pluses are in there...
|
||||
* hopefully any real + is encoded as %2B.
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
private static String decode(String s) {
|
||||
if (!s.contains("%"))
|
||||
if (!(s.contains("%") || s.contains("+")))
|
||||
return s;
|
||||
StringBuilder buf = new StringBuilder(s.length());
|
||||
boolean utf8 = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c != '%') {
|
||||
if (c == '+') {
|
||||
buf.append(' ');
|
||||
} else if (c != '%') {
|
||||
buf.append(c);
|
||||
} else {
|
||||
try {
|
||||
|
@ -55,11 +55,13 @@ class Message
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
// Also SUGGEST, REJECT, ALLOWED_FAST
|
||||
// low byte used for EXTENSION message
|
||||
// low two bytes used for PORT message
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
// Also REJECT
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
@ -104,15 +106,18 @@ class Message
|
||||
int datalen = 1;
|
||||
|
||||
// piece is 4 bytes.
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
datalen += 4;
|
||||
|
||||
// begin/offset is 4 bytes
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// length is 4 bytes
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// msg type is 1 byte
|
||||
@ -131,15 +136,18 @@ class Message
|
||||
dos.writeByte(type & 0xFF);
|
||||
|
||||
// Send additional info (piece number)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
dos.writeInt(piece);
|
||||
|
||||
// Send additional info (begin/offset)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(begin);
|
||||
|
||||
// Send additional info (length); for PIECE this is implicit.
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(length);
|
||||
|
||||
if (type == EXTENSION)
|
||||
@ -173,21 +181,32 @@ class Message
|
||||
case UNINTERESTED:
|
||||
return "UNINTERESTED";
|
||||
case HAVE:
|
||||
return "HAVE(" + piece + ")";
|
||||
return "HAVE(" + piece + ')';
|
||||
case BITFIELD:
|
||||
return "BITFIELD";
|
||||
case REQUEST:
|
||||
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||
return "REQUEST(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PIECE:
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
return "PIECE(" + piece + ',' + begin + ',' + length + ')';
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
return "CANCEL(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PORT:
|
||||
return "PORT(" + piece + ")";
|
||||
return "PORT(" + piece + ')';
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
// fast extensions below here
|
||||
case SUGGEST:
|
||||
return "SUGGEST(" + piece + ')';
|
||||
case HAVE_ALL:
|
||||
return "HAVE_ALL";
|
||||
case HAVE_NONE:
|
||||
return "HAVE_NONE";
|
||||
case REJECT:
|
||||
return "REJECT(" + piece + ',' + begin + ',' + length + ')';
|
||||
case ALLOWED_FAST:
|
||||
return "ALLOWED_FAST(" + piece + ')';
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
return "UNKNOWN (" + type + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,10 +74,11 @@ public class MetaInfo
|
||||
* @param files null for single-file torrent
|
||||
* @param lengths null for single-file torrent
|
||||
* @param announce_list may be null
|
||||
* @param created_by may be null
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
|
||||
List<List<String>> announce_list)
|
||||
List<List<String>> announce_list, String created_by)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
@ -91,8 +92,8 @@ public class MetaInfo
|
||||
this.privateTorrent = privateTorrent;
|
||||
this.announce_list = announce_list;
|
||||
this.comment = null;
|
||||
this.created_by = null;
|
||||
this.creation_date = 0;
|
||||
this.created_by = created_by;
|
||||
this.creation_date = I2PAppContext.getGlobalContext().clock().now();
|
||||
|
||||
// TODO if we add a parameter for other keys
|
||||
//if (other != null) {
|
||||
@ -444,7 +445,7 @@ public class MetaInfo
|
||||
|
||||
/**
|
||||
* The creation date (ms) or zero.
|
||||
* Not available for locally-created torrents.
|
||||
* As of 0.9.19, available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public long getCreationDate() {
|
||||
@ -595,6 +596,14 @@ public class MetaInfo
|
||||
m.put("announce", announce);
|
||||
if (announce_list != null)
|
||||
m.put("announce-list", announce_list);
|
||||
// misc. optional top-level stuff
|
||||
if (comment != null)
|
||||
m.put("comment", comment);
|
||||
if (created_by != null)
|
||||
m.put("created by", created_by);
|
||||
if (creation_date != 0)
|
||||
m.put("creation date", creation_date / 1000);
|
||||
|
||||
Map<String, BEValue> info = createInfoMap();
|
||||
m.put("info", info);
|
||||
// don't save this locally, we should only do this once
|
||||
|
@ -108,7 +108,8 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Convert this PartialPiece to a request for the next chunk.
|
||||
* Used by PeerState only.
|
||||
* Used by PeerState only. This depends on the downloaded value
|
||||
* as set by setDownloaded() or read().
|
||||
*/
|
||||
|
||||
public Request getRequest() {
|
||||
@ -128,14 +129,16 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes are good - only valid by setDownloaded()
|
||||
* How many bytes are good - as set by setDownloaded() or read()
|
||||
*/
|
||||
public int getDownloaded() {
|
||||
return this.off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this before returning a PartialPiece to the PeerCoordinator
|
||||
* Call this if necessary before returning a PartialPiece to the PeerCoordinator.
|
||||
* We do not use a bitmap to track individual chunks received.
|
||||
* Any chunks after a 'hole' will be lost.
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void setDownloaded(int offset) {
|
||||
@ -191,11 +194,20 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* If offset matches the previous downloaded amount
|
||||
* (as set by a previous call to read() or setDownlaoded()),
|
||||
* the downloaded amount will be incremented by len.
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void read(DataInputStream din, int off, int len) throws IOException {
|
||||
public void read(DataInputStream din, int offset, int len) throws IOException {
|
||||
if (bs != null) {
|
||||
din.readFully(bs, off, len);
|
||||
din.readFully(bs, offset, len);
|
||||
synchronized (this) {
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
} else {
|
||||
// read in fully before synching on raf
|
||||
ByteArray ba;
|
||||
@ -211,8 +223,11 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
createTemp();
|
||||
raf.seek(off);
|
||||
raf.seek(offset);
|
||||
raf.write(tmp);
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
if (ba != null)
|
||||
_cache.release(ba, false);
|
||||
|
@ -57,8 +57,8 @@ public class Peer implements Comparable<Peer>
|
||||
private DataOutputStream dout;
|
||||
|
||||
/** running counters */
|
||||
private long downloaded;
|
||||
private long uploaded;
|
||||
private final AtomicLong downloaded = new AtomicLong();
|
||||
private final AtomicLong uploaded = new AtomicLong();
|
||||
|
||||
// Keeps state for in/out connections. Non-null when the handshake
|
||||
// was successful, the connection setup and runs
|
||||
@ -79,15 +79,15 @@ public class Peer implements Comparable<Peer>
|
||||
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;
|
||||
static final long OPTION_DHT = 0x0000000000000001l;
|
||||
// bytes per bt spec: 0011223344556677
|
||||
private static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
private static final long OPTION_FAST = 0x0000000000000004l;
|
||||
//private static final long OPTION_DHT = 0x0000000000000001l;
|
||||
/** we use a different bit since the compact format is different */
|
||||
/* no, let's use an extension message
|
||||
static final long OPTION_I2P_DHT = 0x0000000040000000l;
|
||||
*/
|
||||
static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
//private static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
private long options;
|
||||
|
||||
/**
|
||||
@ -297,7 +297,7 @@ public class Peer implements Comparable<Peer>
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// Use this thread for running the incoming connection.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
Thread.currentThread().setName("Snark reader from " + peerID);
|
||||
@ -338,6 +338,9 @@ public class Peer implements Comparable<Peer>
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - options
|
||||
long myOptions = OPTION_EXTENSION;
|
||||
// we can't handle HAVE_ALL or HAVE_NONE if we don't know the number of pieces
|
||||
if (metainfo != null)
|
||||
myOptions |= OPTION_FAST;
|
||||
// FIXME get util here somehow
|
||||
//if (util.getDHT() != null)
|
||||
// myOptions |= OPTION_I2P_DHT;
|
||||
@ -385,15 +388,15 @@ public class Peer implements Comparable<Peer>
|
||||
if (options != 0) {
|
||||
// send them something in runConnection() above
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
|
||||
_log.debug("Peer supports options 0x" + Long.toHexString(options) + ": " + toString());
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
public long getOptions() {
|
||||
return options;
|
||||
/** @since 0.9.21 */
|
||||
public boolean supportsFast() {
|
||||
return (options & OPTION_FAST) != 0;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
@ -618,7 +621,7 @@ public class Peer implements Comparable<Peer>
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void downloaded(int size) {
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -626,7 +629,7 @@ public class Peer implements Comparable<Peer>
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void uploaded(int size) {
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,7 +638,7 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -644,7 +647,7 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -652,8 +655,8 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public void resetCounters()
|
||||
{
|
||||
downloaded = 0;
|
||||
uploaded = 0;
|
||||
downloaded.set(0);
|
||||
uploaded.set(0);
|
||||
}
|
||||
|
||||
public long getInactiveTime() {
|
||||
|
@ -75,6 +75,8 @@ class PeerCheckerTask implements Runnable
|
||||
List<Peer> removed = new ArrayList<Peer>();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("peers: " + peerList.size() + " limit: " + uploadLimit + " overBW? " + overBWLimit);
|
||||
DHT dht = _util.getDHT();
|
||||
for (Peer peer : peerList) {
|
||||
|
||||
@ -265,7 +267,23 @@ class PeerCheckerTask implements Runnable
|
||||
|
||||
// close out unused files, but we don't need to do it every time
|
||||
Storage storage = coordinator.getStorage();
|
||||
if (storage != null && (_runCount % 4) == 0) {
|
||||
if (storage != null) {
|
||||
// The more files a torrent has, the more often we call the cleaner,
|
||||
// to keep from running out of FDs
|
||||
int files = storage.getFileCount();
|
||||
int skip;
|
||||
if (files == 1)
|
||||
skip = 6;
|
||||
else if (files <= 4)
|
||||
skip = 4;
|
||||
else if (files <= 20)
|
||||
skip = 3;
|
||||
else if (files <= 50)
|
||||
skip = 2;
|
||||
else
|
||||
skip = 1;
|
||||
|
||||
if ((_runCount % skip) == 0)
|
||||
storage.cleanRAFs();
|
||||
}
|
||||
|
||||
|
@ -98,44 +98,48 @@ class PeerConnectionIn implements Runnable
|
||||
}
|
||||
|
||||
byte b = din.readByte();
|
||||
Message m = new Message();
|
||||
m.type = b;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
case Message.CHOKE:
|
||||
ps.chokeMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received choke from " + peer);
|
||||
break;
|
||||
case 1:
|
||||
|
||||
case Message.UNCHOKE:
|
||||
ps.chokeMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unchoke from " + peer);
|
||||
break;
|
||||
case 2:
|
||||
|
||||
case Message.INTERESTED:
|
||||
ps.interestedMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received interested from " + peer);
|
||||
break;
|
||||
case 3:
|
||||
|
||||
case Message.UNINTERESTED:
|
||||
ps.interestedMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received not interested from " + peer);
|
||||
break;
|
||||
case 4:
|
||||
|
||||
case Message.HAVE:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received havePiece(" + piece + ") from " + peer);
|
||||
break;
|
||||
case 5:
|
||||
|
||||
case Message.BITFIELD:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
|
||||
break;
|
||||
case 6:
|
||||
|
||||
case Message.REQUEST:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
@ -143,7 +147,8 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received request(" + piece + "," + begin + ") from " + peer);
|
||||
break;
|
||||
case 7:
|
||||
|
||||
case Message.PIECE:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
@ -165,7 +170,8 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
|
||||
case Message.CANCEL:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
@ -173,13 +179,15 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
|
||||
break;
|
||||
case 9: // PORT message
|
||||
|
||||
case Message.PORT:
|
||||
int port = din.readUnsignedShort();
|
||||
ps.portMessage(port);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received port message from " + peer);
|
||||
break;
|
||||
case 20: // Extension message
|
||||
|
||||
case Message.EXTENSION:
|
||||
int id = din.readUnsignedByte();
|
||||
byte[] payload = new byte[i-2];
|
||||
din.readFully(payload);
|
||||
@ -187,6 +195,43 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received extension message from " + peer);
|
||||
ps.extensionMessage(id, payload);
|
||||
break;
|
||||
|
||||
// fast extensions below here
|
||||
case Message.SUGGEST:
|
||||
piece = din.readInt();
|
||||
ps.suggestMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received suggest(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_ALL:
|
||||
ps.haveMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_all from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_NONE:
|
||||
ps.haveMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_none from " + peer);
|
||||
break;
|
||||
|
||||
case Message.REJECT:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.rejectMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received reject(" + piece + ',' + begin + ',' + len + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.ALLOWED_FAST:
|
||||
piece = din.readInt();
|
||||
ps.allowedFastMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received allowed_fast(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
|
@ -22,15 +22,15 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
//import net.i2p.util.SimpleScheduler;
|
||||
//import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
@ -43,7 +43,7 @@ class PeerConnectionOut implements Runnable
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private final List<Message> sendQueue = new ArrayList<Message>();
|
||||
private final BlockingQueue<Message> sendQueue = new LinkedBlockingQueue<Message>();
|
||||
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private final long _id;
|
||||
@ -125,6 +125,16 @@ class PeerConnectionOut implements Runnable
|
||||
if (state.choking) {
|
||||
it.remove();
|
||||
//SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
if (peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = nm.piece;
|
||||
r.begin = nm.begin;
|
||||
r.length = nm.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
r.sendMessage(dout);
|
||||
}
|
||||
}
|
||||
nm = null;
|
||||
}
|
||||
@ -142,8 +152,8 @@ class PeerConnectionOut implements Runnable
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && !sendQueue.isEmpty()) {
|
||||
m = sendQueue.remove(0);
|
||||
if (m == null) {
|
||||
m = sendQueue.poll();
|
||||
//SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
}
|
||||
@ -160,6 +170,8 @@ class PeerConnectionOut implements Runnable
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
// FiXME this causes REJECT messages to be sent before sending the CHOKE;
|
||||
// BEP 6 recommends sending them after.
|
||||
if (m.type == Message.CHOKE)
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
@ -234,7 +246,7 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -278,11 +290,22 @@ class PeerConnectionOut implements Runnable
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
if (m.type == type) {
|
||||
it.remove();
|
||||
removed = true;
|
||||
}
|
||||
if (type == Message.PIECE && peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = m.piece;
|
||||
r.begin = m.begin;
|
||||
r.length = m.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
try {
|
||||
r.sendMessage(dout);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
@ -297,7 +320,7 @@ class PeerConnectionOut implements Runnable
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@ -350,12 +373,19 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendBitfield(BitField bitfield)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
boolean fast = peer.supportsFast();
|
||||
if (fast && bitfield.complete()) {
|
||||
sendHaveAll();
|
||||
} else if (fast && bitfield.count() <= 0) {
|
||||
sendHaveNone();
|
||||
} else {
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
@ -480,7 +510,6 @@ class PeerConnectionOut implements Runnable
|
||||
m.len = length;
|
||||
// since we have the data already loaded, queue a timeout to remove it
|
||||
// no longer prefetched
|
||||
//SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@ -511,7 +540,8 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all Request messages from the queue
|
||||
* Remove all Request messages from the queue.
|
||||
* Does not send a cancel message.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void cancelRequestMessages() {
|
||||
@ -523,9 +553,12 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
/**
|
||||
* 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.
|
||||
* Does not send a cancel message.
|
||||
*/
|
||||
void cancelRequest(int piece, int begin, int length)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
@ -561,4 +594,50 @@ class PeerConnectionOut implements Runnable
|
||||
m.piece = port;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendSuggest(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.SUGGEST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveAll() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_ALL;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveNone() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_NONE;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
void sendReject(int piece, int begin, int length) {
|
||||
Message m = new Message();
|
||||
m.type = Message.REJECT;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendAllowedFast(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.ALLOWED_FAST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
@ -25,14 +25,15 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
@ -52,7 +53,7 @@ import org.klomp.snark.dht.DHT;
|
||||
*/
|
||||
class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
|
||||
private final Log _log;
|
||||
|
||||
/**
|
||||
* External use by PeerMonitorTask only.
|
||||
@ -87,8 +88,8 @@ class PeerCoordinator implements PeerListener
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
private final AtomicLong uploaded = new AtomicLong();
|
||||
private final AtomicLong downloaded = new AtomicLong();
|
||||
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
|
||||
private final long uploaded_old[] = {-1,-1,-1};
|
||||
private final long downloaded_old[] = {-1,-1,-1};
|
||||
@ -98,7 +99,7 @@ class PeerCoordinator implements PeerListener
|
||||
* This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking.
|
||||
* External use by PeerMonitorTask only.
|
||||
*/
|
||||
final Queue<Peer> peers;
|
||||
final Deque<Peer> peers;
|
||||
|
||||
/**
|
||||
* Peers we heard about via PEX
|
||||
@ -144,6 +145,7 @@ class PeerCoordinator implements PeerListener
|
||||
{
|
||||
_util = util;
|
||||
_random = util.getContext().random();
|
||||
_log = util.getContext().logManager().getLog(PeerCoordinator.class);
|
||||
this.id = id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
@ -154,7 +156,7 @@ class PeerCoordinator implements PeerListener
|
||||
wantedPieces = new ArrayList<Piece>();
|
||||
setWantedPieces();
|
||||
partialPieces = new ArrayList<PartialPiece>(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue<Peer>();
|
||||
peers = new LinkedBlockingDeque<Peer>();
|
||||
magnetState = new MagnetState(infohash, metainfo);
|
||||
pexPeers = new ConcurrentHashSet<PeerID>();
|
||||
|
||||
@ -278,7 +280,15 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial total of uploaded bytes of all peers (from a saved status)
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public void setUploaded(long up) {
|
||||
uploaded.set(up);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -286,7 +296,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,16 +322,22 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
public long getUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getCurrentUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
// no need to synchronize, only one value
|
||||
long r = uploaded_old[0];
|
||||
if (r <= 0)
|
||||
@ -514,7 +530,10 @@ class PeerCoordinator implements PeerListener
|
||||
// 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);
|
||||
if (_util.getContext().random().nextInt(4) == 0)
|
||||
peers.push(peer);
|
||||
else
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
@ -901,6 +920,7 @@ class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
*/
|
||||
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
|
||||
@ -932,7 +952,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@ -943,7 +963,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@ -964,8 +984,9 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
int piece = pp.getPiece();
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
// try/catch outside the synch to avoid deadlock in the catch
|
||||
try {
|
||||
synchronized(wantedPieces) {
|
||||
Piece p = new Piece(piece);
|
||||
if (!wantedPieces.contains(p))
|
||||
{
|
||||
@ -981,8 +1002,7 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// try/catch moved outside of synch
|
||||
// this takes forever if complete, as it rechecks
|
||||
if (storage.putPiece(pp))
|
||||
{
|
||||
@ -991,26 +1011,38 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
else
|
||||
{
|
||||
// so we will try again
|
||||
markUnrequested(peer, piece);
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
||||
// Mark this peer as not having the piece. PeerState will update its bitfield.
|
||||
for (Piece pc : wantedPieces) {
|
||||
if (pc.getId() == piece) {
|
||||
pc.removePeer(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
|
||||
wantedPieces.remove(p);
|
||||
wantedBytes -= metainfo.getPieceLength(p.getId());
|
||||
} // synch
|
||||
} catch (IOException ioe) {
|
||||
String msg = "Error writing storage (piece " + piece + ") for " + metainfo.getName() + ": " + ioe;
|
||||
_log.error(msg, ioe);
|
||||
if (listener != null) {
|
||||
listener.addMessage(msg);
|
||||
listener.addMessage("Fatal storage error: Stopping torrent " + metainfo.getName());
|
||||
}
|
||||
// deadlock was here
|
||||
snark.stopTorrent();
|
||||
throw new RuntimeException(msg, ioe);
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
wantedBytes -= metainfo.getPieceLength(p.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
@ -1122,8 +1154,9 @@ class PeerCoordinator implements PeerListener
|
||||
*
|
||||
* Also mark the piece unrequested if this peer was the only one.
|
||||
*
|
||||
* @param peer partials, must include the zero-offset (empty) ones too
|
||||
* No dup pieces, piece.setDownloaded() must be set
|
||||
* @param peer partials, must include the zero-offset (empty) ones too.
|
||||
* No dup pieces, piece.setDownloaded() must be set.
|
||||
* len field in Requests is ignored.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public void savePartialPieces(Peer peer, List<Request> partials)
|
||||
@ -1453,8 +1486,8 @@ class PeerCoordinator implements PeerListener
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (listener != null && listener.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
} else if (uploaders < MAX_UPLOADERS)
|
||||
return uploaders + 1;
|
||||
|
@ -21,6 +21,7 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -155,15 +156,28 @@ class PeerState implements DataLoader
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
void bitfieldMessage(byte[] bitmap)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
void bitfieldMessage(byte[] bitmap) {
|
||||
bitfieldMessage(bitmap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmap null to use the isAll param
|
||||
* @param isAll only if bitmap == null: true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (bitmap != null)
|
||||
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
|
||||
else if (isAll)
|
||||
_log.debug(peer + " rcv bitfield HAVE_ALL");
|
||||
else
|
||||
_log.debug(peer + " rcv bitfield HAVE_NONE");
|
||||
}
|
||||
synchronized(this) {
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you except?
|
||||
// XXX - Be liberal in what you accept?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got unexpected bitfield message from " + peer);
|
||||
return;
|
||||
@ -172,10 +186,24 @@ class PeerState implements DataLoader
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
// FIXME will have to regenerate the bitfield after we know exactly
|
||||
// how many pieces there are, as we don't know how many spare bits there are.
|
||||
if (metainfo == null)
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
else
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
if (metainfo == null) {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
} else {
|
||||
// we can't handle this situation
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("have_x w/o metainfo: " + isAll);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
} else {
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
if (isAll)
|
||||
bitfield.setAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (metainfo == null)
|
||||
return;
|
||||
@ -198,14 +226,21 @@ class PeerState implements DataLoader
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (metainfo == null)
|
||||
return;
|
||||
if (choking)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Request received, but choking " + peer);
|
||||
if (choking) {
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, sending reject to choked " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, but choking " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
// There is no check here that we actually have the piece;
|
||||
// this will be caught in loadData() below
|
||||
if (piece < 0
|
||||
|| piece >= metainfo.getPieces()
|
||||
|| begin < 0
|
||||
@ -219,6 +254,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -227,8 +264,14 @@ class PeerState implements DataLoader
|
||||
// Todo: limit number of requests also? (robert 64 x 4KB)
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Rejecting request over pipeline limit from " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -243,7 +286,8 @@ class PeerState implements DataLoader
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls
|
||||
*
|
||||
* @return bytes or null for errors
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public ByteArray loadData(int piece, int begin, int length) {
|
||||
@ -253,6 +297,8 @@ class PeerState implements DataLoader
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -265,6 +311,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -322,6 +370,11 @@ class PeerState implements DataLoader
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
||||
synchronized(this) {
|
||||
// so we don't ask again
|
||||
if (bitfield != null)
|
||||
bitfield.clear(req.getPiece());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,7 +508,12 @@ class PeerState implements DataLoader
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null) {
|
||||
req.getPartialPiece().setDownloaded(req.off);
|
||||
PartialPiece pp = req.getPartialPiece();
|
||||
synchronized(pp) {
|
||||
int dl = pp.getDownloaded();
|
||||
if (req.off != dl)
|
||||
req = new Request(pp, dl, 1);
|
||||
}
|
||||
rv.add(req);
|
||||
}
|
||||
}
|
||||
@ -536,6 +594,89 @@ class PeerState implements DataLoader
|
||||
listener.gotPort(peer, port, port + 1);
|
||||
}
|
||||
|
||||
/////////// fast message handlers /////////
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Treated as "have" for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void suggestMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Handling suggest as have(" + piece + ") from " + peer);
|
||||
haveMessage(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* @param isAll true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void haveMessage(boolean isAll) {
|
||||
bitfieldMessage(null, isAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* If the peer rejects lower chunks but not higher ones, thus creating holes,
|
||||
* we won't figure it out and the piece will fail, since we don't currently
|
||||
* keep a chunk bitmap in PartialPiece.
|
||||
* As long as the peer rejects all the chunks, or rejects only the last chunks,
|
||||
* no holes are created and we will be fine. The reject messages may be in any order,
|
||||
* just don't make a hole when it's over.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void rejectMessage(int piece, int begin, int length) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
out.cancelRequest(piece, begin, length);
|
||||
synchronized(this) {
|
||||
Request deletedRequest = null;
|
||||
// for this piece only
|
||||
boolean haveMoreRequests = false;
|
||||
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
||||
Request req = iter.next();
|
||||
if (req.getPiece() == piece) {
|
||||
if (req.off == begin && req.len == length) {
|
||||
iter.remove();
|
||||
deletedRequest = req;
|
||||
} else {
|
||||
haveMoreRequests = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedRequest != null && !haveMoreRequests) {
|
||||
// We must return the piece to the coordinator
|
||||
// Create a new fake request so we can set the offset correctly
|
||||
PartialPiece pp = deletedRequest.getPartialPiece();
|
||||
int downloaded = pp.getDownloaded();
|
||||
Request req;
|
||||
if (deletedRequest.off == downloaded)
|
||||
req = deletedRequest;
|
||||
else
|
||||
req = new Request(pp, downloaded, 1);
|
||||
List<Request> pcs = Collections.singletonList(req);
|
||||
listener.savePartialPieces(this.peer, pcs);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
}
|
||||
if (lastRequest != null && lastRequest.getPiece() == piece &&
|
||||
lastRequest.off == begin && lastRequest.len == length)
|
||||
lastRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Ignored for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void allowedFastMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -543,6 +684,8 @@ class PeerState implements DataLoader
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
/////////// end message handlers /////////
|
||||
|
||||
/**
|
||||
* We now have this piece.
|
||||
* Tell the peer and cancel any requests for the piece.
|
||||
|
@ -43,13 +43,13 @@ class Request
|
||||
*/
|
||||
Request(PartialPiece piece, int off, int len)
|
||||
{
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
|
||||
this.piece = piece;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,13 +27,13 @@ import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
@ -221,7 +221,7 @@ public class Snark
|
||||
private PeerCoordinator coordinator;
|
||||
private ConnectionAcceptor acceptor;
|
||||
private TrackerClient trackerclient;
|
||||
private String rootDataDir = ".";
|
||||
private final File rootDataDir;
|
||||
private final CompleteListener completeListener;
|
||||
private volatile boolean stopped;
|
||||
private volatile boolean starting;
|
||||
@ -236,15 +236,27 @@ public class Snark
|
||||
private volatile boolean _autoStoppable;
|
||||
// String indicating main activity
|
||||
private volatile String activity = "Not started";
|
||||
private final long savedUploaded;
|
||||
|
||||
|
||||
/** from main() via parseArguments() single torrent */
|
||||
/**
|
||||
* from main() via parseArguments() single torrent
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
|
||||
}
|
||||
****/
|
||||
|
||||
/** single torrent - via router */
|
||||
/**
|
||||
* single torrent - via router
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
public Snark(I2PAppContext ctx, Properties opts, String torrent,
|
||||
StorageListener slistener, boolean start, String rootDir) {
|
||||
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
|
||||
@ -274,12 +286,32 @@ public class Snark
|
||||
if (start)
|
||||
this.startTorrent();
|
||||
}
|
||||
****/
|
||||
|
||||
/** multitorrent */
|
||||
/**
|
||||
* multitorrent
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
|
||||
{
|
||||
this(util, torrent, ip, user_port, slistener, clistener, complistener,
|
||||
peerCoordinatorSet, connectionAcceptor, start, rootDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* multitorrent
|
||||
*
|
||||
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
|
||||
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir, File baseFile)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
@ -291,7 +323,7 @@ public class Snark
|
||||
acceptor = connectionAcceptor;
|
||||
|
||||
this.torrent = torrent;
|
||||
this.rootDataDir = rootDir;
|
||||
this.rootDataDir = new File(rootDir);
|
||||
|
||||
stopped = true;
|
||||
activity = "Network setup";
|
||||
@ -394,13 +426,22 @@ public class Snark
|
||||
try
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(_util, meta, slistener);
|
||||
boolean shouldPreserve = completeListener != null && completeListener.getSavedPreserveNamesSetting(this);
|
||||
if (baseFile == null) {
|
||||
String base = meta.getName();
|
||||
if (!shouldPreserve)
|
||||
base = Storage.filterName(base);
|
||||
if (_util.getFilesPublic())
|
||||
baseFile = new File(rootDataDir, base);
|
||||
else
|
||||
baseFile = new SecureFile(rootDataDir, base);
|
||||
}
|
||||
storage = new Storage(_util, baseFile, meta, slistener, shouldPreserve);
|
||||
if (completeListener != null) {
|
||||
storage.check(rootDataDir,
|
||||
completeListener.getSavedTorrentTime(this),
|
||||
storage.check(completeListener.getSavedTorrentTime(this),
|
||||
completeListener.getSavedTorrentBitField(this));
|
||||
} else {
|
||||
storage.check(rootDataDir);
|
||||
storage.check();
|
||||
}
|
||||
// have to figure out when to reopen
|
||||
// if (!start)
|
||||
@ -428,6 +469,7 @@ public class Snark
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
*/
|
||||
|
||||
savedUploaded = (completeListener != null) ? completeListener.getSavedUploaded(this) : 0;
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
@ -438,6 +480,7 @@ public class Snark
|
||||
* @param torrent a fake name for now (not a file name)
|
||||
* @param ih 20-byte info hash
|
||||
* @param trackerURL may be null
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL,
|
||||
@ -452,7 +495,8 @@ public class Snark
|
||||
this.torrent = torrent;
|
||||
this.infoHash = ih;
|
||||
this.additionalTrackerURL = trackerURL;
|
||||
this.rootDataDir = rootDir;
|
||||
this.rootDataDir = rootDir != null ? new File(rootDir) : null; // null only for FetchAndAdd extension
|
||||
savedUploaded = 0;
|
||||
stopped = true;
|
||||
id = generateID();
|
||||
|
||||
@ -477,24 +521,21 @@ public class Snark
|
||||
|
||||
// Create a new ID and fill it with something random. First nine
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
// eight random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] rv = new byte[20];
|
||||
Random random = I2PAppContext.getGlobalContext().random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
rv[i] = 0;
|
||||
rv[i++] = snark;
|
||||
rv[i++] = snark;
|
||||
rv[i++] = snark;
|
||||
while (i < 20)
|
||||
rv[i++] = (byte)random.nextInt(256);
|
||||
rv[9] = snark;
|
||||
rv[10] = snark;
|
||||
rv[11] = snark;
|
||||
I2PAppContext.getGlobalContext().random().nextBytes(rv, 12, 8);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start up contacting peers and querying the tracker.
|
||||
* Blocks if tunnel is not yet open.
|
||||
*
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public synchronized void startTorrent() {
|
||||
starting = true;
|
||||
@ -521,6 +562,7 @@ public class Snark
|
||||
_log.info("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient");
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
|
||||
coordinator.setUploaded(savedUploaded);
|
||||
if (_peerCoordinatorSet != null) {
|
||||
// multitorrent
|
||||
_peerCoordinatorSet.add(coordinator);
|
||||
@ -547,7 +589,7 @@ public class Snark
|
||||
} else if (trackerclient.halted()) {
|
||||
if (storage != null) {
|
||||
try {
|
||||
storage.reopen(rootDataDir);
|
||||
storage.reopen();
|
||||
} catch (IOException ioe) {
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
@ -575,7 +617,6 @@ public class Snark
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public synchronized void stopTorrent(boolean fast) {
|
||||
stopped = true;
|
||||
TrackerClient tc = trackerclient;
|
||||
if (tc != null)
|
||||
tc.halt(fast);
|
||||
@ -583,17 +624,28 @@ public class Snark
|
||||
if (pc != null)
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (!fast)
|
||||
// HACK: Needed a way to distinguish between user-stop and
|
||||
// shutdown-stop. stopTorrent(true) is in stopAllTorrents().
|
||||
// (#766)
|
||||
stopped = true;
|
||||
if (st != null) {
|
||||
boolean changed = storage.isChanged();
|
||||
// TODO: Cache the config-in-mem to compare vs config-on-disk
|
||||
// (needed for auto-save to not double-save in some cases)
|
||||
//boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
|
||||
boolean changed = true;
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
}
|
||||
if (fast)
|
||||
// HACK: See above if(!fast)
|
||||
stopped = true;
|
||||
if (pc != null && _peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.remove(pc);
|
||||
if (_peerCoordinatorSet == null)
|
||||
@ -693,6 +745,18 @@ public class Snark
|
||||
return storage != null && storage.isChecking();
|
||||
}
|
||||
|
||||
/**
|
||||
* If checking is in progress, return completion 0.0 ... 1.0,
|
||||
* else return 1.0.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public double getCheckingProgress() {
|
||||
if (storage != null && storage.isChecking())
|
||||
return storage.getCheckingProgress();
|
||||
else
|
||||
return 1.0d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disk allocation (ballooning) in progress.
|
||||
* @since 0.9.3
|
||||
@ -738,7 +802,7 @@ public class Snark
|
||||
PeerCoordinator coord = coordinator;
|
||||
if (coord != null)
|
||||
return coord.getUploaded();
|
||||
return 0;
|
||||
return savedUploaded;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -762,6 +826,7 @@ public class Snark
|
||||
}
|
||||
|
||||
/**
|
||||
* Not HTML escaped.
|
||||
* @return String returned from tracker, or null if no error
|
||||
* @since 0.8.4
|
||||
*/
|
||||
@ -834,7 +899,7 @@ public class Snark
|
||||
}
|
||||
|
||||
/**
|
||||
* Bytes still wanted. DOES account for skipped files.
|
||||
* Bytes still wanted. DOES account for (i.e. does not include) skipped files.
|
||||
* FIXME -1 when not running.
|
||||
* @return exact value. or -1 if no storage yet or when not running.
|
||||
* @since 0.9.1
|
||||
@ -847,7 +912,7 @@ public class Snark
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not account for skipped files.
|
||||
* Does not account (i.e. includes) for skipped files.
|
||||
* @return number of pieces still needed (magnet mode or not), or -1 if unknown
|
||||
* @since 0.8.4
|
||||
*/
|
||||
@ -918,6 +983,7 @@ public class Snark
|
||||
* non-valid argument list. The given listeners will be
|
||||
* passed to all components that take one.
|
||||
*/
|
||||
/****
|
||||
private static Snark parseArguments(String[] args,
|
||||
StorageListener slistener,
|
||||
CoordinatorListener clistener)
|
||||
@ -932,6 +998,7 @@ public class Snark
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
****/
|
||||
/*
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
@ -953,7 +1020,9 @@ public class Snark
|
||||
catch (NumberFormatException nfe) { }
|
||||
}
|
||||
}
|
||||
else */ if (args[i].equals("--port"))
|
||||
else */
|
||||
/****
|
||||
if (args[i].equals("--port"))
|
||||
{
|
||||
if (args.length - 1 < i + 1)
|
||||
usage("--port needs port number to listen on");
|
||||
@ -1059,6 +1128,7 @@ public class Snark
|
||||
System.out.println
|
||||
(" \tor (with --share) a file to share.");
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
@ -1102,9 +1172,15 @@ public class Snark
|
||||
*/
|
||||
public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
|
||||
try {
|
||||
String base = Storage.filterName(metainfo.getName());
|
||||
File baseFile;
|
||||
if (_util.getFilesPublic())
|
||||
baseFile = new File(rootDataDir, base);
|
||||
else
|
||||
baseFile = new SecureFile(rootDataDir, base);
|
||||
// The following two may throw IOE...
|
||||
storage = new Storage(_util, metainfo, this);
|
||||
storage.check(rootDataDir);
|
||||
storage = new Storage(_util, baseFile, metainfo, this, false);
|
||||
storage.check();
|
||||
// ... so don't set meta until here
|
||||
meta = metainfo;
|
||||
if (completeListener != null) {
|
||||
@ -1200,7 +1276,8 @@ public class Snark
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
if (coordinator != null)
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
///////////// End StorageListener methods
|
||||
@ -1209,7 +1286,7 @@ public class Snark
|
||||
/** SnarkSnutdown callback unused */
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
// Should not be necessary since all non-daemon threads should
|
||||
// have died. But in reality this does not always happen.
|
||||
//System.exit(0);
|
||||
}
|
||||
@ -1239,7 +1316,8 @@ public class Snark
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = _util.getMaxUploaders();
|
||||
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Total uploaders: " + totalUploaders + " Limit: " + limit);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -32,10 +33,14 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import gnu.getopt.Getopt;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.data.ByteArray;
|
||||
@ -48,10 +53,11 @@ import net.i2p.util.SystemVersion;
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
public class Storage
|
||||
public class Storage implements Closeable
|
||||
{
|
||||
private final MetaInfo metainfo;
|
||||
private final List<TorrentFile> _torrentFiles;
|
||||
private final File _base;
|
||||
private final StorageListener listener;
|
||||
private final I2PSnarkUtil _util;
|
||||
private final Log _log;
|
||||
@ -63,35 +69,42 @@ public class Storage
|
||||
private final int piece_size;
|
||||
private final int pieces;
|
||||
private final long total_length;
|
||||
private final boolean _preserveFileNames;
|
||||
private boolean changed;
|
||||
private volatile boolean _isChecking;
|
||||
private final AtomicInteger _allocateCount = new AtomicInteger();
|
||||
private final AtomicInteger _checkProgress = new AtomicInteger();
|
||||
|
||||
/** The default piece size. */
|
||||
private static final int DEFAULT_PIECE_SIZE = 256*1024;
|
||||
/** bigger than this will be rejected */
|
||||
public static final int MAX_PIECE_SIZE = 4*1024*1024;
|
||||
public static final int MAX_PIECE_SIZE = 16*1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
public static final int MAX_PIECES = 10*1024;
|
||||
public static final int MAX_PIECES = 20*1024;
|
||||
public static final long MAX_TOTAL_SIZE = MAX_PIECE_SIZE * (long) MAX_PIECES;
|
||||
|
||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap<String, String>();
|
||||
|
||||
private static final boolean _isWindows = SystemVersion.isWindows();
|
||||
private static final boolean _isARM = SystemVersion.isARM();
|
||||
|
||||
private static final int BUFSIZE = PeerState.PARTSIZE;
|
||||
private static final ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
||||
|
||||
/**
|
||||
* Creates a new storage based on the supplied MetaInfo. This will
|
||||
* Creates a new storage based on the supplied MetaInfo.
|
||||
*
|
||||
* Does not check storage. Caller MUST call check(), which will
|
||||
* try to create and/or check all needed files in the MetaInfo.
|
||||
*
|
||||
* Does not check storage. Caller MUST call check()
|
||||
* @param baseFile the torrent data file or dir
|
||||
* @param preserveFileNames if true, do not remap names to a 'safe' charset
|
||||
*/
|
||||
public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
|
||||
public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener, boolean preserveFileNames)
|
||||
{
|
||||
_util = util;
|
||||
_log = util.getContext().logManager().getLog(Storage.class);
|
||||
_base = baseFile;
|
||||
this.metainfo = metainfo;
|
||||
this.listener = listener;
|
||||
needed = metainfo.getPieces();
|
||||
@ -102,6 +115,7 @@ public class Storage
|
||||
List<List<String>> files = metainfo.getFiles();
|
||||
int sz = files != null ? files.size() : 1;
|
||||
_torrentFiles = new ArrayList<TorrentFile>(sz);
|
||||
_preserveFileNames = preserveFileNames;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,16 +127,20 @@ public class Storage
|
||||
*
|
||||
* @param announce may be null
|
||||
* @param listener may be null
|
||||
* @param created_by may be null
|
||||
* @throws IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(I2PSnarkUtil util, File baseFile, String announce,
|
||||
List<List<String>> announce_list,
|
||||
String created_by,
|
||||
boolean privateTorrent, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
_util = util;
|
||||
_base = baseFile;
|
||||
_log = util.getContext().logManager().getLog(Storage.class);
|
||||
this.listener = listener;
|
||||
_preserveFileNames = true;
|
||||
// Create names, rafs and lengths arrays.
|
||||
_torrentFiles = getFiles(baseFile);
|
||||
|
||||
@ -183,7 +201,7 @@ public class Storage
|
||||
byte[] piece_hashes = fast_digestCreate();
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
|
||||
lengthsList, piece_size, piece_hashes, total, privateTorrent,
|
||||
announce_list);
|
||||
announce_list, created_by);
|
||||
|
||||
}
|
||||
|
||||
@ -295,6 +313,18 @@ public class Storage
|
||||
return _isChecking;
|
||||
}
|
||||
|
||||
/**
|
||||
* If checking is in progress, return completion 0.0 ... 1.0,
|
||||
* else return 1.0.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public double getCheckingProgress() {
|
||||
if (_isChecking)
|
||||
return _checkProgress.get() / (double) pieces;
|
||||
else
|
||||
return 1.0d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disk allocation (ballooning) in progress.
|
||||
* Always false on Windows.
|
||||
@ -305,39 +335,48 @@ public class Storage
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* Get index to pass to remaining(), getPriority(), setPriority()
|
||||
*
|
||||
* @param file non-canonical path (non-directory)
|
||||
* @return internal index of file; -1 if unknown file
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public int indexOf(File file) {
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
File f = _torrentFiles.get(i).RAFfile;
|
||||
if (f.equals(file))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileIndex as obtained from indexOf
|
||||
* @return number of bytes remaining; -1 if unknown file
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public long remaining(String file) {
|
||||
/****
|
||||
public long remaining(int fileIndex) {
|
||||
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
|
||||
return -1;
|
||||
if (complete())
|
||||
return 0;
|
||||
long bytes = 0;
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
String canonical = null;
|
||||
if (f != null) {
|
||||
try {
|
||||
canonical = f.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
f = null;
|
||||
}
|
||||
}
|
||||
if (f != null && canonical.equals(file)) {
|
||||
if (complete())
|
||||
return 0;
|
||||
int psz = piece_size;
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
if (i == fileIndex) {
|
||||
long start = bytes;
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / psz);
|
||||
int pc = (int) (bytes / piece_size);
|
||||
long rv = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rv = Math.min(psz - (start % psz), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
|
||||
rv = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*psz < end)
|
||||
rv += psz;
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
rv += piece_size;
|
||||
else
|
||||
rv += end - (((long)j) * psz);
|
||||
rv += end - (((long)j) * piece_size);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
@ -346,51 +385,66 @@ public class Storage
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* For efficiency, calculate remaining bytes for all files at once
|
||||
*
|
||||
* @return number of bytes remaining for each file, use indexOf() to get index for a file
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public long[] remaining() {
|
||||
long[] rv = new long[_torrentFiles.size()];
|
||||
if (complete())
|
||||
return rv;
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
long start = bytes;
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / piece_size);
|
||||
long rvi = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rvi = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
rvi += piece_size;
|
||||
else
|
||||
rvi += end - (((long)j) * piece_size);
|
||||
}
|
||||
}
|
||||
rv[i] = rvi;
|
||||
bytes += tf.length;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileIndex as obtained from indexOf
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public int getPriority(String file) {
|
||||
public int getPriority(int fileIndex) {
|
||||
if (complete() || metainfo.getFiles() == null)
|
||||
return 0;
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file))
|
||||
return tf.priority;
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
|
||||
return 0;
|
||||
return _torrentFiles.get(fileIndex).priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must call Snark.updatePiecePriorities()
|
||||
* (which calls getPiecePriorities()) after calling this.
|
||||
* @param file canonical path (non-directory)
|
||||
* @param fileIndex as obtained from indexOf
|
||||
* @param pri default 0; <0 to disable
|
||||
* @since 0.8.1
|
||||
*/
|
||||
public void setPriority(String file, int pri) {
|
||||
public void setPriority(int fileIndex, int pri) {
|
||||
if (complete() || metainfo.getFiles() == null)
|
||||
return;
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
// use canonical in case snark dir or sub dirs are symlinked
|
||||
if (f != null) {
|
||||
try {
|
||||
String canonical = f.getCanonicalPath();
|
||||
if (canonical.equals(file)) {
|
||||
tf.priority = pri;
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
|
||||
return;
|
||||
_torrentFiles.get(fileIndex).priority = pri;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -448,9 +502,8 @@ public class Storage
|
||||
int file = 0;
|
||||
long pcEnd = -1;
|
||||
long fileEnd = _torrentFiles.get(0).length - 1;
|
||||
int psz = piece_size;
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
pcEnd += psz;
|
||||
pcEnd += piece_size;
|
||||
int pri = _torrentFiles.get(file).priority;
|
||||
while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) {
|
||||
file++;
|
||||
@ -483,31 +536,37 @@ public class Storage
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public String getBaseName() {
|
||||
return filterName(metainfo.getName());
|
||||
return optFilterName(metainfo.getName());
|
||||
}
|
||||
|
||||
/** @since 0.9.15 */
|
||||
public boolean getPreserveFileNames() {
|
||||
return _preserveFileNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
* Use recheck() to check again later.
|
||||
*
|
||||
* @throws IllegalStateException if called more than once
|
||||
*/
|
||||
public void check(String rootDir) throws IOException
|
||||
public void check() throws IOException
|
||||
{
|
||||
check(rootDir, 0, null);
|
||||
check(0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Use a saved bitfield and timestamp from a config file.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
* Use recheck() to check again later.
|
||||
*
|
||||
* @throws IllegalStateException if called more than once
|
||||
*/
|
||||
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
|
||||
public void check(long savedTime, BitField savedBitField) throws IOException
|
||||
{
|
||||
File base;
|
||||
boolean areFilesPublic = _util.getFilesPublic();
|
||||
if (areFilesPublic)
|
||||
base = new File(rootDir, filterName(metainfo.getName()));
|
||||
else
|
||||
base = new SecureFile(rootDir, filterName(metainfo.getName()));
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
if (!_torrentFiles.isEmpty())
|
||||
@ -517,18 +576,18 @@ public class Storage
|
||||
{
|
||||
// Create base as file.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating/Checking file: " + base);
|
||||
_log.info("Creating/Checking file: " + _base);
|
||||
// createNewFile() can throw a "Permission denied" IOE even if the file exists???
|
||||
// so do it second
|
||||
if (!base.exists() && !base.createNewFile())
|
||||
throw new IOException("Could not create file " + base);
|
||||
if (!_base.exists() && !_base.createNewFile())
|
||||
throw new IOException("Could not create file " + _base);
|
||||
|
||||
_torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength()));
|
||||
_torrentFiles.add(new TorrentFile(_base, _base, metainfo.getTotalLength()));
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
long lm = _base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
else if (base.length() != metainfo.getTotalLength())
|
||||
else if (_base.length() != metainfo.getTotalLength())
|
||||
useSavedBitField = false;
|
||||
}
|
||||
}
|
||||
@ -536,9 +595,9 @@ public class Storage
|
||||
{
|
||||
// Create base as dir.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Creating/Checking directory: " + base);
|
||||
if (!base.mkdir() && !base.isDirectory())
|
||||
throw new IOException("Could not create directory " + base);
|
||||
_log.info("Creating/Checking directory: " + _base);
|
||||
if (!_base.mkdir() && !_base.isDirectory())
|
||||
throw new IOException("Could not create directory " + _base);
|
||||
|
||||
List<Long> ls = metainfo.getLengths();
|
||||
int size = files.size();
|
||||
@ -546,7 +605,7 @@ public class Storage
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
List<String> path = files.get(i);
|
||||
File f = createFileFromNames(base, path, areFilesPublic);
|
||||
File f = createFileFromNames(_base, path, areFilesPublic);
|
||||
// dup file name check after filtering
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (f.equals(_torrentFiles.get(j).RAFfile)) {
|
||||
@ -562,12 +621,12 @@ public class Storage
|
||||
else
|
||||
lastPath = '_' + lastPath;
|
||||
path.set(last, lastPath);
|
||||
f = createFileFromNames(base, path, areFilesPublic);
|
||||
f = createFileFromNames(_base, path, areFilesPublic);
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
long len = ls.get(i).longValue();
|
||||
_torrentFiles.add(new TorrentFile(base, f, len));
|
||||
_torrentFiles.add(new TorrentFile(_base, f, len));
|
||||
total += len;
|
||||
if (useSavedBitField) {
|
||||
long lm = f.lastModified();
|
||||
@ -611,10 +670,9 @@ public class Storage
|
||||
* Doesn't really reopen the file descriptors for a restart.
|
||||
* Just does an existence check but no length check or data reverification
|
||||
*
|
||||
* @param rootDir ignored
|
||||
* @throws IOE on fail
|
||||
*/
|
||||
public void reopen(String rootDir) throws IOException
|
||||
public void reopen() throws IOException
|
||||
{
|
||||
if (_torrentFiles.isEmpty())
|
||||
throw new IOException("Storage not checked yet");
|
||||
@ -637,6 +695,19 @@ public class Storage
|
||||
0x2028, 0x2029
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter the name, but only if configured to do so.
|
||||
* We will do so on torrents received from others, but not
|
||||
* on those we created ourselves, so we do not lose track of files.
|
||||
*
|
||||
* @since 0.9.15
|
||||
*/
|
||||
private String optFilterName(String name) {
|
||||
if (_preserveFileNames)
|
||||
return name;
|
||||
return filterName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the given file name.
|
||||
* http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
|
||||
@ -690,14 +761,16 @@ public class Storage
|
||||
* Note that filtering each path element individually may lead to
|
||||
* things going in the wrong place if there are duplicates
|
||||
* in intermediate path elements after filtering.
|
||||
*
|
||||
* @param names path elements
|
||||
*/
|
||||
private static File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
|
||||
private File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
|
||||
{
|
||||
File f = null;
|
||||
Iterator<String> it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName(it.next());
|
||||
String name = optFilterName(it.next());
|
||||
if (it.hasNext())
|
||||
{
|
||||
// Another dir in the hierarchy.
|
||||
@ -725,15 +798,73 @@ public class Storage
|
||||
return f;
|
||||
}
|
||||
|
||||
public static File getFileFromNames(File base, List<String> names)
|
||||
{
|
||||
Iterator<String> it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName(it.next());
|
||||
base = new File(base, name);
|
||||
/**
|
||||
* The base file or directory.
|
||||
* @return the File
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public File getBase() {
|
||||
return _base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not include directories. Unsorted.
|
||||
* @return a new List
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public List<File> getFiles() {
|
||||
List<File> rv = new ArrayList<File>(_torrentFiles.size());
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
rv.add(tf.RAFfile);
|
||||
}
|
||||
return base;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not include directories.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public int getFileCount() {
|
||||
return _torrentFiles.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes the base for a multi-file torrent.
|
||||
* Sorted bottom-up for easy deletion.
|
||||
* Slow. Use for deletion only.
|
||||
* @return a new Set or null for a single-file torrent
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public SortedSet<File> getDirectories() {
|
||||
if (!_base.isDirectory())
|
||||
return null;
|
||||
SortedSet<File> rv = new TreeSet<File>(Collections.reverseOrder());
|
||||
rv.add(_base);
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
File f = tf.RAFfile;
|
||||
do {
|
||||
f = f.getParentFile();
|
||||
} while (f != null && rv.add(f));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking. Holds lock.
|
||||
* Recommend running only when stopped.
|
||||
* Caller should thread.
|
||||
* Calls listener.setWantedPieces() on completion if anything changed.
|
||||
*
|
||||
* @return true if anything changed, false otherwise
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public boolean recheck() throws IOException {
|
||||
int previousNeeded = needed;
|
||||
checkCreateFiles(true);
|
||||
boolean changed = previousNeeded != needed;
|
||||
if (listener != null && changed)
|
||||
listener.setWantedPieces(this);
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -760,6 +891,7 @@ public class Storage
|
||||
|
||||
private void locked_checkCreateFiles(boolean recheck) throws IOException
|
||||
{
|
||||
_checkProgress.set(0);
|
||||
// Whether we are resuming or not,
|
||||
// if any of the files already exists we assume we are resuming.
|
||||
boolean resume = false;
|
||||
@ -776,13 +908,16 @@ public class Storage
|
||||
|
||||
// Make sure all files are available and of correct length
|
||||
// The files should all exist as they have been created with zero length by createFilesFromNames()
|
||||
long lengthProgress = 0;
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
long length = tf.RAFfile.length();
|
||||
lengthProgress += tf.length;
|
||||
if(tf.RAFfile.exists() && length == tf.length)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
_checkProgress.set(0);
|
||||
resume = true; // XXX Could dynamicly check
|
||||
}
|
||||
else if (length == 0) {
|
||||
@ -794,6 +929,8 @@ public class Storage
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (!resume)
|
||||
_checkProgress.set((int) (pieces * lengthProgress / total_length));
|
||||
} else {
|
||||
String msg = "File '" + tf.name + "' exists, but has wrong length (expected " +
|
||||
tf.length + " but found " + length + ") - repairing corruption";
|
||||
@ -802,6 +939,7 @@ public class Storage
|
||||
_log.error(msg);
|
||||
changed = true;
|
||||
resume = true;
|
||||
_checkProgress.set(0);
|
||||
_probablyComplete = false; // to force RW
|
||||
synchronized(tf) {
|
||||
RandomAccessFile raf = tf.checkRAF();
|
||||
@ -822,17 +960,16 @@ public class Storage
|
||||
long pieceEnd = 0;
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
_checkProgress.set(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) {
|
||||
TorrentFile tf = _torrentFiles.get(file);
|
||||
synchronized(tf) {
|
||||
try {
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
try {
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
if (++file >= _torrentFiles.size())
|
||||
break;
|
||||
fileEnd += _torrentFiles.get(file).length;
|
||||
@ -848,6 +985,7 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
_checkProgress.set(pieces);
|
||||
_probablyComplete = complete();
|
||||
// close all the files so we don't end up with a zillion open ones;
|
||||
// we will reopen as needed
|
||||
@ -904,9 +1042,7 @@ public class Storage
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
try {
|
||||
synchronized(tf) {
|
||||
tf.closeRAF();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing " + tf, ioe);
|
||||
// gobble gobble
|
||||
@ -1131,17 +1267,15 @@ public class Storage
|
||||
return length;
|
||||
}
|
||||
|
||||
private static final long RAFCloseDelay = 4*60*1000;
|
||||
private static final long RAF_CLOSE_DELAY = 4*60*1000;
|
||||
|
||||
/**
|
||||
* Close unused RAFs - call periodically
|
||||
*/
|
||||
public void cleanRAFs() {
|
||||
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
|
||||
long cutoff = System.currentTimeMillis() - RAF_CLOSE_DELAY;
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
synchronized(tf) {
|
||||
tf.closeRAF(cutoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1267,7 +1401,9 @@ public class Storage
|
||||
// Windows will zero-fill up to the point of the write, which
|
||||
// will make the file fairly unfragmented, on average, at least until
|
||||
// near the end where it will get exponentially more fragmented.
|
||||
if (!_isWindows)
|
||||
// Also don't ballon on ARM, as a proxy for solid state disk, where fragmentation doesn't matter too much.
|
||||
// Actual detection of SSD is almost impossible.
|
||||
if (!_isWindows && !_isARM)
|
||||
isSparse = true;
|
||||
}
|
||||
|
||||
@ -1324,18 +1460,44 @@ public class Storage
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1 || args.length > 2) {
|
||||
System.err.println("Usage: Storage file-or-dir [announceURL]");
|
||||
boolean error = false;
|
||||
String created_by = null;
|
||||
String announce = null;
|
||||
Getopt g = new Getopt("Storage", args, "a:c:");
|
||||
try {
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
announce = g.getOptarg();
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
created_by = g.getOptarg();
|
||||
break;
|
||||
|
||||
case '?':
|
||||
case ':':
|
||||
default:
|
||||
error = true;
|
||||
break;
|
||||
} // switch
|
||||
} // while
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
error = true;
|
||||
}
|
||||
if (error || args.length - g.getOptind() != 1) {
|
||||
System.err.println("Usage: Storage [-a announceURL] [-c created-by] file-or-dir");
|
||||
System.exit(1);
|
||||
}
|
||||
File base = new File(args[0]);
|
||||
String announce = args.length == 2 ? args[1] : null;
|
||||
File base = new File(args[g.getOptind()]);
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
I2PSnarkUtil util = new I2PSnarkUtil(ctx);
|
||||
File file = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
Storage storage = new Storage(util, base, announce, null, false, null);
|
||||
Storage storage = new Storage(util, base, announce, null, created_by, false, null);
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
file = new File(storage.getBaseName() + ".torrent");
|
||||
out = new FileOutputStream(file);
|
||||
|
@ -36,7 +36,9 @@ import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@ -72,8 +74,8 @@ public class TrackerClient implements Runnable {
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
|
||||
private static final String NOT_REGISTERED_2 = "torrent not found"; // diftracker
|
||||
/** this is our equivalent to router.utorrent.com for bootstrap */
|
||||
private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
|
||||
private static final String NOT_REGISTERED_3 = "torrent unauthorised"; // vuze
|
||||
private static final String ERROR_GOT_HTML = "received html"; // fake return
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
private final static int DELAY_MIN = 2000; // 2 secs.
|
||||
@ -88,6 +90,8 @@ public class TrackerClient implements Runnable {
|
||||
private static final int DHT_ANNOUNCE_PEERS = 4;
|
||||
public static final int PORT = 6881;
|
||||
private static final int MAX_TRACKERS = 12;
|
||||
// tracker.welterde.i2p
|
||||
private static final Hash DSA_ONLY_TRACKER = ConvertToHash.getHash("cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p");
|
||||
|
||||
private final I2PSnarkUtil _util;
|
||||
private final MetaInfo meta;
|
||||
@ -156,6 +160,7 @@ public class TrackerClient implements Runnable {
|
||||
consecutiveFails = 0;
|
||||
runStarted = false;
|
||||
_fastUnannounce = false;
|
||||
snark.setTrackerProblems(null);
|
||||
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
|
||||
_thread.start();
|
||||
started = true;
|
||||
@ -342,7 +347,9 @@ public class TrackerClient implements Runnable {
|
||||
_log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
if (backupTrackers.isEmpty()) {
|
||||
backupTrackers.add(new TCTracker(DEFAULT_BACKUP_TRACKER, false));
|
||||
backupTrackers.add(new TCTracker(SnarkManager.DEFAULT_BACKUP_TRACKER, false));
|
||||
} else if (trackers.size() > 1) {
|
||||
Collections.shuffle(backupTrackers, _util.getContext().random());
|
||||
}
|
||||
}
|
||||
this.completed = coordinator.getLeft() == 0;
|
||||
@ -360,12 +367,21 @@ public class TrackerClient implements Runnable {
|
||||
if (h == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad announce URL: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
// comment this out if tracker.welterde.i2p upgrades
|
||||
if (h.equals(DSA_ONLY_TRACKER)) {
|
||||
Destination dest = _util.getMyDestination();
|
||||
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Skipping incompatible tracker: " + ann);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (existing.size() >= MAX_TRACKERS) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Not using announce URL, we have enough: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
boolean rv = existing.add(h);
|
||||
if (!rv) {
|
||||
@ -526,9 +542,9 @@ public class TrackerClient implements Runnable {
|
||||
!snark.isChecking() &&
|
||||
info.getSeedCount() > 100 &&
|
||||
coordinator.getPeerCount() <= 0 &&
|
||||
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
|
||||
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
|
||||
snark.getTotalLength() > 0 &&
|
||||
uploaded >= snark.getTotalLength() * 3 / 2) {
|
||||
uploaded >= snark.getTotalLength() / 2) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Auto stopping " + snark.getBaseName());
|
||||
snark.setAutoStoppable(false);
|
||||
@ -577,14 +593,20 @@ public class TrackerClient implements Runnable {
|
||||
if (tr.isPrimary)
|
||||
snark.setTrackerProblems(tr.trackerProblems);
|
||||
String tplc = tr.trackerProblems.toLowerCase(Locale.US);
|
||||
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2)) {
|
||||
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2) ||
|
||||
tplc.startsWith(NOT_REGISTERED_3) || tplc.startsWith(ERROR_GOT_HTML)) {
|
||||
// Give a guy some time to register it if using opentrackers too
|
||||
//if (trckrs.size() == 1) {
|
||||
// stop = true;
|
||||
// snark.stopTorrent();
|
||||
//} else { // hopefully each on the opentrackers list is really open
|
||||
if (tr.registerFails++ > MAX_REGISTER_FAILS ||
|
||||
!completed || // no use retrying if we aren't seeding
|
||||
tplc.startsWith(ERROR_GOT_HTML) || // fake msg from doRequest()
|
||||
(!tr.isPrimary && tr.registerFails > MAX_REGISTER_FAILS / 2))
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Not longer announcing to " + tr.announce + " : " +
|
||||
tr.trackerProblems + " after " + tr.registerFails + " failures");
|
||||
tr.stop = true;
|
||||
//
|
||||
}
|
||||
@ -797,10 +819,15 @@ public class TrackerClient implements Runnable {
|
||||
tr.lastRequestTime = System.currentTimeMillis();
|
||||
// Don't wait for a response to stopped when shutting down
|
||||
boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
|
||||
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 32*1024);
|
||||
if (fetched == null)
|
||||
throw new IOException("Error fetching");
|
||||
if (fetched.length == 0)
|
||||
throw new IOException("No data");
|
||||
// The HTML check only works if we didn't exceed the maxium fetch size specified in get(),
|
||||
// otherwise we already threw an IOE.
|
||||
if (fetched[0] == '<')
|
||||
throw new IOException(ERROR_GOT_HTML);
|
||||
|
||||
InputStream in = new ByteArrayInputStream(fetched);
|
||||
|
||||
@ -885,7 +912,7 @@ public class TrackerClient implements Runnable {
|
||||
if (path == null || path.length() < 517 ||
|
||||
!path.startsWith("/"))
|
||||
return null;
|
||||
String[] parts = path.substring(1).split("/?&;", 2);
|
||||
String[] parts = path.substring(1).split("[/\\?&;]", 2);
|
||||
return ConvertToHash.getHash(parts[0]);
|
||||
}
|
||||
return null;
|
||||
|
@ -196,6 +196,9 @@ class TrackerInfo
|
||||
return complete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not HTML escaped.
|
||||
*/
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
|
@ -6,6 +6,7 @@ import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.update.*;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
@ -109,7 +110,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
_umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
|
||||
continue;
|
||||
}
|
||||
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
|
||||
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, null, this);
|
||||
if (_snark != null) {
|
||||
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>");
|
||||
new Timeout();
|
||||
@ -290,12 +291,21 @@ class UpdateRunner implements UpdateTask, CompleteListener {
|
||||
return _smgr.getSavedTorrentBitField(snark);
|
||||
}
|
||||
|
||||
public boolean getSavedPreserveNamesSetting(Snark snark) {
|
||||
return _smgr.getSavedPreserveNamesSetting(snark);
|
||||
}
|
||||
|
||||
public long getSavedUploaded(Snark snark) {
|
||||
return _smgr.getSavedUploaded(snark);
|
||||
}
|
||||
|
||||
//////// end CompleteListener methods
|
||||
|
||||
private static String linkify(String url) {
|
||||
String durl = url.length() <= 28 ? url :
|
||||
url.substring(0, 25) + "…";
|
||||
return "<a target=\"_blank\" href=\"" + url + "\"/>" + durl + "</a>";
|
||||
String durl = url.length() <= 28 ? DataHelper.escapeHTML(url) :
|
||||
DataHelper.escapeHTML(url.substring(0, 25)) + "…";
|
||||
// TODO urlEncode instead
|
||||
return "<a target=\"_blank\" href=\"" + DataHelper.escapeHTML(url) + "\"/>" + durl + "</a>";
|
||||
}
|
||||
|
||||
private void updateStatus(String s) {
|
||||
|
@ -25,6 +25,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
@ -208,7 +209,7 @@ public class BEValue
|
||||
} else if (bin) {
|
||||
buf.append(bs.length).append(" bytes: ").append(Base64.encode(bs));
|
||||
} else {
|
||||
buf.append('"').append(new String(bs)).append('"');
|
||||
buf.append('"').append(DataHelper.getUTF8(bs)).append('"');
|
||||
}
|
||||
valueString = buf.toString();
|
||||
} else
|
||||
|
@ -106,6 +106,7 @@ class DHTTracker {
|
||||
* @param noSeeds true if we do not want seeds in the result
|
||||
* @return list or empty list (never null)
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
List<Hash> getPeers(InfoHash ih, int max, boolean noSeeds) {
|
||||
Peers peers = _torrents.get(ih);
|
||||
if (peers == null || max <= 0)
|
||||
|
@ -40,6 +40,7 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.SnarkManager;
|
||||
import org.klomp.snark.TrackerClient;
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
import org.klomp.snark.bencode.BEncoder;
|
||||
@ -152,7 +153,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private static final long CLEAN_TIME = 63*1000;
|
||||
private static final long EXPLORE_TIME = 877*1000;
|
||||
private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
|
||||
private static final String DHT_FILE_SUFFIX = ".dht.dat";
|
||||
public static final String DHT_FILE_SUFFIX = ".dht.dat";
|
||||
|
||||
private static final int SEND_CRYPTO_TAGS = 8;
|
||||
private static final int LOW_CRYPTO_TAGS = 4;
|
||||
@ -185,8 +186,14 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
_myNID = new NID(_myID);
|
||||
}
|
||||
_myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
|
||||
_dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
|
||||
_backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
|
||||
File conf = new File(ctx.getConfigDir(), baseName + ".config" + SnarkManager.CONFIG_DIR_SUFFIX);
|
||||
_dhtFile = new File(conf, "i2psnark" + DHT_FILE_SUFFIX);
|
||||
if (baseName.equals("i2psnark")) {
|
||||
_backupDhtFile = null;
|
||||
} else {
|
||||
File bconf = new File(ctx.getConfigDir(), "i2psnark.config" + SnarkManager.CONFIG_DIR_SUFFIX);
|
||||
_backupDhtFile = new File(bconf, "i2psnark" + DHT_FILE_SUFFIX);
|
||||
}
|
||||
_knownNodes = new DHTNodes(ctx, _myNID);
|
||||
|
||||
start();
|
||||
@ -236,6 +243,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param maxWait how long to wait for each to reply (not total) must be > 0
|
||||
* @param parallel how many outstanding at once (unimplemented, always 1)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void explore(NID target, int maxNodes, long maxWait, int parallel) {
|
||||
List<NodeInfo> nodes = _knownNodes.findClosest(target, maxNodes);
|
||||
if (nodes.isEmpty()) {
|
||||
@ -320,6 +328,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param noSeeds true if we do not want seeds in the result
|
||||
* @return possibly empty (never null)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<Hash> getPeersAndAnnounce(byte[] ih, int max, long maxWait,
|
||||
int annMax, long annMaxWait,
|
||||
boolean isSeed, boolean noSeeds) {
|
||||
@ -835,7 +844,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending error " + msg + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap<String, Object>(4);
|
||||
List<Object> error = new ArrayList(2);
|
||||
List<Object> error = new ArrayList<Object>(2);
|
||||
error.add(Integer.valueOf(err));
|
||||
error.add(msg);
|
||||
map.put("e", error);
|
||||
@ -851,6 +860,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param repliable true for all but announce
|
||||
* @return null on error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private ReplyWaiter sendQuery(NodeInfo nInfo, Map<String, Object> map, boolean repliable) {
|
||||
if (nInfo.equals(_myNodeInfo))
|
||||
throw new IllegalArgumentException("wtf don't send to ourselves");
|
||||
@ -900,6 +910,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param toPort the query port, we will increment here
|
||||
* @return success
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean sendResponse(NodeInfo nInfo, MsgID msgID, Map<String, Object> map) {
|
||||
if (nInfo.equals(_myNodeInfo))
|
||||
throw new IllegalArgumentException("wtf don't send to ourselves");
|
||||
@ -1287,7 +1298,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
} else {
|
||||
List<byte[]> hashes;
|
||||
if (peers.isEmpty()) {
|
||||
hashes = Collections.EMPTY_LIST;
|
||||
hashes = Collections.emptyList();
|
||||
} else {
|
||||
hashes = new ArrayList<byte[]>(peers.size());
|
||||
for (Hash peer : peers) {
|
||||
|
@ -87,6 +87,8 @@ abstract class PersistDHT {
|
||||
out.println(ni.toPersistentString());
|
||||
count++;
|
||||
}
|
||||
if (out.checkError())
|
||||
throw new IOException("Failed write to " + file);
|
||||
} catch (IOException ioe) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Error writing the DHT File", ioe);
|
||||
|
8
apps/i2psnark/java/src/org/klomp/snark/package.html
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
I2P version of the snark bittorrent client, imported in 2005 and heavily enhanced
|
||||
to add a web UI, DHT support, and other features.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
@ -181,11 +181,10 @@ class BasicServlet extends HttpServlet
|
||||
HttpContent r = null;
|
||||
if (_warBase != null && pathInContext.startsWith(_warBase)) {
|
||||
r = new JarContent(pathInContext);
|
||||
} else if (!pathInContext.contains("..") &&
|
||||
!pathInContext.endsWith("/")) {
|
||||
File f = new File(_resourceBase, pathInContext);
|
||||
} else {
|
||||
File f = getResource(pathInContext);
|
||||
// exists && !directory
|
||||
if (f.isFile())
|
||||
if (f != null && f.isFile())
|
||||
r = new FileContent(f);
|
||||
}
|
||||
return r;
|
||||
@ -283,7 +282,12 @@ class BasicServlet extends HttpServlet
|
||||
{
|
||||
if (content.getLastModified()/1000 <= ifmsl/1000)
|
||||
{
|
||||
response.reset();
|
||||
try {
|
||||
response.reset();
|
||||
} catch (IllegalStateException ise) {
|
||||
// committed
|
||||
return true;
|
||||
}
|
||||
response.setStatus(304);
|
||||
response.flushBuffer();
|
||||
return false;
|
||||
@ -354,6 +358,7 @@ class BasicServlet extends HttpServlet
|
||||
writeHeaders(response, content, content_length);
|
||||
response.setStatus(416);
|
||||
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
in.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -555,14 +560,17 @@ class BasicServlet extends HttpServlet
|
||||
/**
|
||||
* Simple version of URIUtil.encodePath()
|
||||
*/
|
||||
protected static String encodePath(String path) throws MalformedURLException {
|
||||
try {
|
||||
URI uri = new URI(null, null, path, null);
|
||||
return uri.toString();
|
||||
} catch (URISyntaxException use) {
|
||||
// for ease of use, since a USE is not an IOE but a MUE is...
|
||||
throw new MalformedURLException(use.getMessage());
|
||||
}
|
||||
protected static String encodePath(String path) /* throws MalformedURLException */ {
|
||||
// Does NOT handle a ':' correctly, throws MUE.
|
||||
// Can't convert to %3a before hand or the % gets escaped
|
||||
//try {
|
||||
// URI uri = new URI(null, null, path, null);
|
||||
// return uri.toString();
|
||||
//} catch (URISyntaxException use) {
|
||||
// // for ease of use, since a USE is not an IOE but a MUE is...
|
||||
// throw new MalformedURLException(use.getMessage());
|
||||
//}
|
||||
return URIUtil.encodePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
private final String _url;
|
||||
private final byte[] _fakeHash;
|
||||
private final String _name;
|
||||
private final File _dataDir;
|
||||
private volatile long _remaining = -1;
|
||||
private volatile long _total = -1;
|
||||
private volatile long _transferred;
|
||||
@ -65,8 +66,10 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
/**
|
||||
* Caller should call _mgr.addDownloader(this), which
|
||||
* will start things off.
|
||||
*
|
||||
* @param dataDir null to default to snark data directory
|
||||
*/
|
||||
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url) {
|
||||
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url, File dataDir) {
|
||||
// magnet constructor
|
||||
super(mgr.util(), "Torrent download",
|
||||
null, null, null, null, null, false, null);
|
||||
@ -74,7 +77,8 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
_log = ctx.logManager().getLog(FetchAndAdd.class);
|
||||
_mgr = mgr;
|
||||
_url = url;
|
||||
_name = _("Download torrent file from {0}", url);
|
||||
_name = _t("Download torrent file from {0}", url);
|
||||
_dataDir = dataDir;
|
||||
byte[] fake = null;
|
||||
try {
|
||||
fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1"));
|
||||
@ -86,7 +90,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
* Set off by startTorrent()
|
||||
*/
|
||||
public void run() {
|
||||
_mgr.addMessageNoEscape(_("Fetching {0}", urlify(_url)));
|
||||
_mgr.addMessageNoEscape(_t("Fetching {0}", urlify(_url)));
|
||||
File file = get();
|
||||
if (!_isRunning) // stopped?
|
||||
return;
|
||||
@ -96,7 +100,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
_mgr.deleteMagnet(this);
|
||||
add(file);
|
||||
} else {
|
||||
_mgr.addMessageNoEscape(_("Torrent was not retrieved from {0}", urlify(_url)) +
|
||||
_mgr.addMessageNoEscape(_t("Torrent was not retrieved from {0}", urlify(_url)) +
|
||||
((_failCause != null) ? (": " + DataHelper.stripHTML(_failCause)) : ""));
|
||||
}
|
||||
if (file != null)
|
||||
@ -123,7 +127,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
out.deleteOnExit();
|
||||
|
||||
if (!_mgr.util().connected()) {
|
||||
_mgr.addMessage(_("Opening the I2P tunnel"));
|
||||
_mgr.addMessage(_t("Opening the I2P tunnel"));
|
||||
if (!_mgr.util().connect())
|
||||
return null;
|
||||
}
|
||||
@ -150,7 +154,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
* This Snark may then be deleted.
|
||||
*/
|
||||
private void add(File file) {
|
||||
_mgr.addMessageNoEscape(_("Torrent fetched from {0}", urlify(_url)));
|
||||
_mgr.addMessageNoEscape(_t("Torrent fetched from {0}", urlify(_url)));
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
@ -159,7 +163,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
|
||||
if (snark != null) {
|
||||
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||
_mgr.addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -171,12 +175,12 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_mgr.getTorrent(canonical) != null)
|
||||
_mgr.addMessage(_("Torrent already running: {0}", name));
|
||||
_mgr.addMessage(_t("Torrent already running: {0}", name));
|
||||
else
|
||||
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
|
||||
_mgr.addMessage(_t("Torrent already in the queue: {0}", name));
|
||||
} else {
|
||||
// This may take a LONG time to create the storage.
|
||||
_mgr.copyAndAddTorrent(file, canonical);
|
||||
_mgr.copyAndAddTorrent(file, canonical, _dataDir);
|
||||
snark = _mgr.getTorrentByBaseName(originalName);
|
||||
if (snark != null)
|
||||
snark.startTorrent();
|
||||
@ -184,9 +188,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
throw new IOException("Unknown error - check logs");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_mgr.addMessageNoEscape(_("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
|
||||
_mgr.addMessageNoEscape(_t("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_mgr.addMessageNoEscape(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
|
||||
_mgr.addMessageNoEscape(_t("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
|
||||
} finally {
|
||||
try { if (in != null) in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
@ -341,11 +345,11 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
|
||||
// End of EepGet status listeners
|
||||
|
||||
private String _(String s) {
|
||||
private String _t(String s) {
|
||||
return _mgr.util().getString(s);
|
||||
}
|
||||
|
||||
private String _(String s, String o) {
|
||||
private String _t(String s, String o) {
|
||||
return _mgr.util().getString(s, o);
|
||||
}
|
||||
|
||||
|
643
apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java
Normal file
@ -0,0 +1,643 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.text.Collator;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.klomp.snark.MetaInfo;
|
||||
import org.klomp.snark.Snark;
|
||||
import org.klomp.snark.Storage;
|
||||
|
||||
/**
|
||||
* Comparators for various columns
|
||||
*
|
||||
* @since 0.9.16 from TorrentNameComparator, moved from I2PSnarkservlet
|
||||
*/
|
||||
class Sorters {
|
||||
|
||||
/**
|
||||
* See below
|
||||
*/
|
||||
private static final Pattern PATTERN_DE, PATTERN_EN, PATTERN_ES, PATTERN_FR,
|
||||
PATTERN_IT, PATTERN_NL, PATTERN_PT;
|
||||
private static Pattern _pattern;
|
||||
|
||||
/**
|
||||
* Negative is reverse
|
||||
*
|
||||
*<ul>
|
||||
*<li>0, 1: Name
|
||||
*<li>2: Status
|
||||
*<li>3: Peers
|
||||
*<li>4: ETA
|
||||
*<li>5: Size
|
||||
*<li>6: Downloaded
|
||||
*<li>7: Uploaded
|
||||
*<li>8: Down rate
|
||||
*<li>9: Up rate
|
||||
*<li>10: Remaining (needed)
|
||||
*<li>11: Upload ratio
|
||||
*<li>12: File type
|
||||
*</ul>
|
||||
*
|
||||
* @param servlet for file type callback only
|
||||
*/
|
||||
public static Comparator<Snark> getComparator(int type, I2PSnarkServlet servlet) {
|
||||
boolean rev = type < 0;
|
||||
Comparator<Snark> rv;
|
||||
switch (type) {
|
||||
|
||||
case -1:
|
||||
case 0:
|
||||
case 1:
|
||||
default:
|
||||
rv = new TorrentNameComparator();
|
||||
if (rev)
|
||||
rv = Collections.reverseOrder(rv);
|
||||
break;
|
||||
|
||||
case -2:
|
||||
case 2:
|
||||
rv = new StatusComparator(rev);
|
||||
break;
|
||||
|
||||
case -3:
|
||||
case 3:
|
||||
rv = new PeersComparator(rev);
|
||||
break;
|
||||
|
||||
case -4:
|
||||
case 4:
|
||||
rv = new ETAComparator(rev);
|
||||
break;
|
||||
|
||||
case -5:
|
||||
case 5:
|
||||
rv = new SizeComparator(rev);
|
||||
break;
|
||||
|
||||
case -6:
|
||||
case 6:
|
||||
rv = new DownloadedComparator(rev);
|
||||
break;
|
||||
|
||||
case -7:
|
||||
case 7:
|
||||
rv = new UploadedComparator(rev);
|
||||
break;
|
||||
|
||||
case -8:
|
||||
case 8:
|
||||
rv = new DownRateComparator(rev);
|
||||
break;
|
||||
|
||||
case -9:
|
||||
case 9:
|
||||
rv = new UpRateComparator(rev);
|
||||
break;
|
||||
|
||||
case -10:
|
||||
case 10:
|
||||
rv = new RemainingComparator(rev);
|
||||
break;
|
||||
|
||||
case -11:
|
||||
case 11:
|
||||
rv = new RatioComparator(rev);
|
||||
break;
|
||||
|
||||
case -12:
|
||||
case 12:
|
||||
rv = new FileTypeComparator(rev, servlet);
|
||||
break;
|
||||
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading
|
||||
* articles such as "the" if the pattern is set by setPattern()
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
|
||||
|
||||
public int compare(Snark l, Snark r) {
|
||||
return comp(l, r);
|
||||
}
|
||||
|
||||
public static int comp(Snark l, Snark r) {
|
||||
// put downloads and magnets first
|
||||
if (l.getStorage() == null && r.getStorage() != null)
|
||||
return -1;
|
||||
if (l.getStorage() != null && r.getStorage() == null)
|
||||
return 1;
|
||||
String ls = l.getBaseName();
|
||||
String rs = r.getBaseName();
|
||||
Pattern p = _pattern;
|
||||
if (p != null) {
|
||||
Matcher m = p.matcher(ls);
|
||||
if (m.matches())
|
||||
ls = ls.substring(m.group(1).length());
|
||||
m = p.matcher(rs);
|
||||
if (m.matches())
|
||||
rs = rs.substring(m.group(1).length());
|
||||
}
|
||||
return Collator.getInstance().compare(ls, rs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward or reverse sort, but the fallback is always forward
|
||||
*/
|
||||
private static abstract class Sort implements Comparator<Snark>, Serializable {
|
||||
|
||||
private final boolean _rev;
|
||||
|
||||
public Sort(boolean rev) {
|
||||
_rev = rev;
|
||||
}
|
||||
|
||||
public int compare(Snark l, Snark r) {
|
||||
int rv = compareIt(l, r);
|
||||
if (rv != 0)
|
||||
return _rev ? 0 - rv : rv;
|
||||
return TorrentNameComparator.comp(l, r);
|
||||
}
|
||||
|
||||
protected abstract int compareIt(Snark l, Snark r);
|
||||
|
||||
protected static int compLong(long l, long r) {
|
||||
if (l < r)
|
||||
return -1;
|
||||
if (l > r)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class StatusComparator extends Sort {
|
||||
|
||||
private StatusComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
int rv = getStatus(l) - getStatus(r);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
// use reverse remaining as first tie break
|
||||
return compLong(r.getNeededLength(), l.getNeededLength());
|
||||
}
|
||||
|
||||
private static int getStatus(Snark snark) {
|
||||
long remaining = snark.getRemainingLength();
|
||||
if (snark.isStopped()) {
|
||||
if (remaining < 0)
|
||||
return 0;
|
||||
if (remaining > 0)
|
||||
return 5;
|
||||
return 10;
|
||||
}
|
||||
if (snark.isStarting())
|
||||
return 15;
|
||||
if (snark.isAllocating())
|
||||
return 20;
|
||||
if (remaining < 0)
|
||||
return 15; // magnet
|
||||
if (remaining == 0)
|
||||
return 100;
|
||||
if (snark.isChecking())
|
||||
return 95;
|
||||
if (snark.getNeededLength() <= 0)
|
||||
return 90;
|
||||
if (snark.getPeerCount() <= 0)
|
||||
return 40;
|
||||
if (snark.getDownloadRate() <= 0)
|
||||
return 50;
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PeersComparator extends Sort {
|
||||
|
||||
public PeersComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return l.getPeerCount() - r.getPeerCount();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemainingComparator extends Sort {
|
||||
|
||||
public RemainingComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getNeededLength(), r.getNeededLength());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ETAComparator extends Sort {
|
||||
|
||||
public ETAComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(eta(l), eta(r));
|
||||
}
|
||||
|
||||
private static long eta(Snark snark) {
|
||||
long needed = snark.getNeededLength();
|
||||
if (needed <= 0)
|
||||
return 0;
|
||||
long total = snark.getTotalLength();
|
||||
if (needed > total)
|
||||
needed = total;
|
||||
long downBps = snark.getDownloadRate();
|
||||
if (downBps > 0)
|
||||
return needed / downBps;
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SizeComparator extends Sort {
|
||||
|
||||
public SizeComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getTotalLength(), r.getTotalLength());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DownloadedComparator extends Sort {
|
||||
|
||||
public DownloadedComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
long ld = l.getTotalLength() - l.getRemainingLength();
|
||||
long rd = r.getTotalLength() - r.getRemainingLength();
|
||||
return compLong(ld, rd);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UploadedComparator extends Sort {
|
||||
|
||||
public UploadedComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getUploaded(), r.getUploaded());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DownRateComparator extends Sort {
|
||||
|
||||
public DownRateComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getDownloadRate(), r.getDownloadRate());
|
||||
}
|
||||
}
|
||||
|
||||
private static class UpRateComparator extends Sort {
|
||||
|
||||
public UpRateComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getUploadRate(), r.getUploadRate());
|
||||
}
|
||||
}
|
||||
|
||||
private static class RatioComparator extends Sort {
|
||||
|
||||
private static final long M = 128 * 1024 * 1024;
|
||||
|
||||
public RatioComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
long lt = l.getTotalLength();
|
||||
long ld = lt > 0 ? ((M * l.getUploaded()) / lt) : 0;
|
||||
long rt = r.getTotalLength();
|
||||
long rd = rt > 0 ? ((M * r.getUploaded()) / rt) : 0;
|
||||
return compLong(ld, rd);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileTypeComparator extends Sort {
|
||||
|
||||
private final I2PSnarkServlet servlet;
|
||||
|
||||
public FileTypeComparator(boolean rev, I2PSnarkServlet servlet) {
|
||||
super(rev);
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
String ls = toName(l);
|
||||
String rs = toName(r);
|
||||
return ls.compareTo(rs);
|
||||
}
|
||||
|
||||
private String toName(Snark snark) {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null)
|
||||
return "0";
|
||||
if (meta.getFiles() != null)
|
||||
return "1";
|
||||
// arbitrary sort based on icon name
|
||||
return servlet.toIcon(meta.getName());
|
||||
}
|
||||
}
|
||||
|
||||
////////////// Comparators for details page below
|
||||
|
||||
/**
|
||||
* Class to precompute and efficiently sort data
|
||||
* on a torrent file entry.
|
||||
*/
|
||||
public static class FileAndIndex {
|
||||
public final File file;
|
||||
public final boolean isDirectory;
|
||||
public final long length;
|
||||
public final long remaining;
|
||||
public final int priority;
|
||||
public final int index;
|
||||
|
||||
/**
|
||||
* @param storage may be null
|
||||
* @param remainingArray precomputed, non-null iff storage is non-null
|
||||
*/
|
||||
public FileAndIndex(File file, Storage storage, long[] remainingArray) {
|
||||
this.file = file;
|
||||
index = storage != null ? storage.indexOf(file) : -1;
|
||||
if (index >= 0) {
|
||||
isDirectory = false;
|
||||
remaining = remainingArray[index];
|
||||
priority = storage.getPriority(index);
|
||||
} else {
|
||||
isDirectory = file.isDirectory();
|
||||
remaining = -1;
|
||||
priority = -999;
|
||||
}
|
||||
length = isDirectory ? 0 : file.length();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Negative is reverse
|
||||
*
|
||||
*<ul>
|
||||
*<li>0, 1: Name
|
||||
*<li>5: Size
|
||||
*<li>10: Remaining (needed)
|
||||
*<li>12: File type
|
||||
*<li>13: Priority
|
||||
*</ul>
|
||||
*
|
||||
* @param servlet for file type callback only
|
||||
*/
|
||||
public static Comparator<FileAndIndex> getFileComparator(int type, I2PSnarkServlet servlet) {
|
||||
boolean rev = type < 0;
|
||||
Comparator<FileAndIndex> rv;
|
||||
|
||||
switch (type) {
|
||||
|
||||
case -1:
|
||||
case 0:
|
||||
case 1:
|
||||
default:
|
||||
rv = new FileNameComparator();
|
||||
if (rev)
|
||||
rv = Collections.reverseOrder(rv);
|
||||
break;
|
||||
|
||||
case -5:
|
||||
case 5:
|
||||
rv = new FAISizeComparator(rev);
|
||||
break;
|
||||
|
||||
case -10:
|
||||
case 10:
|
||||
rv = new FAIRemainingComparator(rev);
|
||||
break;
|
||||
|
||||
case -12:
|
||||
case 12:
|
||||
rv = new FAITypeComparator(rev, servlet);
|
||||
break;
|
||||
|
||||
case -13:
|
||||
case 13:
|
||||
rv = new FAIPriorityComparator(rev);
|
||||
break;
|
||||
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case,
|
||||
* directories first
|
||||
* @since 0.9.6 moved from I2PSnarkServlet in 0.9.16
|
||||
*/
|
||||
private static class FileNameComparator implements Comparator<FileAndIndex>, Serializable {
|
||||
|
||||
public int compare(FileAndIndex l, FileAndIndex r) {
|
||||
return comp(l, r);
|
||||
}
|
||||
|
||||
public static int comp(FileAndIndex l, FileAndIndex r) {
|
||||
boolean ld = l.isDirectory;
|
||||
boolean rd = r.isDirectory;
|
||||
if (ld && !rd)
|
||||
return -1;
|
||||
if (rd && !ld)
|
||||
return 1;
|
||||
return Collator.getInstance().compare(l.file.getName(), r.file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward or reverse sort, but the fallback is always forward
|
||||
*/
|
||||
private static abstract class FAISort implements Comparator<FileAndIndex>, Serializable {
|
||||
|
||||
private final boolean _rev;
|
||||
|
||||
public FAISort(boolean rev) {
|
||||
_rev = rev;
|
||||
}
|
||||
|
||||
public int compare(FileAndIndex l, FileAndIndex r) {
|
||||
int rv = compareIt(l, r);
|
||||
if (rv != 0)
|
||||
return _rev ? 0 - rv : rv;
|
||||
return FileNameComparator.comp(l, r);
|
||||
}
|
||||
|
||||
protected abstract int compareIt(FileAndIndex l, FileAndIndex r);
|
||||
|
||||
protected static int compLong(long l, long r) {
|
||||
if (l < r)
|
||||
return -1;
|
||||
if (l > r)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static class FAIRemainingComparator extends FAISort {
|
||||
|
||||
public FAIRemainingComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(FileAndIndex l, FileAndIndex r) {
|
||||
return compLong(l.remaining, r.remaining);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FAISizeComparator extends FAISort {
|
||||
|
||||
public FAISizeComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(FileAndIndex l, FileAndIndex r) {
|
||||
return compLong(l.length, r.length);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FAITypeComparator extends FAISort {
|
||||
|
||||
private final I2PSnarkServlet servlet;
|
||||
|
||||
public FAITypeComparator(boolean rev, I2PSnarkServlet servlet) {
|
||||
super(rev);
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
public int compareIt(FileAndIndex l, FileAndIndex r) {
|
||||
String ls = toName(l);
|
||||
String rs = toName(r);
|
||||
return ls.compareTo(rs);
|
||||
}
|
||||
|
||||
private String toName(FileAndIndex fai) {
|
||||
if (fai.isDirectory)
|
||||
return "0";
|
||||
// arbitrary sort based on icon name
|
||||
return servlet.toIcon(fai.file.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static class FAIPriorityComparator extends FAISort {
|
||||
|
||||
public FAIPriorityComparator(boolean rev) { super(rev); }
|
||||
|
||||
/** highest first */
|
||||
public int compareIt(FileAndIndex l, FileAndIndex r) {
|
||||
return r.priority - l.priority;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Match an indefinite or definite article in the language,
|
||||
* followed by one or more whitespace, '.', or '_'.
|
||||
* Does not match "partitive" articles.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Article_%28grammar%29
|
||||
* http://www.loc.gov/marc/bibliographic/bdapndxf.html
|
||||
*/
|
||||
static {
|
||||
PATTERN_DE = Pattern.compile(
|
||||
// can't make the non-capturing innner group work
|
||||
//"^((?:" +
|
||||
"^((" +
|
||||
"der|die|das|des|dem|den|ein|eine|einer|eines|einem|einen" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_EN = Pattern.compile(
|
||||
"^((" +
|
||||
"a|an|the" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_ES = Pattern.compile(
|
||||
"^((" +
|
||||
"el|la|lo|los|las|un|una|unos|unas" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_FR = Pattern.compile(
|
||||
// note l' doesn't require whitespace after
|
||||
"^(l'|((" +
|
||||
"le|la|les|un|une|des" +
|
||||
")[\\s\\._]+)).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_IT = Pattern.compile(
|
||||
// note l' and un' don't require whitespace after
|
||||
"^(l'|un'|((" +
|
||||
"il|lo|la|i|gli|le|uno|una|un" +
|
||||
")[\\s\\._]+)).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_NL = Pattern.compile(
|
||||
"^((" +
|
||||
"de|het|het'n|een|een'n" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_PT = Pattern.compile(
|
||||
"^((" +
|
||||
"o|a|os|as|um|uma|uns|umas" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets static field, oh well
|
||||
* @param lang null for none
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public static void setPattern(String lang) {
|
||||
Pattern p;
|
||||
if (lang == null)
|
||||
p = null;
|
||||
else if (lang.equals("de"))
|
||||
p = PATTERN_DE;
|
||||
else if (lang.equals("en"))
|
||||
p = PATTERN_EN;
|
||||
else if (lang.equals("es"))
|
||||
p = PATTERN_ES;
|
||||
else if (lang.equals("fr"))
|
||||
p = PATTERN_FR;
|
||||
else if (lang.equals("it"))
|
||||
p = PATTERN_IT;
|
||||
else if (lang.equals("nl"))
|
||||
p = PATTERN_NL;
|
||||
else if (lang.equals("pt"))
|
||||
p = PATTERN_PT;
|
||||
else
|
||||
p = null;
|
||||
_pattern = p;
|
||||
}
|
||||
|
||||
/****
|
||||
public static final void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: Sorters lang 'string'");
|
||||
System.exit(1);
|
||||
}
|
||||
String lang = args[0];
|
||||
setPattern(lang);
|
||||
if (_pattern == null) {
|
||||
System.out.println("Unsupported " + lang);
|
||||
System.exit(1);
|
||||
}
|
||||
String s = args[1];
|
||||
Matcher m = _pattern.matcher(s);
|
||||
if (m.matches()) {
|
||||
System.out.println("Match is \"" + m.group(1) + '"');
|
||||
} else {
|
||||
System.out.println("No match for \"" + s + '"');
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
250
apps/i2psnark/java/src/org/klomp/snark/web/URIUtil.java
Normal file
@ -0,0 +1,250 @@
|
||||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
|
||||
/** URI Holder.
|
||||
* This class assists with the decoding and encoding or HTTP URI's.
|
||||
* It differs from the java.net.URL class as it does not provide
|
||||
* communications ability, but it does assist with query string
|
||||
* formatting.
|
||||
* <P>UTF-8 encoding is used by default for % encoded characters. This
|
||||
* may be overridden with the org.eclipse.jetty.util.URI.charset system property.
|
||||
* see UrlEncoded
|
||||
*
|
||||
* I2P modded from Jetty 8.1.15
|
||||
* @since 0.9.15
|
||||
*/
|
||||
class URIUtil
|
||||
{
|
||||
|
||||
/** Encode a URI path.
|
||||
* This is the same encoding offered by URLEncoder, except that
|
||||
* the '/' character is not encoded.
|
||||
* @param path The path the encode
|
||||
* @return The encoded path
|
||||
*/
|
||||
public static String encodePath(String path)
|
||||
{
|
||||
if (path==null || path.length()==0)
|
||||
return path;
|
||||
|
||||
StringBuilder buf = encodePath(null,path);
|
||||
return buf==null?path:buf.toString();
|
||||
}
|
||||
|
||||
/** Encode a URI path.
|
||||
*
|
||||
* Somewhat oddly, this encodes all chars >= 0x80 if buf is null, (strict RFC 2396)
|
||||
* but only the control, space, and special chars if buf is non-null.
|
||||
*
|
||||
* @param path The path the encode
|
||||
* @param buf StringBuilder to encode path into (or null)
|
||||
* @return The StringBuilder or null if no substitutions required.
|
||||
*/
|
||||
public static StringBuilder encodePath(StringBuilder buf, String path)
|
||||
{
|
||||
byte[] bytes=null;
|
||||
if (buf==null)
|
||||
{
|
||||
loop:
|
||||
for (int i=0;i<path.length();i++)
|
||||
{
|
||||
char c=path.charAt(i);
|
||||
switch(c)
|
||||
{
|
||||
case '%':
|
||||
case '?':
|
||||
case ';':
|
||||
case '#':
|
||||
case '\'':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case ' ':
|
||||
case ':':
|
||||
case '[':
|
||||
case ']':
|
||||
buf=new StringBuilder(path.length()*2);
|
||||
break loop;
|
||||
default:
|
||||
if (c >= 0x7f || c <= 0x1f)
|
||||
{
|
||||
bytes = DataHelper.getUTF8(path);
|
||||
buf=new StringBuilder(path.length()*2);
|
||||
break loop;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (buf==null)
|
||||
return null;
|
||||
}
|
||||
|
||||
//synchronized(buf)
|
||||
//{
|
||||
if (bytes!=null)
|
||||
{
|
||||
for (int i=0;i<bytes.length;i++)
|
||||
{
|
||||
byte c=bytes[i];
|
||||
switch(c)
|
||||
{
|
||||
case '%':
|
||||
buf.append("%25");
|
||||
continue;
|
||||
case '?':
|
||||
buf.append("%3F");
|
||||
continue;
|
||||
case ';':
|
||||
buf.append("%3B");
|
||||
continue;
|
||||
case '#':
|
||||
buf.append("%23");
|
||||
continue;
|
||||
case '"':
|
||||
buf.append("%22");
|
||||
continue;
|
||||
case '\'':
|
||||
buf.append("%27");
|
||||
continue;
|
||||
case '<':
|
||||
buf.append("%3C");
|
||||
continue;
|
||||
case '>':
|
||||
buf.append("%3E");
|
||||
continue;
|
||||
case ' ':
|
||||
buf.append("%20");
|
||||
continue;
|
||||
case 0x7f:
|
||||
buf.append("%7F");
|
||||
continue;
|
||||
case ':':
|
||||
buf.append("%3A");
|
||||
continue;
|
||||
case '[':
|
||||
buf.append("%5B");
|
||||
continue;
|
||||
case ']':
|
||||
buf.append("%5D");
|
||||
continue;
|
||||
default:
|
||||
if (c <= 0x1f) // includes negative
|
||||
toHex(c,buf);
|
||||
else
|
||||
buf.append((char)c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i=0;i<path.length();i++)
|
||||
{
|
||||
char c=path.charAt(i);
|
||||
switch(c)
|
||||
{
|
||||
case '%':
|
||||
buf.append("%25");
|
||||
continue;
|
||||
case '?':
|
||||
buf.append("%3F");
|
||||
continue;
|
||||
case ';':
|
||||
buf.append("%3B");
|
||||
continue;
|
||||
case '#':
|
||||
buf.append("%23");
|
||||
continue;
|
||||
case '"':
|
||||
buf.append("%22");
|
||||
continue;
|
||||
case '\'':
|
||||
buf.append("%27");
|
||||
continue;
|
||||
case '<':
|
||||
buf.append("%3C");
|
||||
continue;
|
||||
case '>':
|
||||
buf.append("%3E");
|
||||
continue;
|
||||
case ' ':
|
||||
buf.append("%20");
|
||||
continue;
|
||||
case ':':
|
||||
buf.append("%3A");
|
||||
continue;
|
||||
case '[':
|
||||
buf.append("%5B");
|
||||
continue;
|
||||
case ']':
|
||||
buf.append("%5D");
|
||||
continue;
|
||||
default:
|
||||
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || Character.isSpaceChar(c))
|
||||
toHex(c,buf);
|
||||
else
|
||||
buf.append(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modded from Jetty TypeUtil
|
||||
*/
|
||||
private static void toHex(byte b, StringBuilder buf)
|
||||
{
|
||||
buf.append('%');
|
||||
int d=0xf&((0xF0&b)>>4);
|
||||
buf.append((char)((d>9?('A'-10):'0')+d));
|
||||
d=0xf&b;
|
||||
buf.append((char)((d>9?('A'-10):'0')+d));
|
||||
}
|
||||
|
||||
/**
|
||||
* UTF-8
|
||||
*/
|
||||
private static void toHex(char c, StringBuilder buf)
|
||||
{
|
||||
if (c > 0x7f) {
|
||||
byte[] b = DataHelper.getUTF8(Character.toString(c));
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
toHex(b[i], buf);
|
||||
}
|
||||
} else {
|
||||
toHex((byte) c, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
7
apps/i2psnark/java/src/org/klomp/snark/web/package.html
Normal file
@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The i2psnark user interface, implemented as a webapp in i2psnark.war.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
1400
apps/i2psnark/locale/messages_pt_bR.po
Normal file
@ -3,13 +3,16 @@
|
||||
7z = application/x-7z-compressed
|
||||
ape = audio/x-monkeys-audio
|
||||
bz2 = application/x-bzip2
|
||||
cue = application/x-cue
|
||||
dmg = application/apple-diskimage
|
||||
epub = application/epub+zip
|
||||
flac = audio/flac
|
||||
flv = video/x-flv
|
||||
iso = application/x-iso9660-image
|
||||
m4a = audio/mp4a-latm
|
||||
m4b = audio/mp4a-latm
|
||||
m4v = video/x-m4v
|
||||
mka = audio/x-matroska
|
||||
mkv = video/x-matroska
|
||||
mobi = application/x-mobipocket-ebook
|
||||
mp4 = video/mp4
|
||||
@ -24,8 +27,10 @@ su2 = application/zip
|
||||
su3 = application/zip
|
||||
sud = application/zip
|
||||
tbz = application/x-bzip2
|
||||
torrent = application/x-bittorrent
|
||||
txt = text/plain
|
||||
war = application/java-archive
|
||||
webm = video/webm
|
||||
wma = audio/x-ms-wma
|
||||
wmv = video/x-ms-wmv
|
||||
xz = application/x-xz
|
||||
|
@ -1,6 +1,4 @@
|
||||
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
|
||||
to run it with the web UI, run "launch-i2psnark". I2PSnark is
|
||||
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
|
||||
(http://www.i2p2.de/) within a webserver (such as the bundled Jetty from
|
||||
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
|
||||
with the folks at http://forum.i2p2.de/
|
||||
i2psnark is packaged as a webapp running in the router console.
|
||||
Command line and standalone operation of i2psnark are not currently supported.
|
||||
See http://trac.i2p2.i2p/ticket/1191 or http://trac.i2p2.de/ticket/1191
|
||||
for the status of restoring standalone support.
|
||||
|
@ -1,11 +1,9 @@
|
||||
This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
|
||||
This is i2psnark, an I2P port of snark http://klomp.org/snark/ , a GPLv2 bittorrent client.
|
||||
It contains significant enhancements including a web UI and support for
|
||||
multitorrent, magnet, PEX and DHT.
|
||||
|
||||
The build in tracker has been removed for simplicity.
|
||||
i2psnark is packaged as a webapp running in the router console.
|
||||
|
||||
Example usage:
|
||||
java -jar lib/i2psnark.jar myFile.torrent
|
||||
|
||||
or, a more verbose setting:
|
||||
java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
|
||||
--i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
|
||||
--debug 6 myFile.torrent
|
||||
See http://i2p-projekt.i2p/en/docs/applications/bittorrent
|
||||
or https://geti2p.net/en/docs/applications/bittorrent
|
||||
for the specification of the protocols for bittorrent over I2P.
|
||||
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 733 B |
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
Before Width: | Height: | Size: 889 B After Width: | Height: | Size: 889 B |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |