forked from I2P_Developers/i2p.i2p
Compare commits
1807 Commits
i2p_post_g
...
i2p_0_6_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdf94295f3 | ||
|
|
fbf1705c4e | ||
|
|
453ecc4208 | ||
|
|
d1f2b447ac | ||
|
|
70c4560f02 | ||
|
|
9089fdd2d5 | ||
|
|
ef82cc4f20 | ||
|
|
f2c2a5b386 | ||
|
|
fc858bc950 | ||
|
|
dbb4b3d0c2 | ||
|
|
2b841ad667 | ||
|
|
5e094b43b3 | ||
|
|
33d57dd545 | ||
|
|
61f75b5f09 | ||
|
|
3f65e53592 | ||
|
|
99ae3ee459 | ||
|
|
f7236d7d58 | ||
|
|
5182008b38 | ||
|
|
9d030327e6 | ||
|
|
a15c90d2cc | ||
|
|
84c2a713e1 | ||
|
|
7db9ce6e5b | ||
|
|
da42f5717b | ||
|
|
5241953e5d | ||
|
|
b1a5f61ba2 | ||
|
|
024a5a1ad4 | ||
|
|
b031de5404 | ||
|
|
dceac73951 | ||
|
|
8f95143488 | ||
|
|
a8f3043aae | ||
|
|
6c91b2d4a9 | ||
|
|
ddd438de35 | ||
|
|
16fd46db2b | ||
|
|
1159c155a4 | ||
|
|
30b4e2aa2a | ||
|
|
ae46fa2e6d | ||
|
|
9fc34895c9 | ||
|
|
08be4c7b3d | ||
|
|
7443457af4 | ||
|
|
979a4cfb69 | ||
|
|
ed285871bf | ||
|
|
8c70b8b32a | ||
|
|
14134694d7 | ||
|
|
807d2d3509 | ||
|
|
b222cd43f4 | ||
|
|
7f6aa327f2 | ||
|
|
49564a3878 | ||
|
|
12ddaff0ce | ||
|
|
a8ea239dcc | ||
|
|
ca391097a9 | ||
|
|
4297edc88f | ||
|
|
6de4673e9e | ||
|
|
f6979c811f | ||
|
|
bd86483204 | ||
|
|
53cf03cec6 | ||
|
|
e284a8878b | ||
|
|
14cd469c6d | ||
|
|
9050d7c218 | ||
|
|
0ad18cd0ba | ||
|
|
ca0af146b7 | ||
|
|
a2d2b031f4 | ||
|
|
2f36912ac0 | ||
|
|
53e32c8e64 | ||
|
|
10dde610dc | ||
|
|
f7c2ae9a3b | ||
|
|
ac3b88b9e9 | ||
|
|
60124cdcdc | ||
|
|
e7d2281772 | ||
|
|
52ace2d695 | ||
|
|
b5a25801b4 | ||
|
|
3fc0558810 | ||
|
|
bd9c6ff463 | ||
|
|
a0c822af96 | ||
|
|
4de302101d | ||
|
|
84383c3dab | ||
|
|
a94abb13a4 | ||
|
|
ad59ab691b | ||
|
|
ee9ac31c8b | ||
|
|
788998307a | ||
|
|
2f8a2879bb | ||
|
|
c7b9525d2c | ||
|
|
3fbc6f41af | ||
|
|
6534c84578 | ||
|
|
3816c79193 | ||
|
|
8458e4e0af | ||
|
|
0b9e4967a0 | ||
|
|
05e2da7c22 | ||
|
|
13bda1f6d7 | ||
|
|
ea22c73a73 | ||
|
|
aa5f1cb18d | ||
|
|
ab9c6d59cf | ||
|
|
76655d01d0 | ||
|
|
138f7d3b8d | ||
|
|
df4b998a6a | ||
|
|
2d70103f88 | ||
|
|
731e26e7d6 | ||
|
|
f9d3b157f0 | ||
|
|
12775c416d | ||
|
|
2ca4e63216 | ||
|
|
918d8f851f | ||
|
|
20ea680ff0 | ||
|
|
cabb607211 | ||
|
|
00a4761b5e | ||
|
|
3516701272 | ||
|
|
c4d785667a | ||
|
|
123e0ba589 | ||
|
|
197237aa32 | ||
|
|
f30dc2b480 | ||
|
|
d4ff34eacb | ||
|
|
978769a05d | ||
|
|
993c70f600 | ||
|
|
5dfa9ad7f6 | ||
|
|
f282fe3854 | ||
|
|
9297564555 | ||
|
|
e7ad516685 | ||
|
|
ad574c8504 | ||
|
|
38617fe0a7 | ||
|
|
cdee5b2c31 | ||
|
|
7f6e65c76f | ||
|
|
4dd628dbc8 | ||
|
|
3b5b48ad8a | ||
|
|
c4cac3f3f1 | ||
|
|
4a49e98c31 | ||
|
|
0c0e269e72 | ||
|
|
70b6f97abe | ||
|
|
0013677b83 | ||
|
|
a98ceda64d | ||
|
|
91ea1d0395 | ||
|
|
4aa65c3bb3 | ||
|
|
0a1f59940a | ||
|
|
f540dc798b | ||
|
|
30f6f26a68 | ||
|
|
ea3bf3ffc8 | ||
|
|
831d5ac70c | ||
|
|
1962867ad9 | ||
|
|
6019a03029 | ||
|
|
df5736f571 | ||
|
|
9a73c6defe | ||
|
|
9dfa87ba47 | ||
|
|
934a269753 | ||
|
|
1c0dfc242b | ||
|
|
3bc3e5d47e | ||
|
|
55869af2cc | ||
|
|
9f336dd05b | ||
|
|
411ca5e6c3 | ||
|
|
c528e4db03 | ||
|
|
848ead7683 | ||
|
|
1b8419b9b5 | ||
|
|
900420719e | ||
|
|
ef7d1ba964 | ||
|
|
ab1654c784 | ||
|
|
24bad8e4bb | ||
|
|
f6d8200bc8 | ||
|
|
aef33548b3 | ||
|
|
56ecdcce82 | ||
|
|
b9b59ff95f | ||
|
|
aa9dd3e5c6 | ||
|
|
30bd659149 | ||
|
|
3286ca49c8 | ||
|
|
7700d12178 | ||
|
|
557b7e3f2e | ||
|
|
3e1e9146e1 | ||
|
|
40d8d1aac1 | ||
|
|
1457b8efba | ||
|
|
3821e80ac8 | ||
|
|
d40bb459ea | ||
|
|
edf04f07c9 | ||
|
|
2bdea23986 | ||
|
|
6be0c4b694 | ||
|
|
2a272f465c | ||
|
|
a8ecd32b45 | ||
|
|
20c42a175d | ||
|
|
d6c3ffde87 | ||
|
|
177e0ae6a3 | ||
|
|
dab1b4d256 | ||
|
|
3aba12631b | ||
|
|
cfee6430d4 | ||
|
|
6b96df1cec | ||
|
|
deecfa5047 | ||
|
|
6ca3f01038 | ||
|
|
d89f589f2b | ||
|
|
8c1895e04f | ||
|
|
c3d0132a98 | ||
|
|
d955279d17 | ||
|
|
76266dce0d | ||
|
|
5694206b35 | ||
|
|
4293a18726 | ||
|
|
9865af4174 | ||
|
|
c8c109093d | ||
|
|
b5784d6025 | ||
|
|
31bdb8909a | ||
|
|
ee921c22ae | ||
|
|
172ffd0434 | ||
|
|
d9b4406c09 | ||
|
|
8ac0e85df4 | ||
|
|
249ccd5e3c | ||
|
|
727d76d43e | ||
|
|
44770b7c07 | ||
|
|
b5d571c75f | ||
|
|
da56d83716 | ||
|
|
f777e213ce | ||
|
|
79906f5a7d | ||
|
|
54074e76b5 | ||
|
|
c2ea8db683 | ||
|
|
744671a518 | ||
|
|
7f5b127bbc | ||
|
|
89eff0c628 | ||
|
|
177aeebb1c | ||
|
|
e0e6bde4a5 | ||
|
|
e6b145716f | ||
|
|
f958342704 | ||
|
|
5a1f738505 | ||
|
|
8147cdf40c | ||
|
|
6afc64ac39 | ||
|
|
61b8e3598b | ||
|
|
3bb445ff40 | ||
|
|
59a8037599 | ||
|
|
09cb5fad59 | ||
|
|
ee8e45ecf7 | ||
|
|
339868838d | ||
|
|
c5579fa349 | ||
|
|
d4a859547c | ||
|
|
779aa240d2 | ||
|
|
9aaad00383 | ||
|
|
6422f7ef78 | ||
|
|
3e51584b3c | ||
|
|
4ff8a53084 | ||
|
|
ccb73437c4 | ||
|
|
b43114f61b | ||
|
|
9bd87ab511 | ||
|
|
b6ea55f7ef | ||
|
|
5f18cec97d | ||
|
|
3ba921ec0e | ||
|
|
e313da254c | ||
|
|
8660cf0d74 | ||
|
|
e0bfdff152 | ||
|
|
c27aed3603 | ||
|
|
cdc6002f0e | ||
|
|
4cf3d9c1a2 | ||
|
|
0473e08e21 | ||
|
|
346faa3de2 | ||
|
|
5ec6dca64d | ||
|
|
1a6b49cfb8 | ||
|
|
c7b75df390 | ||
|
|
f97c09291b | ||
|
|
8f2a5b403c | ||
|
|
ea41a90eae | ||
|
|
b1dd29e64d | ||
|
|
46e47c47ac | ||
|
|
b7bf431f0d | ||
|
|
7f432122d9 | ||
|
|
e7be8c6097 | ||
|
|
adf56a16e1 | ||
|
|
11204b8a2b | ||
|
|
cade27dceb | ||
|
|
5597d28e59 | ||
|
|
0502fec432 | ||
|
|
a6714fc2de | ||
|
|
1219dadbd5 | ||
|
|
77b995f5ed | ||
|
|
2f53b9ff68 | ||
|
|
d84d045849 | ||
|
|
d8e72dfe48 | ||
|
|
88b9f7a74c | ||
|
|
6a19501214 | ||
|
|
ba30b56c5f | ||
|
|
a375e4b2ce | ||
|
|
44fd71e17f | ||
|
|
b41c378de9 | ||
|
|
4ce6b308b3 | ||
|
|
72c6e7d1c5 | ||
|
|
7ca3f22e77 | ||
|
|
59790dafef | ||
|
|
7227cae6ef | ||
|
|
03bba51c1e | ||
|
|
0637050cbc | ||
|
|
7f58a68c5a | ||
|
|
8120b0397c | ||
|
|
fbe42b7dce | ||
|
|
def24e34ad | ||
|
|
593253e6a3 | ||
|
|
56dd4cb8b5 | ||
|
|
10c6f67500 | ||
|
|
5c1f968afa | ||
|
|
aaaf437d62 | ||
|
|
a8a866b5f6 | ||
|
|
aeb8f02269 | ||
|
|
45767360ab | ||
|
|
3563aa2e4d | ||
|
|
843d5b625a | ||
|
|
0f8ede85ca | ||
|
|
9267d7cae2 | ||
|
|
dade5a981b | ||
|
|
f873cba27e | ||
|
|
108dec53a5 | ||
|
|
e9592ed400 | ||
|
|
4c230522a2 | ||
|
|
16bd19c6dc | ||
|
|
b4b6d49d34 | ||
|
|
9d5f16a889 | ||
|
|
51c492b842 | ||
|
|
d3380228ac | ||
|
|
ad47bf5da3 | ||
|
|
76e8631e31 | ||
|
|
f688b9112d | ||
|
|
18d3f5d25d | ||
|
|
440cf2c983 | ||
|
|
adeb09576a | ||
|
|
fd52bcf8cd | ||
|
|
c2696bba00 | ||
|
|
fef9d57483 | ||
|
|
c250692ef0 | ||
|
|
2a6024e196 | ||
|
|
835662b3c9 | ||
|
|
6b5b880ab6 | ||
|
|
3de23d4206 | ||
|
|
ea82f2a8cc | ||
|
|
b5ad7642bc | ||
|
|
0fbe84e9f0 | ||
|
|
8063889d23 | ||
|
|
6e1ac8e173 | ||
|
|
1b0bb5ea19 | ||
|
|
4ce51261f1 | ||
|
|
6e34d9b73e | ||
|
|
6e01637400 | ||
|
|
9a96798f9f | ||
|
|
c9db6f87d1 | ||
|
|
567ce84e1e | ||
|
|
cde7ac7e52 | ||
|
|
b2f0d17e94 | ||
|
|
dae6be14b7 | ||
|
|
20cec857d2 | ||
|
|
739f694cfe | ||
|
|
84779002fb | ||
|
|
df926fb60d | ||
|
|
a2c7c5a516 | ||
|
|
1861379d43 | ||
|
|
408a344aae | ||
|
|
e9c1ed70d0 | ||
|
|
916dcca2b0 | ||
|
|
31e81bab17 | ||
|
|
6a5170c341 | ||
|
|
42bff8093c | ||
|
|
d1df94f284 | ||
|
|
9cf1744291 | ||
|
|
f0545c8c9a | ||
|
|
ef9ed87d30 | ||
|
|
58ffd92a34 | ||
|
|
418facc7e0 | ||
|
|
7f3c953e14 | ||
|
|
addab1fa2a | ||
|
|
39343ce957 | ||
|
|
7389cec78f | ||
|
|
9e5fe7d2b6 | ||
|
|
a7dfaee5ac | ||
|
|
7beb92b1cc | ||
|
|
5b56d22da9 | ||
|
|
e6b343070a | ||
|
|
8496b88518 | ||
|
|
aa542b7876 | ||
|
|
3f7d46378b | ||
|
|
b36def1f72 | ||
|
|
5a6a3a5e8d | ||
|
|
c3bd26d9b4 | ||
|
|
967e106ee7 | ||
|
|
7c73e59482 | ||
|
|
03dfa913d1 | ||
|
|
348e845793 | ||
|
|
80827c3aad | ||
|
|
3b4cf0a024 | ||
|
|
941252fd80 | ||
|
|
bc626ece2d | ||
|
|
400feb3ba7 | ||
|
|
756a4e3995 | ||
|
|
578301240e | ||
|
|
9b8f91c7f9 | ||
|
|
c7c389d4fb | ||
|
|
68f7adfa0b | ||
|
|
c4ac5170c7 | ||
|
|
32e0c8ac71 | ||
|
|
c9c1eae32f | ||
|
|
33366cc291 | ||
|
|
083ac1f125 | ||
|
|
80c6290b89 | ||
|
|
6492ad165a | ||
|
|
f0d1b1a40e | ||
|
|
17f044e6cd | ||
|
|
63f3a9cd7b | ||
|
|
b8ddbf13b4 | ||
|
|
be9bdbfe0f | ||
|
|
bc74bf1402 | ||
|
|
5c2a57f95a | ||
|
|
9cd8cc692e | ||
|
|
ebac4df2d3 | ||
|
|
0626f714c6 | ||
|
|
21842291e9 | ||
|
|
d461c295f6 | ||
|
|
85b3450525 | ||
|
|
75d7c81b7c | ||
|
|
1433e20f73 | ||
|
|
e614a2f726 | ||
|
|
32be7f1fd8 | ||
|
|
66e1d95a2a | ||
|
|
ff03be217e | ||
|
|
a52f8b89dc | ||
|
|
21c7c043b3 | ||
|
|
45e6608ad3 | ||
|
|
28978e3680 | ||
|
|
904f755c8c | ||
|
|
a2c309ddd3 | ||
|
|
677eeac8f7 | ||
|
|
b232cc0f24 | ||
|
|
18bbae1d1e | ||
|
|
08ee62b52c | ||
|
|
5b83aed719 | ||
|
|
b5875ca07b | ||
|
|
3f9bf28382 | ||
|
|
a2bd71c75b | ||
|
|
89509490c5 | ||
|
|
a997a46040 | ||
|
|
538dd07e7b | ||
|
|
046778404e | ||
|
|
766f83d653 | ||
|
|
b20aee6753 | ||
|
|
f9aa3aef18 | ||
|
|
d74aa6e53d | ||
|
|
ea6fbc7835 | ||
|
|
536e604b8e | ||
|
|
49d6f5018f | ||
|
|
4a830e422a | ||
|
|
df6c52fe75 | ||
|
|
01979c08b3 | ||
|
|
7928ef83cc | ||
|
|
10afe0a060 | ||
|
|
ef230cfa3d | ||
|
|
2d15a42137 | ||
|
|
57d6a2f645 | ||
|
|
469a0852d7 | ||
|
|
7983bb1490 | ||
|
|
2e7eac02ed | ||
|
|
238389fc7f | ||
|
|
4cec9da0a6 | ||
|
|
00f27d4400 | ||
|
|
f61618e4a4 | ||
|
|
265d5e306e | ||
|
|
10ed058c2e | ||
|
|
8a21f0efec | ||
|
|
b8291ac5a4 | ||
|
|
c17433cb93 | ||
|
|
35fe7f8203 | ||
|
|
21f13dba43 | ||
|
|
0db239a3fe | ||
|
|
4745d61f9b | ||
|
|
b9a4c3ba52 | ||
|
|
cbf6a70a1a | ||
|
|
7d4e093b58 | ||
|
|
d27feabcb3 | ||
|
|
0d9efa17de | ||
|
|
b125b04c8d | ||
|
|
0539f1d794 | ||
|
|
a4b6709f02 | ||
|
|
f2db143a6f | ||
|
|
b615f54d41 | ||
|
|
db2328e03e | ||
|
|
e2071935ad | ||
|
|
1c40ff773f | ||
|
|
37a3645663 | ||
|
|
eb8accd1e0 | ||
|
|
3af97894b4 | ||
|
|
15a0dcf4d8 | ||
|
|
aa3a44c42a | ||
|
|
40f4b47b87 | ||
|
|
dca09d96b3 | ||
|
|
dd10747460 | ||
|
|
77176162af | ||
|
|
8b9ee4dfd7 | ||
|
|
6e8e77b9ec | ||
|
|
7ef9ce8cc6 | ||
|
|
9646ac2911 | ||
|
|
566a713baa | ||
|
|
36f7e98e90 | ||
|
|
4da755816a | ||
|
|
3ef0258faf | ||
|
|
293ceaee93 | ||
|
|
7b58d0fa0f | ||
|
|
bd68c1e056 | ||
|
|
200162d973 | ||
|
|
4b37a53f1c | ||
|
|
2d41de7ae0 | ||
|
|
bc5bc62c18 | ||
|
|
a0d680024e | ||
|
|
45013feea7 | ||
|
|
2abbe992dd | ||
|
|
d7081b3eeb | ||
|
|
a2f5289bd9 | ||
|
|
b366a4b942 | ||
|
|
27e92653fe | ||
|
|
80120b7b7d | ||
|
|
af8a618826 | ||
|
|
af0e554562 | ||
|
|
382cbb18db | ||
|
|
252b523155 | ||
|
|
4303b3b716 | ||
|
|
87715dc21a | ||
|
|
8552494fc1 | ||
|
|
1c2290b613 | ||
|
|
5f6060b801 | ||
|
|
b39958604d | ||
|
|
22ca1491bc | ||
|
|
690d7e30cf | ||
|
|
4fac2f1094 | ||
|
|
eb0935d577 | ||
|
|
425fedf55b | ||
|
|
a33de09ae6 | ||
|
|
5018e56103 | ||
|
|
de2c975ac2 | ||
|
|
d86e2c0f59 | ||
|
|
14023163b3 | ||
|
|
d85dc8213e | ||
|
|
f6a34055ac | ||
|
|
3beb0d9c12 | ||
|
|
60968fe6f1 | ||
|
|
517c3101c7 | ||
|
|
998f03ba68 | ||
|
|
f3b0e0cfc7 | ||
|
|
a65e6c888c | ||
|
|
cd939d3379 | ||
|
|
29e5aeff5c | ||
|
|
0e5cf81fca | ||
|
|
61f217c610 | ||
|
|
ccb1f491c7 | ||
|
|
49fdac9b4e | ||
|
|
6b6a9490f6 | ||
|
|
2c783e9876 | ||
|
|
ecd971c0e5 | ||
|
|
c48875a6fb | ||
|
|
a245ccb8b7 | ||
|
|
75a18debcb | ||
|
|
1a15d3bb55 | ||
|
|
ffdcae47e3 | ||
|
|
34a2bc8590 | ||
|
|
8ae4d00ccb | ||
|
|
9ed6d5e7fb | ||
|
|
c9243b241c | ||
|
|
9c364a64e3 | ||
|
|
b34306205c | ||
|
|
77f778dbf9 | ||
|
|
23fa4e4161 | ||
|
|
5b6fd0b829 | ||
|
|
8fa8d7739f | ||
|
|
dc552c7a29 | ||
|
|
cf84f453d3 | ||
|
|
daf32a24bc | ||
|
|
34ecfd9857 | ||
|
|
4838564460 | ||
|
|
3dd2f67ff3 | ||
|
|
0ccec3dde0 | ||
|
|
ad77879caa | ||
|
|
27999983cc | ||
|
|
48b039940d | ||
|
|
84dc7d9d82 | ||
|
|
70d6332bad | ||
|
|
aec0b0c86a | ||
|
|
099f6a88c2 | ||
|
|
00a5d42d3d | ||
|
|
28f4a2cb67 | ||
|
|
1ac18ba10e | ||
|
|
1503ee2dfa | ||
|
|
484b528d4f | ||
|
|
758293dc02 | ||
|
|
6cb316b33e | ||
|
|
1d31831e7d | ||
|
|
ee32b07995 | ||
|
|
81f04ca692 | ||
|
|
1756997608 | ||
|
|
ec11ea4ca7 | ||
|
|
a1ebf85e1b | ||
|
|
4b2a734cda | ||
|
|
97ae8f78a0 | ||
|
|
834665c3ba | ||
|
|
d969dd2d8d | ||
|
|
3cb727561c | ||
|
|
cbc89376d3 | ||
|
|
66aa29e3d4 | ||
|
|
5c72aca5ee | ||
|
|
8824815d6d | ||
|
|
ad72e5cbdf | ||
|
|
b2f183fc17 | ||
|
|
9e16bc203a | ||
|
|
83c6eac017 | ||
|
|
d5b277a536 | ||
|
|
77ce6c33e3 | ||
|
|
60f8d349cf | ||
|
|
f539c3df70 | ||
|
|
fe1cf1758c | ||
|
|
8c71c26487 | ||
|
|
2ce39d1fd4 | ||
|
|
88a994b712 | ||
|
|
24c8cc1a0c | ||
|
|
caf684394c | ||
|
|
4b74510450 | ||
|
|
0ddcfc423e | ||
|
|
b4ac56e204 | ||
|
|
3b19ac3942 | ||
|
|
af52cad4ea | ||
|
|
d88396c1e2 | ||
|
|
4c5f7b9451 | ||
|
|
e601cedbb8 | ||
|
|
fa12dc867f | ||
|
|
acfb6c4578 | ||
|
|
e52d637092 | ||
|
|
2fba055696 | ||
|
|
88bb176f3b | ||
|
|
499eeb275b | ||
|
|
61a8d679bb | ||
|
|
2bbde91625 | ||
|
|
9ce098ee06 | ||
|
|
927ae57d24 | ||
|
|
d65c2d3539 | ||
|
|
2d9d8f32dc | ||
|
|
1a30cd5f4a | ||
|
|
f54687f398 | ||
|
|
9f4b4c5de1 | ||
|
|
33bfa94229 | ||
|
|
61e5f190a6 | ||
|
|
a4946272d0 | ||
|
|
8abd99d134 | ||
|
|
97e8ab7c5b | ||
|
|
cb930a7ab5 | ||
|
|
610f1f7dd4 | ||
|
|
516d0b4db8 | ||
|
|
df61ae5c6f | ||
|
|
9f6584b55e | ||
|
|
e4b41f5bb0 | ||
|
|
8d0cea93e9 | ||
|
|
d294d07919 | ||
|
|
153eea2bd5 | ||
|
|
571e3c5c13 | ||
|
|
a2d268f3d6 | ||
|
|
02d456d7a0 | ||
|
|
b3626ad86f | ||
|
|
9b6eab451f | ||
|
|
72be9b5f04 | ||
|
|
35e94a7f65 | ||
|
|
8e02586cc9 | ||
|
|
0b5a640896 | ||
|
|
64b5089909 | ||
|
|
e0e09bfa45 | ||
|
|
8c7f9f2c65 | ||
|
|
aff5cea949 | ||
|
|
8bd99f699f | ||
|
|
b0513fff8a | ||
|
|
f10db9d91a | ||
|
|
9b5fb17068 | ||
|
|
608d713dca | ||
|
|
6d5fc8ca21 | ||
|
|
8c3145b70f | ||
|
|
12a6f3e938 | ||
|
|
2c59435762 | ||
|
|
7336bf5c55 | ||
|
|
2b21b97277 | ||
|
|
603bc99a2f | ||
|
|
426ede1c99 | ||
|
|
21506c1d1b | ||
|
|
0b48b18e7e | ||
|
|
c8f6d9c7a1 | ||
|
|
ed8eced9dd | ||
|
|
4a029b7853 | ||
|
|
6bd9e58ece | ||
|
|
cd075fc8a6 | ||
|
|
e733427920 | ||
|
|
107da0ae22 | ||
|
|
3629d7a32c | ||
|
|
71e1152cde | ||
|
|
d01ab7fd23 | ||
|
|
f46d0a720c | ||
|
|
d943b4993a | ||
|
|
4a4f57d6ac | ||
|
|
085da16268 | ||
|
|
306f6b0037 | ||
|
|
3780d290fa | ||
|
|
ad7dc66f90 | ||
|
|
258244fed8 | ||
|
|
5f7982540f | ||
|
|
b1c0de4b77 | ||
|
|
45b3fecfff | ||
|
|
9774ded4dd | ||
|
|
7ec027854e | ||
|
|
b457001b42 | ||
|
|
6fc6866eb4 | ||
|
|
299e5528bc | ||
|
|
881524a5e4 | ||
|
|
ffc405138d | ||
|
|
f6ff74af16 | ||
|
|
73a12d47de | ||
|
|
83165df7e5 | ||
|
|
30074be5a5 | ||
|
|
16715aa309 | ||
|
|
53f3802a81 | ||
|
|
07626b5cc2 | ||
|
|
9ea603caf2 | ||
|
|
18ab9b80d2 | ||
|
|
71c1cb4e12 | ||
|
|
0c049f39d9 | ||
|
|
096b807c37 | ||
|
|
9018af4765 | ||
|
|
323f28e306 | ||
|
|
e9dbd00f42 | ||
|
|
98b5252a2d | ||
|
|
8abf42023a | ||
|
|
b792238f3f | ||
|
|
2486e5e75f | ||
|
|
5f113f1610 | ||
|
|
592e9dc3ff | ||
|
|
314316cee0 | ||
|
|
9cf663063e | ||
|
|
9ea9210a4b | ||
|
|
2bf1a94608 | ||
|
|
7a0236ad29 | ||
|
|
4341a0c198 | ||
|
|
8071612c12 | ||
|
|
ea9cc3da04 | ||
|
|
0df588fffb | ||
|
|
a622311dbc | ||
|
|
1c95ac2470 | ||
|
|
6ef22166f9 | ||
|
|
1107e50108 | ||
|
|
c19355a7b2 | ||
|
|
65d415fade | ||
|
|
b37313d3f9 | ||
|
|
58fcbad20a | ||
|
|
b571f331ec | ||
|
|
2547d4b3e7 | ||
|
|
892786bf0c | ||
|
|
0c51f2b583 | ||
|
|
d5607ca195 | ||
|
|
48cdf17a4f | ||
|
|
669a8fae15 | ||
|
|
d592936873 | ||
|
|
87898dd2f1 | ||
|
|
15c227f568 | ||
|
|
8de41acfe1 | ||
|
|
9680effb9f | ||
|
|
40df846e3f | ||
|
|
eee94fbf84 | ||
|
|
813679ba25 | ||
|
|
2b9e16c9c9 | ||
|
|
f9bb7f7cff | ||
|
|
41e9569094 | ||
|
|
336ee07191 | ||
|
|
81e0a145f1 | ||
|
|
a95a968fa8 | ||
|
|
6c08941d8b | ||
|
|
e13a5b3865 | ||
|
|
9011d5604a | ||
|
|
78aa4ca137 | ||
|
|
93111842df | ||
|
|
88693f8adc | ||
|
|
f904b012e9 | ||
|
|
cebe0a151f | ||
|
|
8fffad0891 | ||
|
|
fb1263dad7 | ||
|
|
28c5d6c10d | ||
|
|
e7a6f6836e | ||
|
|
f8ffe016d1 | ||
|
|
ec322f0966 | ||
|
|
0674709fc6 | ||
|
|
d91ac7ef21 | ||
|
|
2f0c3c7baf | ||
|
|
be68407707 | ||
|
|
f799a25aeb | ||
|
|
8329d045f1 | ||
|
|
503b289240 | ||
|
|
35e3bbb862 | ||
|
|
8dc261da79 | ||
|
|
65676f8988 | ||
|
|
730da3aa27 | ||
|
|
ff8674bca9 | ||
|
|
c7cfef3b61 | ||
|
|
32188b1cc0 | ||
|
|
37479d8c0d | ||
|
|
f5c7d6576d | ||
|
|
38c422bbc0 | ||
|
|
39d4e5ea81 | ||
|
|
4191ad1cbf | ||
|
|
29287da37c | ||
|
|
98c780415b | ||
|
|
756af9c699 | ||
|
|
7f9076bb1d | ||
|
|
2404f1ab9a | ||
|
|
64bcfd09ec | ||
|
|
6251d22c6e | ||
|
|
de1b4937a1 | ||
|
|
d092dd79ba | ||
|
|
a3ba968386 | ||
|
|
5ca2b97128 | ||
|
|
c9daad1cfd | ||
|
|
0526d5b53a | ||
|
|
34163fb8e4 | ||
|
|
98d2d661a8 | ||
|
|
d9f0a0fd74 | ||
|
|
d20d043e0f | ||
|
|
ce186e1872 | ||
|
|
a14da92e1d | ||
|
|
2b54d850ea | ||
|
|
a63c1b19fc | ||
|
|
34f74cd6ef | ||
|
|
c0b8e62135 | ||
|
|
ea24166b8e | ||
|
|
178b229d66 | ||
|
|
276493da65 | ||
|
|
e85dadfef2 | ||
|
|
1c70efb350 | ||
|
|
6804a0c564 | ||
|
|
6eb7ecc2d4 | ||
|
|
f4956b06b6 | ||
|
|
9a2f7c2660 | ||
|
|
b6017c558a | ||
|
|
62ed6c6a58 | ||
|
|
24966c812f | ||
|
|
ea8dc2e0af | ||
|
|
010b285e67 | ||
|
|
774231f347 | ||
|
|
ff1dfd8f25 | ||
|
|
2741ac195d | ||
|
|
cf780e296e | ||
|
|
0361246db0 | ||
|
|
63355ecd5b | ||
|
|
0f54ba59fb | ||
|
|
b67b243ebd | ||
|
|
4c29c20613 | ||
|
|
4c2619d948 | ||
|
|
ea5662a4a2 | ||
|
|
7c1ce777a1 | ||
|
|
3bb85f2d61 | ||
|
|
93e36b3113 | ||
|
|
932fb670e3 | ||
|
|
54dce61a95 | ||
|
|
e686c0e0a2 | ||
|
|
05acf32f39 | ||
|
|
67064012c9 | ||
|
|
10e93c3b1b | ||
|
|
5b2ec1cbb5 | ||
|
|
972f701c5c | ||
|
|
51285efbc3 | ||
|
|
e2635705f9 | ||
|
|
7762107543 | ||
|
|
9123ad89c8 | ||
|
|
39f3d6cc80 | ||
|
|
af5665f67c | ||
|
|
c2175cc692 | ||
|
|
1ef371a467 | ||
|
|
58461ff5bb | ||
|
|
665959da90 | ||
|
|
8e63974f94 | ||
|
|
eae86f54ba | ||
|
|
30128a122d | ||
|
|
56e22a39ac | ||
|
|
6ceb330baa | ||
|
|
f30509c7ba | ||
|
|
9489136bd6 | ||
|
|
05cd3d736b | ||
|
|
29b17772e5 | ||
|
|
6151d63eac | ||
|
|
e57aa68854 | ||
|
|
73fa6d9bd0 | ||
|
|
da3c4b87c1 | ||
|
|
0eedc1b128 | ||
|
|
db339d40de | ||
|
|
f72aa7884d | ||
|
|
1434f1bb40 | ||
|
|
6bc92b26a7 | ||
|
|
63937d0fba | ||
|
|
7b86edaf7f | ||
|
|
49d4e565c6 | ||
|
|
44c54ecc16 | ||
|
|
6e543f825d | ||
|
|
591800a28a | ||
|
|
3340d74e3f | ||
|
|
446d863106 | ||
|
|
8b2d27a916 | ||
|
|
9b31f2257d | ||
|
|
252ec98e24 | ||
|
|
77dde5711b | ||
|
|
94e891880b | ||
|
|
c414b3fad2 | ||
|
|
c0b63ee7a8 | ||
|
|
6fbbfbaa43 | ||
|
|
4d663e4500 | ||
|
|
88ba2436c9 | ||
|
|
bfda22ad57 | ||
|
|
b888f17672 | ||
|
|
8aa07e6f12 | ||
|
|
79e973af65 | ||
|
|
83c6fd43e5 | ||
|
|
1323d89912 | ||
|
|
6b89996b0b | ||
|
|
8a9a60410c | ||
|
|
09e8678369 | ||
|
|
2ff7efadc2 | ||
|
|
5f3fcd2f37 | ||
|
|
7dffae4620 | ||
|
|
ce2e7305d4 | ||
|
|
4783b09f03 | ||
|
|
15c089ca9c | ||
|
|
e93a2ddd93 | ||
|
|
57b9c40609 | ||
|
|
caaadd63ec | ||
|
|
ca1b707fab | ||
|
|
a0499451a5 | ||
|
|
bbdf1a0b30 | ||
|
|
bcd5230854 | ||
|
|
5ec3d2a587 | ||
|
|
9cb2be6ec4 | ||
|
|
4e90ca0b26 | ||
|
|
8c4c72c8b5 | ||
|
|
8690d4d7a9 | ||
|
|
d5d9c9b483 | ||
|
|
0de8129457 | ||
|
|
13f70ad42c | ||
|
|
49d7b568df | ||
|
|
93afcd5c0c | ||
|
|
07ef3582f7 | ||
|
|
53c7ff14df | ||
|
|
6b993688fc | ||
|
|
b9e667e155 | ||
|
|
3cd26781b0 | ||
|
|
2d20ac6f29 | ||
|
|
944d467654 | ||
|
|
f68271c3d7 | ||
|
|
4eb5070753 | ||
|
|
f57adc9cc4 | ||
|
|
3e0b7bfeff | ||
|
|
faa78c67d8 | ||
|
|
a5ed02eb1c | ||
|
|
bfe11110aa | ||
|
|
d8b1d2382d | ||
|
|
7881a13610 | ||
|
|
aaa328950e | ||
|
|
d8eb1a0a4f | ||
|
|
e3379b31cb | ||
|
|
f36ce3d245 | ||
|
|
c73f3385c0 | ||
|
|
2eb8b84bbd | ||
|
|
b31378ad1a | ||
|
|
53213bb553 | ||
|
|
36b446c012 | ||
|
|
ca70fc8dc8 | ||
|
|
18ff889b56 | ||
|
|
fab3c0df3e | ||
|
|
7e7f97d72a | ||
|
|
3a1fcf2865 | ||
|
|
fd85416088 | ||
|
|
18a6a9e965 | ||
|
|
eed8d9c61b | ||
|
|
cbe12adbe6 | ||
|
|
84f8931ddd | ||
|
|
db135e502c | ||
|
|
6f205f8adf | ||
|
|
e81c1df19f | ||
|
|
71577c9b0e | ||
|
|
c88c245094 | ||
|
|
30ce04bc84 | ||
|
|
205d8f7db2 | ||
|
|
d70c22d73f | ||
|
|
920161bc07 | ||
|
|
cdafefebd3 | ||
|
|
f220300212 | ||
|
|
a2b86acc22 | ||
|
|
8e53028d78 | ||
|
|
852dfa4abf | ||
|
|
5d6845a58a | ||
|
|
be846e69c5 | ||
|
|
eef8c06b39 | ||
|
|
54aa0fdb11 | ||
|
|
3c62a5d2b4 | ||
|
|
0fe70b660a | ||
|
|
4f787ddb03 | ||
|
|
be33752eb3 | ||
|
|
a88dbbe5ba | ||
|
|
9b4144b815 | ||
|
|
bce5b44275 | ||
|
|
9f7320fa67 | ||
|
|
e9310ee8dd | ||
|
|
be93db51f7 | ||
|
|
8e3e8ada32 | ||
|
|
4564a6e8fd | ||
|
|
240190fa8f | ||
|
|
4ca7c0d978 | ||
|
|
190d0f9304 | ||
|
|
b8d2a363fb | ||
|
|
2382785240 | ||
|
|
476994595c | ||
|
|
287969f169 | ||
|
|
ca93c52161 | ||
|
|
b126b19e03 | ||
|
|
2c907060d4 | ||
|
|
e28c0d0b4a | ||
|
|
918df735ed | ||
|
|
294936d137 | ||
|
|
115da03a23 | ||
|
|
cc085755aa | ||
|
|
cb5e3efd8a | ||
|
|
274fd0b528 | ||
|
|
1431d1fecd | ||
|
|
dc3d6bfc43 | ||
|
|
c9d4745a59 | ||
|
|
921aef7f2c | ||
|
|
3c772f1974 | ||
|
|
bee9c7ee17 | ||
|
|
75ca438f2f | ||
|
|
9ea6eed22f | ||
|
|
56f13c53ce | ||
|
|
fbc63c957a | ||
|
|
6052a9382b | ||
|
|
f7f05cfc8b | ||
|
|
f4754d7481 | ||
|
|
7dc8d0cfec | ||
|
|
846c393168 | ||
|
|
78b7f228f5 | ||
|
|
84e03f8b16 | ||
|
|
288580aed7 | ||
|
|
de63bbcc86 | ||
|
|
80b8c284b4 | ||
|
|
ffff6d701f | ||
|
|
0b084ece08 | ||
|
|
28855d3fd1 | ||
|
|
f7d356dc95 | ||
|
|
104b332906 | ||
|
|
8b30852639 | ||
|
|
bdaa14c257 | ||
|
|
0234fb62fb | ||
|
|
687ca781ab | ||
|
|
3053c797e8 | ||
|
|
62d6709949 | ||
|
|
410abaf92c | ||
|
|
5e07c478f5 | ||
|
|
3eda53a97f | ||
|
|
4e25382901 | ||
|
|
fccb172e20 | ||
|
|
5a761242f5 | ||
|
|
aaaf1e14a5 | ||
|
|
3dcb9f6424 | ||
|
|
04621ff64a | ||
|
|
5053808058 | ||
|
|
9912c673bf | ||
|
|
4636f7be7b | ||
|
|
0ffc0a1959 | ||
|
|
e86032b129 | ||
|
|
87941a0975 | ||
|
|
3d6a40a683 | ||
|
|
9753470dcb | ||
|
|
a45e1b4781 | ||
|
|
54f52d37ca | ||
|
|
6e295a7afb | ||
|
|
692cd7adae | ||
|
|
7794547d30 | ||
|
|
35eaaee627 | ||
|
|
8029901ed7 | ||
|
|
342c55043d | ||
|
|
3cf363667c | ||
|
|
2f8993995b | ||
|
|
8e9c541eba | ||
|
|
7ed310ffd2 | ||
|
|
bc1b020e95 | ||
|
|
5fdff16b1e | ||
|
|
43e22a9028 | ||
|
|
e102bf9eed | ||
|
|
bf3ee5c158 | ||
|
|
2e99e3d9c5 | ||
|
|
3d7029493a | ||
|
|
a6ad2bbc5b | ||
|
|
4dc17773c4 | ||
|
|
e5d66f46c6 | ||
|
|
d2fc24e792 | ||
|
|
ec52c81f46 | ||
|
|
0eb0c4cc83 | ||
|
|
b54e6bc933 | ||
|
|
83f891138d | ||
|
|
c621940b0f | ||
|
|
a27b0a0a1e | ||
|
|
23a52dbc9a | ||
|
|
f8a57c7885 | ||
|
|
a295d0ad1e | ||
|
|
190a2147cc | ||
|
|
49573b9e72 | ||
|
|
e60b30ed44 | ||
|
|
5c10ddf54c | ||
|
|
600ece819f | ||
|
|
6bc7a3d8aa | ||
|
|
0af07e5352 | ||
|
|
8732f54c64 | ||
|
|
437d5d76e9 | ||
|
|
7378be05d3 | ||
|
|
75febe4b75 | ||
|
|
f6d8d93a1b | ||
|
|
130310fddd | ||
|
|
8bd312046d | ||
|
|
9cc96f45d0 | ||
|
|
c18fc1984d | ||
|
|
3b651076d1 | ||
|
|
352396bdc2 | ||
|
|
3c9b0273d4 | ||
|
|
5122f9989c | ||
|
|
8ebd22da96 | ||
|
|
c2d55013a6 | ||
|
|
25eda1378e | ||
|
|
dfac7bde9c | ||
|
|
348168d6c0 | ||
|
|
a1c772c8d8 | ||
|
|
f1ce1b5361 | ||
|
|
ebdc7d70a1 | ||
|
|
c5947c23bb | ||
|
|
eeb1852d95 | ||
|
|
2f28a635a9 | ||
|
|
d524c77560 | ||
|
|
0025d94aa4 | ||
|
|
bb5ae2922d | ||
|
|
fbe9fe1ba8 | ||
|
|
007194d674 | ||
|
|
cdd74505d7 | ||
|
|
0aa023189d | ||
|
|
79aa10dfcb | ||
|
|
9ecfda0110 | ||
|
|
b89e26c460 | ||
|
|
97e5952544 | ||
|
|
8627328047 | ||
|
|
ec0c912c6f | ||
|
|
953de3f1f2 | ||
|
|
e1264de514 | ||
|
|
5abd2b400c | ||
|
|
2c2a103676 | ||
|
|
44af799b66 | ||
|
|
ec22ba3248 | ||
|
|
7fcc05c037 | ||
|
|
edf17d0a46 | ||
|
|
9cccd0bfc9 | ||
|
|
e57c010e3d | ||
|
|
4dfcf1c1c8 | ||
|
|
8d7786e97d | ||
|
|
2cb519cd06 | ||
|
|
bc46ad4331 | ||
|
|
be08e8f23b | ||
|
|
f937809903 | ||
|
|
c0f32c942d | ||
|
|
39c5c830bb | ||
|
|
83c8953d1b | ||
|
|
4b100a5a64 | ||
|
|
b7e50e0b3a | ||
|
|
6933052de7 | ||
|
|
22d945f7b7 | ||
|
|
b81c5628ce | ||
|
|
cdb4576bd7 | ||
|
|
4859cd7dcf | ||
|
|
3f70593ca8 | ||
|
|
676288e6c0 | ||
|
|
1aa3e0cc5a | ||
|
|
b0f8064d0d | ||
|
|
e5e85732d4 | ||
|
|
f97c1ef0d9 | ||
|
|
83cf815160 | ||
|
|
fea62a529b | ||
|
|
2cff5ae2bb | ||
|
|
8aa29f5340 | ||
|
|
8051bfef1d | ||
|
|
85bc79ab1b | ||
|
|
97e5588184 | ||
|
|
e622fdc885 | ||
|
|
4f81e1debe | ||
|
|
9ccfd852d8 | ||
|
|
9df57a47d5 | ||
|
|
f2cadb7278 | ||
|
|
3f6e7cb84c | ||
|
|
4ed4ce8240 | ||
|
|
4373956a3f | ||
|
|
36fb99a00d | ||
|
|
8f5d325c4d | ||
|
|
8d8b6da0bf | ||
|
|
3a61d260d7 | ||
|
|
5bc433d1c8 | ||
|
|
a0e4bbac6f | ||
|
|
d44d8cc53d | ||
|
|
1305969247 | ||
|
|
94becebafa | ||
|
|
8add433966 | ||
|
|
c5b289fb1f | ||
|
|
f85ce180ed | ||
|
|
8101fa1c92 | ||
|
|
8a091e0205 | ||
|
|
edc3a54ad3 | ||
|
|
a3cd7d1068 | ||
|
|
337441b8de | ||
|
|
bd78a66bd4 | ||
|
|
96f9618081 | ||
|
|
cf7be2d601 | ||
|
|
598732915e | ||
|
|
99c18396ab | ||
|
|
6d5dd81066 | ||
|
|
7cd9451a22 | ||
|
|
29b5a7c5c2 | ||
|
|
393a04165e | ||
|
|
e97e834a5b | ||
|
|
bec685682b | ||
|
|
34f119ca23 | ||
|
|
09ed1b1f9e | ||
|
|
fcb109f46d | ||
|
|
f30823e4ac | ||
|
|
c04885449d | ||
|
|
8c31e47eeb | ||
|
|
d8ee5c180b | ||
|
|
823f4a26b3 | ||
|
|
a05e8a446d | ||
|
|
21126f766c | ||
|
|
8f46ead756 | ||
|
|
75652fc2c4 | ||
|
|
7cdc46f007 | ||
|
|
ed9f9625ae | ||
|
|
7b60d3dab9 | ||
|
|
a6993fa489 | ||
|
|
bc2774bde4 | ||
|
|
d10dc1e8d3 | ||
|
|
48556de92b | ||
|
|
7f6b477d2e | ||
|
|
11d8c67d12 | ||
|
|
fd2a4029e7 | ||
|
|
4467928845 | ||
|
|
cc85a00bfd | ||
|
|
15d58ecdcd | ||
|
|
08d93b9a78 | ||
|
|
a75a999e3b | ||
|
|
684ef709f5 | ||
|
|
2e98dd09e7 | ||
|
|
6635425bbc | ||
|
|
5d4bdc5697 | ||
|
|
0fdb286005 | ||
|
|
59a8493aa7 | ||
|
|
25378e894b | ||
|
|
3b9fea20b6 | ||
|
|
c6bb8f09ca | ||
|
|
4a9bd84bf0 | ||
|
|
c02522b0fe | ||
|
|
c2a71ef756 | ||
|
|
e669110cf4 | ||
|
|
f4cf31c13d | ||
|
|
7b23a5dcce | ||
|
|
b2fda0c79d | ||
|
|
5af96f5ccb | ||
|
|
ca445ac178 | ||
|
|
16fb31b6eb | ||
|
|
a1ff325b7b | ||
|
|
5eaec4c841 | ||
|
|
ffcc34c4f9 | ||
|
|
2dbe33e769 | ||
|
|
60c7db0733 | ||
|
|
0ed95bbdf1 | ||
|
|
c901bcf9b7 | ||
|
|
0ccf915a18 | ||
|
|
52b1c0a926 | ||
|
|
399865e6c8 | ||
|
|
54aeab1524 | ||
|
|
91f83277e2 | ||
|
|
c937cb2f07 | ||
|
|
ebd150e473 | ||
|
|
9218f7b82c | ||
|
|
edaf7aee5d | ||
|
|
43c18d0f4d | ||
|
|
65d85f7479 | ||
|
|
476e23db5b | ||
|
|
abaa5d87f6 | ||
|
|
ce3e7e623c | ||
|
|
3fd35a9c18 | ||
|
|
f170ae741e | ||
|
|
03562b037d | ||
|
|
472312709a | ||
|
|
b68463249e | ||
|
|
740a2da702 | ||
|
|
85c8e56417 | ||
|
|
481ef56e74 | ||
|
|
008795770f | ||
|
|
834fb7e317 | ||
|
|
da4827f287 | ||
|
|
9f4439583d | ||
|
|
69981e4d78 | ||
|
|
a857c6a88f | ||
|
|
e8d19439f8 | ||
|
|
56216250a7 | ||
|
|
bea331db26 | ||
|
|
bc4e833a47 | ||
|
|
83f399fffc | ||
|
|
5214436d18 | ||
|
|
8603250d73 | ||
|
|
9a8a099701 | ||
|
|
a5a0c8c837 | ||
|
|
604bcd5874 | ||
|
|
d29f9409bf | ||
|
|
b5a0f5910d | ||
|
|
ccb2600e67 | ||
|
|
f06e21ff5a | ||
|
|
bb0817a2ec | ||
|
|
6911f865ca | ||
|
|
fe28b2732c | ||
|
|
e8e8c37496 | ||
|
|
ef0f1ca1e7 | ||
|
|
31ca34b954 | ||
|
|
c4e6a2f0a8 | ||
|
|
b56845e200 | ||
|
|
d7a1fee781 | ||
|
|
b1f802c42d | ||
|
|
5f022e6e1f | ||
|
|
392cbb817e | ||
|
|
130399a1e7 | ||
|
|
37d5531737 | ||
|
|
f0b6cbaf89 | ||
|
|
707b173e77 | ||
|
|
4381bb5026 | ||
|
|
5850ad1217 | ||
|
|
806d598a04 | ||
|
|
e737e5c950 | ||
|
|
bbcde2f52b | ||
|
|
f6ef77429c | ||
|
|
44491c1514 | ||
|
|
71a6cf4ee6 | ||
|
|
744ce6966f | ||
|
|
d25cec02c2 | ||
|
|
f02bf37fd3 | ||
|
|
304b9d41d7 | ||
|
|
2d6af89f60 | ||
|
|
d6425973e2 | ||
|
|
d5ad56c4de | ||
|
|
4f1f2cc99e | ||
|
|
da439dd127 | ||
|
|
1375d01bdf | ||
|
|
7b9db07f13 | ||
|
|
f2f26136c1 | ||
|
|
0f60ac5acf | ||
|
|
c28f19fe8a | ||
|
|
09a6dbc755 | ||
|
|
3bc0e0fc8a | ||
|
|
eb0e187a54 | ||
|
|
a788d30f34 | ||
|
|
591dfc961e | ||
|
|
809e12b034 | ||
|
|
1669d174e1 | ||
|
|
3cfd28de43 | ||
|
|
4888207eca | ||
|
|
294cb96107 | ||
|
|
b648fa2b70 | ||
|
|
ab99122211 | ||
|
|
dd014fee88 | ||
|
|
c81f864de3 | ||
|
|
90fe7dceec | ||
|
|
3a568096f2 | ||
|
|
94e694fc61 | ||
|
|
bdfa6e4af5 | ||
|
|
8e64ffb4f6 | ||
|
|
6c162643cb | ||
|
|
ff7742bca3 | ||
|
|
9685884279 | ||
|
|
a8b0d7f999 | ||
|
|
dd84233085 | ||
|
|
fe3eac07f4 | ||
|
|
0948686989 | ||
|
|
4c82970319 | ||
|
|
fe2fede8ed | ||
|
|
b23b1e5f1f | ||
|
|
dca66c8de8 | ||
|
|
49090014cc | ||
|
|
fa4f100705 | ||
|
|
bbf68cd9a8 | ||
|
|
bbc5f6588a | ||
|
|
b3632a6a35 | ||
|
|
3943c51bb4 | ||
|
|
4c19ddde69 | ||
|
|
d8f0f1a1d3 | ||
|
|
121c0d89f2 | ||
|
|
badfb9088e | ||
|
|
ff392fee14 | ||
|
|
a13693161a | ||
|
|
4b8ac81669 | ||
|
|
219a704ee0 | ||
|
|
3996cd1f08 | ||
|
|
c636b0a0ec | ||
|
|
aae9f671f0 | ||
|
|
aec6e901ee | ||
|
|
f49be25288 | ||
|
|
f9a96126e1 | ||
|
|
8c9f58b939 | ||
|
|
8a7e787f42 | ||
|
|
148dcc084d | ||
|
|
15b1cbd762 | ||
|
|
e9b7ca3697 | ||
|
|
1b03e9a3ee | ||
|
|
2b951e3f61 | ||
|
|
f51e064cf6 | ||
|
|
9640e93895 | ||
|
|
d5bd22040c | ||
|
|
dcdcb7521a | ||
|
|
4058c63884 | ||
|
|
dd34548cc6 | ||
|
|
a6b5211fa7 | ||
|
|
4e89b9c363 | ||
|
|
f3e267d2d0 | ||
|
|
2fd87dc1f1 | ||
|
|
40b6b77cfa | ||
|
|
d0c61dbf4d | ||
|
|
1cd5a3fcf7 | ||
|
|
af81cf2c50 | ||
|
|
04373c5d1b | ||
|
|
e9cdb0f78d | ||
|
|
021b933ad3 | ||
|
|
9fd067c9da | ||
|
|
d3f3f3bdf7 | ||
|
|
72727dacd8 | ||
|
|
caeb2bc4e3 | ||
|
|
13974b601f | ||
|
|
cbc6aea8b4 | ||
|
|
290a2f7ccd | ||
|
|
349e80f206 | ||
|
|
5c1e001a73 | ||
|
|
f312318fab | ||
|
|
dc04b7cf09 | ||
|
|
77a8a46d8e | ||
|
|
95c7cd55c2 | ||
|
|
5f0ef5e0e8 | ||
|
|
1f26c603e0 | ||
|
|
7e2227ad42 | ||
|
|
9b4899da07 | ||
|
|
83c88ac0c6 | ||
|
|
44623065b4 | ||
|
|
47c7c8177d | ||
|
|
bde7a5ff59 | ||
|
|
a8ad8644c8 | ||
|
|
4e91bb88a5 | ||
|
|
f60a90e2da | ||
|
|
784dc0f6a7 | ||
|
|
e80e627fba | ||
|
|
d5987c51c9 | ||
|
|
5ced441b17 | ||
|
|
57801202fd | ||
|
|
a019399c3c | ||
|
|
e6f610a86c | ||
|
|
7ef528bbde | ||
|
|
a351a29bf3 | ||
|
|
983d258bce | ||
|
|
f6d38dd5e0 | ||
|
|
d51245aada | ||
|
|
56cf51f0f9 | ||
|
|
eb40fb9c5d | ||
|
|
085da0cea7 | ||
|
|
5539b19938 | ||
|
|
94feb762ca | ||
|
|
40b59d5a5a | ||
|
|
9ffd147470 | ||
|
|
3fea4ad2ba | ||
|
|
1ab5536879 | ||
|
|
9690a89a6d | ||
|
|
8f895f4349 | ||
|
|
980c0aa1d7 | ||
|
|
52fd6ca513 | ||
|
|
eb5dd2ff2e | ||
|
|
7ca35452eb | ||
|
|
dd781e256c | ||
|
|
551a7ab82f | ||
|
|
2901287d9e | ||
|
|
2b714967aa | ||
|
|
e8734ef1e7 | ||
|
|
14b9f9509f | ||
|
|
b1f973d304 | ||
|
|
2f17bfd71c | ||
|
|
b6670ee23a | ||
|
|
f1036df1f6 | ||
|
|
5c3e815757 | ||
|
|
55e780d885 | ||
|
|
d502df7d56 | ||
|
|
beb6cc8c0f | ||
|
|
c99db5e75c | ||
|
|
65cd70a85b | ||
|
|
5166eab5ee | ||
|
|
232f6f158d | ||
|
|
2a07ceba62 | ||
|
|
3e4b8c7dd4 | ||
|
|
26138e213f | ||
|
|
d82796e3ad | ||
|
|
cdcb81c867 | ||
|
|
5669e8f060 | ||
|
|
d84a40b4dc | ||
|
|
591be43763 | ||
|
|
97d0686354 | ||
|
|
e2da05b197 | ||
|
|
4f0052043d | ||
|
|
cfc1d1a2db | ||
|
|
9957e6ef17 | ||
|
|
6a02c8383c | ||
|
|
bc0a4ee68d | ||
|
|
1ca615da77 | ||
|
|
7da0cee29a | ||
|
|
f25bccd19f | ||
|
|
4e5a2e012c | ||
|
|
c9ee2a92a3 | ||
|
|
95a7938328 | ||
|
|
baedcdb2c1 | ||
|
|
bc06b3671a | ||
|
|
a9172811ca | ||
|
|
91b1fd6d07 | ||
|
|
bab7b8b9ed | ||
|
|
1b7fb96ca8 | ||
|
|
6d84b8c02f | ||
|
|
3e3749f011 | ||
|
|
52384fb3a5 | ||
|
|
e401670087 | ||
|
|
ae0b4c59cc | ||
|
|
0a8dc8afcc | ||
|
|
d59b94df66 | ||
|
|
e28502454b | ||
|
|
bbf73f0937 | ||
|
|
592519c45c | ||
|
|
cc904ba9dc | ||
|
|
1679ba6719 | ||
|
|
07fadd4a6c | ||
|
|
57e1ff39e0 | ||
|
|
51e259c198 | ||
|
|
de334b003d | ||
|
|
a61ff12390 | ||
|
|
f4697be159 | ||
|
|
3835fe3960 | ||
|
|
76c374ef06 | ||
|
|
57d24bd948 | ||
|
|
deff14dfd8 | ||
|
|
0a479be370 | ||
|
|
ba6a2e3fd2 | ||
|
|
c3a395a41e | ||
|
|
a3136a19e9 | ||
|
|
b631568003 | ||
|
|
1d0c03eca4 | ||
|
|
eb30525a26 | ||
|
|
3e66ea3f56 | ||
|
|
9f1189e606 | ||
|
|
698927bed4 | ||
|
|
8fd02ee8dd | ||
|
|
f3154e8f5e | ||
|
|
878af163a9 | ||
|
|
da8341d014 | ||
|
|
95c33e88ed | ||
|
|
fed8369a5f | ||
|
|
d85806e3d5 | ||
|
|
097660ce53 | ||
|
|
b08cfbbb35 | ||
|
|
97ee3c47a0 | ||
|
|
05918de6ab | ||
|
|
8d7abd8298 | ||
|
|
727f4c3bb5 | ||
|
|
7372ad0cc4 | ||
|
|
ca6884dbca | ||
|
|
1ebb0ac5fb | ||
|
|
bf0e53f13b | ||
|
|
8888a960c0 | ||
|
|
04be41aac5 | ||
|
|
fd1313d49f | ||
|
|
3599dba5c3 | ||
|
|
67edc437d2 | ||
|
|
7a39d9240c | ||
|
|
6d2d9aed7e | ||
|
|
3c2e5f22b6 | ||
|
|
ddb6348bfd | ||
|
|
d70c5df5a0 | ||
|
|
b2799d198c | ||
|
|
f2fa2038b1 | ||
|
|
bfd59e64ea | ||
|
|
60e05e270a | ||
|
|
242b9a6af9 | ||
|
|
e7e8ad9bdc | ||
|
|
0e5d164a8a | ||
|
|
7293a8d3c0 | ||
|
|
097a4647a8 | ||
|
|
0942a7f3ff | ||
|
|
2df4370477 | ||
|
|
7243963106 | ||
|
|
9f17654052 | ||
|
|
1a65d7061d | ||
|
|
292363eb65 | ||
|
|
07e79ce61a | ||
|
|
1cf7dac82b | ||
|
|
6003b2902f | ||
|
|
ff0023a889 | ||
|
|
8c6bf5a1cc | ||
|
|
61c97ab940 | ||
|
|
b0a1b3b5ca | ||
|
|
4c7af01edc | ||
|
|
0d431213cd | ||
|
|
ad9dd9a2e2 | ||
|
|
c7895ed905 | ||
|
|
57d7979d51 | ||
|
|
61f6871cd1 | ||
|
|
406048f7b9 | ||
|
|
6dd5b0fe45 | ||
|
|
fd4bc5e3cf | ||
|
|
3bab2d8957 | ||
|
|
af2f5cd2e1 | ||
|
|
d4bb32da82 | ||
|
|
08aca6ca61 | ||
|
|
697b3c6772 | ||
|
|
878525ced8 | ||
|
|
418531736b | ||
|
|
6c175440c6 | ||
|
|
723a2f2008 | ||
|
|
ea9b9fbf17 | ||
|
|
303e257841 | ||
|
|
07b6a8ba92 | ||
|
|
e216e18368 | ||
|
|
e57c5b4bc2 | ||
|
|
f772d6ddeb | ||
|
|
cd37c301d9 | ||
|
|
f5fa26639e | ||
|
|
45ec73c115 | ||
|
|
13952ebd8b | ||
|
|
2df0007a10 | ||
|
|
89bc5db3e1 | ||
|
|
4021deec7f | ||
|
|
a3977f37f7 | ||
|
|
766c12242e | ||
|
|
a82b951aff | ||
|
|
997a94eecc | ||
|
|
635535aac2 | ||
|
|
25314fd91a | ||
|
|
9a06a5758d | ||
|
|
e5a2a9644f | ||
|
|
e0e7211852 | ||
|
|
cdaeb4d176 | ||
|
|
07aa2e280d | ||
|
|
6c4bc67ff3 | ||
|
|
d9f0cc27ef | ||
|
|
59aec9d289 | ||
|
|
451f4c503d | ||
|
|
cd82089d4d | ||
|
|
3db8b63cde | ||
|
|
6edf5d1e4f | ||
|
|
a23fa6fadd | ||
|
|
51eb77e409 | ||
|
|
691326cea8 | ||
|
|
3cac1238ed | ||
|
|
b04512a4f6 | ||
|
|
3a4d0549aa | ||
|
|
d7467f5dc3 | ||
|
|
141902b86d | ||
|
|
5aa680fc93 | ||
|
|
a790117f5a | ||
|
|
2a5a52c810 | ||
|
|
2156f4c2f3 | ||
|
|
2585460286 | ||
|
|
1b4af66986 | ||
|
|
0324bac044 | ||
|
|
2bfbe1ca27 | ||
|
|
60584228d9 | ||
|
|
44e34f7b11 | ||
|
|
7912050647 | ||
|
|
2231abd407 | ||
|
|
8d17ba4d66 | ||
|
|
8244bdb440 | ||
|
|
bc3b7ffd86 | ||
|
|
e22cb62493 | ||
|
|
e923aa1f72 | ||
|
|
68a21f1fbb | ||
|
|
74209e2607 | ||
|
|
1a38271104 | ||
|
|
e5ab5d6a5a | ||
|
|
2745ff727f | ||
|
|
24ea383937 | ||
|
|
9cb11d4d5f | ||
|
|
7202ea3340 | ||
|
|
d234ea01d0 | ||
|
|
e246cd37dd | ||
|
|
0a4ddedac9 | ||
|
|
0e4b80b002 | ||
|
|
a460a0dc44 | ||
|
|
f7212112b8 | ||
|
|
86d55b32a6 | ||
|
|
4b0d1aac15 | ||
|
|
fb7c06aa01 | ||
|
|
5c41be3470 | ||
|
|
a78df1a152 | ||
|
|
34e8db0fe3 | ||
|
|
70faecb8b5 | ||
|
|
237f278479 | ||
|
|
e766a00a12 | ||
|
|
ea03637ec1 | ||
|
|
d0f6d47b14 | ||
|
|
5bf1658d9a | ||
|
|
4ce9fb5b5a | ||
|
|
f80f02da73 | ||
|
|
52ece833a7 | ||
|
|
1ad6dde146 | ||
|
|
64bcfd23bd | ||
|
|
d659447879 | ||
|
|
e73eb55d75 | ||
|
|
a52cea29f4 | ||
|
|
3d91e59386 | ||
|
|
393b1d7674 | ||
|
|
c29a6b95ae | ||
|
|
567a4e8361 | ||
|
|
4d3e4c1a15 | ||
|
|
afeecdf4af | ||
|
|
4fe7105e2f | ||
|
|
d7c3a53f2d | ||
|
|
58e7574a6a | ||
|
|
94ab703c7c | ||
|
|
90350786e6 | ||
|
|
8038e1ee7d | ||
|
|
65f1a5fed6 | ||
|
|
64b94ab124 | ||
|
|
c03cb1de5e | ||
|
|
f0004290b4 | ||
|
|
7e1b49a742 | ||
|
|
d26c56e467 | ||
|
|
a51e0c26e5 | ||
|
|
180d39534c | ||
|
|
203d0e870a | ||
|
|
bed7d09764 | ||
|
|
3c762c9a02 | ||
|
|
ba5f0fb70b | ||
|
|
ebc3e05f23 | ||
|
|
674ad899f9 | ||
|
|
d945eb6fcf | ||
|
|
fb170e3c42 | ||
|
|
3658cca3e5 | ||
|
|
5e78a41b75 | ||
|
|
c23d8efe08 | ||
|
|
0d3d4b60ce | ||
|
|
38091c3c25 | ||
|
|
f0cd04ebc3 | ||
|
|
3295c18829 | ||
|
|
ccb309fd92 | ||
|
|
8206a26267 | ||
|
|
8c7b91bd79 | ||
|
|
061460f978 | ||
|
|
a47c7b8f27 | ||
|
|
a859908e83 | ||
|
|
86759d2f9c | ||
|
|
58c145ba08 | ||
|
|
c0bb3da22f | ||
|
|
031338d84d | ||
|
|
2a619f3fba | ||
|
|
1c145cbc09 | ||
|
|
5d71fde51b | ||
|
|
eec29f5c68 | ||
|
|
8fbd7acf2a | ||
|
|
7b824e6178 | ||
|
|
24c69a26ea | ||
|
|
7b03c95cfd | ||
|
|
d2b09ecfda | ||
|
|
4cdd42f391 | ||
|
|
4d0b3b287f | ||
|
|
0d7f784773 | ||
|
|
66ad54fbf0 | ||
|
|
33782859b8 | ||
|
|
3d294d5771 | ||
|
|
c889a83707 | ||
|
|
6a55af1950 | ||
|
|
a85cf2c2af | ||
|
|
4c0e3f92d3 | ||
|
|
e3354a805e | ||
|
|
ceb9a935de | ||
|
|
d3ad111b85 | ||
|
|
de740b1d9d | ||
|
|
7c155545ae | ||
|
|
a7597b2a6d | ||
|
|
22916c1904 | ||
|
|
a72e479e50 | ||
|
|
60c1776994 | ||
|
|
fbddb24728 | ||
|
|
e716f9e63a | ||
|
|
51c49d7c1b | ||
|
|
17a1b11f66 | ||
|
|
8a8e68146f | ||
|
|
dbe5dea525 | ||
|
|
604af822aa | ||
|
|
435759c21c | ||
|
|
847f094975 | ||
|
|
b939d86975 | ||
|
|
3e5f56b19b | ||
|
|
d81f42e3e9 | ||
|
|
4fb3d34786 | ||
|
|
9a0a527b2b | ||
|
|
ba8fb23b9d | ||
|
|
99790695a2 | ||
|
|
e3a86bb150 | ||
|
|
0ac1e885bd | ||
|
|
36aca1d39b | ||
|
|
d0192b4cab | ||
|
|
a3a7a585d7 | ||
|
|
b437d858f0 | ||
|
|
06f78178da | ||
|
|
06412cc5c0 | ||
|
|
1b86b9170f | ||
|
|
6e71d34390 | ||
|
|
0e880f2479 | ||
|
|
400a35de1d | ||
|
|
c47bfce010 | ||
|
|
73db7b399e | ||
|
|
f8a47c3c6a | ||
|
|
ee119de6c4 | ||
|
|
f37c0ed612 | ||
|
|
6b1d671aed | ||
|
|
44bbcd7033 | ||
|
|
e56dcba9d3 | ||
|
|
e8a2130094 | ||
|
|
be13c14376 | ||
|
|
6f0d0bed0b |
100
Makefile.gcj
Normal file
100
Makefile.gcj
Normal file
@@ -0,0 +1,100 @@
|
||||
# Makefile for building native I2P binaries and libraries with GCJ
|
||||
#
|
||||
# WARNING: Do not use this yet, as it may explode (etc).
|
||||
#
|
||||
GCJ=gcj #/usr/local/gcc-4.0.2/bin/gcj
|
||||
EXTRA_LD_PATH= #/usr/local/gcc-4.0.2/lib
|
||||
ANT=ant #/opt/apache-ant-1.6.5/bin/ant
|
||||
ANT_TARGET=buildclean
|
||||
NATIVE_DIR=native
|
||||
|
||||
##
|
||||
# Define what jar files get into libi2p.so. The current setup is
|
||||
# *incredibly* lazy, throwing everything in the .so, rather than
|
||||
# give each .jar file its own .so.
|
||||
# i2p.jar: base SDK
|
||||
# mstreaming.jar: streaming API
|
||||
# streaming.jar: full streaming lib implementation
|
||||
# i2ptunnel.jar: I2PTunnel proxy
|
||||
# sam.jar: SAM bridge and API
|
||||
# i2psnark.jar: bittorrent client
|
||||
# router.jar: full I2P router
|
||||
# jbigi.jar: collection of native optimized GMP routines for crypto
|
||||
JAR_BASE=i2p.jar mstreaming.jar streaming.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
|
||||
JAR_ROUTER=router.jar
|
||||
JAR_JBIGI=jbigi.jar
|
||||
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
|
||||
JAR_CONSOLE=\
|
||||
javax.servlet.jar \
|
||||
commons-el.jar \
|
||||
commons-logging.jar \
|
||||
jasper-runtime.jar \
|
||||
ant-apache-bcel.jar \
|
||||
ant.jar \
|
||||
jasper-compiler.jar \
|
||||
org.mortbay.jetty.jar \
|
||||
routerconsole.jar
|
||||
JAR_SUCKER=jdom.jar rome-0.7.jar sucker.jar
|
||||
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
|
||||
# unfortunately, its not quite ready for most end users, as the
|
||||
# ${JAR_CONSOLE} fails to compile with:
|
||||
# org/apache/commons/logging/impl/LogKitLogger.java: In class 'org.apache.commons.logging.impl.LogKitLogger':
|
||||
# .../LogKitLogger.java: In constructor '(java.lang.String)':
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: confused by earlier errors, bailing out
|
||||
|
||||
#${JAR_CONSOLE}\
|
||||
#${JAR_XML} \
|
||||
#${JAR_SUCKER}
|
||||
#${JAR_CONSOLE}
|
||||
|
||||
SYSTEM_PROPS=-DloggerFilenameOverride=logs/log-router-@.txt \
|
||||
-Dorg.mortbay.http.Version.paranoid=true \
|
||||
-Dorg.mortbay.util.FileResource.checkAliases=false \
|
||||
-Dorg.mortbay.xml.XmlParser.NotValidating=true
|
||||
#SYSTEM_PROPS=-Di2p.weakPRNG=true
|
||||
OPTIMIZE=-O2
|
||||
#OPTIMIZE=-O3
|
||||
|
||||
LD_LIBRARY_PATH=${EXTRA_LD_PATH}:.
|
||||
|
||||
all: jars native
|
||||
@echo "* Build complete"
|
||||
|
||||
jars:
|
||||
@${ANT} ${ANT_TARGET}
|
||||
|
||||
clean: native_clean
|
||||
|
||||
native: native_clean native_shared
|
||||
@echo "* Native code build in ${NATIVE}"
|
||||
|
||||
native_clean:
|
||||
@rm -rf ${NATIVE_DIR}
|
||||
@mkdir ${NATIVE_DIR}
|
||||
|
||||
native_shared: libi2p.so
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.Fortuna
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
|
||||
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
|
||||
@echo " and launching the appropriate proxies"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@echo "* i2psnark is an anonymous bittorrent client"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@echo "* i2prouter is the main I2P router"
|
||||
@echo " it can be used, and while the router console won't load,"
|
||||
@echo " i2ptunnel will, so it will start all the proxies defined in i2ptunnel.config"
|
||||
|
||||
libi2p.so:
|
||||
@echo "* Building libi2p.so"
|
||||
@(cd build ; ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@ls -l ${NATIVE_DIR}/libi2p.so
|
||||
@echo "* libi2p.so built"
|
||||
43
apps/addressbook/README.txt
Normal file
43
apps/addressbook/README.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
addressbook v2.0.2 - A simple name resolution mechanism for I2P
|
||||
|
||||
addressbook is a simple implementation of subscribable address books for I2P.
|
||||
Addresses are stored in userhosts.txt and a second copy of the address book is
|
||||
placed on your eepsite as hosts.txt.
|
||||
|
||||
subscriptions.txt contains a list of urls to check for new addresses.
|
||||
Since the urls are checked in order, and conflicting addresses are not added,
|
||||
addressbook.subscriptions can be considered to be ranked in order of trust.
|
||||
|
||||
The system created by addressbook is similar to the early days of DNS,
|
||||
when everyone ran a local name server. The major difference is the lack of
|
||||
authority. Name cannot be guaranteed to be globally unique, but in practise
|
||||
they probably will be, for a variety of social reasons.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
i2p with a running http proxy
|
||||
|
||||
Installation and Usage
|
||||
**********************
|
||||
|
||||
1. Unzip addressbook-%ver.zip into your i2p directory.
|
||||
2. Restart your router.
|
||||
|
||||
The addressbook daemon will automatically run while the router is up.
|
||||
|
||||
Aside from the daemon itself, the other elements of the addressbook interface
|
||||
are the config.txt, myhosts.txt, and subscriptions.txt files found in the addressbook
|
||||
directory.
|
||||
|
||||
config.txt is the configuration file for addressbook.
|
||||
|
||||
myhosts.txt is the addressbook master address book. Addresses placed in this file
|
||||
take precidence over those in the router address book and in remote address books.
|
||||
If changes are made to this file, they will be reflected in the router address book
|
||||
and published address book after the next update. Do not make changes directly to the
|
||||
router address book, as they could be lost during an update.
|
||||
|
||||
subscriptions.txt is the subscription list for addressbook. Each entry is an absolute
|
||||
url to a file in hosts.txt format. Since the list is checked in order, url's should be
|
||||
listed in order of trust.
|
||||
51
apps/addressbook/build.xml
Normal file
51
apps/addressbook/build.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="addressbook" default="war" basedir=".">
|
||||
|
||||
<property name="src" value="java/src/addressbook"/>
|
||||
<property name="build" value="build"/>
|
||||
<property name="dist" location="dist"/>
|
||||
<property name="jar" value="addressbook.jar"/>
|
||||
<property name="war" value="addressbook.war"/>
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="${build}"/>
|
||||
<mkdir dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="${build}"/>
|
||||
<delete dir="${dist}"/>
|
||||
</target>
|
||||
|
||||
<target name="distclean" depends="clean" />
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
srcdir="${src}" destdir="${build}">
|
||||
<classpath>
|
||||
<pathelement location="../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../jetty/jettylib/javax.servlet.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar basedir="${build}" destfile="${dist}/${jar}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="addressbook.Daemon"/>
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="war" depends="compile">
|
||||
<mkdir dir="${dist}/tmp"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF"/>
|
||||
<mkdir dir="${dist}/tmp/WEB-INF/classes"/>
|
||||
<copy todir="${dist}/tmp/WEB-INF/classes">
|
||||
<fileset dir="${build}"/>
|
||||
</copy>
|
||||
<war basedir="${dist}/tmp" webxml="web.xml" destfile="${dist}/${war}"/>
|
||||
<delete dir="${dist}/tmp"/>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
43
apps/addressbook/config.txt
Normal file
43
apps/addressbook/config.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
# This is the configuration file for addressbook.
|
||||
#
|
||||
# Options
|
||||
# *******
|
||||
# All paths are realitive to i2p/addressbook. Default value for
|
||||
# each option is given in parentheses.
|
||||
#
|
||||
# proxy_host The hostname of your I2P http proxy.
|
||||
# (localhost)
|
||||
#
|
||||
# proxy_port The port of your I2P http proxy. (4444)
|
||||
#
|
||||
# master_addressbook The path to your master address book, used for local
|
||||
# changes only. (myhosts.txt)
|
||||
#
|
||||
# router_addressbook The path to the address book used by the router.
|
||||
# Contains the addresses from your master address book
|
||||
# and your subscribed address books. (../userhosts.txt)
|
||||
#
|
||||
# published_addressbook The path to the copy of your address book made
|
||||
# available on i2p. (../eepsite/docroot/hosts.txt)
|
||||
#
|
||||
# log The path to your addressbook log. (log.txt)
|
||||
#
|
||||
# subscriptions The path to your subscription file. (subscriptions.txt)
|
||||
#
|
||||
# etags The path to the etags header storage file. (etags)
|
||||
#
|
||||
# last_modified The path to the last-modified header storage file.
|
||||
# (last_modified)
|
||||
#
|
||||
# update_delay The time (in hours) between each update. (1)
|
||||
|
||||
proxy_host=localhost
|
||||
proxy_port=4444
|
||||
master_addressbook=myhosts.txt
|
||||
router_addressbook=../userhosts.txt
|
||||
published_addressbook=../eepsite/docroot/hosts.txt
|
||||
log=log.txt
|
||||
subscriptions=subscriptions.txt
|
||||
etags=etags
|
||||
last_modified=last_modified
|
||||
update_delay=1
|
||||
218
apps/addressbook/java/src/addressbook/AddressBook.java
Normal file
218
apps/addressbook/java/src/addressbook/AddressBook.java
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* An address book for storing human readable names mapped to base64 i2p
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
* together, and written out to local files.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
|
||||
private String location;
|
||||
|
||||
private Map addresses;
|
||||
|
||||
private boolean modified;
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the Map addresses.
|
||||
*
|
||||
* @param addresses
|
||||
* A Map containing human readable addresses as keys, mapped to
|
||||
* base64 i2p destinations.
|
||||
*/
|
||||
public AddressBook(Map addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at url. If the
|
||||
* remote file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param url
|
||||
* A URL pointing at a file with lines in the format "key=value",
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp", url, true,
|
||||
null);
|
||||
get.fetch();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
* address book at subscription has not changed since the last time it was
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
this.location = subscription.getLocation();
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp",
|
||||
subscription.getLocation(), true, subscription.getEtag());
|
||||
get.fetch();
|
||||
subscription.setEtag(get.getETag());
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the file at file. If the
|
||||
* file cannot be read, construct an empty AddressBook
|
||||
*
|
||||
* @param file
|
||||
* A File pointing at a file with lines in the format
|
||||
* "key=value", where key is a human readable name, and value is
|
||||
* a base64 i2p destination.
|
||||
*/
|
||||
public AddressBook(File file) {
|
||||
this.location = file.toString();
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
this.addresses = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map containing the addresses in the AddressBook.
|
||||
*
|
||||
* @return A Map containing the addresses in the AddressBook, where the key
|
||||
* is a human readable name, and the value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
public Map getAddresses() {
|
||||
return this.addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location of the file this AddressBook was constructed from.
|
||||
*
|
||||
* @return A String representing either an abstract path, or a url,
|
||||
* depending on how the instance was constructed.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the contents of the AddressBook.
|
||||
*
|
||||
* @return A String representing the contents of the AddressBook.
|
||||
*/
|
||||
public String toString() {
|
||||
return this.addresses.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with AddressBook other, writing messages about new
|
||||
* addresses or conflicts to log. Addresses in AddressBook other that are
|
||||
* not in this AddressBook are added to this AddressBook. In case of a
|
||||
* conflict, addresses in this AddressBook take precedence
|
||||
*
|
||||
* @param other
|
||||
* An AddressBook to merge with.
|
||||
* @param log
|
||||
* The log to write messages about new addresses or conflicts to.
|
||||
*/
|
||||
public void merge(AddressBook other, boolean overwrite, Log log) {
|
||||
Iterator otherIter = other.addresses.keySet().iterator();
|
||||
|
||||
while (otherIter.hasNext()) {
|
||||
String otherKey = (String) otherIter.next();
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
log.append("Conflict for " + otherKey + " from "
|
||||
+ other.location
|
||||
+ ". Destination in remote address book is "
|
||||
+ otherValue);
|
||||
}
|
||||
} else if (!this.addresses.containsKey(otherKey)
|
||||
|| !this.addresses.get(otherKey).equals(otherValue)) {
|
||||
this.addresses.put(otherKey, otherValue);
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the contents of this AddressBook out to the File file. If the file
|
||||
* cannot be writen to, this method will silently fail.
|
||||
*
|
||||
* @param file
|
||||
* The file to write the contents of this AddressBook too.
|
||||
*/
|
||||
public void write(File file) {
|
||||
if (this.modified) {
|
||||
try {
|
||||
ConfigParser.write(this.addresses, file);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this AddressBook out to the file it was read from. Requires that
|
||||
* AddressBook was constructed from a file on the local filesystem. If the
|
||||
* file cannot be writen to, this method will silently fail.
|
||||
*/
|
||||
public void write() {
|
||||
this.write(new File(this.location));
|
||||
}
|
||||
}
|
||||
304
apps/addressbook/java/src/addressbook/ConfigParser.java
Normal file
304
apps/addressbook/java/src/addressbook/ConfigParser.java
Normal file
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Iterator;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
* format, and subscription file format.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class ConfigParser {
|
||||
|
||||
/**
|
||||
* Strip the comments from a String. Lines that begin with '#' and ';' are
|
||||
* considered comments, as well as any part of a line after a '#'.
|
||||
*
|
||||
* @param inputLine
|
||||
* A String to strip comments from.
|
||||
* @return A String without comments, but otherwise identical to inputLine.
|
||||
*/
|
||||
public static String stripComments(String inputLine) {
|
||||
if (inputLine.startsWith(";")) {
|
||||
return "";
|
||||
}
|
||||
if (inputLine.split("#").length > 0) {
|
||||
return inputLine.split("#")[0];
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of BufferedReader input. input must have
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
* a Map.
|
||||
* @return A Map containing the key, value pairs from input.
|
||||
* @throws IOException
|
||||
* if the BufferedReader cannot be read.
|
||||
*
|
||||
*/
|
||||
public static Map parse(BufferedReader input) throws IOException {
|
||||
Map result = new HashMap();
|
||||
String inputLine;
|
||||
inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim(), splitLine[1].trim());
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. See parseBufferedReader
|
||||
* for details of the input format.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A Map containing the key, value pairs from file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the String string. See
|
||||
* parseBufferedReader for details of the input format.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A Map containing the key, value pairs from string.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map parse(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. If file cannot be read,
|
||||
* use map instead, and write the result to where file should have been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param map
|
||||
* A Map to use as the default, if file fails.
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
public static Map parse(File file, Map map) {
|
||||
Map result = new HashMap();
|
||||
try {
|
||||
result = ConfigParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
result = map;
|
||||
try {
|
||||
ConfigParser.write(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the BufferedReader input.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader to parse.
|
||||
* @return A List consisting of one element for each line in input.
|
||||
* @throws IOException
|
||||
* if input cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(BufferedReader input)
|
||||
throws IOException {
|
||||
List result = new LinkedList();
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine).trim();
|
||||
if (inputLine.length() > 0) {
|
||||
result.add(inputLine);
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A List consisting of one element for each line in file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List where each element is a line from the String string.
|
||||
*
|
||||
* @param string
|
||||
* A String to parse.
|
||||
* @return A List consisting of one element for each line in string.
|
||||
* @throws IOException
|
||||
* if string cannot be read.
|
||||
*/
|
||||
public static List parseSubscriptions(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return ConfigParser.parseSubscriptions(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a List using the contents of the File file. If file cannot be
|
||||
* read, use list instead, and write the result to where file should have
|
||||
* been.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param string
|
||||
* A List to use as the default, if file fails.
|
||||
* @return A List consisting of one element for each line in file, or if
|
||||
* file cannot be read, list.
|
||||
*/
|
||||
public static List parseSubscriptions(File file, List list) {
|
||||
List result = new LinkedList();
|
||||
try {
|
||||
result = ConfigParser.parseSubscriptions(file);
|
||||
} catch (IOException exp) {
|
||||
result = list;
|
||||
try {
|
||||
ConfigParser.writeSubscriptions(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to BufferedWriter output. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to output.
|
||||
* @param output
|
||||
* A BufferedWriter to write the Map to.
|
||||
* @throws IOException
|
||||
* if the BufferedWriter cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, BufferedWriter output) throws IOException {
|
||||
Iterator keyIter = map.keySet().iterator();
|
||||
|
||||
while (keyIter.hasNext()) {
|
||||
String key = (String) keyIter.next();
|
||||
output.write(key + "=" + (String) map.get(key));
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to the File file. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to file.
|
||||
* @param file
|
||||
* A File to write the Map to.
|
||||
* @throws IOException
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map map, File file) throws IOException {
|
||||
ConfigParser
|
||||
.write(map, new BufferedWriter(new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to BufferedReader output. Output is written
|
||||
* with each element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param output
|
||||
* A BufferedReader to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, BufferedWriter output)
|
||||
throws IOException {
|
||||
Iterator iter = list.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
output.write((String) iter.next());
|
||||
output.newLine();
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of List list to File file. Output is written with each
|
||||
* element of list on a new line.
|
||||
*
|
||||
* @param list
|
||||
* A List to write to file.
|
||||
* @param file
|
||||
* A File to write list to.
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List list, File file)
|
||||
throws IOException {
|
||||
ConfigParser.writeSubscriptions(list, new BufferedWriter(
|
||||
new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
}
|
||||
191
apps/addressbook/java/src/addressbook/Daemon.java
Normal file
191
apps/addressbook/java/src/addressbook/Daemon.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main class of addressbook. Performs updates, and runs the main loop.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
* subscribed address books listed in subscriptions.
|
||||
*
|
||||
* @param master
|
||||
* The master AddressBook. This address book is never
|
||||
* overwritten, so it is safe for the user to write to.
|
||||
* @param router
|
||||
* The router AddressBook. This is the address book read by
|
||||
* client applications.
|
||||
* @param published
|
||||
* The published AddressBook. This address book is published on
|
||||
* the user's eepsite so that others may subscribe to it.
|
||||
* @param subscriptions
|
||||
* A SubscriptionList listing the remote address books to update
|
||||
* from.
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
*/
|
||||
public void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
router.merge(master, true, null);
|
||||
Iterator iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
router.merge((AddressBook) iter.next(), false, log);
|
||||
}
|
||||
router.write();
|
||||
if (published != null)
|
||||
router.write(published);
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update, using the Map settings to provide the parameters.
|
||||
*
|
||||
* @param settings
|
||||
* A Map containg the parameters needed by update.
|
||||
* @param home
|
||||
* The directory containing addressbook's configuration files.
|
||||
*/
|
||||
public void update(Map settings, String home) {
|
||||
File masterFile = new File(home, (String) settings
|
||||
.get("master_addressbook"));
|
||||
File routerFile = new File(home, (String) settings
|
||||
.get("router_addressbook"));
|
||||
File published = null;
|
||||
if ("true".equals(settings.get("should_publish")))
|
||||
published = new File(home, (String) settings
|
||||
.get("published_addressbook"));
|
||||
File subscriptionFile = new File(home, (String) settings
|
||||
.get("subscriptions"));
|
||||
File logFile = new File(home, (String) settings.get("log"));
|
||||
File etagsFile = new File(home, (String) settings.get("etags"));
|
||||
File lastModifiedFile = new File(home, (String) settings
|
||||
.get("last_modified"));
|
||||
|
||||
AddressBook master = new AddressBook(masterFile);
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
|
||||
List defaultSubs = new LinkedList();
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
.get("proxy_host"), Integer.parseInt((String) settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings, set the proxy, then enter into the main loop. The main
|
||||
* loop performs an immediate update, and then an update every number of
|
||||
* hours, as configured in the settings file.
|
||||
*
|
||||
* @param args
|
||||
* Command line arguments. If there are any arguments provided,
|
||||
* the first is taken as addressbook's home directory, and the
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
_instance.run(args);
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
String settingsLocation = "config.txt";
|
||||
Map settings = new HashMap();
|
||||
String home;
|
||||
if (args.length > 0) {
|
||||
home = args[0];
|
||||
} else {
|
||||
home = ".";
|
||||
}
|
||||
|
||||
Map defaultSettings = new HashMap();
|
||||
defaultSettings.put("proxy_host", "localhost");
|
||||
defaultSettings.put("proxy_port", "4444");
|
||||
defaultSettings.put("master_addressbook", "../userhosts.txt");
|
||||
defaultSettings.put("router_addressbook", "../hosts.txt");
|
||||
defaultSettings.put("published_addressbook", "../eepsite/docroot/hosts.txt");
|
||||
defaultSettings.put("should_publish", "false");
|
||||
defaultSettings.put("log", "log.txt");
|
||||
defaultSettings.put("subscriptions", "subscriptions.txt");
|
||||
defaultSettings.put("etags", "etags");
|
||||
defaultSettings.put("last_modified", "last_modified");
|
||||
defaultSettings.put("update_delay", "12");
|
||||
|
||||
File homeFile = new File(home);
|
||||
if (!homeFile.exists()) {
|
||||
boolean created = homeFile.mkdirs();
|
||||
if (created)
|
||||
System.out.println("INFO: Addressbook directory " + homeFile.getName() + " created");
|
||||
else
|
||||
System.out.println("ERROR: Addressbook directory " + homeFile.getName() + " could not be created");
|
||||
}
|
||||
|
||||
File settingsFile = new File(homeFile, settingsLocation);
|
||||
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
// wait
|
||||
try {
|
||||
Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
}
|
||||
|
||||
update(settings, home);
|
||||
try {
|
||||
synchronized (this) {
|
||||
wait(delay * 60 * 60 * 1000);
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this to get the addressbook to reread its config and
|
||||
* refetch its subscriptions.
|
||||
*/
|
||||
public static void wakeup() {
|
||||
synchronized (_instance) {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
apps/addressbook/java/src/addressbook/DaemonThread.java
Normal file
53
apps/addressbook/java/src/addressbook/DaemonThread.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A thread that waits five minutes, then runs the addressbook daemon.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread {
|
||||
|
||||
private String[] args;
|
||||
|
||||
/**
|
||||
* Construct a DaemonThread with the command line arguments args.
|
||||
* @param args
|
||||
* A String array to pass to Daemon.main().
|
||||
*/
|
||||
public DaemonThread(String[] args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
//try {
|
||||
// Thread.sleep(5 * 60 * 1000);
|
||||
//} catch (InterruptedException exp) {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
||||
76
apps/addressbook/java/src/addressbook/Log.java
Normal file
76
apps/addressbook/java/src/addressbook/Log.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* A simple log with automatic time stamping.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Construct a Log instance that writes to the File file.
|
||||
*
|
||||
* @param file
|
||||
* A File for the log to write to.
|
||||
*/
|
||||
public Log(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write entry to a new line in the log, with appropriate time stamp.
|
||||
*
|
||||
* @param entry
|
||||
* A String containing a message to append to the log.
|
||||
*/
|
||||
public void append(String entry) {
|
||||
try {
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
bw.close();
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the File that the Log is writing to.
|
||||
*
|
||||
* @return The File that the log is writing to.
|
||||
*/
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
}
|
||||
61
apps/addressbook/java/src/addressbook/Servlet.java
Normal file
61
apps/addressbook/java/src/addressbook/Servlet.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import javax.servlet.GenericServlet;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* A wrapper for addressbook to allow it to be started as a web application.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Servlet extends GenericServlet {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
|
||||
*/
|
||||
public void service(ServletRequest request, ServletResponse response) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
public void init(ServletConfig config) {
|
||||
try {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
String[] args = new String[1];
|
||||
args[0] = config.getInitParameter("home");
|
||||
DaemonThread thread = new DaemonThread(args);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Addressbook " + Daemon.VERSION);
|
||||
System.out.println("INFO: config root under " + args[0]);
|
||||
}
|
||||
|
||||
}
|
||||
105
apps/addressbook/java/src/addressbook/Subscription.java
Normal file
105
apps/addressbook/java/src/addressbook/Subscription.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
/**
|
||||
* A subscription to a remote address book.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class Subscription {
|
||||
|
||||
private String location;
|
||||
|
||||
private String etag;
|
||||
|
||||
private String lastModified;
|
||||
|
||||
/**
|
||||
* Construct a Subscription pointing to the address book at location, that
|
||||
* was last read at the time represented by etag and lastModified.
|
||||
*
|
||||
* @param location
|
||||
* A String representing a url to a remote address book.
|
||||
* @param etag
|
||||
* The etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
* @param lastModified
|
||||
* the last-modified header we recieved the last time we read
|
||||
* this subscription.
|
||||
*/
|
||||
public Subscription(String location, String etag, String lastModified) {
|
||||
this.location = location;
|
||||
this.etag = etag;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location this Subscription points at.
|
||||
*
|
||||
* @return A String representing a url to a remote address book.
|
||||
*/
|
||||
public String getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
*
|
||||
* @return A String containing the etag header.
|
||||
*/
|
||||
public String getEtag() {
|
||||
return this.etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the etag header.
|
||||
*
|
||||
* @param etag
|
||||
* A String containing the etag header.
|
||||
*/
|
||||
public void setEtag(String etag) {
|
||||
this.etag = etag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last-modified header that we recieved the last time we read
|
||||
* this subscription.
|
||||
*
|
||||
* @return A String containing the last-modified header.
|
||||
*/
|
||||
public String getLastModified() {
|
||||
return this.lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last-modified header.
|
||||
*
|
||||
* @param lastModified
|
||||
* A String containing the last-modified header.
|
||||
*/
|
||||
public void setLastModified(String lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
* returns AddressBook objects, and not Subscription objects.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
public class SubscriptionIterator implements Iterator {
|
||||
|
||||
private Iterator subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionIterator using the Subscriprions in List subscriptions.
|
||||
*
|
||||
* @param subscriptions
|
||||
* List of Subscription objects that represent address books.
|
||||
*/
|
||||
public SubscriptionIterator(List subscriptions, String proxyHost, int proxyPort) {
|
||||
this.subIterator = subscriptions.iterator();
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#hasNext()
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return this.subIterator.hasNext();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#next()
|
||||
*/
|
||||
public Object next() {
|
||||
Subscription sub = (Subscription) this.subIterator.next();
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.Iterator#remove()
|
||||
*/
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
129
apps/addressbook/java/src/addressbook/SubscriptionList.java
Normal file
129
apps/addressbook/java/src/addressbook/SubscriptionList.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Ragnarok
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package addressbook;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A list of Subscriptions loaded from a file.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class SubscriptionList {
|
||||
|
||||
private List subscriptions;
|
||||
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
private int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
* available, the etags and last-modified headers loaded from etagsFile and
|
||||
* lastModifiedFile.
|
||||
*
|
||||
* @param locationsFile
|
||||
* A file containing one url on each line.
|
||||
* @param etagsFile
|
||||
* A file containg the etag headers used for conditional GET. The
|
||||
* file is in the format "url=etag".
|
||||
* @param lastModifiedFile
|
||||
* A file containg the last-modified headers used for conditional
|
||||
* GET. The file is in the format "url=leastmodified".
|
||||
*/
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, List defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList();
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
this.proxyHost = proxyHost;
|
||||
this.proxyPort = proxyPort;
|
||||
Map etags;
|
||||
Map lastModified;
|
||||
String location;
|
||||
List locations = ConfigParser.parseSubscriptions(locationsFile,
|
||||
defaultSubs);
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
etags = new HashMap();
|
||||
}
|
||||
try {
|
||||
lastModified = ConfigParser.parse(lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap();
|
||||
}
|
||||
Iterator iter = locations.iterator();
|
||||
while (iter.hasNext()) {
|
||||
location = (String) iter.next();
|
||||
this.subscriptions.add(new Subscription(location, (String) etags
|
||||
.get(location), (String) lastModified.get(location)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over the AddressBooks represented by the Subscriptions
|
||||
* in this SubscriptionList.
|
||||
*
|
||||
* @return A SubscriptionIterator.
|
||||
*/
|
||||
public SubscriptionIterator iterator() {
|
||||
return new SubscriptionIterator(this.subscriptions, this.proxyHost,
|
||||
this.proxyPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the etag and last-modified headers for each Subscription to files.
|
||||
*/
|
||||
public void write() {
|
||||
Iterator iter = this.subscriptions.iterator();
|
||||
Subscription sub;
|
||||
Map etags = new HashMap();
|
||||
Map lastModified = new HashMap();
|
||||
while (iter.hasNext()) {
|
||||
sub = (Subscription) iter.next();
|
||||
if (sub.getEtag() != null) {
|
||||
etags.put(sub.getLocation(), sub.getEtag());
|
||||
}
|
||||
if (sub.getLastModified() != null) {
|
||||
lastModified.put(sub.getLocation(), sub.getLastModified());
|
||||
}
|
||||
}
|
||||
try {
|
||||
ConfigParser.write(etags, this.etagsFile);
|
||||
ConfigParser.write(lastModified, this.lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
10
apps/addressbook/myhosts.txt
Normal file
10
apps/addressbook/myhosts.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# addressbook master address book. Addresses placed in this file take precidence
|
||||
# over those in the router address book and in remote address books. If changes
|
||||
# are made to this file, they will be reflected in the router address book and
|
||||
# published address book after the next update.
|
||||
#
|
||||
# Do not make changes directly to the router address book, as they could be lost
|
||||
# during an update.
|
||||
#
|
||||
# This file takes addresses in the hosts.txt format, i.e.
|
||||
# example.i2p=somereallylongbase64thingAAAA
|
||||
7
apps/addressbook/subscriptions.txt
Normal file
7
apps/addressbook/subscriptions.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# Subscription list for addressbook
|
||||
#
|
||||
# Each entry is an absolute url to a file in hosts.txt format.
|
||||
# Since the list is checked in order, url's should be listed in order of trust.
|
||||
#
|
||||
http://dev.i2p/i2p/hosts.txt
|
||||
http://duck.i2p/hosts.txt
|
||||
16
apps/addressbook/web.xml
Normal file
16
apps/addressbook/web.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<servlet>
|
||||
<servlet-name>addressbook</servlet-name>
|
||||
<servlet-class>addressbook.Servlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>home</param-name>
|
||||
<param-value>./addressbook</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
</web-app>
|
||||
349
apps/bogobot/Bogobot.java
Normal file
349
apps/bogobot/Bogobot.java
Normal file
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* bogobot - A simple join/part stats logger bot for I2P IRC.
|
||||
*
|
||||
* Bogobot.java
|
||||
* 2004 The I2P Project
|
||||
* http://www.i2p.net
|
||||
* This code is public domain.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.log4j.DailyRollingFileAppender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
|
||||
import org.jibble.pircbot.IrcException;
|
||||
import org.jibble.pircbot.NickAlreadyInUseException;
|
||||
import org.jibble.pircbot.PircBot;
|
||||
import org.jibble.pircbot.User;
|
||||
|
||||
/**
|
||||
* TODO 0.5 Add multi-server capability.
|
||||
*
|
||||
* @author hypercubus, oOo
|
||||
* @version 0.4
|
||||
*/
|
||||
public class Bogobot extends PircBot {
|
||||
|
||||
private static final String INTERVAL_DAILY = "daily";
|
||||
private static final String INTERVAL_MONTHLY = "monthly";
|
||||
private static final String INTERVAL_WEEKLY = "weekly";
|
||||
|
||||
private boolean _isIntentionalDisconnect = false;
|
||||
private long _lastUserlistCommandTimestamp = 0;
|
||||
private Logger _logger = Logger.getLogger(Bogobot.class);
|
||||
|
||||
private int _currentAutoRoundTripTag = 0;
|
||||
private long _lastAutoRoundTripSentTime = 0;
|
||||
private Timer _tickTimer;
|
||||
|
||||
private String _configFile;
|
||||
|
||||
private String _botPrimaryNick;
|
||||
private String _botSecondaryNick;
|
||||
private String _botNickservPassword;
|
||||
private String _botUsername;
|
||||
private String _ownerPrimaryNick;
|
||||
private String _ownerSecondaryNick;
|
||||
private String _botShutdownPassword;
|
||||
private String _ircChannel;
|
||||
private String _ircServer;
|
||||
private int _ircServerPort;
|
||||
private boolean _isLoggerEnabled;
|
||||
private String _loggedHostnamePattern;
|
||||
private boolean _isUserlistCommandEnabled;
|
||||
private String _logFilePrefix;
|
||||
private String _logFileRotationInterval;
|
||||
private long _commandAntiFloodInterval;
|
||||
private String _userlistCommandTrigger;
|
||||
private boolean _isRoundTripDelayEnabled;
|
||||
private int _roundTripDelayPeriod;
|
||||
|
||||
class BogobotTickTask extends TimerTask {
|
||||
private Bogobot _caller;
|
||||
|
||||
public BogobotTickTask(Bogobot caller) {
|
||||
_caller = caller;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
_caller.onTick();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfigFile(String configFileName) {
|
||||
|
||||
_configFile = configFileName;
|
||||
|
||||
Properties config = new Properties();
|
||||
FileInputStream fis = null;
|
||||
|
||||
try {
|
||||
fis = new FileInputStream(configFileName);
|
||||
config.load(fis);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error loading configuration file");
|
||||
System.exit(2);
|
||||
|
||||
} finally {
|
||||
if (fis != null) try {
|
||||
fis.close();
|
||||
} catch (IOException ioe) { // nop
|
||||
}
|
||||
}
|
||||
|
||||
_botPrimaryNick = config.getProperty("botPrimaryNick", "somebot");
|
||||
_botSecondaryNick = config.getProperty("botSecondaryNick", "somebot_");
|
||||
_botNickservPassword = config.getProperty("botNickservPassword", "");
|
||||
_botUsername = config.getProperty("botUsername", "somebot");
|
||||
|
||||
_ownerPrimaryNick = config.getProperty("ownerPrimaryNick", "somenick");
|
||||
_ownerSecondaryNick = config.getProperty("ownerSecondaryNick", "somenick_");
|
||||
|
||||
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
|
||||
|
||||
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
|
||||
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
|
||||
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
|
||||
|
||||
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
|
||||
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
|
||||
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
|
||||
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
|
||||
|
||||
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
|
||||
_roundTripDelayPeriod = Integer.parseInt(config.getProperty("roundTripDelayPeriod", "300"));
|
||||
|
||||
_isUserlistCommandEnabled = Boolean.valueOf(config.getProperty("isUserlistCommandEnabled", "true")).booleanValue();
|
||||
_userlistCommandTrigger = config.getProperty("userlistCommandTrigger", "!who");
|
||||
_commandAntiFloodInterval = Long.parseLong(config.getProperty("commandAntiFloodInterval", "60"));
|
||||
}
|
||||
|
||||
public Bogobot(String configFileName) {
|
||||
|
||||
loadConfigFile(configFileName);
|
||||
|
||||
this.setName(_botPrimaryNick);
|
||||
this.setLogin(_botUsername);
|
||||
_tickTimer = new Timer();
|
||||
_tickTimer.scheduleAtFixedRate(new BogobotTickTask(this), 1000, 10 * 1000);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Bogobot bogobot;
|
||||
|
||||
if (args.length > 1) {
|
||||
System.err.println("Too many arguments, the only allowed parameter is configuration file name");
|
||||
System.exit(3);
|
||||
}
|
||||
if (args.length == 1) {
|
||||
bogobot = new Bogobot(args[0]);
|
||||
} else {
|
||||
bogobot = new Bogobot("bogobot.config");
|
||||
}
|
||||
|
||||
bogobot.setVerbose(true);
|
||||
|
||||
if (bogobot._isLoggerEnabled)
|
||||
bogobot.initLogger();
|
||||
|
||||
bogobot.connectToServer();
|
||||
}
|
||||
|
||||
protected void onTick() {
|
||||
// Tick about once every ten seconds
|
||||
|
||||
if (this.isConnected() && _isRoundTripDelayEnabled) {
|
||||
if( ( (System.currentTimeMillis() - _lastAutoRoundTripSentTime) >= (_roundTripDelayPeriod * 1000) ) && (this.getOutgoingQueueSize() == 0) ) {
|
||||
// Connected, sending queue is empty and last RoundTrip is more then 5 minutes old -> Send a new one
|
||||
_currentAutoRoundTripTag ++;
|
||||
_lastAutoRoundTripSentTime = System.currentTimeMillis();
|
||||
sendNotice(this.getNick(),"ROUNDTRIP " + _currentAutoRoundTripTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDisconnect() {
|
||||
|
||||
if (_isIntentionalDisconnect)
|
||||
System.exit(0);
|
||||
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " *** (Lost connection)");
|
||||
|
||||
try {
|
||||
Thread.sleep(60000);
|
||||
} catch (InterruptedException e) {
|
||||
// No worries.
|
||||
}
|
||||
connectToServer();
|
||||
}
|
||||
|
||||
protected void onJoin(String channel, String sender, String login, String hostname) {
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (sender.equals(this.getName())) {
|
||||
|
||||
_logger.info(System.currentTimeMillis() + " joins *** " + _botPrimaryNick + " ***");
|
||||
|
||||
} else {
|
||||
|
||||
String prependedHostname = "@" + hostname;
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " joins " + sender);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMessage(String channel, String sender, String login, String hostname, String message) {
|
||||
message = message.replaceFirst("<.+?> ", "");
|
||||
if (_isUserlistCommandEnabled && message.equals(_userlistCommandTrigger)) {
|
||||
|
||||
if (System.currentTimeMillis() - _lastUserlistCommandTimestamp < _commandAntiFloodInterval * 1000)
|
||||
return;
|
||||
|
||||
Object[] users = getUsers(_ircChannel);
|
||||
String output = "Userlist for " + _ircChannel + ": ";
|
||||
|
||||
for (int i = 0; i < users.length; i++)
|
||||
output += "[" + ((User) users[i]).getNick() + "] ";
|
||||
|
||||
sendMessage(_ircChannel, output);
|
||||
_lastUserlistCommandTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPart(String channel, String sender, String login, String hostname) {
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (sender.equals(this.getName())) {
|
||||
_logger.info(System.currentTimeMillis() + " parts *** " + _botPrimaryNick + " ***");
|
||||
} else {
|
||||
String prependedHostname = "@" + hostname;
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " parts " + sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void onPrivateMessage(String sender, String login, String hostname, String message) {
|
||||
/*
|
||||
* Nobody else except the bot's owner can shut it down, unless of
|
||||
* course the owner's nick isn't registered and someone's spoofing it.
|
||||
*/
|
||||
if ((sender.equals(_ownerPrimaryNick) || sender.equals(_ownerSecondaryNick)) && message.equals(_botShutdownPassword)) {
|
||||
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " ***");
|
||||
|
||||
_isIntentionalDisconnect = true;
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {
|
||||
String prependedHostname = "@" + sourceHostname;
|
||||
|
||||
if (sourceNick.equals(_botPrimaryNick))
|
||||
changeNick(_botPrimaryNick);
|
||||
|
||||
if (_isLoggerEnabled) {
|
||||
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
|
||||
_logger.info(System.currentTimeMillis() + " quits " + sourceNick + " " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void connectToServer() {
|
||||
|
||||
int loginAttempts = 0;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
connect(_ircServer, _ircServerPort);
|
||||
break;
|
||||
} catch (NickAlreadyInUseException e) {
|
||||
if (loginAttempts == 1) {
|
||||
System.out.println("Sorry, the primary and secondary bot nicks are already taken. Exiting.");
|
||||
System.exit(1);
|
||||
}
|
||||
loginAttempts++;
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e1) {
|
||||
// Hmph.
|
||||
}
|
||||
|
||||
if (getName().equals(_botPrimaryNick))
|
||||
setName(_botSecondaryNick);
|
||||
else
|
||||
setName(_botPrimaryNick);
|
||||
|
||||
continue;
|
||||
} catch (IOException e) {
|
||||
System.out.println("Error during login: ");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
} catch (IrcException e) {
|
||||
System.out.println("Error during login: ");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
joinChannel(_ircChannel);
|
||||
}
|
||||
|
||||
protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {
|
||||
|
||||
if (sourceNick.equals("NickServ") && (notice.indexOf("/msg NickServ IDENTIFY") >= 0) && (_botNickservPassword != "")) {
|
||||
sendRawLineViaQueue("NICKSERV IDENTIFY " + _botNickservPassword);
|
||||
}
|
||||
|
||||
if (sourceNick.equals(getNick()) && notice.equals( "ROUNDTRIP " + _currentAutoRoundTripTag)) {
|
||||
int delay = (int)((System.currentTimeMillis() - _lastAutoRoundTripSentTime) / 100);
|
||||
// sendMessage(_ircChannel, "Round-trip delay = " + (delay / 10.0f) + " seconds");
|
||||
if (_isLoggerEnabled)
|
||||
_logger.info(System.currentTimeMillis() + " roundtrip " + delay);
|
||||
}
|
||||
}
|
||||
|
||||
private void initLogger() {
|
||||
|
||||
String logFilePath = "logs" + File.separator + _logFilePrefix;
|
||||
DailyRollingFileAppender rollingFileAppender = null;
|
||||
|
||||
if (!(new File("logs").exists()))
|
||||
(new File("logs")).mkdirs();
|
||||
|
||||
try {
|
||||
|
||||
if (_logFileRotationInterval.equals("monthly"))
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM'.log'");
|
||||
else if (_logFileRotationInterval.equals("weekly"))
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-ww'.log'");
|
||||
else
|
||||
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM-dd'.log'");
|
||||
|
||||
rollingFileAppender.setThreshold(Level.INFO);
|
||||
_logger.addAppender(rollingFileAppender);
|
||||
} catch (IOException ex) {
|
||||
System.out.println("Error: Couldn't create or open an existing log file. Exiting.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
353
apps/bogobot/Bogoparser.java
Normal file
353
apps/bogobot/Bogoparser.java
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
* bogoparser - A simple logfile analyzer for bogobot.
|
||||
*
|
||||
* Bogoparser.java
|
||||
* 2004 The I2P Project
|
||||
* http://www.i2p.net
|
||||
* This code is public domain.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author hypercubus
|
||||
* @version 0.4
|
||||
*/
|
||||
public class Bogoparser {
|
||||
|
||||
private static void displayUsageAndExit() {
|
||||
System.out.println("\r\nUsage:\r\n\r\n java Bogoparser [--by-duration] <logfile>\r\n");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Bogoparser bogoparser;
|
||||
|
||||
if (args.length < 1 || args.length > 2)
|
||||
displayUsageAndExit();
|
||||
|
||||
if (args.length == 2) {
|
||||
if (!args[0].equals("--by-duration"))
|
||||
displayUsageAndExit();
|
||||
bogoparser = new Bogoparser(args[1], true);
|
||||
}
|
||||
|
||||
if (args.length == 1)
|
||||
bogoparser = new Bogoparser(args[0], false);
|
||||
}
|
||||
|
||||
private Bogoparser(String logfile, boolean sortByDuration) {
|
||||
|
||||
ArrayList sortedSessions;
|
||||
|
||||
if (sortByDuration) {
|
||||
sortedSessions = sortSessionsByDuration(calculateSessionDurations(sortSessionsByTime(readLogfile(logfile))));
|
||||
formatAndOutputByDuration(sortedSessions);
|
||||
} else {
|
||||
sortedSessions = calculateSessionDurations(sortSessionsByQuitReason(sortSessionsByNick(sortSessionsByTime(readLogfile(logfile)))));
|
||||
formatAndOutput(sortedSessions);
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList calculateSessionDurations(ArrayList sortedSessionsByQuitReasonOrDuration) {
|
||||
|
||||
ArrayList calculatedSessionDurations = new ArrayList();
|
||||
|
||||
for (int i = 0; i+1 < sortedSessionsByQuitReasonOrDuration.size(); i += 2) {
|
||||
|
||||
String joinsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i);
|
||||
String[] joinsEntryFields = joinsEntry.split(" ");
|
||||
|
||||
String quitsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i+1);
|
||||
Pattern p = Pattern.compile("^([^ ]+) [^ ]+ ([^ ]+) (.*)$");
|
||||
Matcher m = p.matcher(quitsEntry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
String currentJoinTime = joinsEntryFields[0];
|
||||
String currentNick = m.group(2);
|
||||
String currentQuitReason = m.group(3);
|
||||
String currentQuitTime = m.group(1);
|
||||
long joinsTimeInMilliseconds;
|
||||
long quitsTimeInMilliseconds;
|
||||
long sessionLengthInMilliseconds;
|
||||
|
||||
joinsTimeInMilliseconds = Long.parseLong(currentJoinTime);
|
||||
quitsTimeInMilliseconds = Long.parseLong(currentQuitTime);
|
||||
sessionLengthInMilliseconds = quitsTimeInMilliseconds - joinsTimeInMilliseconds;
|
||||
|
||||
String hours = "" + sessionLengthInMilliseconds/1000/60/60;
|
||||
String minutes = "" + (sessionLengthInMilliseconds/1000/60)%60;
|
||||
|
||||
if (hours.length() < 2)
|
||||
hours = "0" + hours;
|
||||
|
||||
if (hours.length() < 3)
|
||||
hours = "0" + hours;
|
||||
|
||||
if (minutes.length() < 2)
|
||||
minutes = "0" + minutes;
|
||||
|
||||
int columnPadding = 19-currentNick.length();
|
||||
String columnPaddingString = " ";
|
||||
|
||||
for (int j = 0; j < columnPadding; j++)
|
||||
columnPaddingString = columnPaddingString + " ";
|
||||
|
||||
calculatedSessionDurations.add(sessionLengthInMilliseconds + " " + currentNick + columnPaddingString + " online " + hours + " hours " + minutes + " minutes " + currentQuitReason);
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + quitsEntry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
return calculatedSessionDurations;
|
||||
}
|
||||
|
||||
private void formatAndOutput(ArrayList sortedSessions) {
|
||||
|
||||
String quitReason = null;
|
||||
|
||||
for (int i = 0; i < sortedSessions.size(); i++) {
|
||||
|
||||
String entry = (String) sortedSessions.get(i);
|
||||
Pattern p = Pattern.compile("^[\\d]+ ([^ ]+ +online [\\d]+ hours [\\d]+ minutes) (.*)$");
|
||||
Matcher m = p.matcher(entry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
if (quitReason == null) {
|
||||
quitReason = m.group(2);
|
||||
System.out.println("\r\nQUIT: " + ((m.group(2).equals("")) ? "No Reason Given" : quitReason) + "\r\n");
|
||||
}
|
||||
|
||||
String tempQuitReason = m.group(2);
|
||||
String tempSession = m.group(1);
|
||||
|
||||
if (tempQuitReason.equals(quitReason)) {
|
||||
System.out.println(" " + tempSession);
|
||||
} else {
|
||||
quitReason = null;
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
System.out.println("\r\n");
|
||||
}
|
||||
|
||||
private void formatAndOutputByDuration(ArrayList sortedSessions) {
|
||||
System.out.println("\r\n");
|
||||
|
||||
for (int i = 0; i < sortedSessions.size(); i++) {
|
||||
String[] columns = ((String) sortedSessions.get(i)).split(" ", 2);
|
||||
System.out.println(columns[1]);
|
||||
}
|
||||
|
||||
System.out.println("\r\n");
|
||||
}
|
||||
|
||||
private ArrayList readLogfile(String logfile) {
|
||||
|
||||
ArrayList log = new ArrayList();
|
||||
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(logfile)));
|
||||
|
||||
for (String line; (line = in.readLine()) != null; )
|
||||
log.add(line);
|
||||
|
||||
in.close();
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
System.out.println("\r\nError: Can't find logfile '" + logfile + "'.\r\n");
|
||||
System.exit(1);
|
||||
|
||||
} catch (IOException e) {
|
||||
System.out.println("\r\nError: Can't read logfile '" + logfile + "'.\r\n");
|
||||
System.exit(1);
|
||||
}
|
||||
return log;
|
||||
}
|
||||
|
||||
/*
|
||||
* Performs an odd-even transposition sort.
|
||||
*/
|
||||
private ArrayList sortSessionsByDuration(ArrayList calculatedSessionDurations) {
|
||||
|
||||
for (int i = 0; i < calculatedSessionDurations.size()/2; i++) {
|
||||
for (int j = 0; j+1 < calculatedSessionDurations.size(); j += 2) {
|
||||
|
||||
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
|
||||
long currentDuration = Long.parseLong(currentDurationString[0]);
|
||||
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
|
||||
long nextDuration = Long.parseLong(nextDurationString[0]);
|
||||
|
||||
if (currentDuration > nextDuration) {
|
||||
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
|
||||
calculatedSessionDurations.remove(j+2);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 1; j+1 < calculatedSessionDurations.size(); j += 2) {
|
||||
|
||||
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
|
||||
long currentDuration = Long.parseLong(currentDurationString[0]);
|
||||
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
|
||||
long nextDuration = Long.parseLong(nextDurationString[0]);
|
||||
|
||||
if (currentDuration > nextDuration) {
|
||||
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
|
||||
calculatedSessionDurations.remove(j+2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return calculatedSessionDurations;
|
||||
}
|
||||
|
||||
private ArrayList sortSessionsByNick(ArrayList sortedSessionsByTime) {
|
||||
|
||||
ArrayList sortedSessionsByNick = new ArrayList();
|
||||
|
||||
while (sortedSessionsByTime.size() != 0) {
|
||||
|
||||
String entry = (String) sortedSessionsByTime.get(0);
|
||||
String[] entryFields = entry.split(" ");
|
||||
String currentNick = entryFields[2];
|
||||
|
||||
sortedSessionsByNick.add(entry);
|
||||
sortedSessionsByNick.add(sortedSessionsByTime.get(1));
|
||||
sortedSessionsByTime.remove(0);
|
||||
sortedSessionsByTime.remove(0);
|
||||
for (int i = 0; i+1 < sortedSessionsByTime.size(); i += 2) {
|
||||
|
||||
String nextEntry = (String) sortedSessionsByTime.get(i);
|
||||
String[] nextEntryFields = nextEntry.split(" ");
|
||||
|
||||
if (nextEntryFields[2].equals(currentNick)) {
|
||||
sortedSessionsByNick.add(nextEntry);
|
||||
sortedSessionsByNick.add(sortedSessionsByTime.get(i+1));
|
||||
sortedSessionsByTime.remove(i);
|
||||
sortedSessionsByTime.remove(i);
|
||||
i -= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sortedSessionsByNick;
|
||||
}
|
||||
|
||||
private ArrayList sortSessionsByQuitReason(ArrayList sortedSessionsByNick) {
|
||||
|
||||
ArrayList sortedSessionsByQuitReason = new ArrayList();
|
||||
|
||||
while (sortedSessionsByNick.size() != 0) {
|
||||
|
||||
String entry = (String) sortedSessionsByNick.get(1);
|
||||
Pattern p = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
|
||||
Matcher m = p.matcher(entry);
|
||||
|
||||
if (m.matches()) {
|
||||
|
||||
String currentQuitReason = m.group(1);
|
||||
|
||||
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(0));
|
||||
sortedSessionsByQuitReason.add(entry);
|
||||
sortedSessionsByNick.remove(0);
|
||||
sortedSessionsByNick.remove(0);
|
||||
for (int i = 0; i+1 < sortedSessionsByNick.size(); i += 2) {
|
||||
|
||||
String nextEntry = (String) sortedSessionsByNick.get(i+1);
|
||||
Pattern p2 = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
|
||||
Matcher m2 = p2.matcher(nextEntry);
|
||||
|
||||
if (m2.matches()) {
|
||||
|
||||
String nextQuitReason = m2.group(1);
|
||||
|
||||
if (nextQuitReason.equals(currentQuitReason)) {
|
||||
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(i));
|
||||
sortedSessionsByQuitReason.add(nextEntry);
|
||||
sortedSessionsByNick.remove(i);
|
||||
sortedSessionsByNick.remove(i);
|
||||
i -= 2;
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + nextEntry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
return sortedSessionsByQuitReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sessions terminated with "parts" messages instead of "quits" are filtered
|
||||
* out.
|
||||
*/
|
||||
private ArrayList sortSessionsByTime(ArrayList log) {
|
||||
|
||||
ArrayList sortedSessionsByTime = new ArrayList();
|
||||
|
||||
mainLoop:
|
||||
while (log.size() > 0) {
|
||||
|
||||
String entry = (String) log.get(0);
|
||||
String[] entryFields = entry.split(" ");
|
||||
|
||||
if (entryFields[1].equals("quits") && !entryFields[1].equals("joins")) {
|
||||
/*
|
||||
* Discard entry. The specified log either doesn't contain
|
||||
* the corresponding "joins" time for this quit entry or the
|
||||
* entry is a "parts" or unknown message, and in both cases
|
||||
* the entry's data is useless.
|
||||
*/
|
||||
log.remove(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 1; i < log.size(); i++) { // Find corresponding "quits" entry.
|
||||
|
||||
String tempEntry = (String) log.get(i);
|
||||
String[] tempEntryFields = tempEntry.split(" ");
|
||||
|
||||
if (tempEntryFields[2].equals(entryFields[2])) { // Check if the nick fields for the two entries match.
|
||||
if (!tempEntryFields[1].equals("quits")) {
|
||||
if (tempEntryFields[1].equals("joins")) { // Don't discard a subsequent "joins" entry.
|
||||
log.remove(0);
|
||||
continue mainLoop;
|
||||
}
|
||||
log.remove(i);
|
||||
continue;
|
||||
}
|
||||
sortedSessionsByTime.add(entry);
|
||||
sortedSessionsByTime.add(tempEntry);
|
||||
log.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Discard "joins" entry. The specified log doesn't contain the
|
||||
* corresponding "quits" time for this entry so the entry's
|
||||
* data is useless.
|
||||
*/
|
||||
|
||||
log.remove(0);
|
||||
}
|
||||
|
||||
return sortedSessionsByTime;
|
||||
}
|
||||
}
|
||||
48
apps/bogobot/LICENSE.log4j.txt
Normal file
48
apps/bogobot/LICENSE.log4j.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* ============================================================================
|
||||
* The Apache Software License, Version 1.1
|
||||
* ============================================================================
|
||||
*
|
||||
* Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if any, must
|
||||
* include the following acknowledgment: "This product includes software
|
||||
* developed by the Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowledgment may appear in the software itself, if
|
||||
* and wherever such third-party acknowledgments normally appear.
|
||||
*
|
||||
* 4. The names "log4j" and "Apache Software Foundation" must not be used to
|
||||
* endorse or promote products derived from this software without prior
|
||||
* written permission. For written permission, please contact
|
||||
* apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache", nor may
|
||||
* "Apache" appear in their name, without prior written permission of the
|
||||
* Apache Software Foundation.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
|
||||
* DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* on behalf of the Apache Software Foundation. For more information on the
|
||||
* Apache Software Foundation, please see <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
340
apps/bogobot/LICENSE.pircbot.txt
Normal file
340
apps/bogobot/LICENSE.pircbot.txt
Normal file
@@ -0,0 +1,340 @@
|
||||
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.
|
||||
1
apps/bogobot/bogobot.bat
Normal file
1
apps/bogobot/bogobot.bat
Normal file
@@ -0,0 +1 @@
|
||||
java -cp .;log4j-1.2.8.jar;pircbot.jar Bogobot
|
||||
101
apps/bogobot/bogobot.config
Normal file
101
apps/bogobot/bogobot.config
Normal file
@@ -0,0 +1,101 @@
|
||||
#####
|
||||
# Bogobot user configuration
|
||||
#####
|
||||
|
||||
###
|
||||
# The bot's nick and backup nick. You will probably want to register these with
|
||||
# the IRC server's NickServ.(a NickServ interface is forthcoming).
|
||||
#
|
||||
botPrimaryNick=somebot
|
||||
botSecondaryNick=somebot_
|
||||
|
||||
###
|
||||
# The bot's password required by Nickserv service's identify command.
|
||||
# You have to register the nickname yourself first, the bot will not.
|
||||
#
|
||||
botNickservPassword=
|
||||
|
||||
###
|
||||
# The bot's username. Appears in the whois replies
|
||||
#
|
||||
botUsername=somebot
|
||||
|
||||
#####
|
||||
# The bot owner's nick and backup nick. One of these must match the owner's
|
||||
# currently-used nick or else remote shutdown will not be possible. You will
|
||||
# probably want to register these with the IRC server's NickServ.
|
||||
#
|
||||
ownerPrimaryNick=somenick
|
||||
ownerSecondaryNick=somenick_
|
||||
|
||||
###
|
||||
# The bot will disconnect and shut down when sent this password via private
|
||||
# message (aka query) from either of the owner nicks specified above. DO NOT USE
|
||||
# THIS DEFAULT VALUE!
|
||||
#
|
||||
botShutdownPassword=take off eh
|
||||
|
||||
###
|
||||
# The server, channel, and port the bot will connect to.
|
||||
#
|
||||
ircChannel=#i2p-chat
|
||||
ircServer=irc.duck.i2p
|
||||
ircServerPort=6668
|
||||
|
||||
###
|
||||
# Set to "true" to enable logging, else "false" (but don't use quotation marks).
|
||||
#
|
||||
isLoggerEnabled=true
|
||||
|
||||
###
|
||||
# Restrict logging of joins and parts on the user hostname.
|
||||
# Leave empty to log all of them
|
||||
# Prepend with a @ for a perfect match
|
||||
# Otherwise, specify the required end of the user hostname
|
||||
#
|
||||
loggedHostnamePattern=@free.duck.i2p
|
||||
|
||||
###
|
||||
# The prefix to be used for the filenames of logs.
|
||||
#
|
||||
logFilePrefix=irc.duck.i2p.i2p-chat
|
||||
|
||||
###
|
||||
# How often the logs should be rotated. Either "daily", "weekly", or "monthly"
|
||||
# (but don't use quotation marks).
|
||||
#
|
||||
logFileRotationInterval=daily
|
||||
|
||||
###
|
||||
# Set to "true" to enable the regular round-trip delay computation,
|
||||
# else "false" (but don't use quotation marks).
|
||||
#
|
||||
isRoundTripDelayEnabled=false
|
||||
|
||||
###
|
||||
# How often should the round-trip delay be recorded.
|
||||
# (in seconds)
|
||||
#
|
||||
roundTripDelayPeriod=300
|
||||
|
||||
###
|
||||
# Set to "true" to enable the userlist command, else "false" (but don't use
|
||||
# quotation marks).
|
||||
#
|
||||
isUserlistCommandEnabled=true
|
||||
|
||||
###
|
||||
# The userlist trigger command to listen for. It is a good idea to prefix
|
||||
# triggers with some non-alphanumeric character in order to avoid accidental
|
||||
# trigger use during normal channel conversation. In most cases you will
|
||||
# probably want to choose a unique trigger here that no other bots in the
|
||||
# channel will respond to.
|
||||
#
|
||||
userlistCommandTrigger=!who
|
||||
|
||||
###
|
||||
# The number of seconds to rest after replying to a userlist command issued by
|
||||
# a user in the channel. The bot will ignore subsequent userlist commands during
|
||||
# this period. This helps prevent flooding.
|
||||
#
|
||||
commandAntiFloodInterval=60
|
||||
2
apps/bogobot/bogobot.sh
Normal file
2
apps/bogobot/bogobot.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
java -cp .:log4j-1.2.8.jar:pircbot.jar Bogobot
|
||||
58
apps/bogobot/build-eclipse.xml
Normal file
58
apps/bogobot/build-eclipse.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ********************************************************** -->
|
||||
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
|
||||
<!-- -->
|
||||
<!-- build-eclipse.xml -->
|
||||
<!-- 2004 The I2P Project -->
|
||||
<!-- http://www.i2p.net -->
|
||||
<!-- This code is public domain. -->
|
||||
<!-- -->
|
||||
<!-- authors: hypercubus, oOo -->
|
||||
<!-- version 0.4 -->
|
||||
<!-- ********************************************************** -->
|
||||
|
||||
<project basedir="." default="dist" name="Bogobot">
|
||||
|
||||
<!-- init:
|
||||
Create distribution directory if missing and initialize time stamp for
|
||||
archive naming -->
|
||||
<target name="init">
|
||||
<mkdir dir="dist" />
|
||||
<tstamp>
|
||||
<format pattern="yyyy-MM-dd" property="DSTAMP" />
|
||||
</tstamp>
|
||||
</target>
|
||||
|
||||
<!-- dist.bin:
|
||||
Create the binary distribution archive -->
|
||||
<target depends="init" description="Create the binary distribution archive" name="dist.bin">
|
||||
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist.source:
|
||||
Create the source distribution archive -->
|
||||
<target depends="init" description="Create the source distribution archive" name="dist.source">
|
||||
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist:
|
||||
Create both the binary and source distribution archives -->
|
||||
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
|
||||
<echo message="Successfully created binary and source distribution archives in directory 'dist'." />
|
||||
</target>
|
||||
|
||||
<!-- clean:
|
||||
Delete all class files and temporary directories -->
|
||||
<target description="Delete all class files and temporary directories" name="clean">
|
||||
<delete>
|
||||
<fileset dir="${basedir}" includes="**/*.class" />
|
||||
</delete>
|
||||
<echo message="Clean successful." />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
64
apps/bogobot/build.xml
Normal file
64
apps/bogobot/build.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ********************************************************** -->
|
||||
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
|
||||
<!-- -->
|
||||
<!-- build.xml -->
|
||||
<!-- 2004 The I2P Project -->
|
||||
<!-- http://www.i2p.net -->
|
||||
<!-- This code is public domain. -->
|
||||
<!-- -->
|
||||
<!-- authors: hypercubus, oOo -->
|
||||
<!-- version 0.4 -->
|
||||
<!-- ********************************************************** -->
|
||||
|
||||
<project basedir="." default="compile" name="Bogobot">
|
||||
|
||||
<!-- init:
|
||||
Create distribution directory if missing and initialize time stamp for
|
||||
archive naming -->
|
||||
<target name="init">
|
||||
<mkdir dir="dist" />
|
||||
<tstamp>
|
||||
<format pattern="yyyy-MM-dd" property="DSTAMP" />
|
||||
</tstamp>
|
||||
</target>
|
||||
|
||||
<!-- compile:
|
||||
Compile source code -->
|
||||
<target depends="init" description="Compile source code" name="compile">
|
||||
<javac classpath="${basedir};log4j-1.2.8.jar;pircbot.jar" source="1.4" srcdir="." />
|
||||
</target>
|
||||
|
||||
<!-- dist.bin:
|
||||
Create the binary distribution archive -->
|
||||
<target depends="init,compile" description="Create the binary distribution archive" name="dist.bin">
|
||||
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class Bogobot$BogobotTickTask.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist.source:
|
||||
Create the source distribution archive -->
|
||||
<target depends="init" description="Create the source distribution archive" name="dist.source">
|
||||
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
|
||||
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<!-- dist:
|
||||
Create both the binary and source distribution archives -->
|
||||
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
|
||||
<echo message="Successfully created binary and source distribution archives in directory 'dist'." />
|
||||
</target>
|
||||
|
||||
<!-- clean:
|
||||
Delete all class files and temporary directories -->
|
||||
<target description="Delete all class files and temporary directories" name="clean">
|
||||
<delete>
|
||||
<fileset dir="${basedir}" includes="**/*.class" />
|
||||
</delete>
|
||||
<echo message="Clean successful." />
|
||||
</target>
|
||||
|
||||
</project>
|
||||
BIN
apps/bogobot/log4j-1.2.8.jar
Normal file
BIN
apps/bogobot/log4j-1.2.8.jar
Normal file
Binary file not shown.
BIN
apps/bogobot/pircbot.jar
Normal file
BIN
apps/bogobot/pircbot.jar
Normal file
Binary file not shown.
106
apps/fortuna/build.xml
Normal file
106
apps/fortuna/build.xml
Normal file
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="fortuna">
|
||||
|
||||
<property name="cvs.base.dir" value="java/gnu-crypto" />
|
||||
<property name="cvs.etc.dir" value="${cvs.base.dir}/etc" />
|
||||
<property name="cvs.lib.dir" value="${cvs.base.dir}/lib" />
|
||||
<property name="cvs.object.dir" value="${cvs.base.dir}/classes" />
|
||||
<property name="cvs.base.crypto.object.dir" value="${cvs.object.dir}/gnu/crypto" />
|
||||
<property name="cvs.cipher.object.dir" value="${cvs.base.crypto.object.dir}/cipher" />
|
||||
<property name="cvs.hash.object.dir" value="${cvs.base.crypto.object.dir}/hash" />
|
||||
<property name="cvs.prng.object.dir" value="${cvs.base.crypto.object.dir}/prng" />
|
||||
|
||||
<patternset id="fortuna.files">
|
||||
<include name="${cvs.base.crypto.object.dir}/Registry.class"/>
|
||||
<include name="${cvs.prng.object.dir}/Fortuna*.class"/>
|
||||
<include name="${cvs.prng.object.dir}/BasePRNG.class"/>
|
||||
<include name="${cvs.prng.object.dir}/RandomEventListener.class"/>
|
||||
<include name="${cvs.prng.object.dir}/IRandom.class"/>
|
||||
<include name="${cvs.cipher.object.dir}/CipherFactory.class"/>
|
||||
<include name="${cvs.cipher.object.dir}/IBlockCipher.class"/>
|
||||
<include name="${cvs.hash.object.dir}/HashFactory.class"/>
|
||||
<include name="${cvs.hash.object.dir}/IMessageDigest.class"/>
|
||||
</patternset>
|
||||
|
||||
<target name="all" depends="build,jar"
|
||||
description="Create and test the custom Fortuna library" />
|
||||
|
||||
<target name="build" depends="-init,checkout"
|
||||
description="Build the source and tests">
|
||||
<ant dir="${cvs.base.dir}" target="jar" />
|
||||
</target>
|
||||
|
||||
<target name="builddep" />
|
||||
|
||||
<target name="checkout" depends="-init" unless="cvs.source.available"
|
||||
description="Check out GNU Crypto sources from CVS HEAD">
|
||||
<cvs cvsRoot=":ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto"
|
||||
cvsRsh="ssh"
|
||||
dest="java"
|
||||
package="gnu-crypto" />
|
||||
</target>
|
||||
|
||||
<target name="clean"
|
||||
description="Remove generated tests and object files">
|
||||
<ant dir="${cvs.base.dir}" target="clean" />
|
||||
</target>
|
||||
|
||||
<target name="cleandep" />
|
||||
|
||||
<target name="compile" />
|
||||
|
||||
<target name="distclean" depends="clean"
|
||||
description="Remove all generated files">
|
||||
<delete dir="build" />
|
||||
<delete dir="jartemp" />
|
||||
<!--
|
||||
Annoyingly the GNU Crypto distclean task called here doesn't clean
|
||||
*all* derived files from java/gnu-crypto/lib like it should.....
|
||||
-->
|
||||
<ant dir="${cvs.base.dir}" target="distclean" />
|
||||
<!--
|
||||
.....and so we mop up the rest ourselves.
|
||||
-->
|
||||
<delete dir="${cvs.lib.dir}" />
|
||||
</target>
|
||||
|
||||
<target name="-init">
|
||||
<available property="cvs.source.available" file="${cvs.base.dir}" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="build"
|
||||
description="Create the custom Fortuna jar library">
|
||||
<delete dir="build" />
|
||||
<delete dir="jartemp" />
|
||||
<mkdir dir="build" />
|
||||
<mkdir dir="jartemp/${cvs.object.dir}" />
|
||||
<copy todir="jartemp">
|
||||
<fileset dir=".">
|
||||
<patternset refid="fortuna.files" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<jar basedir="jartemp/${cvs.object.dir}" jarfile="build/fortuna.jar">
|
||||
<manifest>
|
||||
<section name="fortuna">
|
||||
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
|
||||
<attribute name="Implementation-Version" value="CVS HEAD" />
|
||||
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
|
||||
<attribute name="Implementation-Vendor-Id" value="FSF" />
|
||||
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
|
||||
</section>
|
||||
</manifest>
|
||||
</jar>
|
||||
<delete dir="jartemp" />
|
||||
</target>
|
||||
|
||||
<target name="test" depends="jar"
|
||||
description="Perform crypto tests on custom Fortuna jar library" />
|
||||
<!--
|
||||
Add this when Fortuna tests are added to GNU Crypto, else write some
|
||||
-->
|
||||
|
||||
<target name="update" depends="checkout"
|
||||
description="Update GNU Crypto sources to latest CVS HEAD">
|
||||
<cvs command="update -d" cvsRsh="ssh" dest="java/gnu-crypto" />
|
||||
</target>
|
||||
</project>
|
||||
39
apps/heartbeat/doc/readme.gui.txt
Normal file
39
apps/heartbeat/doc/readme.gui.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
The Heartbeat GUI loads up the stat files generated by the Heartbeat
|
||||
engine and renders them visually, offering a way to drill through different
|
||||
data points and take snapshots as things change (by saving particular stat
|
||||
files for later). The GUI itself doesn't need to be on the same machine
|
||||
as the Heartbeat engine - it pulls the stat files through any URL - even
|
||||
through the EepProxy.
|
||||
|
||||
An example Heartbeat GUI config file follows
|
||||
|
||||
# how often do we want to pull new data to render
|
||||
refreshFrequency=60
|
||||
## for each peer test we may want to include in the GUI:
|
||||
# where to find the current stat file (URL or filename)
|
||||
stat.0.location=http://dev.i2p.net/stats/heartbeatStat_khWY_30s_1kb.txt
|
||||
## optional entries for each peer test describing what we want shown
|
||||
## (and how we want it shown)
|
||||
# do we want to plot the send time (from when the ping was sent until the pong server got it)?
|
||||
stat.0.plot.current.send=true
|
||||
# do we want to plot the receive time (from when the pong was sent until reception)?
|
||||
stat.0.plot.current.receive=true
|
||||
# do we want to plot the lost messages?
|
||||
stat.0.plot.current.lost=true
|
||||
# what color should the current lines be rendered in?
|
||||
stat.0.plot.current.color=BLUE
|
||||
## optional entries for each peer test describing what averages we want
|
||||
## rendered
|
||||
# plot 1 minute send average?
|
||||
stat.0.plot.1m.send=true
|
||||
# plot 1 minute receive average?
|
||||
stat.0.plot.1m.receive=true
|
||||
# plot 1 minute lost message average?
|
||||
stat.0.plot.1m.lost=true
|
||||
# what color should the 1 minute averages be rendered as?
|
||||
stat.0.plot.1m.color=GREEN
|
||||
## repeated for all of the averaged periods, e.g.
|
||||
## stat.0.plot.30m, .60m, 1440m (1 day)
|
||||
|
||||
There may be some other options, such as where to store snapshot files, whether
|
||||
to generate PNG images, etc.
|
||||
122
apps/heartbeat/doc/readme.txt
Normal file
122
apps/heartbeat/doc/readme.txt
Normal file
@@ -0,0 +1,122 @@
|
||||
Heartbeat
|
||||
|
||||
Application layer tool for monitoring the long term health of the
|
||||
network by periodically testing peers, generating stats, and
|
||||
rendering them visually. The engine (both server and client) should
|
||||
work headless and seperate from the GUI, exposing the data in a simple
|
||||
to parse (and human readable) text file for each peer being tested.
|
||||
The GUI then periodically refreshes itself by loading those files (
|
||||
either locally or from a URL) and renders the current state accordingly,
|
||||
giving users a way to check that the network is alive, devs a tool to
|
||||
both monitor the state of the network and to debug different situations (by
|
||||
accessing the stat file - either live or archived).
|
||||
|
||||
The heartbeat configuration file is organized as a standard properties
|
||||
file (by default located at heartbeat.config, but that can be overridden by
|
||||
passing a filename as the first argument to the Heartbeat command):
|
||||
|
||||
# where the router is located (default is localhost)
|
||||
i2cpHost=localhost
|
||||
# I2CP port for the router (default is 7654)
|
||||
i2cpPort=4001
|
||||
# How many hops we want the router to put in our tunnels (default is 2)
|
||||
numHops=2
|
||||
# where our private destination keys are located - if this doesn't exist,
|
||||
# a new one will be created and saved there (by default, heartbeat.keys)
|
||||
privateDestinationFile=heartbeat_r2.keys
|
||||
|
||||
## peer tests configured below:
|
||||
|
||||
# destination peer for test 0
|
||||
peer.0.peer=[destination in base64]
|
||||
# where will we write out the stat data?
|
||||
peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
# how many minutes will we keep stats for?
|
||||
peer.0.statDuration=30
|
||||
# how often will we write out new stat data (in seconds)?
|
||||
peer.0.statFrequency=60
|
||||
# how often will we send a ping to the peer (in seconds)?
|
||||
peer.0.sendFrequency=30
|
||||
# how many bytes will be included in the ping?
|
||||
peer.0.sendSize=1024
|
||||
# take a guess...
|
||||
peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
# we can keep track of a few moving averages - this value includes a whitespace
|
||||
# delimited list of numbers, each specifying a period to calculate the average
|
||||
# over (in minutes)
|
||||
peer.0.averagePeriods=1 5 30
|
||||
## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
|
||||
If there are no peer.* lines, it will simply run a pong server. If any data is
|
||||
missing, it will use the defaults (though there are no defaults for peer.* lines) -
|
||||
running the Heartbeat app with no heartbeat configuration file whatsoever will create
|
||||
a new pong server (storing its keys at heartbeat.keys) and using the I2P router at
|
||||
localhost:7654.
|
||||
|
||||
The stat file generated for each set of peer.n.* lines contains the current state
|
||||
of the test, its averages, as well as any other interesting data points. An example
|
||||
stat file follows (hopefully it is self explanatory):
|
||||
|
||||
peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
peerDest [base 64 of the full destination]
|
||||
localDest [base 64 of the full destination]
|
||||
numTunnelHops 2
|
||||
comment Test with localhost sending 30KB every 20 seconds
|
||||
sendFrequency 20
|
||||
sendSize 30720
|
||||
sessionStart 20040409.22:51:10.915
|
||||
currentTime 20040409.23:31:39.607
|
||||
numPending 2
|
||||
lifetimeSent 118
|
||||
lifetimeRecv 113
|
||||
#averages minutes sendMs recvMs numLost
|
||||
periodAverage 1 1843 771 0
|
||||
periodAverage 5 786 752 1
|
||||
periodAverage 30 855 735 3
|
||||
#action status date and time sent sendMs replyMs
|
||||
EVENT OK 20040409.23:21:44.742 691 670
|
||||
EVENT OK 20040409.23:22:05.201 671 581
|
||||
EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
EVENT OK 20040409.23:23:29.332 1392 641
|
||||
EVENT OK 20040409.23:23:50.262 641 761
|
||||
EVENT OK 20040409.23:24:11.102 651 701
|
||||
EVENT OK 20040409.23:24:31.401 841 621
|
||||
EVENT OK 20040409.23:24:52.061 651 681
|
||||
EVENT OK 20040409.23:25:12.480 701 1623
|
||||
EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
EVENT OK 20040409.23:25:54.230 591 631
|
||||
EVENT OK 20040409.23:26:14.620 620 691
|
||||
EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
EVENT OK 20040409.23:26:56.570 661 641
|
||||
EVENT OK 20040409.23:27:17.200 641 660
|
||||
EVENT OK 20040409.23:27:38.120 611 921
|
||||
EVENT OK 20040409.23:27:58.699 831 621
|
||||
EVENT OK 20040409.23:28:19.559 801 661
|
||||
EVENT OK 20040409.23:28:40.279 601 611
|
||||
EVENT OK 20040409.23:29:00.648 601 621
|
||||
EVENT OK 20040409.23:29:21.288 701 661
|
||||
EVENT LOST 20040409.23:29:41.828
|
||||
EVENT LOST 20040409.23:30:02.327
|
||||
EVENT LOST 20040409.23:30:22.656
|
||||
EVENT OK 20040409.23:31:24.305 1843 771
|
||||
|
||||
The actual ping and pong messages sent are formatted trivially -
|
||||
ping messages contain
|
||||
$from $series $type $sentOn $size $payload
|
||||
while pong messages contain
|
||||
$from $series $type $sentOn $receivedOn $size $payload
|
||||
|
||||
$series is a number describing the sending client's test (so that you can
|
||||
ping the same peer with different configurations concurrently, varying things
|
||||
like the frequency and size of the message, window, etc).
|
||||
|
||||
They are sent as raw binary messages though, so see I2PAdapter.sendPing(..)
|
||||
and I2PAdapter.sendPong(..) for the details.
|
||||
|
||||
To get valid measurements, of course, you will want to make sure that
|
||||
both the heartbeat client and pong server have synchronized clocks (even
|
||||
more so than I2P requires). It is highly recommended that only NTP
|
||||
synchronized peers be used for heartbeat tests.
|
||||
66
apps/heartbeat/java/build.xml
Normal file
66
apps/heartbeat/java/build.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="heartbeat">
|
||||
<target name="all" depends="clean, buildGUI" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="buildGUI" depends="build, jarGUI" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" deprecation="on" source="1.3" target="1.3" destdir="./build/obj" includes="**/*.java" excludes="net/i2p/heartbeat/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="compileGUI">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/heartbeat.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.Heartbeat" />
|
||||
<attribute name="Class-Path" value="i2p.jar heartbeat.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/heartbeatGUI.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.gui.HeartbeatMonitor" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar heartbeatGUI.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P heartbeat monitor" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
468
apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java
Normal file
468
apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java
Normal file
@@ -0,0 +1,468 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define the configuration for testing against one particular peer as a client
|
||||
*/
|
||||
public class ClientConfig {
|
||||
private static final Log _log = new Log(ClientConfig.class);
|
||||
private Destination _peer;
|
||||
private Destination _us;
|
||||
private String _statFile;
|
||||
private int _statDuration;
|
||||
private int _statFrequency;
|
||||
private int _sendFrequency;
|
||||
private int _sendSize;
|
||||
private int _numHops;
|
||||
private String _comment;
|
||||
private int _averagePeriods[];
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PREFIX = "peer.";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PEER = ".peer";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFILE = ".statFile";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATDURATION = ".statDuration";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFREQUENCY = ".statFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDFREQUENCY = ".sendFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDSIZE = ".sendSize";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_COMMENT = ".comment";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_AVERAGEPERIODS = ".averagePeriods";
|
||||
|
||||
/**
|
||||
* Default constructor...
|
||||
*/
|
||||
public ClientConfig() {
|
||||
this(null, null, null, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy client config to be fetched from the specified location
|
||||
* @param location the location to fetch from
|
||||
*/
|
||||
public ClientConfig(String location) {
|
||||
this(null, null, location, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param peer who we will test against
|
||||
* @param us who we are
|
||||
* @param statLocation where the stat data should be stored/fetched
|
||||
* @param duration how many minutes to keep events for
|
||||
* @param statFreq how often to write out stats
|
||||
* @param sendFreq how often to send pings
|
||||
* @param sendSize how large the pings should be
|
||||
* @param numHops how many hops is the current Heartbeat app using
|
||||
* @param comment describe this test
|
||||
* @param averagePeriods list of minutes to summarize over
|
||||
*/
|
||||
public ClientConfig(Destination peer, Destination us, String statLocation, int duration, int statFreq, int sendFreq,
|
||||
int sendSize, int numHops, String comment, int averagePeriods[]) {
|
||||
_peer = peer;
|
||||
_us = us;
|
||||
_statFile = statLocation;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_numHops = numHops;
|
||||
_comment = comment;
|
||||
_averagePeriods = averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the peer to test against
|
||||
*
|
||||
* @return the Destination (peer)
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the peer to test against
|
||||
*
|
||||
* @param peer the Destination (peer)
|
||||
*/
|
||||
public void setPeer(Destination peer) {
|
||||
_peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves who we are when we test
|
||||
*
|
||||
* @return the Destination (us)
|
||||
*/
|
||||
public Destination getUs() {
|
||||
return _us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who we are when we test
|
||||
*
|
||||
* @param us the Destination (us)
|
||||
*/
|
||||
public void setUs(Destination us) {
|
||||
_us = us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the location to write the current stats to
|
||||
*
|
||||
* @return the name of the file
|
||||
*/
|
||||
public String getStatFile() {
|
||||
return _statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the location we write the current stats to
|
||||
*
|
||||
* @param statFile the name of the file
|
||||
*/
|
||||
public void setStatFile(String statFile) {
|
||||
_statFile = statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @return the number of minutes
|
||||
*/
|
||||
public int getStatDuration() {
|
||||
return _statDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @param durationMinutes the number of minutes
|
||||
*/
|
||||
public void setStatDuration(int durationMinutes) {
|
||||
_statDuration = durationMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getStatFrequency() {
|
||||
return _statFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setStatFrequency(int freqSeconds) {
|
||||
_statFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getSendFrequency() {
|
||||
return _sendFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setSendFrequency(int freqSeconds) {
|
||||
_sendFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @return the size in bytes
|
||||
*/
|
||||
public int getSendSize() {
|
||||
return _sendSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @param numBytes the size in bytes
|
||||
*/
|
||||
public void setSendSize(int numBytes) {
|
||||
_sendSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the brief, 1 line description of the test. Useful comments are along the lines of "The peer is located on a fast router and connection with 2
|
||||
* hop tunnels".
|
||||
*
|
||||
* @return the brief comment
|
||||
*/
|
||||
public String getComment() {
|
||||
return _comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a brief, 1 line description (comment) of the test.
|
||||
*
|
||||
* @param comment the brief comment
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
_comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @return list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return _averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @param periods the list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public void setAveragePeriods(int periods[]) {
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we're keeping track of the average over the given time period.
|
||||
*
|
||||
* @param minutes how many minutes to monitor
|
||||
*/
|
||||
public void addAveragePeriod(int minutes) {
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
if (_averagePeriods[i] == minutes)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int numPeriods = 1;
|
||||
if (_averagePeriods != null)
|
||||
numPeriods += _averagePeriods.length;
|
||||
int periods[] = new int[numPeriods];
|
||||
if (_averagePeriods != null)
|
||||
System.arraycopy(_averagePeriods, 0, periods, 0, _averagePeriods.length);
|
||||
periods[periods.length-1] = minutes;
|
||||
Arrays.sort(periods);
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @param numHops the number of hops
|
||||
*/
|
||||
public void setNumHops(int numHops) {
|
||||
_numHops = numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the client config from the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to load from
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was loaded correctly, false if there were errors
|
||||
*/
|
||||
public boolean load(Properties clientConfig, int peerNum) {
|
||||
if ((clientConfig == null) || (peerNum < 0)) return false;
|
||||
String peerVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_PEER);
|
||||
String statFileVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFILE);
|
||||
String statDurationVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATDURATION);
|
||||
String statFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY);
|
||||
String sendFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY);
|
||||
String sendSizeVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE);
|
||||
String commentVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_COMMENT);
|
||||
String periodsVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS);
|
||||
|
||||
if ((peerVal == null) || (statFileVal == null) || (statDurationVal == null) || (statFrequencyVal == null)
|
||||
|| (sendFrequencyVal == null) || (sendSizeVal == null)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Peer number " + peerNum + " does not exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
int duration = getInt(statDurationVal);
|
||||
int statFreq = getInt(statFrequencyVal);
|
||||
int sendFreq = getInt(sendFrequencyVal);
|
||||
int sendSize = getInt(sendSizeVal);
|
||||
|
||||
if ((duration <= 0) || (statFreq <= 0) || (sendFreq < 0) || (sendSize <= 0)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid client config: duration [" + statDurationVal + "] stat frequency ["
|
||||
+ statFrequencyVal + "] send frequency [" + sendFrequencyVal + "] send size ["
|
||||
+ sendSizeVal + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
statFileVal = statFileVal.trim();
|
||||
if (statFileVal.length() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Stat file is blank for peer " + peerNum);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(peerVal);
|
||||
|
||||
if (commentVal == null) {
|
||||
commentVal = "";
|
||||
}
|
||||
|
||||
commentVal = commentVal.trim();
|
||||
commentVal = commentVal.replace('\n', '_');
|
||||
|
||||
List periods = new ArrayList(4);
|
||||
if (periodsVal != null) {
|
||||
StringTokenizer tok = new StringTokenizer(periodsVal);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String periodVal = tok.nextToken();
|
||||
int minutes = getInt(periodVal);
|
||||
if (minutes > 0) {
|
||||
periods.add(new Integer(minutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
int avgPeriods[] = new int[periods.size()];
|
||||
for (int i = 0; i < periods.size(); i++) {
|
||||
avgPeriods[i] = ((Integer) periods.get(i)).intValue();
|
||||
}
|
||||
|
||||
_comment = commentVal;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_statFile = statFileVal;
|
||||
_peer = d;
|
||||
_averagePeriods = avgPeriods;
|
||||
return true;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination for " + peerNum + " was invalid: " + peerVal);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the client config to the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to store to
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was stored correctly, false if there were errors
|
||||
*/
|
||||
public boolean store(Properties clientConfig, int peerNum) {
|
||||
if ((_peer == null) || (_sendFrequency < 0) || (_sendSize <= 0) || (_statDuration <= 0)
|
||||
|| (_statFrequency <= 0) || (_statFile == null)) { return false; }
|
||||
|
||||
String comment = _comment;
|
||||
if (comment == null) {
|
||||
comment = "";
|
||||
}
|
||||
|
||||
comment = comment.trim();
|
||||
comment = comment.replace('\n', '_');
|
||||
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
buf.append(_averagePeriods[i]).append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_PEER, _peer.toBase64());
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFILE, _statFile);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATDURATION, _statDuration + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY, _statFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY, _sendFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE, _sendSize + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_COMMENT, comment);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS, buf.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final int getInt(String val) {
|
||||
if (val == null) return -1;
|
||||
try {
|
||||
int i = Integer.parseInt(val);
|
||||
return i;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Value [" + val + "] is not a valid integer");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
133
apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java
Normal file
133
apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
* stats, and the management of the rates. This has its own thread specific for
|
||||
* pumping data around as well.
|
||||
*
|
||||
*/
|
||||
class ClientEngine {
|
||||
private static final Log _log = new Log(ClientEngine.class);
|
||||
/** who can send our pings? */
|
||||
private Heartbeat _heartbeat;
|
||||
/** actual test state */
|
||||
private PeerData _data;
|
||||
/** have we been stopped? */
|
||||
private boolean _active;
|
||||
/** used to generate engine IDs */
|
||||
private static int __id = 0;
|
||||
/** this engine's id, unique to the {test,sendingClient,startTime} */
|
||||
private int _id;
|
||||
private static PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
/**
|
||||
* Create a new engine that will send its pings through the given heartbeat
|
||||
* system, and will coordinate the test according to the configuration specified.
|
||||
* @param heartbeat the Heartbeat to send pings through
|
||||
* @param config the Configuration to load configuration from =p
|
||||
*/
|
||||
public ClientEngine(Heartbeat heartbeat, ClientConfig config) {
|
||||
_heartbeat = heartbeat;
|
||||
_data = new PeerData(config);
|
||||
_active = false;
|
||||
_id = ++__id;
|
||||
}
|
||||
|
||||
/** stop sending any more pings or writing any more state */
|
||||
public void stopEngine() {
|
||||
_active = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Stopping engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
}
|
||||
|
||||
/** start up the test (this does not block, as it fires up the test thread) */
|
||||
public void startEngine() {
|
||||
_active = true;
|
||||
I2PThread t = new I2PThread(new ClientRunner());
|
||||
t.setName("HeartbeatClient " + _id);
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Who are we testing?
|
||||
* @return the Destination (peer) we're testing
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _data.getConfig().getPeer();
|
||||
}
|
||||
|
||||
/**
|
||||
* What is our series identifier (used to locally identify a test)
|
||||
* @return the series identifier
|
||||
*/
|
||||
public int getSeriesNum() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive notification from the heartbeat system that a pong was received in
|
||||
* reply to a ping we have sent.
|
||||
*
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did the peer send the pong?
|
||||
*/
|
||||
public void receivePong(long sentOn, long replyOn) {
|
||||
_data.pongReceived(sentOn, replyOn);
|
||||
}
|
||||
|
||||
/** fire off a new ping */
|
||||
private void doSend() {
|
||||
long now = Clock.getInstance().now();
|
||||
_data.addPing(now);
|
||||
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
|
||||
}
|
||||
|
||||
/** our actual heartbeat pumper - this drives the test */
|
||||
private class ClientRunner implements Runnable {
|
||||
|
||||
/**
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
|
||||
// when do we need to send the next PING?
|
||||
long nextSend = Clock.getInstance().now();
|
||||
// when do we need to write out the next state data?
|
||||
long nextWrite = Clock.getInstance().now();
|
||||
|
||||
while (_active) {
|
||||
|
||||
if (Clock.getInstance().now() >= nextSend) {
|
||||
doSend();
|
||||
nextSend = Clock.getInstance().now() + _data.getConfig().getSendFrequency() * 1000;
|
||||
}
|
||||
|
||||
if (Clock.getInstance().now() >= nextWrite) {
|
||||
boolean written = writer.persist(_data);
|
||||
if (!written) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Unable to write the client state data");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Client state data written");
|
||||
}
|
||||
}
|
||||
|
||||
_data.cleanup();
|
||||
|
||||
long timeToWait = nextSend - Clock.getInstance().now();
|
||||
if (timeToWait > 0) {
|
||||
try {
|
||||
Thread.sleep(timeToWait);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
254
apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java
Normal file
254
apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the heartbeat engine, loading 0 or more tests, firing
|
||||
* up a ClientEngine for each, and serving as a pong server. If there isn't
|
||||
* a configuration file, or if the configuration file doesn't specify any tests,
|
||||
* it simply sits around as a pong server, passively responding to whatever is
|
||||
* sent its way. <p />
|
||||
*
|
||||
* The config file format is examplified below:
|
||||
* <pre>
|
||||
* # where the router is located (default is localhost)
|
||||
* i2cpHost=localhost
|
||||
* # I2CP port for the router (default is 7654)
|
||||
* i2cpPort=4001
|
||||
* # How many hops we want the router to put in our tunnels (default is 2)
|
||||
* numHops=2
|
||||
* # where our private destination keys are located - if this doesn't exist,
|
||||
* # a new one will be created and saved there (by default, heartbeat.keys)
|
||||
* privateDestinationFile=heartbeat_r2.keys
|
||||
* # where do we want to export the plain base64 of our destination?
|
||||
* publicDestinationFile=heartbeat_r2.txt
|
||||
*
|
||||
* ## peer tests configured below:
|
||||
*
|
||||
* # destination peer for test 0
|
||||
* peer.0.peer=[destination in base64]
|
||||
* # where will we write out the stat data?
|
||||
* peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
* # how many minutes will we keep stats for?
|
||||
* peer.0.statDuration=30
|
||||
* # how often will we write out new stat data (in seconds)?
|
||||
* peer.0.statFrequency=60
|
||||
* # how often will we send a ping to the peer (in seconds)?
|
||||
* peer.0.sendFrequency=30
|
||||
* # how many bytes will be included in the ping?
|
||||
* peer.0.sendSize=1024
|
||||
* # take a guess...
|
||||
* peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
* # we can keep track of a few moving averages - this value includes a whitespace
|
||||
* # delimited list of numbers, each specifying a period to calculate the average
|
||||
* # over (in minutes)
|
||||
* peer.0.averagePeriods=1 5 30
|
||||
* ## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class Heartbeat {
|
||||
private static final Log _log = new Log(Heartbeat.class);
|
||||
/** location containing this heartbeat's config */
|
||||
private String _configFile;
|
||||
/** clientNum (Integer) to ClientConfig mapping */
|
||||
private Map _clientConfigs;
|
||||
/** series num (Integer) to ClientEngine mapping */
|
||||
private Map _clientEngines;
|
||||
/** helper class for managing our I2P send/receive and message formatting */
|
||||
private I2PAdapter _adapter;
|
||||
/** our own callback that the I2PAdapter notifies on ping or pong messages */
|
||||
private PingPongAdapter _eventAdapter;
|
||||
|
||||
/** if there are no command line arguments, load the config from "heartbeat.config" */
|
||||
public static final String CONFIG_FILE_DEFAULT = "heartbeat.config";
|
||||
|
||||
/**
|
||||
* build up a new heartbeat manager, but don't actually do anything
|
||||
* @param configFile the name of the configuration file
|
||||
*/
|
||||
public Heartbeat(String configFile) {
|
||||
_configFile = configFile;
|
||||
_clientConfigs = new HashMap();
|
||||
_clientEngines = new HashMap();
|
||||
_eventAdapter = new PingPongAdapter();
|
||||
_adapter = new I2PAdapter();
|
||||
_adapter.setListener(_eventAdapter);
|
||||
}
|
||||
|
||||
private Heartbeat() {
|
||||
}
|
||||
|
||||
/** load up the config data (but don't build any engines or start them up) */
|
||||
public void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fin = null;
|
||||
File configFile = new File(_configFile);
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(_configFile);
|
||||
props.load(fin);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error reading the config data", ioe);
|
||||
}
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadBaseConfig(props);
|
||||
loadClientConfigs(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*/
|
||||
void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_adapter.getIsConnected()) _adapter.sendPing(peer, seriesNum, now, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up the base data (I2CP config, etc)
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
private void loadBaseConfig(Properties props) {
|
||||
_adapter.loadConfig(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up all of the test config data
|
||||
* @param props the properties to load from
|
||||
* */
|
||||
private void loadClientConfigs(Properties props) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
ClientConfig config = new ClientConfig();
|
||||
if (!config.load(props, i)) {
|
||||
break;
|
||||
}
|
||||
_clientConfigs.put(new Integer(i), config);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** connect to the network */
|
||||
private void connect() {
|
||||
boolean connected = _adapter.connect();
|
||||
if (!connected) _log.error("Unable to connect to the router");
|
||||
}
|
||||
|
||||
/** disconnect from the network */
|
||||
private void disconnect() { /* UNUSED */
|
||||
_adapter.disconnect();
|
||||
}
|
||||
|
||||
/** start up all of the tests */
|
||||
public void startEngines() {
|
||||
for (Iterator iter = _clientConfigs.values().iterator(); iter.hasNext();) {
|
||||
ClientConfig config = (ClientConfig) iter.next();
|
||||
ClientEngine engine = new ClientEngine(this, config);
|
||||
config.setUs(_adapter.getLocalDestination());
|
||||
config.setNumHops(_adapter.getNumHops());
|
||||
_clientEngines.put(new Integer(engine.getSeriesNum()), engine);
|
||||
engine.startEngine();
|
||||
}
|
||||
}
|
||||
|
||||
/** stop all of the tests */
|
||||
public void stopEngines() {
|
||||
for (Iterator iter = _clientEngines.values().iterator(); iter.hasNext();) {
|
||||
ClientEngine engine = (ClientEngine) iter.next();
|
||||
engine.stopEngine();
|
||||
}
|
||||
_clientEngines.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up a new heartbeat system, waiting until, well, forever. Builds
|
||||
* a new heartbeat system, loads the config, connects to the network, starts
|
||||
* the engines, and then sits back and relaxes, responding to any pings and
|
||||
* running any tests. <p />
|
||||
*
|
||||
* <code> <b>Usage: </b> Heartbeat [<i>configFileName</i>]</code> <p />
|
||||
* @param args the list of args passed to the program from the command-line
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String configFile = CONFIG_FILE_DEFAULT;
|
||||
if (args.length == 1) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Starting up with config file " + configFile);
|
||||
}
|
||||
Heartbeat heartbeat = new Heartbeat(configFile);
|
||||
heartbeat.loadConfig();
|
||||
heartbeat.connect();
|
||||
heartbeat.startEngines();
|
||||
Object o = new Object();
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (o) {
|
||||
o.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive event notification from the I2PAdapter
|
||||
*
|
||||
*/
|
||||
private class PingPongAdapter implements I2PAdapter.PingPongEventListener {
|
||||
/**
|
||||
* We were pinged, so always just send a pong back.
|
||||
*
|
||||
* @param from who sent us the ping?
|
||||
* @param seriesNum what series did the sender specify?
|
||||
* @param sentOn when did the sender say they sent their ping?
|
||||
* @param data arbitrary payload data
|
||||
*/
|
||||
public void receivePing(Destination from, int seriesNum, Date sentOn, byte[] data) {
|
||||
if (_adapter.getIsConnected()) {
|
||||
_adapter.sendPong(from, seriesNum, sentOn, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a pong, so find the right client engine and tell it about the pong.
|
||||
*
|
||||
* @param from who sent us the pong
|
||||
* @param seriesNum our client ID
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did they send their pong?
|
||||
* @param data the arbitrary data we sent in the ping (that they sent back in the pong)
|
||||
*/
|
||||
public void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte[] data) {
|
||||
ClientEngine engine = (ClientEngine) _clientEngines.get(new Integer(seriesNum));
|
||||
if (engine.getPeer().equals(from)) {
|
||||
engine.receivePong(sentOn.getTime(), replyOn.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
604
apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java
Normal file
604
apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java
Normal file
@@ -0,0 +1,604 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Tie-in to the I2P SDK for the Heartbeat system, talking to the I2PSession and
|
||||
* dealing with the raw ping and pong messages.
|
||||
*
|
||||
*/
|
||||
class I2PAdapter {
|
||||
private final static Log _log = new Log(I2PAdapter.class);
|
||||
/** I2CP host */
|
||||
private String _i2cpHost;
|
||||
/** I2CP port */
|
||||
private int _i2cpPort;
|
||||
/** how long do we want our tunnels to be? */
|
||||
private int _numHops;
|
||||
/** filename containing the heartbeat engine's private destination info */
|
||||
private String _privateDestFile;
|
||||
/** filename to store the heartbeat engine's public destination in base64*/
|
||||
private String _publicDestFile;
|
||||
/** our destination */
|
||||
private Destination _localDest;
|
||||
/** who do we tell? */
|
||||
private PingPongEventListener _listener;
|
||||
/** how do we talk to the router */
|
||||
private I2PSession _session;
|
||||
/** object that receives our i2cp notifications from the session and tells us */
|
||||
private I2PListener _i2pListener; /* UNUSED */
|
||||
|
||||
/**
|
||||
* This config property tells us where the private destination data for our
|
||||
* connection (or if it doesn't exist, where will we save it)
|
||||
*/
|
||||
private static final String DEST_FILE_PROP = "privateDestinationFile";
|
||||
/** by default, the private destination data is in "heartbeat.keys" */
|
||||
private static final String DEST_FILE_DEFAULT = "heartbeat.keys";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_PROP = "publicDestinationFile";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_DEFAULT = "heartbeat.txt";
|
||||
/** This config property defines where the I2P router is */
|
||||
private static final String I2CP_HOST_PROP = "i2cpHost";
|
||||
/** by default, the I2P host is "localhost" */
|
||||
private static final String I2CP_HOST_DEFAULT = "localhost";
|
||||
/** This config property defines the I2CP port on the router */
|
||||
private static final String I2CP_PORT_PROP = "i2cpPort";
|
||||
/** by default, the I2CP port is 7654 */
|
||||
private static final int I2CP_PORT_DEFAULT = 7654;
|
||||
|
||||
/** This property defines how many hops we want in our tunnels. */
|
||||
public static final String NUMHOPS_PROP = "numHops";
|
||||
/** by default, use 2 hop tunnels */
|
||||
public static final int NUMHOPS_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Constructs an I2PAdapter . . .
|
||||
*/
|
||||
public I2PAdapter() {
|
||||
_privateDestFile = null;
|
||||
_publicDestFile = null;
|
||||
_i2cpHost = null;
|
||||
_i2cpPort = -1;
|
||||
_localDest = null;
|
||||
_listener = null;
|
||||
_session = null;
|
||||
_numHops = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* who are we?
|
||||
* @return the destination (us)
|
||||
*/
|
||||
public Destination getLocalDestination() {
|
||||
return _localDest;
|
||||
}
|
||||
|
||||
/**
|
||||
* who gets notified when we receive a ping or a pong?
|
||||
* @return the event listener who gets notified
|
||||
*/
|
||||
public PingPongEventListener getListener() {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who gets notified when we receive a ping or a pong
|
||||
* @param listener the event listener to get notified
|
||||
*/
|
||||
public void setListener(PingPongEventListener listener) {
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many hops do we want in our tunnels?
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* are we connected?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getIsConnected() {
|
||||
return _session != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all of the config data
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
void loadConfig(Properties props) {
|
||||
String privDestFile = props.getProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
String pubDestFile = props.getProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
String host = props.getProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
String port = props.getProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
String numHops = props.getProperty(NUMHOPS_PROP, "" + NUMHOPS_DEFAULT);
|
||||
|
||||
int portNum = -1;
|
||||
try {
|
||||
portNum = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
portNum = I2CP_PORT_DEFAULT;
|
||||
}
|
||||
int hops = -1;
|
||||
try {
|
||||
hops = Integer.parseInt(numHops);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid # hops specified [" + numHops + "]");
|
||||
}
|
||||
hops = NUMHOPS_DEFAULT;
|
||||
}
|
||||
|
||||
_numHops = hops;
|
||||
_privateDestFile = privDestFile;
|
||||
_publicDestFile = pubDestFile;
|
||||
_i2cpHost = host;
|
||||
_i2cpPort = portNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* write out the config to the props
|
||||
* @param props the properties to write to
|
||||
*/
|
||||
void storeConfig(Properties props) {
|
||||
if (_privateDestFile != null) {
|
||||
props.setProperty(DEST_FILE_PROP, _privateDestFile);
|
||||
} else {
|
||||
props.setProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_publicDestFile != null) {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, _publicDestFile);
|
||||
} else {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpHost != null) {
|
||||
props.setProperty(I2CP_HOST_PROP, _i2cpHost);
|
||||
} else {
|
||||
props.setProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpPort > 0) {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + _i2cpPort);
|
||||
} else {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
}
|
||||
|
||||
props.setProperty(NUMHOPS_PROP, "" + _numHops);
|
||||
}
|
||||
|
||||
private static final int TYPE_PING = 0;
|
||||
private static final int TYPE_PONG = 1;
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PING);
|
||||
DataHelper.writeDate(baos, new Date(now));
|
||||
int padding = size - baos.size();
|
||||
byte paddingData[] = new byte[padding];
|
||||
I2PAppContext.getGlobalContext().random().nextBytes(paddingData);
|
||||
//Arrays.fill(paddingData, (byte) 0x2A);
|
||||
DataHelper.writeLong(baos, 2, padding);
|
||||
baos.write(paddingData);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send a pong message to the peer
|
||||
*
|
||||
* @param peer peer to pong
|
||||
* @param seriesNum id given to us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload the peer sent us in the ping
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPong(Destination peer, int seriesNum, Date sentOn, byte data[]) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length + 768);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PONG);
|
||||
DataHelper.writeDate(baos, sentOn);
|
||||
DataHelper.writeDate(baos, new Date(Clock.getInstance().now()));
|
||||
DataHelper.writeLong(baos, 2, data.length);
|
||||
baos.write(data);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the pong to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum + " which was sent on " + sentOn);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum
|
||||
+ " which was sent on " + sentOn);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received this data from I2P - parse it into a ping or a pong
|
||||
* and notify accordingly
|
||||
* @param data the data to handle
|
||||
*/
|
||||
private void handleMessage(byte data[]) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
try {
|
||||
Destination from = new Destination();
|
||||
from.readBytes(bais);
|
||||
int series = (int) DataHelper.readLong(bais, 2);
|
||||
long type = DataHelper.readLong(bais, 1);
|
||||
Date sentOn = DataHelper.readDate(bais);
|
||||
Date receivedOn = null;
|
||||
if (type == TYPE_PONG) {
|
||||
receivedOn = DataHelper.readDate(bais);
|
||||
}
|
||||
int size = (int) DataHelper.readLong(bais, 2);
|
||||
byte payload[] = new byte[size];
|
||||
int read = DataHelper.read(bais, payload);
|
||||
if (read != size) { throw new IOException("Malformed payload - read " + read + " instead of " + size); }
|
||||
|
||||
if (_listener == null) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Listener isn't set, but we received a valid message of type " + type + " sent from "
|
||||
+ from.calculateHash().toBase64());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == TYPE_PING) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " containing " + size + " bytes");
|
||||
}
|
||||
_listener.receivePing(from, series, sentOn, payload);
|
||||
} else if (type == TYPE_PONG) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " with pong sent on " + receivedOn + " containing " + size
|
||||
+ " bytes");
|
||||
}
|
||||
_listener.receivePong(from, series, sentOn, receivedOn, payload);
|
||||
} else {
|
||||
throw new IOException("Invalid message type " + type);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error handling the message", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error parsing the message", dfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* connect to the I2P router and either authenticate ourselves with the
|
||||
* destination we're given, or create a new one and write that to the
|
||||
* destination file.
|
||||
*
|
||||
* @return true if we connect successfully, false otherwise
|
||||
*/
|
||||
boolean connect() {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination us = null;
|
||||
File destFile = new File(_privateDestFile);
|
||||
us = verifyDestination(client, destFile);
|
||||
if (us == null) return false;
|
||||
|
||||
// if we're here, we got a destination. lets connect
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
Properties options = getOptions();
|
||||
I2PSession session = client.createSession(fin, options);
|
||||
I2PListener lsnr = new I2PListener();
|
||||
session.setSessionListener(lsnr);
|
||||
session.connect();
|
||||
_localDest = session.getMyDestination();
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
|
||||
}
|
||||
_session = session;
|
||||
_i2pListener = lsnr;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error connecting", ise);
|
||||
}
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error loading the destionation", ioe);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* load, verify, or create a destination
|
||||
*
|
||||
* @param client the client
|
||||
* @param destFile the file holding the destination
|
||||
* @return the destination loaded, or null if there was an error
|
||||
*/
|
||||
private Destination verifyDestination(I2PClient client, File destFile) {
|
||||
Destination us = null;
|
||||
FileInputStream fin = null;
|
||||
if (destFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
us = new Destination();
|
||||
us.readBytes(fin);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Existing destination loaded: [" + us.toBase64() + "]");
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (us == null) {
|
||||
// need to create a new one
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(destFile);
|
||||
us = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("New destination created: [" + us.toBase64() + "]");
|
||||
}
|
||||
fos.close();
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
fos = null;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the destination keys being created", ioe);
|
||||
}
|
||||
return null;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error creating the destination", ie);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return us;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2PSession connect options
|
||||
* @return the options as Properties
|
||||
*/
|
||||
private Properties getOptions() {
|
||||
Properties props = new Properties();
|
||||
// this should be BEST_EFFORT, but i'm too lazy to update the code to handle tracking
|
||||
// sessionTags and sessionKeys, marking them as delivered on pong.
|
||||
props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
props.setProperty(I2PClient.PROP_TCP_HOST, _i2cpHost);
|
||||
props.setProperty(I2PClient.PROP_TCP_PORT, _i2cpPort + "");
|
||||
props.setProperty("tunnels.depthInbound", "" + _numHops);
|
||||
props.setProperty("tunnels.depthOutbound", "" + _numHops);
|
||||
return props;
|
||||
}
|
||||
|
||||
/** disconnect from the I2P router */
|
||||
void disconnect() {
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error destroying the session", ise);
|
||||
}
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an event notification system for receiving pings and pongs
|
||||
*
|
||||
*/
|
||||
public interface PingPongEventListener {
|
||||
/**
|
||||
* receive a ping message from the peer
|
||||
*
|
||||
* @param from peer that sent us the ping
|
||||
* @param seriesNum id the peer sent us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload from the ping
|
||||
*/
|
||||
void receivePing(Destination from, int seriesNum, Date sentOn, byte data[]);
|
||||
|
||||
/**
|
||||
* receive a pong message from the peer
|
||||
*
|
||||
* @param from peer that sent us the pong
|
||||
* @param seriesNum id the peer sent us in the pong (that we sent them in the ping)
|
||||
* @param sentOn when we sent out the ping
|
||||
* @param replyOn when they sent out the pong
|
||||
* @param data payload from the ping/pong
|
||||
*/
|
||||
void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte data[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive data from the session and pass it along to handleMessage for parsing/dispersal
|
||||
*
|
||||
*/
|
||||
private class I2PListener implements I2PSessionListener {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#disconnected(net.i2p.client.I2PSession)
|
||||
*/
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Session disconnected");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#errorOccurred(net.i2p.client.I2PSession, java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred: " + message, error);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#reportAbuse(net.i2p.client.I2PSession, int)
|
||||
*/
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Abuse reported with severity " + String.valueOf(severity));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#messageAvailable(net.i2p.client.I2PSession, int, long)
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
handleMessage(data);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error receiving the message", ise);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
412
apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java
Normal file
412
apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java
Normal file
@@ -0,0 +1,412 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Contain the current window of data for a particular series of ping/pong stats
|
||||
* sent to a peer. This should be periodically kept clean by calling cleanup()
|
||||
* to timeout expired pings and to drop data outside the window.
|
||||
*
|
||||
*/
|
||||
public class PeerData {
|
||||
private final static Log _log = new Log(PeerData.class);
|
||||
/** peer / sequence / config in this data series */
|
||||
private ClientConfig _peer;
|
||||
/** date sent (Long) to EventDataPoint containing the datapoints sent in the current period */
|
||||
private Map _dataPoints;
|
||||
/** date sent (Long) to EventDataPoint containing pings that haven't yet timed out or been ponged */
|
||||
private TreeMap _pendingPings;
|
||||
private long _sessionStart;
|
||||
private long _lifetimeSent;
|
||||
private long _lifetimeReceived;
|
||||
/** rate averaging the time to send over a variety of periods */
|
||||
private RateStat _sendRate;
|
||||
/** rate averaging the time to receive over a variety of periods */
|
||||
private RateStat _receiveRate;
|
||||
/** rate averaging the frequency of lost messages over a variety of periods */
|
||||
private RateStat _lostRate;
|
||||
|
||||
/** how long we wait before timing out pending pings (30 seconds) */
|
||||
private static final long TIMEOUT_PERIOD = 60 * 1000;
|
||||
|
||||
/** synchronize on this when updating _dataPoints or _pendingPings */
|
||||
private Object _updateLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a PeerData . . .
|
||||
* @param config configuration to load from
|
||||
*/
|
||||
public PeerData(ClientConfig config) {
|
||||
_peer = config;
|
||||
_dataPoints = new TreeMap();
|
||||
_pendingPings = new TreeMap();
|
||||
_sessionStart = Clock.getInstance().now();
|
||||
_lifetimeSent = 0;
|
||||
_lifetimeReceived = 0;
|
||||
_sendRate = new RateStat("sendRate", "How long it takes to send", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_receiveRate = new RateStat("receiveRate", "How long it takes to receive", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_lostRate = new RateStat("lostRate", "How frequently we lose messages", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
}
|
||||
|
||||
/**
|
||||
* turn the periods (# minutes) into rate periods (# milliseconds)
|
||||
* @param periods (in minutes)
|
||||
* @return an array of periods (in milliseconds)
|
||||
*/
|
||||
private static long[] getPeriods(int periods[]) {
|
||||
long rv[] = null;
|
||||
if (periods == null) periods = new int[0];
|
||||
rv = new long[periods.length];
|
||||
for (int i = 0; i < periods.length; i++)
|
||||
rv[i] = (long) periods[i] * 60 * 1000; // they're in minutes
|
||||
Arrays.sort(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _pendingPings.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* how many data points are available in the current window?
|
||||
* @return the number of datapoints available
|
||||
*/
|
||||
public int getDataPointCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _dataPoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* when did this test begin?
|
||||
* @return when the test began
|
||||
*/
|
||||
public long getSessionStart() { return _sessionStart; }
|
||||
|
||||
/**
|
||||
* sets when the test began
|
||||
* @param when when it began
|
||||
*/
|
||||
public void setSessionStart(long when) { _sessionStart = when; }
|
||||
|
||||
/**
|
||||
* how many pings have we sent for this test?
|
||||
* @return the number of pings sent
|
||||
*/
|
||||
public long getLifetimeSent() { return _lifetimeSent; }
|
||||
|
||||
/**
|
||||
* how many pongs have we received for this test?
|
||||
* @return the number of pings received
|
||||
*/
|
||||
public long getLifetimeReceived() { return _lifetimeReceived; }
|
||||
|
||||
/**
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getConfig() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* What periods are we averaging the data over (in minutes)?
|
||||
* @return the periods as an array of ints (in minutes)
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return (_peer.getAveragePeriods() != null ? _peer.getAveragePeriods() : new int[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
return getAverage(_sendRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
return getAverage(_receiveRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Rate rate = _lostRate.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getCurrentTotalValue();
|
||||
}
|
||||
|
||||
private double getAverage(RateStat stat, int period) {
|
||||
Rate rate = stat.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getAverageValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ordered list of data points in the current window (after doing a cleanup)
|
||||
*
|
||||
* @return list of EventDataPoint objects
|
||||
*/
|
||||
public List getDataPoints() {
|
||||
cleanup();
|
||||
synchronized (_updateLock) {
|
||||
return new ArrayList(_dataPoints.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have sent the peer a ping on this series (using the send time as given)
|
||||
* @param dateSent when the ping was sent
|
||||
*/
|
||||
public void addPing(long dateSent) {
|
||||
EventDataPoint sent = new EventDataPoint(dateSent);
|
||||
synchronized (_updateLock) {
|
||||
_pendingPings.put(new Long(dateSent), sent);
|
||||
}
|
||||
_lifetimeSent++;
|
||||
}
|
||||
|
||||
/**
|
||||
* we have received a pong from the peer on this series
|
||||
*
|
||||
* @param dateSent when we sent the ping
|
||||
* @param pongSent when the peer received the ping and sent the pong
|
||||
*/
|
||||
public void pongReceived(long dateSent, long pongSent) {
|
||||
long now = Clock.getInstance().now();
|
||||
synchronized (_updateLock) {
|
||||
if (_pendingPings.size() <= 0) {
|
||||
_log.warn("Pong received (sent at " + dateSent + ", " + (now-dateSent)
|
||||
+ "ms ago, pong delay " + (pongSent-dateSent) + "ms, pong receive delay "
|
||||
+ (now-pongSent) + "ms)");
|
||||
return;
|
||||
}
|
||||
Long first = (Long)_pendingPings.firstKey();
|
||||
EventDataPoint data = (EventDataPoint)_pendingPings.remove(new Long(dateSent));
|
||||
|
||||
if (data != null) {
|
||||
data.setPongReceived(now);
|
||||
data.setPongSent(pongSent);
|
||||
data.setWasPonged(true);
|
||||
locked_addDataPoint(data);
|
||||
|
||||
if (dateSent != first.longValue()) {
|
||||
_log.error("Out of order delivery: received " + dateSent
|
||||
+ " but the first pending is " + first.longValue()
|
||||
+ " (delta " + (dateSent - first.longValue()) + ")");
|
||||
} else {
|
||||
_log.info("In order delivery for " + dateSent + " in ping "
|
||||
+ _peer.getComment());
|
||||
}
|
||||
} else {
|
||||
_log.warn("Pong received, but no matching ping? ping sent at = " + dateSent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_sendRate.addData(pongSent - dateSent, 0);
|
||||
_receiveRate.addData(now - pongSent, 0);
|
||||
_lifetimeReceived++;
|
||||
}
|
||||
|
||||
protected void addDataPoint(EventDataPoint data) {
|
||||
synchronized (_updateLock) {
|
||||
locked_addDataPoint(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_addDataPoint(EventDataPoint data) {
|
||||
Object val = _dataPoints.put(new Long(data.getPingSent()), data);
|
||||
if (val != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate data point received: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drop all datapoints outside the window we're watching, and timeout all
|
||||
* pending pings not ponged in the TIMEOUT_PERIOD, both updating the lost message
|
||||
* rate and coallescing all of the rates.
|
||||
*
|
||||
*/
|
||||
public void cleanup() {
|
||||
long dropBefore = Clock.getInstance().now() - _peer.getStatDuration() * 60 * 1000;
|
||||
long timeoutBefore = Clock.getInstance().now() - TIMEOUT_PERIOD;
|
||||
long numDropped = 0;
|
||||
long numTimedOut = 0;
|
||||
|
||||
synchronized (_updateLock) {
|
||||
numDropped = locked_dropExpired(dropBefore);
|
||||
numTimedOut = locked_timeoutPending(timeoutBefore);
|
||||
}
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
+ " old entries");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all data points that are already too old for us to be interested in
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of data points dropped
|
||||
*/
|
||||
private int locked_dropExpired(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
// drop the failed and really old
|
||||
for (Iterator iter = _dataPoints.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when)
|
||||
toDrop.add(pingTime);
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_dataPoints.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* timeout and remove all pings that were sent before the given time,
|
||||
* moving them from the set of pending pings to the set of data points
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of pings timed out
|
||||
*/
|
||||
private int locked_timeoutPending(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
for (Iterator iter = _pendingPings.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when) {
|
||||
toDrop.add(pingTime);
|
||||
EventDataPoint point = (EventDataPoint)_pendingPings.get(pingTime);
|
||||
point.setWasPonged(false);
|
||||
locked_addDataPoint(point);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_pendingPings.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/** actual data point for the peer */
|
||||
public class EventDataPoint {
|
||||
private boolean _wasPonged;
|
||||
private long _pingSent;
|
||||
private long _pongSent;
|
||||
private long _pongReceived;
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint
|
||||
*/
|
||||
public EventDataPoint() { this(-1); }
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint with pingtime associated with it =)
|
||||
* @param pingSentOn the time a ping was sent
|
||||
*/
|
||||
public EventDataPoint(long pingSentOn) {
|
||||
_wasPonged = false;
|
||||
_pingSent = pingSentOn;
|
||||
_pongSent = -1;
|
||||
_pongReceived = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we send this ping?
|
||||
* @return the time the ping was sent
|
||||
*/
|
||||
public long getPingSent() { return _pingSent; }
|
||||
|
||||
/**
|
||||
* sets when we sent this ping
|
||||
* @param when when we sent the ping
|
||||
*/
|
||||
public void setPingSent(long when) { _pingSent = when; }
|
||||
|
||||
/**
|
||||
* when did the peer receive the ping?
|
||||
* @return the time the ping was receieved
|
||||
*/
|
||||
public long getPongSent() {
|
||||
return _pongSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer received the ping
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongSent(long when) {
|
||||
_pongSent = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we receive the peer's pong?
|
||||
* @return the time we receieved the pong
|
||||
*/
|
||||
public long getPongReceived() {
|
||||
return _pongReceived;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer's pong was receieved
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongReceived(long when) {
|
||||
_pongReceived = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* did the peer reply in time?
|
||||
* @return true or false, whether we got a reply in time */
|
||||
public boolean getWasPonged() {
|
||||
return _wasPonged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we receieved the peer's reply in time
|
||||
* @param pong true or false
|
||||
*/
|
||||
public void setWasPonged(boolean pong) {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java
Normal file
142
apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Actually write out the stats for peer test
|
||||
*
|
||||
*/
|
||||
public class PeerDataWriter {
|
||||
private final static Log _log = new Log(PeerDataWriter.class);
|
||||
|
||||
/**
|
||||
* persist the peer state to the location specified in the peer config
|
||||
*
|
||||
* @param data the peer data to persist
|
||||
* @return true if it was persisted correctly, false on error
|
||||
*/
|
||||
public boolean persist(PeerData data) {
|
||||
String filename = data.getConfig().getStatFile();
|
||||
File statFile = new File(filename);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(statFile);
|
||||
persist(data, fos);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error persisting the peer data for "
|
||||
+ data.getConfig().getPeer().calculateHash().toBase64(), ioe);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* persists the peer state to the output stream
|
||||
* @param data the peer data to persist
|
||||
* @param out where to persist the data
|
||||
* @return true if it was persisted correctly [always (as implemented)], false on error
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean persist(PeerData data, OutputStream out) throws IOException {
|
||||
String header = getHeader(data);
|
||||
|
||||
out.write(header.getBytes());
|
||||
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\troundTrip\n".getBytes());
|
||||
for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next();
|
||||
String line = getEvent(point);
|
||||
out.write(line.getBytes());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHeader(PeerData data) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("peer \t").append(data.getConfig().getPeer().calculateHash().toBase64()).append('\n');
|
||||
buf.append("local \t").append(data.getConfig().getUs().calculateHash().toBase64()).append('\n');
|
||||
buf.append("peerDest \t").append(data.getConfig().getPeer().toBase64()).append('\n');
|
||||
buf.append("localDest \t").append(data.getConfig().getUs().toBase64()).append('\n');
|
||||
buf.append("numTunnelHops\t").append(data.getConfig().getNumHops()).append('\n');
|
||||
buf.append("comment \t").append(data.getConfig().getComment()).append('\n');
|
||||
buf.append("sendFrequency\t").append(data.getConfig().getSendFrequency()).append('\n');
|
||||
buf.append("sendSize \t").append(data.getConfig().getSendSize()).append('\n');
|
||||
buf.append("sessionStart \t").append(getTime(data.getSessionStart())).append('\n');
|
||||
buf.append("currentTime \t").append(getTime(Clock.getInstance().now())).append('\n');
|
||||
buf.append("numPending \t").append(data.getPendingCount()).append('\n');
|
||||
buf.append("lifetimeSent \t").append(data.getLifetimeSent()).append('\n');
|
||||
buf.append("lifetimeRecv \t").append(data.getLifetimeReceived()).append('\n');
|
||||
int periods[] = data.getAveragePeriods();
|
||||
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\troundTrip\n");
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
buf.append("periodAverage\t").append(periods[i]).append('\t');
|
||||
buf.append(getNum(data.getAverageSendTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getAverageReceiveTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getLostMessages(periods[i]))).append('\t');
|
||||
double rtt = data.getAverageSendTime(periods[i])
|
||||
+ data.getAverageReceiveTime(periods[i]);
|
||||
buf.append(getNum(rtt)).append('\n');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String getEvent(PeerData.EventDataPoint point) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("EVENT\t");
|
||||
if (point.getWasPonged())
|
||||
buf.append("OK\t");
|
||||
else
|
||||
buf.append("LOST\t");
|
||||
buf.append(getTime(point.getPingSent())).append('\t');
|
||||
if (point.getWasPonged()) {
|
||||
buf.append(point.getPongSent() - point.getPingSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPongSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPingSent()).append('\t');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
private final DecimalFormat _numFmt = new DecimalFormat("#0", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getNum(double val) {
|
||||
synchronized (_numFmt) {
|
||||
return _numFmt.format(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the control widgets (refresh/load/snapshot and the
|
||||
* tabbed panel with the plot config data)
|
||||
*
|
||||
*/
|
||||
class HeartbeatControlPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatControlPane.class);
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JTabbedPane _configPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private final static Color LIGHT_BLUE = new Color(180, 180, 255); /* UNUSED */
|
||||
private final static Color BLACK = new Color(0, 0, 0);
|
||||
private Color _background = WHITE;
|
||||
private Color _foreground = BLACK;
|
||||
|
||||
/**
|
||||
* Constructs a control panel onto the gui
|
||||
* @param gui the gui the panel is associated with
|
||||
*/
|
||||
public HeartbeatControlPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a test to the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void addTest(PeerPlotConfig config) {
|
||||
_configPane.addTab(config.getTitle(), null, new JScrollPane(new PeerPlotConfigPane(config, this)), config.getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
_gui.pack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a test from the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig config) {
|
||||
_gui.getMonitor().getState().removeTest(config);
|
||||
int index = _configPane.indexOfTab(config.getTitle());
|
||||
if (index >= 0)
|
||||
_configPane.removeTabAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when tests have changed
|
||||
*/
|
||||
public void testsUpdated() {
|
||||
List knownNames = new ArrayList(8);
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
PeerPlotState state = _gui.getMonitor().getState().getTest(i);
|
||||
String title = state.getPlotConfig().getTitle();
|
||||
knownNames.add(state.getPlotConfig().getTitle());
|
||||
if (_configPane.indexOfTab(title) >= 0) {
|
||||
_log.debug("We already know about [" + title + "]");
|
||||
} else {
|
||||
_log.info("The test [" + title + "] is new to us");
|
||||
PeerPlotConfigPane pane = new PeerPlotConfigPane(state.getPlotConfig(), this);
|
||||
_configPane.addTab(state.getPlotConfig().getTitle(), null, new JScrollPane(pane), state.getPlotConfig().getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
}
|
||||
}
|
||||
List toRemove = new ArrayList(4);
|
||||
for (int i = 0; i < _configPane.getTabCount(); i++) {
|
||||
if (knownNames.contains(_configPane.getTitleAt(i))) {
|
||||
// noop
|
||||
} else {
|
||||
toRemove.add(_configPane.getTitleAt(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
String title = (String)toRemove.get(i);
|
||||
_log.info("Removing test [" + title + "]");
|
||||
_configPane.removeTabAt(_configPane.indexOfTab(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
if (_gui != null)
|
||||
setBackground(_gui.getBackground());
|
||||
else
|
||||
setBackground(_background);
|
||||
setLayout(new BorderLayout());
|
||||
HeartbeatMonitorCommandBar bar = new HeartbeatMonitorCommandBar(_gui);
|
||||
bar.setBackground(getBackground());
|
||||
add(bar, BorderLayout.NORTH);
|
||||
_configPane = new JTabbedPane(JTabbedPane.LEFT);
|
||||
_configPane.setBackground(_background);
|
||||
//add(_configPane, BorderLayout.CENTER);
|
||||
add(_configPane, BorderLayout.SOUTH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* The HeartbeatMonitor, complete with main()! Act now, and it's only 5 easy
|
||||
* payments of $19.95 (plus shipping and handling)! You heard me, only _5_
|
||||
* easy payments of $19.95 (plus shipping and handling)! <p />
|
||||
*
|
||||
* (fine print: something about some states in the US requiring the addition
|
||||
* of sales tax... or something) <p />
|
||||
*
|
||||
* (finer print: Satan owns you. Deal with it.) <p />
|
||||
*
|
||||
* (even finer print: usage: <code>HeartbeatMonitor [configFilename]</code>)
|
||||
*/
|
||||
public class HeartbeatMonitor implements PeerPlotStateFetcher.FetchStateReceptor, PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(HeartbeatMonitor.class);
|
||||
private HeartbeatMonitorState _state;
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
|
||||
/**
|
||||
* Delegating constructor.
|
||||
* @see HeartbeatMonitor#HeartbeatMonitor(String)
|
||||
*/
|
||||
public HeartbeatMonitor() { this(null); }
|
||||
|
||||
/**
|
||||
* Creates a HeartbeatMonitor . . .
|
||||
* @param configFilename the configuration file to read from
|
||||
*/
|
||||
public HeartbeatMonitor(String configFilename) {
|
||||
_state = new HeartbeatMonitorState(configFilename);
|
||||
_gui = new HeartbeatMonitorGUI(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game rollin'
|
||||
*/
|
||||
public void runMonitor() {
|
||||
loadConfig();
|
||||
I2PThread t = new I2PThread(new HeartbeatMonitorRunner(this));
|
||||
t.setName("HeartbeatMonitor");
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
_log.debug("Monitor started");
|
||||
}
|
||||
|
||||
/**
|
||||
* give us all the data/config available
|
||||
* @return the current state (data/config)
|
||||
*/
|
||||
HeartbeatMonitorState getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
/** for all of the peer tests being monitored, refetch the data and rerender */
|
||||
void refetchData() {
|
||||
_log.debug("Refetching data");
|
||||
for (int i = 0; i < _state.getTestCount(); i++)
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, _state.getTest(i));
|
||||
}
|
||||
|
||||
/** (re)load the config defining what peer tests we are monitoring (and how to render) */
|
||||
void loadConfig() {
|
||||
//for (int i = 0; i < 10; i++) {
|
||||
// load("fake" + i);
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config data
|
||||
* @param location the name of the location to load data from
|
||||
*/
|
||||
public void load(String location) {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig(location);
|
||||
cfg.addListener(this);
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched
|
||||
*/
|
||||
public synchronized void peerPlotStateFetched(PeerPlotState state) {
|
||||
_state.addTest(state);
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* store the config defining what peer tests we are monitoring (and how to render)
|
||||
*/
|
||||
void storeConfig() {}
|
||||
|
||||
/**
|
||||
* And now, the main function, the one you've all been waiting for! . . .
|
||||
* @param args da args. Should take 1, which is the location to load config data from
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
Thread.currentThread().setName("HeartbeatMonitor.main");
|
||||
if (args.length == 1)
|
||||
new HeartbeatMonitor(args[0]).runMonitor();
|
||||
else
|
||||
new HeartbeatMonitor().runMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the config is updated
|
||||
* @param config the updated config
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) {
|
||||
_log.debug("Config updated, revamping the gui");
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
class HeartbeatMonitorCommandBar extends JPanel {
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JComboBox _refreshRate;
|
||||
private JTextField _location;
|
||||
|
||||
/**
|
||||
* Constructs a command bar onto the gui
|
||||
* @param gui the gui the command bar is associated with
|
||||
*/
|
||||
public HeartbeatMonitorCommandBar(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void refreshChanged(ItemEvent evt) {}
|
||||
private void loadCalled() {
|
||||
_gui.getMonitor().load(_location.getText());
|
||||
}
|
||||
|
||||
private void browseCalled() {
|
||||
JFileChooser chooser = new JFileChooser(_location.getText());
|
||||
chooser.setBackground(_gui.getBackground());
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
int rv = chooser.showDialog(this, "Load");
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
_gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
|
||||
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
|
||||
_refreshRate.setEnabled(false);
|
||||
_refreshRate.setBackground(_gui.getBackground());
|
||||
//add(_refreshRate);
|
||||
JLabel loadLabel = new JLabel("Load from: ");
|
||||
loadLabel.setBackground(_gui.getBackground());
|
||||
add(loadLabel);
|
||||
_location = new JTextField(20);
|
||||
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
|
||||
_location.setBackground(_gui.getBackground());
|
||||
add(_location);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
|
||||
browse.setBackground(_gui.getBackground());
|
||||
add(browse);
|
||||
JButton load = new JButton("Load");
|
||||
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
|
||||
load.setBackground(_gui.getBackground());
|
||||
add(load);
|
||||
setBackground(_gui.getBackground());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
class HeartbeatMonitorGUI extends JFrame {
|
||||
private HeartbeatMonitor _monitor;
|
||||
private HeartbeatPlotPane _plotPane;
|
||||
private HeartbeatControlPane _controlPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Creates the GUI for all youz who be too shoopid for text based shitz
|
||||
* @param monitor the monitor the gui operates over
|
||||
*/
|
||||
public HeartbeatMonitorGUI(HeartbeatMonitor monitor) {
|
||||
super("Heartbeat Monitor");
|
||||
_monitor = monitor;
|
||||
initializeComponents();
|
||||
pack();
|
||||
//setResizable(false);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
HeartbeatMonitor getMonitor() { return _monitor; }
|
||||
|
||||
/** build up all our widgets */
|
||||
private void initializeComponents() {
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
setBackground(_background);
|
||||
|
||||
_plotPane = new JFreeChartHeartbeatPlotPane(this); // new HeartbeatPlotPane(this);
|
||||
_plotPane.setBackground(_background);
|
||||
//JScrollPane pane = new JScrollPane(_plotPane);
|
||||
//pane.setBackground(_background);
|
||||
getContentPane().add(new JScrollPane(_plotPane), BorderLayout.CENTER);
|
||||
|
||||
_controlPane = new HeartbeatControlPane(this);
|
||||
_controlPane.setBackground(_background);
|
||||
getContentPane().add(_controlPane, BorderLayout.SOUTH);
|
||||
|
||||
//JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(_plotPane), new JScrollPane(_controlPane));
|
||||
//getContentPane().add(pane, BorderLayout.CENTER);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
initializeMenus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when the state of the world changes . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
_controlPane.testsUpdated();
|
||||
_plotPane.stateUpdated();
|
||||
}
|
||||
|
||||
private void exitCalled() {
|
||||
_monitor.getState().setWasKilled(true);
|
||||
setVisible(false);
|
||||
System.exit(0);
|
||||
}
|
||||
private void loadConfigCalled() {}
|
||||
private void saveConfigCalled() {}
|
||||
private void loadSnapshotCalled() {}
|
||||
private void saveSnapshotCalled() {}
|
||||
|
||||
private void initializeMenus() {
|
||||
JMenuBar bar = new JMenuBar();
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
JMenuItem loadConfig = new JMenuItem("Load config");
|
||||
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
|
||||
JMenuItem saveConfig = new JMenuItem("Save config");
|
||||
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
|
||||
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
|
||||
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
|
||||
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
|
||||
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
|
||||
JMenuItem exit = new JMenuItem("Exit");
|
||||
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
|
||||
|
||||
fileMenu.add(loadConfig);
|
||||
fileMenu.add(saveConfig);
|
||||
fileMenu.add(loadSnapshot);
|
||||
fileMenu.add(saveSnapshot);
|
||||
fileMenu.add(exit);
|
||||
bar.add(fileMenu);
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically fire off necessary events (instructing the heartbeat monitor when
|
||||
* to refetch the data, etc). This is the only active thread in the heartbeat
|
||||
* monitor (outside the swing/jvm threads)
|
||||
*/
|
||||
class HeartbeatMonitorRunner implements Runnable {
|
||||
private final static Log _log = new Log(HeartbeatMonitorRunner.class);
|
||||
private HeartbeatMonitor _monitor;
|
||||
|
||||
/**
|
||||
* Creates the thread . . .
|
||||
* @param monitor the monitor the thread runs over
|
||||
*/
|
||||
public HeartbeatMonitorRunner(HeartbeatMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
while (!_monitor.getState().getWasKilled()) {
|
||||
_monitor.refetchData();
|
||||
try { Thread.sleep(_monitor.getState().getRefreshRateMs()); } catch (InterruptedException ie) {}
|
||||
}
|
||||
_log.info("Stopping the heartbeat monitor runner");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* manage the current state of the GUI - all data points, as well as any
|
||||
* rendering or configuration options.
|
||||
*/
|
||||
class HeartbeatMonitorState {
|
||||
private String _configFile;
|
||||
private List _peerPlotState;
|
||||
private int _currentPeerPlotConfig;
|
||||
private int _refreshRateMs;
|
||||
private boolean _killed;
|
||||
|
||||
/** by default, refresh every 30 seconds */
|
||||
private final static int DEFAULT_REFRESH_RATE = 30*1000;
|
||||
/** where do we load/store config info from? */
|
||||
private final static String DEFAULT_CONFIG_FILE = "heartbeatMonitor.config";
|
||||
|
||||
/**
|
||||
* A delegating constructor.
|
||||
* @see HeartbeatMonitorState#HeartbeatMonitorState(String)
|
||||
*/
|
||||
public HeartbeatMonitorState() { this(DEFAULT_CONFIG_FILE); }
|
||||
|
||||
/**
|
||||
* Constructs the state, loading from the specified location
|
||||
* @param configFile the name of the file to load info from
|
||||
*/
|
||||
public HeartbeatMonitorState(String configFile) {
|
||||
_peerPlotState = Collections.synchronizedList(new ArrayList());
|
||||
_refreshRateMs = DEFAULT_REFRESH_RATE;
|
||||
_configFile = configFile;
|
||||
_killed = false;
|
||||
_currentPeerPlotConfig = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many tests are we monitoring?
|
||||
* @return the number of tests
|
||||
*/
|
||||
public int getTestCount() { return _peerPlotState.size(); }
|
||||
|
||||
/**
|
||||
* Retrieves the current info of a test for a certain peer . . .
|
||||
* @param peer a number associated with a certain peer
|
||||
* @return the test data
|
||||
*/
|
||||
public PeerPlotState getTest(int peer) { return (PeerPlotState)_peerPlotState.get(peer); }
|
||||
|
||||
/**
|
||||
* Adds a test . . .
|
||||
* @param peerState the test (by state) to add . . .
|
||||
*/
|
||||
public void addTest(PeerPlotState peerState) {
|
||||
if (!_peerPlotState.contains(peerState))
|
||||
_peerPlotState.add(peerState);
|
||||
}
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerState the test (by state) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotState peerState) { _peerPlotState.remove(peerState); }
|
||||
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerConfig the test (by config) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig peerConfig) {
|
||||
for (int i = 0; i < getTestCount(); i++) {
|
||||
PeerPlotState state = getTest(i);
|
||||
if (state.getPlotConfig() == peerConfig) {
|
||||
removeTest(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* which of the tests are we currently editing/viewing?
|
||||
* @return the number associated with the test
|
||||
*/
|
||||
public int getPeerPlotConfig() { return _currentPeerPlotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the test we are currently editting/viewing
|
||||
* @param whichTest the number associated with the test
|
||||
*/
|
||||
public void setPeerPlotConfig(int whichTest) { _currentPeerPlotConfig = whichTest; }
|
||||
|
||||
/**
|
||||
* how frequently should we update the data?
|
||||
* @return the current frequency (in milliseconds)
|
||||
*/
|
||||
public int getRefreshRateMs() { return _refreshRateMs; }
|
||||
|
||||
/**
|
||||
* Sets how frequently we should update data
|
||||
* @param ms the frequency (in milliseconds)
|
||||
*/
|
||||
public void setRefreshRateMs(int ms) { _refreshRateMs = ms; }
|
||||
|
||||
/**
|
||||
* where is our config stored?
|
||||
* @return the name of the config file
|
||||
*/
|
||||
public String getConfigFile() { return _configFile; }
|
||||
|
||||
/**
|
||||
* Sets where our config is stored
|
||||
* @param filename the name of the config file
|
||||
*/
|
||||
public void setConfigFile(String filename) { _configFile = filename; }
|
||||
|
||||
/**
|
||||
* have we been shut down?
|
||||
* @return true if we have, false otherwise
|
||||
*/
|
||||
public boolean getWasKilled() { return _killed; }
|
||||
|
||||
/**
|
||||
* Sets if we have been shutdown or not
|
||||
* @param killed true if we've been shutdown, false otherwise
|
||||
*/
|
||||
public void setWasKilled(boolean killed) { _killed = killed; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.i2p.heartbeat.PeerDataWriter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*/
|
||||
class HeartbeatPlotPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatPlotPane.class);
|
||||
protected HeartbeatMonitorGUI _gui;
|
||||
private JTextArea _text;
|
||||
|
||||
/**
|
||||
* Constructs the plot pane
|
||||
* @param gui the gui the pane is attached to
|
||||
*/
|
||||
public HeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when things change . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
StringBuffer buf = new StringBuffer(32*1024);
|
||||
PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
StaticPeerData data = _gui.getMonitor().getState().getTest(i).getCurrentData();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
try {
|
||||
writer.persist(data, baos);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("wtf, error writing to a byte array?", ioe);
|
||||
}
|
||||
buf.append(new String(baos.toByteArray())).append("\n\n\n");
|
||||
}
|
||||
|
||||
_text.setText(buf.toString());
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
setBackground(_gui.getBackground());
|
||||
//Dimension size = new Dimension(800, 600);
|
||||
_text = new JTextArea("",30,80); // 16, 60);
|
||||
_text.setAutoscrolls(true);
|
||||
_text.setEditable(false);
|
||||
// _text.setLineWrap(true);
|
||||
// add(new JScrollPane(_text));
|
||||
add(_text);
|
||||
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
|
||||
// setPreferredSize(size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.List;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class); /* UNUSED */
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
ChartPanel createPanel(HeartbeatMonitorState state) {
|
||||
ChartPanel panel = new ChartPanel(createChart(state));
|
||||
panel.setDisplayToolTips(true);
|
||||
panel.setEnforceFileExtensions(true);
|
||||
panel.setHorizontalZoom(true);
|
||||
panel.setVerticalZoom(true);
|
||||
panel.setMouseZoomable(true, true);
|
||||
panel.getChart().setBackgroundPaint(WHITE);
|
||||
return panel;
|
||||
}
|
||||
|
||||
JFreeChart createChart(HeartbeatMonitorState state) {
|
||||
Plot plot = createPlot(state);
|
||||
JFreeChart chart = new JFreeChart("I2P Heartbeat performance", Font.getFont("arial"), plot, true);
|
||||
return chart;
|
||||
}
|
||||
|
||||
void updateChart(ChartPanel panel, HeartbeatMonitorState state) {
|
||||
XYPlot plot = (XYPlot)panel.getChart().getPlot();
|
||||
plot.setDataset(getCollection(state));
|
||||
updateLines(plot, state);
|
||||
}
|
||||
|
||||
private long getFirst(HeartbeatMonitorState state) { /* UNUSED */
|
||||
long first = -1;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
List dataPoints = state.getTest(i).getCurrentData().getDataPoints();
|
||||
if ( (dataPoints != null) && (dataPoints.size() > 0) ) {
|
||||
PeerData.EventDataPoint data = (PeerData.EventDataPoint)dataPoints.get(0);
|
||||
if ( (first < 0) || (first > data.getPingSent()) )
|
||||
first = data.getPingSent();
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
Plot createPlot(HeartbeatMonitorState state) {
|
||||
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
|
||||
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("ms"), renderer);
|
||||
updateLines(plot, state);
|
||||
return plot;
|
||||
}
|
||||
|
||||
private void updateLines(XYPlot plot, HeartbeatMonitorState state) {
|
||||
if (true) return;
|
||||
if (state == null) return;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
PeerPlotConfig config = state.getTest(i).getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
XYSeriesCollection col = ((XYSeriesCollection)plot.getDataset());
|
||||
for (int j = 0; j < col.getSeriesCount(); j++) {
|
||||
//XYItemRenderer renderer = plot.getRendererForDataset(col.getSeries(j));
|
||||
XYItemRenderer renderer = plot.getRendererForDataset(col);
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " send")) {
|
||||
if (curConfig.getPlotSendTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " receive")) {
|
||||
if (curConfig.getPlotReceiveTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " lost")) {
|
||||
if (curConfig.getPlotLostMessages()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesCollection getCollection(HeartbeatMonitorState state) {
|
||||
XYSeriesCollection col = new XYSeriesCollection();
|
||||
if (state != null) {
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
addTest(col, state.getTest(i));
|
||||
}
|
||||
} else {
|
||||
XYSeries series = new XYSeries("latency", false, false);
|
||||
series.add(System.currentTimeMillis(), 0);
|
||||
col.addSeries(series);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void addTest(XYSeriesCollection col, PeerPlotState state) {
|
||||
PeerPlotConfig config = state.getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
addLines(col, curConfig, config.getTitle(), state.getCurrentData().getDataPoints());
|
||||
addAverageLines(col, config, config.getTitle(), state.getCurrentData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addLines(XYSeriesCollection col, PeerPlotConfig.PlotSeriesConfig config, String lineName, List data) {
|
||||
if (config.getPlotSendTime()) {
|
||||
XYSeries sendSeries = getSendSeries(data);
|
||||
sendSeries.setName(lineName + " send");
|
||||
sendSeries.setDescription("milliseconds for the ping to reach the peer");
|
||||
col.addSeries(sendSeries);
|
||||
}
|
||||
if (config.getPlotReceiveTime()) {
|
||||
XYSeries recvSeries = getReceiveSeries(data);
|
||||
recvSeries.setName(lineName + " receive");
|
||||
recvSeries.setDescription("milliseconds for the peer's pong to reach the sender");
|
||||
col.addSeries(recvSeries);
|
||||
}
|
||||
if (config.getPlotLostMessages()) {
|
||||
XYSeries lostSeries = getLostSeries(data);
|
||||
lostSeries.setName(lineName + " lost");
|
||||
lostSeries.setDescription("number of ping/pong messages lost");
|
||||
col.addSeries(lostSeries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a data series for each average that we're configured to render
|
||||
*
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addAverageLines(XYSeriesCollection col, PeerPlotConfig config, String lineName, StaticPeerData data) {
|
||||
if (data.getDataPointCount() <= 0) return;
|
||||
PeerData.EventDataPoint start = (PeerData.EventDataPoint)data.getDataPoints().get(0);
|
||||
PeerData.EventDataPoint finish = (PeerData.EventDataPoint)data.getDataPoints().get(data.getDataPointCount()-1);
|
||||
|
||||
List configs = config.getAverageSeriesConfigs();
|
||||
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
int minutes = (int)cfg.getPeriod()/(60*1000);
|
||||
if (cfg.getPlotSendTime()) {
|
||||
double time = data.getAverageSendTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " send " + minutes + "m avg [" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("send time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotReceiveTime()) {
|
||||
double time = data.getAverageReceiveTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " receive " + minutes + "m avg[" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("receive time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotLostMessages()) {
|
||||
double num = data.getLostMessages(minutes);
|
||||
if (num > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " lost messages (" + num + " in " + minutes + "m)", false, false);
|
||||
series.add(start.getPingSent(), num);
|
||||
series.add(finish.getPingSent(), num);
|
||||
series.setDescription("number of messages lost in the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeries getSendSeries(List data) {
|
||||
XYSeries series = new XYSeries("sent", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongSent()-point.getPingSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
XYSeries getReceiveSeries(List data) {
|
||||
XYSeries series = new XYSeries("receive", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongReceived()-point.getPongSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
XYSeries getLostSeries(List data) {
|
||||
XYSeries series = new XYSeries("lost", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
//series.add(point.getPingSent(), 0);
|
||||
} else {
|
||||
series.add(point.getPingSent(), 1);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
*/
|
||||
class JFreeChartHeartbeatPlotPane extends HeartbeatPlotPane {
|
||||
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class); /* UNUSED */
|
||||
private ChartPanel _panel;
|
||||
private JFreeChartAdapter _adapter;
|
||||
|
||||
/**
|
||||
* Creates a JFreeChart plot pane for the given gui
|
||||
* @param gui the heartbeat monitor gui
|
||||
*/
|
||||
public JFreeChartHeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
super(gui);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the state is updated
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
if (_panel == null) {
|
||||
remove(0); // remove the dummy
|
||||
|
||||
_adapter = new JFreeChartAdapter();
|
||||
_panel = _adapter.createPanel(_gui.getMonitor().getState());
|
||||
_panel.setBackground(_gui.getBackground());
|
||||
add(new JScrollPane(_panel), BorderLayout.CENTER);
|
||||
_gui.pack();
|
||||
} else {
|
||||
_adapter.updateChart(_panel, _gui.getMonitor().getState());
|
||||
//_gui.pack();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
// noop
|
||||
setLayout(new BorderLayout());
|
||||
add(new JLabel(), BorderLayout.CENTER);
|
||||
//dummy.setBackground(_gui.getBackground());
|
||||
//dummy.setPreferredSize(new Dimension(800,600));
|
||||
//add(dummy);
|
||||
|
||||
//add(_panel);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Configure how we want to render a particular clientConfig in the GUI
|
||||
*/
|
||||
class PeerPlotConfig {
|
||||
private final static Log _log = new Log(PeerPlotConfig.class); /* UNUSED */
|
||||
/** where can we find the current state/data (either as a filename or a URL)? */
|
||||
private String _location;
|
||||
/** what test are we defining the plot data for? */
|
||||
private ClientConfig _config;
|
||||
/** how should we render the current data set? */
|
||||
private PlotSeriesConfig _currentSeriesConfig;
|
||||
/** how should we render the various averages available? */
|
||||
private List _averageSeriesConfigs;
|
||||
private Set _listeners;
|
||||
private boolean _disabled;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(String location) {
|
||||
this(location, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a config =)
|
||||
* @param location the location of the file/URL to get the data from
|
||||
* @param config the client's configuration
|
||||
* @param currentSeriesConfig the series config
|
||||
* @param averageSeriesConfigs the average
|
||||
*/
|
||||
public PeerPlotConfig(String location, ClientConfig config, PlotSeriesConfig currentSeriesConfig, List averageSeriesConfigs) {
|
||||
_location = location;
|
||||
if (config == null)
|
||||
config = new ClientConfig(location);
|
||||
_config = config;
|
||||
if (currentSeriesConfig != null)
|
||||
_currentSeriesConfig = currentSeriesConfig;
|
||||
else
|
||||
_currentSeriesConfig = new PlotSeriesConfig(0);
|
||||
|
||||
if (averageSeriesConfigs != null) {
|
||||
_averageSeriesConfigs = averageSeriesConfigs;
|
||||
} else {
|
||||
rebuildAverageSeriesConfigs();
|
||||
}
|
||||
_listeners = Collections.synchronizedSet(new HashSet(2));
|
||||
_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Rebuilds' the average series stuff from the client configuration
|
||||
*/
|
||||
public void rebuildAverageSeriesConfigs() {
|
||||
int periods[] = _config.getAveragePeriods();
|
||||
if (periods == null) {
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(0));
|
||||
} else {
|
||||
Arrays.sort(periods);
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(periods.length));
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
_averageSeriesConfigs.add(new PlotSeriesConfig(periods[i]*60*1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an average period
|
||||
* @param minutes the number of minutes averaged over
|
||||
*/
|
||||
public void addAverage(int minutes) {
|
||||
_config.addAveragePeriod(minutes);
|
||||
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _averageSeriesConfigs.size(); i++) {
|
||||
PlotSeriesConfig cfg = (PlotSeriesConfig)_averageSeriesConfigs.get(i);
|
||||
ordered.put(new Long(cfg.getPeriod()), cfg);
|
||||
}
|
||||
Long period = new Long(minutes*60*1000);
|
||||
if (!ordered.containsKey(period))
|
||||
ordered.put(period, new PlotSeriesConfig(minutes*60*1000));
|
||||
|
||||
List cfgs = Collections.synchronizedList(new ArrayList(ordered.size()));
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); )
|
||||
cfgs.add(iter.next());
|
||||
|
||||
_averageSeriesConfigs = cfgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where is the current state data supposed to be found? This must either be a
|
||||
* local file path or a URL
|
||||
* @return the current location
|
||||
*/
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
/**
|
||||
* The location the current state data is supposed to be found. This must either be
|
||||
* a local file path or a URL
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(String location) {
|
||||
_location = location;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What are we configuring?
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getClientConfig() { return _config; }
|
||||
|
||||
/**
|
||||
* Sets what we are currently configuring
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setClientConfig(ClientConfig config) {
|
||||
_config = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the current data set?
|
||||
* @return the way we currently render the data
|
||||
*/
|
||||
public PlotSeriesConfig getCurrentSeriesConfig() { return _currentSeriesConfig; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the current data set.
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setCurrentSeriesConfig(PlotSeriesConfig config) {
|
||||
_currentSeriesConfig = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the averages?
|
||||
* @return the way we currently render the averages
|
||||
*/
|
||||
public List getAverageSeriesConfigs() { return _averageSeriesConfigs; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the averages
|
||||
* @param configs the new configs
|
||||
*/
|
||||
public void setAverageSeriesConfigs(List configs) { _averageSeriesConfigs = configs; }
|
||||
|
||||
/**
|
||||
* four char description of the peer
|
||||
* @return the name
|
||||
*/
|
||||
public String getPeerName() {
|
||||
Destination peer = getClientConfig().getPeer();
|
||||
if (peer == null)
|
||||
return "????";
|
||||
|
||||
return peer.calculateHash().toBase64().substring(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* title: name.packetsize.sendfrequency
|
||||
* @return the title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return getPeerName() + '.' + getSize() + '.' + getClientConfig().getSendFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* summary. includes:name, size, sendfrequency, and # of hops
|
||||
* @return the summary
|
||||
*/
|
||||
public String getSummary() {
|
||||
return "Send peer " + getPeerName() + ' ' + getSize() + " every " +
|
||||
getClientConfig().getSendFrequency() + " seconds through " +
|
||||
getClientConfig().getNumHops() + "-hop tunnels";
|
||||
}
|
||||
|
||||
private String getSize() {
|
||||
int bytes = getClientConfig().getSendSize();
|
||||
if (bytes < 1024)
|
||||
return bytes + "b";
|
||||
|
||||
return bytes/1024 + "kb";
|
||||
}
|
||||
|
||||
/**
|
||||
* we've got someone who wants to be notified of changes to the plot config
|
||||
* @param lsnr the listener to be added
|
||||
*/
|
||||
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
|
||||
|
||||
/**
|
||||
* remove a listener
|
||||
* @param lsnr the listener to remove
|
||||
*/
|
||||
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
|
||||
|
||||
void fireUpdate() {
|
||||
if (_disabled) return;
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
((UpdateListener)iter.next()).configUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void disableEvents() { _disabled = true; }
|
||||
|
||||
/**
|
||||
* Enables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void enableEvents() { _disabled = false; }
|
||||
|
||||
/**
|
||||
* How do we want to render a particular dataset (either the current or the averaged values)?
|
||||
*/
|
||||
public class PlotSeriesConfig {
|
||||
private long _period;
|
||||
private boolean _plotSendTime;
|
||||
private boolean _plotReceiveTime;
|
||||
private boolean _plotLostMessages;
|
||||
private Color _plotLineColor;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
*/
|
||||
public PlotSeriesConfig(long period) {
|
||||
this(period, false, false, false, null);
|
||||
if (period <= 0) {
|
||||
_plotSendTime = true;
|
||||
_plotReceiveTime = true;
|
||||
_plotLostMessages = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a config for the rendering of a particular dataset)
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
* @param plotSend do we plot send times?
|
||||
* @param plotReceive do we plot receive times?
|
||||
* @param plotLost do we plot lost packets?
|
||||
* @param plotColor in what color?
|
||||
*/
|
||||
public PlotSeriesConfig(long period, boolean plotSend, boolean plotReceive, boolean plotLost, Color plotColor) {
|
||||
_period = period;
|
||||
_plotSendTime = plotSend;
|
||||
_plotReceiveTime = plotReceive;
|
||||
_plotLostMessages = plotLost;
|
||||
_plotLineColor = plotColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plot config this plot series config is a part of
|
||||
* @return the plot config
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
|
||||
|
||||
/**
|
||||
* What period is this series config describing?
|
||||
* @return 0 for current, otherwise # milliseconds that are being averaged over
|
||||
*/
|
||||
public long getPeriod() { return _period; }
|
||||
|
||||
/**
|
||||
* Sets the period this series config is describing
|
||||
* @param period the period
|
||||
* (0 for current, otherwise # milliseconds that are being averaged over)
|
||||
*/
|
||||
public void setPeriod(long period) {
|
||||
_period = period;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to send (ping to peer)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotSendTime() { return _plotSendTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to send (ping to peer) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotSendTime(boolean shouldPlot) {
|
||||
_plotSendTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to receive (peer pong to us)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotReceiveTime() { return _plotReceiveTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to receive (peer pong to us)
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotReceiveTime(boolean shouldPlot) {
|
||||
_plotReceiveTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* Should we render the number of messages lost (ping sent, no pong received in time)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotLostMessages() { return _plotLostMessages; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the number of messages lost (ping sent, no pong received in time) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotLostMessages(boolean shouldPlot) {
|
||||
_plotLostMessages = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* What color should we plot the data with?
|
||||
* @return the color
|
||||
*/
|
||||
public Color getPlotLineColor() { return _plotLineColor; }
|
||||
|
||||
/**
|
||||
* Sets the color we should plot the data with
|
||||
* @param color the color to use
|
||||
*/
|
||||
public void setPlotLineColor(Color color) {
|
||||
_plotLineColor = color;
|
||||
fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for listening to updates . . .
|
||||
*/
|
||||
public interface UpdateListener {
|
||||
/**
|
||||
* @param config the peer plot config that changes
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
void configUpdated(PeerPlotConfig config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JColorChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(PeerPlotConfigPane.class);
|
||||
private PeerPlotConfig _config;
|
||||
private HeartbeatControlPane _parent;
|
||||
private JLabel _title;
|
||||
private JButton _delete;
|
||||
private JLabel _fromLabel;
|
||||
private JTextField _from;
|
||||
private JTextArea _comments;
|
||||
private JLabel _peerLabel;
|
||||
private JTextField _peerKey;
|
||||
private JLabel _localLabel;
|
||||
private JTextField _localKey;
|
||||
private OptionLine _options[];
|
||||
private Random _rnd = new Random();
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Constructs a pane
|
||||
* @param config the plot config it represents
|
||||
* @param pane the pane this one is attached to
|
||||
*/
|
||||
public PeerPlotConfigPane(PeerPlotConfig config, HeartbeatControlPane pane) {
|
||||
_config = config;
|
||||
_parent = pane;
|
||||
if (_parent != null)
|
||||
_background = _parent.getBackground();
|
||||
_config.addListener(this);
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/** called when the user wants to stop monitoring this test */
|
||||
private void delete() {
|
||||
_parent.removeTest(_config);
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
buildComponents();
|
||||
placeComponents(this);
|
||||
refreshView();
|
||||
//setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
setBackground(_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* place all the gui components onto the given panel
|
||||
* @param body the panel to place the components on
|
||||
*/
|
||||
private void placeComponents(JPanel body) {
|
||||
body.setLayout(new GridBagLayout());
|
||||
GridBagConstraints cts = new GridBagConstraints();
|
||||
|
||||
// row 0: title + delete
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 5;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_title, cts);
|
||||
cts.gridx = 5;
|
||||
cts.gridwidth = 1;
|
||||
cts.anchor = GridBagConstraints.NORTHWEST;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_delete, cts);
|
||||
|
||||
// row 1: from + location
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 1;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_fromLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_from, cts);
|
||||
|
||||
// row 2: comment
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 2;
|
||||
cts.gridwidth = 6;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_comments, cts);
|
||||
|
||||
// row 3: peer + peerKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 3;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_peerLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_peerKey, cts);
|
||||
|
||||
// row 4: local + localKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 4;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_localLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_localKey, cts);
|
||||
|
||||
// row 5-N: data row
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 5 + i;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
if (_options[i]._durationMinutes <= 0)
|
||||
body.add(new JLabel("Data: "), cts);
|
||||
else
|
||||
body.add(new JLabel(_options[i]._durationMinutes + "m avg: "), cts);
|
||||
|
||||
cts.gridx = 1;
|
||||
body.add(_options[i]._send, cts);
|
||||
cts.gridx = 2;
|
||||
body.add(_options[i]._recv, cts);
|
||||
cts.gridx = 3;
|
||||
body.add(_options[i]._lost, cts);
|
||||
cts.gridx = 4;
|
||||
body.add(_options[i]._all, cts);
|
||||
cts.gridx = 5;
|
||||
body.add(_options[i]._color, cts);
|
||||
}
|
||||
}
|
||||
|
||||
/** build all of the gui components */
|
||||
private void buildComponents() {
|
||||
_title = new JLabel(_config.getSummary());
|
||||
_title.setBackground(_background);
|
||||
_delete = new JButton("Delete");
|
||||
_delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { delete(); } });
|
||||
_delete.setEnabled(false);
|
||||
_delete.setBackground(_background);
|
||||
_fromLabel = new JLabel("Location: ");
|
||||
_fromLabel.setBackground(_background);
|
||||
_from = new JTextField(_config.getLocation());
|
||||
_from.setEditable(false);
|
||||
_from.setBackground(_background);
|
||||
_comments = new JTextArea(_config.getClientConfig().getComment(), 2, 20);
|
||||
// _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 40);
|
||||
_comments.setEditable(false);
|
||||
_comments.setBackground(_background);
|
||||
_peerLabel = new JLabel("Peer: ");
|
||||
_peerLabel.setBackground(_background);
|
||||
_peerKey = new JTextField(_config.getClientConfig().getPeer().toBase64(), 8);
|
||||
_peerKey.setBackground(_background);
|
||||
_localLabel = new JLabel("Local: ");
|
||||
_localLabel.setBackground(_background);
|
||||
_localKey = new JTextField(_config.getClientConfig().getUs().toBase64(), 8);
|
||||
_localKey.setBackground(_background);
|
||||
|
||||
int averagedPeriods[] = _config.getClientConfig().getAveragePeriods();
|
||||
if (averagedPeriods == null)
|
||||
averagedPeriods = new int[0];
|
||||
|
||||
_options = new OptionLine[1 + averagedPeriods.length];
|
||||
_options[0] = new OptionLine(0);
|
||||
for (int i = 0; i < averagedPeriods.length; i++) {
|
||||
_options[1+i] = new OptionLine(averagedPeriods[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
private void refreshView() {
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_options[i]._durationMinutes);
|
||||
if (cfg == null) {
|
||||
_log.warn("Config for minutes " + _options[i]._durationMinutes + " was not found?");
|
||||
continue;
|
||||
}
|
||||
//_log.debug("Refreshing view for minutes ["+ _options[i]._durationMinutes + "]: send [" +
|
||||
// _options[i]._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
// _options[i]._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
// _options[i]._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]");
|
||||
_options[i]._send.setSelected(cfg.getPlotSendTime());
|
||||
_options[i]._recv.setSelected(cfg.getPlotReceiveTime());
|
||||
_options[i]._lost.setSelected(cfg.getPlotLostMessages());
|
||||
if (cfg.getPlotLineColor() != null)
|
||||
_options[i]._color.setBackground(cfg.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find the right config for the given period
|
||||
* @param minutes the minutes to locate the config by
|
||||
* @return the config for the given period, or null
|
||||
*/
|
||||
private PeerPlotConfig.PlotSeriesConfig getConfig(int minutes) {
|
||||
if (minutes <= 0)
|
||||
return _config.getCurrentSeriesConfig();
|
||||
|
||||
List configs = _config.getAverageSeriesConfigs();
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
if (cfg.getPeriod() == minutes * 60*1000)
|
||||
return cfg;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* notified that the config has been updated
|
||||
* @param config the config that was been updated
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) { refreshView(); }
|
||||
|
||||
private class ChooseColor implements ActionListener {
|
||||
private int _minutes;
|
||||
private JButton _button;
|
||||
|
||||
/**
|
||||
* @param minutes the minutes (line) to change the color of...
|
||||
* @param button the associated button
|
||||
*/
|
||||
public ChooseColor(int minutes, JButton button) {
|
||||
_minutes = minutes;
|
||||
_button = button;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
Color origColor = null;
|
||||
if (cfg != null)
|
||||
origColor = cfg.getPlotLineColor();
|
||||
Color color = JColorChooser.showDialog(PeerPlotConfigPane.this, "What color should this line be?", origColor);
|
||||
if (color != null) {
|
||||
if (cfg != null)
|
||||
cfg.setPlotLineColor(color);
|
||||
_button.setBackground(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionLine {
|
||||
int _durationMinutes;
|
||||
JCheckBox _send;
|
||||
JCheckBox _recv;
|
||||
JCheckBox _lost;
|
||||
JCheckBox _all;
|
||||
JButton _color;
|
||||
|
||||
/**
|
||||
* Creates an OptionLine.
|
||||
* @param durationMinutes the minutes =)
|
||||
*/
|
||||
public OptionLine(int durationMinutes) {
|
||||
_durationMinutes = durationMinutes;
|
||||
_send = new JCheckBox("send time");
|
||||
_send.setBackground(_background);
|
||||
_recv = new JCheckBox("receive time");
|
||||
_recv.setBackground(_background);
|
||||
_lost = new JCheckBox("lost messages");
|
||||
_lost.setBackground(_background);
|
||||
_all = new JCheckBox("all");
|
||||
_all.setBackground(_background);
|
||||
_color = new JButton("color");
|
||||
int r = _rnd.nextInt(255);
|
||||
if (r < 0) r = -r;
|
||||
int g = _rnd.nextInt(255);
|
||||
if (g < 0) g = -g;
|
||||
int b = _rnd.nextInt(255);
|
||||
if (b < 0) b = -b;
|
||||
//_color.setBackground(new Color(r, g, b));
|
||||
_color.setBackground(_background);
|
||||
|
||||
_send.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_recv.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_lost.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_all.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_color.addActionListener(new ChooseColor(durationMinutes, _color));
|
||||
_color.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateListener implements ActionListener {
|
||||
private OptionLine _line;
|
||||
private int _minutes;
|
||||
|
||||
/**
|
||||
* Update Listener constructor . . .
|
||||
* @param line the line
|
||||
* @param minutes the minutes
|
||||
*/
|
||||
public UpdateListener(OptionLine line, int minutes) {
|
||||
_line = line;
|
||||
_minutes = minutes;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
|
||||
cfg.getPlotConfig().disableEvents();
|
||||
_log.debug("Updating data for minutes ["+ _line._durationMinutes + "]: send [" +
|
||||
_line._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
_line._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
_line._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]: config = " + cfg);
|
||||
|
||||
boolean force = _line._all.isSelected();
|
||||
cfg.setPlotSendTime(_line._send.isSelected() || force);
|
||||
cfg.setPlotReceiveTime(_line._recv.isSelected() || force);
|
||||
cfg.setPlotLostMessages(_line._lost.isSelected() || force);
|
||||
cfg.getPlotConfig().enableEvents();
|
||||
cfg.getPlotConfig().fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit test stuff
|
||||
* @param args da arsg
|
||||
*/
|
||||
public final static void main(String args[]) {
|
||||
Test t = new Test();
|
||||
t.runTest();
|
||||
}
|
||||
|
||||
private final static class Test implements PeerPlotStateFetcher.FetchStateReceptor {
|
||||
/**
|
||||
* Runs da test
|
||||
*/
|
||||
public void runTest() {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig("C:\\testnet\\r2\\heartbeatStat_10s_30kb.txt");
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.gui.PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched(net.i2p.heartbeat.gui.PeerPlotState)
|
||||
*/
|
||||
public void peerPlotStateFetched(PeerPlotState state) {
|
||||
javax.swing.JFrame f = new javax.swing.JFrame("Test");
|
||||
f.getContentPane().add(new JScrollPane(new PeerPlotConfigPane(state.getPlotConfig(), null)));
|
||||
f.pack();
|
||||
f.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
|
||||
/**
|
||||
* Current data + plot config for a particular test
|
||||
*
|
||||
*/
|
||||
class PeerPlotState {
|
||||
private StaticPeerData _currentData;
|
||||
private PeerPlotConfig _plotConfig;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param config plot config
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config) {
|
||||
this(config, new StaticPeerData(config.getClientConfig()));
|
||||
}
|
||||
/**
|
||||
* Creates a PeerPlotState
|
||||
* @param config plot config
|
||||
* @param data peer data
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config, StaticPeerData data) {
|
||||
_plotConfig = config;
|
||||
_currentData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an average
|
||||
* @param minutes mins averaged over
|
||||
* @param sendMs how much later did the peer receieve
|
||||
* @param recvMs how much later did we receieve
|
||||
* @param lost how many were lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
// make sure we've got the config entry for the average
|
||||
_plotConfig.addAverage(minutes);
|
||||
// add the data point...
|
||||
_currentData.addAverage(minutes, sendMs, recvMs, lost);
|
||||
}
|
||||
|
||||
/**
|
||||
* we successfully got a ping/pong through
|
||||
*
|
||||
* @param sendTime when did the ping get sent?
|
||||
* @param sendMs how much later did the peer receive the ping?
|
||||
* @param recvMs how much later than that did we receive the pong?
|
||||
*/
|
||||
public void addSuccess(long sendTime, int sendMs, int recvMs) {
|
||||
_currentData.addData(sendTime, sendMs, recvMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* we lost a ping/pong
|
||||
*
|
||||
* @param sendTime when did we send the ping?
|
||||
*/
|
||||
public void addLost(long sendTime) {
|
||||
_currentData.addData(sendTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* data set to render
|
||||
* @return the data set
|
||||
*/
|
||||
public StaticPeerData getCurrentData() { return _currentData; }
|
||||
|
||||
/**
|
||||
* Sets the data set to render
|
||||
* @param data the data set
|
||||
*/
|
||||
public void setCurrentData(StaticPeerData data) { _currentData = data; }
|
||||
|
||||
/**
|
||||
* configuration options on how to render the data set
|
||||
* @return the config options
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return _plotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the configuration options on how to render the data
|
||||
* @param config the config options
|
||||
*/
|
||||
public void setPlotConfig(PeerPlotConfig config) { _plotConfig = config; }
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotStateFetcher {
|
||||
private final static Log _log = new Log(PeerPlotStateFetcher.class);
|
||||
|
||||
/**
|
||||
* Fetch and fill the specified state structure
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public static void fetchPeerPlotState(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
I2PThread t = new I2PThread(new Fetcher(receptor, state));
|
||||
t.setDaemon(true);
|
||||
t.setName("Fetch state from " + state.getPlotConfig().getLocation());
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback stuff . . .
|
||||
*/
|
||||
public interface FetchStateReceptor {
|
||||
/**
|
||||
* Called when a peer plot state is fetched
|
||||
* @param state state that was fetched
|
||||
*/
|
||||
void peerPlotStateFetched(PeerPlotState state);
|
||||
}
|
||||
|
||||
private static class Fetcher implements Runnable {
|
||||
private PeerPlotState _state;
|
||||
private FetchStateReceptor _receptor;
|
||||
|
||||
/**
|
||||
* Creates a Fetcher thread
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public Fetcher(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
_state = state;
|
||||
_receptor = receptor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
String loc = _state.getPlotConfig().getLocation();
|
||||
_log.debug("Load called [" + loc + "]");
|
||||
InputStream in = null;
|
||||
try {
|
||||
try {
|
||||
URL location = new URL(loc);
|
||||
in = location.openStream();
|
||||
} catch (MalformedURLException mue) {
|
||||
_log.debug("Not a url [" + loc + "]");
|
||||
in = null;
|
||||
}
|
||||
|
||||
if (in == null)
|
||||
in = new FileInputStream(loc);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
handleLine(line);
|
||||
}
|
||||
|
||||
if (valid())
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error retrieving from the location [" + loc + "]", ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check to make sure we've got everything we need
|
||||
* @return true [always]
|
||||
*/
|
||||
boolean valid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle a line from the data set - these can be formatted in one of the
|
||||
* following ways. <p />
|
||||
*
|
||||
* <pre>
|
||||
* peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
* local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
* peerDest [base 64 of the full destination]
|
||||
* localDest [base 64 of the full destination]
|
||||
* numTunnelHops 2
|
||||
* comment Test with localhost sending 30KB every 20 seconds
|
||||
* sendFrequency 20
|
||||
* sendSize 30720
|
||||
* sessionStart 20040409.22:51:10.915
|
||||
* currentTime 20040409.23:31:39.607
|
||||
* numPending 2
|
||||
* lifetimeSent 118
|
||||
* lifetimeRecv 113
|
||||
* #averages minutes sendMs recvMs numLost
|
||||
* periodAverage 1 1843 771 0
|
||||
* periodAverage 5 786 752 1
|
||||
* periodAverage 30 855 735 3
|
||||
* #action status date and time sent sendMs replyMs
|
||||
* EVENT OK 20040409.23:21:44.742 691 670
|
||||
* EVENT OK 20040409.23:22:05.201 671 581
|
||||
* EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
* EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
* EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
* EVENT OK 20040409.23:23:29.332 1392 641
|
||||
* EVENT OK 20040409.23:23:50.262 641 761
|
||||
* EVENT OK 20040409.23:24:11.102 651 701
|
||||
* EVENT OK 20040409.23:24:31.401 841 621
|
||||
* EVENT OK 20040409.23:24:52.061 651 681
|
||||
* EVENT OK 20040409.23:25:12.480 701 1623
|
||||
* EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
* EVENT OK 20040409.23:25:54.230 591 631
|
||||
* EVENT OK 20040409.23:26:14.620 620 691
|
||||
* EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
* EVENT OK 20040409.23:26:56.570 661 641
|
||||
* EVENT OK 20040409.23:27:17.200 641 660
|
||||
* EVENT OK 20040409.23:27:38.120 611 921
|
||||
* EVENT OK 20040409.23:27:58.699 831 621
|
||||
* EVENT OK 20040409.23:28:19.559 801 661
|
||||
* EVENT OK 20040409.23:28:40.279 601 611
|
||||
* EVENT OK 20040409.23:29:00.648 601 621
|
||||
* EVENT OK 20040409.23:29:21.288 701 661
|
||||
* EVENT LOST 20040409.23:29:41.828
|
||||
* EVENT LOST 20040409.23:30:02.327
|
||||
* EVENT LOST 20040409.23:30:22.656
|
||||
* EVENT OK 20040409.23:31:24.305 1843 771
|
||||
* </pre>
|
||||
*
|
||||
* @param line (see above)
|
||||
*/
|
||||
private void handleLine(String line) {
|
||||
if (line.startsWith("peerDest"))
|
||||
handlePeerDest(line);
|
||||
else if (line.startsWith("localDest"))
|
||||
handleLocalDest(line);
|
||||
else if (line.startsWith("numTunnelHops"))
|
||||
handleNumTunnelHops(line);
|
||||
else if (line.startsWith("comment"))
|
||||
handleComment(line);
|
||||
else if (line.startsWith("sendFrequency"))
|
||||
handleSendFrequency(line);
|
||||
else if (line.startsWith("sendSize"))
|
||||
handleSendSize(line);
|
||||
else if (line.startsWith("periodAverage"))
|
||||
handlePeriodAverage(line);
|
||||
else if (line.startsWith("EVENT"))
|
||||
handleEvent(line);
|
||||
else if (line.startsWith("numPending"))
|
||||
handleNumPending(line);
|
||||
else if (line.startsWith("sessionStart"))
|
||||
handleSessionStart(line);
|
||||
else
|
||||
_log.debug("Not handled: " + line);
|
||||
}
|
||||
|
||||
private void handlePeerDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setPeer(d);
|
||||
_log.debug("Setting the peer to " + d.calculateHash().toBase64());
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the peerDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setUs(d);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the localDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleComment(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
StringBuffer buf = new StringBuffer(line.length()-32);
|
||||
while (tok.hasMoreTokens())
|
||||
buf.append(tok.nextToken()).append(' ');
|
||||
_state.getPlotConfig().getClientConfig().setComment(buf.toString());
|
||||
}
|
||||
|
||||
private void handleNumTunnelHops(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numTunnelHops line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNumPending(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getCurrentData().setPendingCount(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numPending line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendFrequency(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendFrequency line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendSize(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSessionStart(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String date = tok.nextToken();
|
||||
try {
|
||||
long when = getDate(date);
|
||||
_state.getCurrentData().setSessionStart(when);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sessionStart line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePeriodAverage(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
try {
|
||||
// periodAverage minutes sendMs recvMs numLost
|
||||
int min = Integer.parseInt(tok.nextToken());
|
||||
int send = Integer.parseInt(tok.nextToken());
|
||||
int recv = Integer.parseInt(tok.nextToken());
|
||||
int lost = Integer.parseInt(tok.nextToken());
|
||||
_state.addAverage(min, send, recv, lost);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEvent(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
|
||||
// * EVENT OK 20040409.23:29:21.288 701 661
|
||||
// * EVENT LOST 20040409.23:29:41.828
|
||||
tok.nextToken(); // ignore first two
|
||||
tok.nextToken();
|
||||
try {
|
||||
long when = getDate(tok.nextToken());
|
||||
if (when < 0) {
|
||||
_log.error("Invalid EVENT line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
int sendMs = Integer.parseInt(tok.nextToken());
|
||||
int recvMs = Integer.parseInt(tok.nextToken());
|
||||
_state.addSuccess(when, sendMs, recvMs);
|
||||
} else {
|
||||
_state.addLost(when);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the EVENT line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
private long getDate(String date) {
|
||||
synchronized (_fmt) {
|
||||
try {
|
||||
return _fmt.parse(date).getTime();
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Unable to parse the date [" + date + "]", pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fakeRun() { /* UNUSED */
|
||||
try {
|
||||
Destination peer = new Destination();
|
||||
Destination us = new Destination();
|
||||
peer.fromBase64("3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJ" +
|
||||
"cS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7g" +
|
||||
"fqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teM" +
|
||||
"d5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA");
|
||||
us.fromBase64("W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYpp" +
|
||||
"K9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63" +
|
||||
"rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HC" +
|
||||
"VilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA");
|
||||
_state.getPlotConfig().getClientConfig().setPeer(peer);
|
||||
_state.getPlotConfig().getClientConfig().setUs(us);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(2);
|
||||
_state.getPlotConfig().getClientConfig().setComment("we do stuff\nreally nifty stuff. really");
|
||||
_state.getPlotConfig().getClientConfig().setAveragePeriods(new int[] { 1, 5, 30, 60 });
|
||||
int rnd = new java.util.Random().nextInt();
|
||||
if (rnd > 0)
|
||||
rnd = rnd % 10;
|
||||
else
|
||||
rnd = (-rnd) % 10;
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(rnd);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(16*1024);
|
||||
_state.getPlotConfig().getClientConfig().setStatDuration(10);
|
||||
_state.getPlotConfig().rebuildAverageSeriesConfigs();
|
||||
_state.setCurrentData(new StaticPeerData(_state.getPlotConfig().getClientConfig()));
|
||||
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
|
||||
/**
|
||||
* Raw data points for a test
|
||||
*/
|
||||
class StaticPeerData extends PeerData {
|
||||
private int _pending;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for sending a ping */
|
||||
private Map _averageSendTimes;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for receiving a pong */
|
||||
private Map _averageReceiveTimes;
|
||||
/** Integer (period, in minutes) to Integer (num messages) of how many messages were lost on average */
|
||||
private Map _lostMessages;
|
||||
|
||||
/**
|
||||
* Creates a static peer data with a specified client config ... duh
|
||||
* @param config the client config
|
||||
*/
|
||||
public StaticPeerData(ClientConfig config) {
|
||||
super(config);
|
||||
_averageSendTimes = new HashMap(4);
|
||||
_averageReceiveTimes = new HashMap(4);
|
||||
_lostMessages = new HashMap(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds averaged data
|
||||
* @param minutes the minutes (averaged over)
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
* @param lost the number lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
_averageSendTimes.put(new Integer(minutes), new Integer(sendMs));
|
||||
_averageReceiveTimes.put(new Integer(minutes), new Integer(recvMs));
|
||||
_lostMessages.put(new Integer(minutes), new Integer(lost));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number pending
|
||||
* @param numPending the number pending
|
||||
*/
|
||||
public void setPendingCount(int numPending) { _pending = numPending; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#setSessionStart(long)
|
||||
*/
|
||||
public void setSessionStart(long when) { super.setSessionStart(when); }
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
*/
|
||||
public void addData(long sendTime, int sendMs, int recvMs) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setPongSent(sendTime + sendMs);
|
||||
dataPoint.setPongReceived(sendTime + sendMs + recvMs);
|
||||
dataPoint.setWasPonged(true);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
*/
|
||||
public void addData(long sendTime) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setWasPonged(false);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() { return _pending; }
|
||||
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
Integer i = (Integer)_averageSendTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
Integer i = (Integer)_averageReceiveTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Integer i = (Integer)_lostMessages.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#cleanup()
|
||||
*/
|
||||
public void cleanup() {}
|
||||
}
|
||||
@@ -4,14 +4,14 @@
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
@@ -37,11 +37,11 @@
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
|
||||
@@ -19,49 +19,69 @@ public class HTTPListener extends Thread {
|
||||
private String listenHost;
|
||||
private SocketManagerProducer smp;
|
||||
|
||||
public HTTPListener(SocketManagerProducer smp, int port,
|
||||
String listenHost) {
|
||||
this.smp = smp;
|
||||
this.port = port;
|
||||
start();
|
||||
/**
|
||||
* A public constructor. It contstructs things. In this case,
|
||||
* it constructs a nice HTTPListener, for all your listening on
|
||||
* HTTP needs. Yep. That's right.
|
||||
* @param smp A SocketManagerProducer, producing Sockets, no doubt
|
||||
* @param port A port, to connect to.
|
||||
* @param listenHost A host, to connect to.
|
||||
*/
|
||||
|
||||
public HTTPListener(SocketManagerProducer smp, int port, String listenHost) {
|
||||
this.smp = smp;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress lh = listenHost == null
|
||||
? null
|
||||
: InetAddress.getByName(listenHost);
|
||||
ServerSocket ss = new ServerSocket(port, 0, lh);
|
||||
while(true) {
|
||||
Socket s = ss.accept();
|
||||
new HTTPSocketHandler(this, s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while accepting connections", ex);
|
||||
}
|
||||
try {
|
||||
InetAddress lh = listenHost == null ? null : InetAddress.getByName(listenHost);
|
||||
ServerSocket ss = new ServerSocket(port, 0, lh);
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
new HTTPSocketHandler(this, s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while accepting connections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proxyUsed=false;
|
||||
|
||||
private boolean proxyUsed = false;
|
||||
|
||||
/**
|
||||
* Query whether this is the first use of the proxy or not
|
||||
* @return Whether this is the first proxy use, no doubt.
|
||||
*/
|
||||
public boolean firstProxyUse() {
|
||||
// FIXME: check a config option here
|
||||
if (true) return false;
|
||||
if (proxyUsed) {
|
||||
return false;
|
||||
} else {
|
||||
proxyUsed=true;
|
||||
return true;
|
||||
}
|
||||
if (true) return false; // FIXME: check a config option here
|
||||
|
||||
if (proxyUsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
proxyUsed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The SocketManagerProducer being used.
|
||||
*/
|
||||
public SocketManagerProducer getSMP() {
|
||||
return smp;
|
||||
return smp;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
/**
|
||||
* Outputs with HTTP 1.1 flair that a feature isn't implemented.
|
||||
* @param out The stream the text goes to.
|
||||
* @deprecated
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleNotImplemented(OutputStream out) throws IOException {
|
||||
out.write(("HTTP/1.1 200 Document following\n\n"+
|
||||
"<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
out.write(("HTTP/1.1 200 Document following\n\n" + "<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,34 +21,42 @@ public class HTTPSocketHandler extends Thread {
|
||||
private HTTPListener httpl;
|
||||
private RootHandler h;
|
||||
|
||||
/**
|
||||
* A public constructor.
|
||||
* @param httpl An HTTPListener, to listen for HTTP, no doubt
|
||||
* @param s A socket.
|
||||
*/
|
||||
public HTTPSocketHandler(HTTPListener httpl, Socket s) {
|
||||
this.httpl = httpl;
|
||||
this.s=s;
|
||||
h = RootHandler.getInstance();
|
||||
start();
|
||||
this.httpl = httpl;
|
||||
this.s = s;
|
||||
h = RootHandler.getInstance();
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(s.getInputStream());
|
||||
out = new BufferedOutputStream(s.getOutputStream());
|
||||
Request req = new Request(in);
|
||||
h.handle(req, httpl, out);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("IOException in finalizer", ex);
|
||||
}
|
||||
}
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(s.getInputStream());
|
||||
out = new BufferedOutputStream(s.getOutputStream());
|
||||
Request req = new Request(in);
|
||||
h.handle(req, httpl, out);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("IOException in finalizer", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,12 @@ public class HTTPTunnel {
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param mcDonaldsMode whether to throw away a manager after use
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers,
|
||||
boolean mcDonaldsMode, int listenPort) {
|
||||
this(initialManagers, maxManagers, mcDonaldsMode, listenPort,
|
||||
"127.0.0.1", 7654);
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort) {
|
||||
this(initialManagers, maxManagers, shouldThrowAwayManagers, listenPort, "127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,56 +54,55 @@ public class HTTPTunnel {
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param mcDonaldsMode whether to throw away a manager after use
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
* @param i2cpAddress the I2CP address
|
||||
* @param i2cpPort the I2CP port
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers,
|
||||
boolean mcDonaldsMode, int listenPort,
|
||||
String i2cpAddress, int i2cpPort) {
|
||||
SocketManagerProducer smp =
|
||||
new SocketManagerProducer(initialManagers, maxManagers,
|
||||
mcDonaldsMode, i2cpAddress, i2cpPort);
|
||||
new HTTPListener(smp, listenPort, "127.0.0.1");
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort, String i2cpAddress, int i2cpPort) {
|
||||
SocketManagerProducer smp = new SocketManagerProducer(initialManagers, maxManagers, shouldThrowAwayManagers,
|
||||
i2cpAddress, i2cpPort);
|
||||
new HTTPListener(smp, listenPort, "127.0.0.1");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The all important main function, allowing HTTPTunnel to be
|
||||
* stand-alone, a program in it's own right, and all that jazz.
|
||||
* @param args A list of String passed to the program
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String host = "127.0.0.1";
|
||||
int port = 7654, max = 1;
|
||||
boolean mc = false;
|
||||
if (args.length >1) {
|
||||
if (args.length == 4) {
|
||||
host = args[2];
|
||||
port = Integer.parseInt(args[3]);
|
||||
} else if (args.length != 2) {
|
||||
showInfo(); return;
|
||||
}
|
||||
max = Integer.parseInt(args[1]);
|
||||
} else if (args.length != 1) {
|
||||
showInfo(); return;
|
||||
}
|
||||
if (max == 0) {
|
||||
max = 1;
|
||||
} else if (max <0) {
|
||||
max = -max;
|
||||
mc = true;
|
||||
}
|
||||
new HTTPTunnel(null, max, mc, Integer.parseInt(args[0]), host, port);
|
||||
String host = "127.0.0.1";
|
||||
int port = 7654, max = 1;
|
||||
boolean throwAwayManagers = false;
|
||||
if (args.length > 1) {
|
||||
if (args.length == 4) {
|
||||
host = args[2];
|
||||
port = Integer.parseInt(args[3]);
|
||||
} else if (args.length != 2) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
max = Integer.parseInt(args[1]);
|
||||
} else if (args.length != 1) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
if (max == 0) {
|
||||
max = 1;
|
||||
} else if (max < 0) {
|
||||
max = -max;
|
||||
throwAwayManagers = true;
|
||||
}
|
||||
new HTTPTunnel(null, max, throwAwayManagers, Integer.parseInt(args[0]), host, port);
|
||||
}
|
||||
|
||||
|
||||
private static void showInfo() {
|
||||
System.out.println
|
||||
("Usage: java HTTPTunnel <listenPort> [<max> "+
|
||||
"[<i2cphost> <i2cpport>]]\n"+
|
||||
" <listenPort> port to listen for browsers\n"+
|
||||
" <max> max number of SocketMangers in pool, "+
|
||||
"use neg. number\n"+
|
||||
" to use each SocketManager only once "+
|
||||
"(default: 1)\n"+
|
||||
" <i2cphost> host to connect to the router "+
|
||||
"(default: 127.0.0.1)\n"+
|
||||
" <i2cpport> port to connect to the router "+
|
||||
"(default: 7654)");
|
||||
System.out.println("Usage: java HTTPTunnel <listenPort> [<max> " + "[<i2cphost> <i2cpport>]]\n"
|
||||
+ " <listenPort> port to listen for browsers\n"
|
||||
+ " <max> max number of SocketMangers in pool, " + "use neg. number\n"
|
||||
+ " to use each SocketManager only once " + "(default: 1)\n"
|
||||
+ " <i2cphost> host to connect to the router " + "(default: 127.0.0.1)\n"
|
||||
+ " <i2cpport> port to connect to the router " + "(default: 7654)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,109 +22,132 @@ public class Request {
|
||||
private String proto;
|
||||
private String params;
|
||||
private String postData;
|
||||
|
||||
|
||||
/**
|
||||
* A constructor, creating a request from an InputStream
|
||||
* @param in InputStream from which we "read-in" a Request
|
||||
* @throws IOException
|
||||
*/
|
||||
public Request(InputStream in) throws IOException {
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(in, "ISO-8859-1"));
|
||||
String line = br.readLine();
|
||||
if (line == null) { // no data at all
|
||||
method = null;
|
||||
_log.error("Connection but no data");
|
||||
return;
|
||||
}
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
method = line;
|
||||
url="";
|
||||
_log.error("Malformed HTTP request: "+line);
|
||||
} else {
|
||||
method = line.substring(0,pos);
|
||||
url=line.substring(pos+1);
|
||||
}
|
||||
proto="";
|
||||
pos = url.indexOf(" ");
|
||||
if (pos != -1) {
|
||||
proto=url.substring(pos); // leading space intended
|
||||
url = url.substring(0,pos);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(512);
|
||||
while((line=br.readLine()) != null) {
|
||||
if (line.length() == 0) break;
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
params = sb.toString(); // no leading empty line!
|
||||
sb = new StringBuffer();
|
||||
// hack for POST requests, ripped from HttpClient
|
||||
// this won't work for large POSTDATA
|
||||
// FIXME: do this better, please.
|
||||
if (!method.equals("GET")) {
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i=br.read();
|
||||
if (i != -1) {
|
||||
sb.append((char)i);
|
||||
}
|
||||
}
|
||||
postData = sb.toString();
|
||||
} else {
|
||||
postData="";
|
||||
}
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
|
||||
String line = br.readLine();
|
||||
if (line == null) { // no data at all
|
||||
method = null;
|
||||
_log.error("Connection but no data");
|
||||
return;
|
||||
}
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
method = line;
|
||||
url = "";
|
||||
_log.error("Malformed HTTP request: " + line);
|
||||
} else {
|
||||
method = line.substring(0, pos);
|
||||
url = line.substring(pos + 1);
|
||||
}
|
||||
proto = "";
|
||||
pos = url.indexOf(" ");
|
||||
if (pos != -1) {
|
||||
proto = url.substring(pos); // leading space intended
|
||||
url = url.substring(0, pos);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(512);
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.length() == 0) break;
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
params = sb.toString(); // no leading empty line!
|
||||
sb = new StringBuffer();
|
||||
// hack for POST requests, ripped from HttpClient
|
||||
// this won't work for large POSTDATA
|
||||
// FIXME: do this better, please.
|
||||
if (!method.equals("GET")) {
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
sb.append((char) i);
|
||||
}
|
||||
}
|
||||
postData = sb.toString();
|
||||
} else {
|
||||
postData = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A Request as an array of bytes of a String in ISO-8859-1 format
|
||||
* @throws IOException
|
||||
*/
|
||||
public byte[] toByteArray() throws IOException {
|
||||
if (method == null) return null;
|
||||
return toISO8859_1String().getBytes("ISO-8859-1");
|
||||
if (method == null) return null;
|
||||
return toISO8859_1String().getBytes("ISO-8859-1");
|
||||
|
||||
}
|
||||
|
||||
private String toISO8859_1String() throws IOException {
|
||||
if (method == null) return null;
|
||||
return method+" "+url+proto+"\r\n"+params+"\r\n"+postData;
|
||||
if (method == null) return null;
|
||||
return method + " " + url + proto + "\r\n" + params + "\r\n" + postData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the URL of the request
|
||||
*/
|
||||
public String getURL() {
|
||||
return url;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of the Request
|
||||
* @param newURL the new URL
|
||||
*/
|
||||
public void setURL(String newURL) {
|
||||
url=newURL;
|
||||
url = newURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a param.
|
||||
* @param name The name of the param
|
||||
* @return The value of the param, or null
|
||||
*/
|
||||
public String getParam(String name) {
|
||||
try {
|
||||
BufferedReader br= new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
return line.substring(name.length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) { return line.substring(name.length()); }
|
||||
}
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a param.
|
||||
* @param name the name of the param
|
||||
* @param value the value to be set
|
||||
*/
|
||||
public void setParam(String name, String value) {
|
||||
try {
|
||||
StringBuffer sb = new StringBuffer(params.length()+value.length());
|
||||
BufferedReader br= new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
boolean replaced = false;
|
||||
while((line=br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
replaced=true;
|
||||
if (value == null) continue; // kill param
|
||||
line = name+value;
|
||||
}
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
if (!replaced && value != null) {
|
||||
sb.append(name).append(value).append("\r\n");
|
||||
}
|
||||
params=sb.toString();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
}
|
||||
try {
|
||||
StringBuffer sb = new StringBuffer(params.length() + value.length());
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
boolean replaced = false;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
replaced = true;
|
||||
if (value == null) continue; // kill param
|
||||
line = name + value;
|
||||
}
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
if (!replaced && value != null) {
|
||||
sb.append(name).append(value).append("\r\n");
|
||||
}
|
||||
params = sb.toString();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.client.streaming.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
|
||||
/**
|
||||
* Produces SocketManagers in a thread and gives them to those who
|
||||
@@ -14,57 +19,58 @@ public class SocketManagerProducer extends Thread {
|
||||
private int port;
|
||||
private String host;
|
||||
private int maxManagers;
|
||||
private boolean mcDonalds;
|
||||
private boolean shouldThrowAwayManagers;
|
||||
|
||||
public SocketManagerProducer(I2PSocketManager[] initialManagers,
|
||||
int maxManagers,
|
||||
boolean mcDonaldsMode,
|
||||
String host, int port) {
|
||||
if (maxManagers < 1) {
|
||||
throw new IllegalArgumentException("maxManagers < 1");
|
||||
}
|
||||
this.host=host;
|
||||
this.port=port;
|
||||
mcDonalds=mcDonaldsMode;
|
||||
if (initialManagers != null) {
|
||||
myManagers.addAll(Arrays.asList(initialManagers));
|
||||
}
|
||||
this.maxManagers=maxManagers;
|
||||
mcDonalds=mcDonaldsMode;
|
||||
setDaemon(true);
|
||||
start();
|
||||
/**
|
||||
* Public constructor creating a SocketManagerProducer
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param host which host to listen on
|
||||
* @param port which port to listen on
|
||||
*/
|
||||
public SocketManagerProducer(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
String host, int port) {
|
||||
if (maxManagers < 1) { throw new IllegalArgumentException("maxManagers < 1"); }
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
if (initialManagers != null) {
|
||||
myManagers.addAll(Arrays.asList(initialManagers));
|
||||
}
|
||||
this.maxManagers = maxManagers;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
setDaemon(true);
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thread producing new SocketManagers.
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
synchronized(this) {
|
||||
// without mcDonalds mode, we most probably need no
|
||||
// new managers.
|
||||
while (!mcDonalds && myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
}
|
||||
// produce a new manager, regardless whether it is needed
|
||||
// or not. Do not synchronized this part, since it can be
|
||||
// quite time-consuming.
|
||||
I2PSocketManager newManager =
|
||||
I2PSocketManagerFactory.createManager(host, port,
|
||||
new Properties());
|
||||
// when done, check if it is needed.
|
||||
synchronized(this) {
|
||||
while(myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
myManagers.add(newManager);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
// without mcDonalds mode, we most probably need no
|
||||
// new managers.
|
||||
while (!shouldThrowAwayManagers && myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
}
|
||||
// produce a new manager, regardless whether it is needed
|
||||
// or not. Do not synchronized this part, since it can be
|
||||
// quite time-consuming.
|
||||
I2PSocketManager newManager = I2PSocketManagerFactory.createManager(host, port, new Properties());
|
||||
// when done, check if it is needed.
|
||||
synchronized (this) {
|
||||
while (myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
myManagers.add(newManager);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a manager for connecting to a given destination. Each
|
||||
* destination will always get the same manager.
|
||||
@@ -73,12 +79,12 @@ public class SocketManagerProducer extends Thread {
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager(String dest) {
|
||||
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
|
||||
if (result == null) {
|
||||
result = getManager();
|
||||
usedManagers.put(dest,result);
|
||||
}
|
||||
return result;
|
||||
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
|
||||
if (result == null) {
|
||||
result = getManager();
|
||||
usedManagers.put(dest, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,23 +95,26 @@ public class SocketManagerProducer extends Thread {
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager() {
|
||||
while (myManagers.size() == 0) {
|
||||
myWait(); // no manager here, so wait until one is produced
|
||||
}
|
||||
int which = (int)(Math.random()*myManagers.size());
|
||||
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
|
||||
if (mcDonalds) {
|
||||
myManagers.remove(which);
|
||||
notifyAll();
|
||||
}
|
||||
return result;
|
||||
while (myManagers.size() == 0) {
|
||||
myWait(); // no manager here, so wait until one is produced
|
||||
}
|
||||
int which = (int) (Math.random() * myManagers.size());
|
||||
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
|
||||
if (shouldThrowAwayManagers) {
|
||||
myManagers.remove(which);
|
||||
notifyAll();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until InterruptedException
|
||||
*/
|
||||
public void myWait() {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Chain multiple filters. Decorator pattern...
|
||||
@@ -11,40 +13,49 @@ import java.util.*;
|
||||
public class ChainFilter implements Filter {
|
||||
|
||||
private static final Log _log = new Log(ChainFilter.class);
|
||||
|
||||
public Collection filters;
|
||||
|
||||
|
||||
private Collection filters; // perhaps protected?
|
||||
|
||||
/**
|
||||
* @param filters A collection (list) of filters to chain to
|
||||
*/
|
||||
public ChainFilter(Collection filters) {
|
||||
this.filters=filters;
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
byte[] buf = toFilter;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
buf = f.filter(buf);
|
||||
}
|
||||
return buf;
|
||||
byte[] buf = toFilter;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
buf = f.filter(buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
// this is a bit complicated. Think about it...
|
||||
try {
|
||||
byte[] buf = EMPTY;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (buf.length != 0) {
|
||||
baos.write(f.filter(buf));
|
||||
}
|
||||
baos.write(f.finish());
|
||||
buf = baos.toByteArray();
|
||||
}
|
||||
return buf;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error chaining filters", ex);
|
||||
return EMPTY;
|
||||
}
|
||||
// this is a bit complicated. Think about it...
|
||||
try {
|
||||
byte[] buf = EMPTY;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (buf.length != 0) {
|
||||
baos.write(f.filter(buf));
|
||||
}
|
||||
baos.write(f.finish());
|
||||
buf = baos.toByteArray();
|
||||
}
|
||||
return buf;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error chaining filters", ex);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,11 +12,14 @@ public interface Filter {
|
||||
|
||||
/**
|
||||
* Filter some data. Not all filtered data need to be returned.
|
||||
* @param toFilter the bytes that are to be filtered.
|
||||
* @return the filtered data
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter);
|
||||
|
||||
/**
|
||||
* Data stream has finished. Return all of the rest data.
|
||||
* @return the rest of the data
|
||||
*/
|
||||
public byte[] finish();
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,17 @@ package net.i2p.httptunnel.filter;
|
||||
*/
|
||||
public class NullFilter implements Filter {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
return toFilter;
|
||||
return toFilter;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
return EMPTY;
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
@@ -24,70 +26,88 @@ import net.i2p.util.Log;
|
||||
public class EepHandler {
|
||||
|
||||
private static final Log _log = new Log(EepHandler.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
protected ErrorHandler errorHandler;
|
||||
|
||||
/* package private */ EepHandler(ErrorHandler eh) {
|
||||
errorHandler=eh;
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy, String destination)
|
||||
throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = NamingService.getInstance().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"Could not lookup host: "+destination);
|
||||
return;
|
||||
}
|
||||
I2PSocketManager sm = smp.getManager(destination);
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
req.setParam("Host: ", dest.toBase64());
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
|
||||
/* package private */EepHandler(ErrorHandler eh) {
|
||||
errorHandler = eh;
|
||||
}
|
||||
|
||||
public boolean handle(Request req, Filter f, OutputStream out,
|
||||
Destination dest, I2PSocketManager sm)
|
||||
throws IOException {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
synchronized(sm) {
|
||||
s = sm.connect(dest, new I2PSocketOptions());
|
||||
}
|
||||
InputStream in = new BufferedInputStream(s.getInputStream());
|
||||
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
|
||||
sout.write(req.toByteArray());
|
||||
sout.flush();
|
||||
byte[] buffer = new byte[16384], filtered;
|
||||
int len;
|
||||
while ((len=in.read(buffer)) != -1) {
|
||||
if (len != buffer.length) {
|
||||
byte[] b2 = new byte[len];
|
||||
System.arraycopy(buffer, 0, b2, 0, len);
|
||||
filtered=f.filter(b2);
|
||||
} else {
|
||||
filtered=f.filter(buffer);
|
||||
}
|
||||
written=true;
|
||||
out.write(filtered);
|
||||
}
|
||||
filtered=f.finish();
|
||||
written=true;
|
||||
out.write(filtered);
|
||||
out.flush();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} finally {
|
||||
if (s != null) s.close();
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param destination destination as a string, (subject to naming
|
||||
* service lookup)
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
/* boolean fromProxy, */String destination) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = _context.namingService().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
|
||||
return;
|
||||
}
|
||||
I2PSocketManager sm = smp.getManager(destination);
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
req.setParam("Host: ", dest.toBase64());
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request to send out
|
||||
* @param f a Filter to apply to the bytes retrieved from the Destination
|
||||
* @param out where to write the results
|
||||
* @param dest the Destination of the Request
|
||||
* @param sm an I2PSocketManager, to get a socket for the Destination
|
||||
* @return boolean, true if something was written, false otherwise.
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean handle(Request req, Filter f, OutputStream out, Destination dest,
|
||||
I2PSocketManager sm) throws IOException {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
synchronized (sm) {
|
||||
s = sm.connect(dest, new I2PSocketOptions());
|
||||
}
|
||||
InputStream in = new BufferedInputStream(s.getInputStream());
|
||||
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
|
||||
sout.write(req.toByteArray());
|
||||
sout.flush();
|
||||
byte[] buffer = new byte[16384], filtered;
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
if (len != buffer.length) {
|
||||
byte[] b2 = new byte[len];
|
||||
System.arraycopy(buffer, 0, b2, 0, len);
|
||||
filtered = f.filter(b2);
|
||||
} else {
|
||||
filtered = f.filter(buffer);
|
||||
}
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
}
|
||||
filtered = f.finish();
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
out.flush();
|
||||
} catch (SocketException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} finally {
|
||||
if (s != null) s.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -11,26 +12,30 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class ErrorHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class);
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */ ErrorHandler() {
|
||||
/* package private */ErrorHandler() {
|
||||
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl,
|
||||
OutputStream out, String error) throws IOException {
|
||||
// FIXME: Make nicer messages for more likely errors.
|
||||
out.write(("HTTP/1.1 500 Internal Server Error\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(("<html><head><title>"+error+"</title></head><body><h1>"+
|
||||
error+"</h1>An internal error occurred while "+
|
||||
"handling a request by HTTPTunnel:<br><b>"+error+
|
||||
"</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(req.toByteArray());
|
||||
out.write(("</pre></i><br><b>---</b></body></html>")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param error the error that happened
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out, String error) throws IOException {
|
||||
// FIXME: Make nicer messages for more likely errors.
|
||||
out
|
||||
.write(("HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out
|
||||
.write(("<html><head><title>" + error + "</title></head><body><h1>" + error
|
||||
+ "</h1>An internal error occurred while " + "handling a request by HTTPTunnel:<br><b>" + error + "</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(req.toByteArray());
|
||||
out.write(("</pre></i><br><b>---</b></body></html>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -12,38 +13,55 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class LocalHandler {
|
||||
|
||||
private static final Log _log = new Log(LocalHandler.class);
|
||||
private static final Log _log = new Log(LocalHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */ LocalHandler() {
|
||||
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy) throws IOException {
|
||||
//FIXME: separate multiple pages, not only a start page
|
||||
//FIXME: provide some info on this page
|
||||
out.write(("HTTP/1.1 200 Document following\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n\r\n"+
|
||||
"<html><head><title>Welcome to I2P HTTPTunnel</title>"+
|
||||
"</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "+
|
||||
"browse Eepsites by adding an eepsite name to the request."+
|
||||
"</body></html>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
/* package private */LocalHandler() {
|
||||
}
|
||||
|
||||
public void handleProxyConfWarning(Request req, HTTPListener httpl,
|
||||
OutputStream out) throws IOException {
|
||||
//FIXME
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
//FIXME: separate multiple pages, not only a start page
|
||||
//FIXME: provide some info on this page
|
||||
out
|
||||
.write(("HTTP/1.1 200 Document following\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
|
||||
+ "<html><head><title>Welcome to I2P HTTPTunnel</title>"
|
||||
+ "</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "
|
||||
+ "browse Eepsites by adding an eepsite name to the request." + "</body></html>")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleProxyConfWarning(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
//FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
//httpl.handleNotImplemented(out);
|
||||
|
||||
}
|
||||
|
||||
public void handleHTTPWarning(Request req, HTTPListener httpl,
|
||||
OutputStream out, boolean fromProxy)
|
||||
throws IOException {
|
||||
// FIXME
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleHTTPWarning(Request req, HTTPListener httpl, OutputStream out /*, boolean fromProxy */)
|
||||
throws IOException {
|
||||
// FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
//httpl.handleNotImplemented(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
@@ -17,31 +18,37 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class ProxyHandler extends EepHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class);
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/* package private */ ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
/* package private */ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
boolean fromProxy) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"Could not find proxy");
|
||||
return;
|
||||
}
|
||||
// one manager for all proxy requests
|
||||
I2PSocketManager sm = smp.getManager("--proxy--");
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not find proxy");
|
||||
return;
|
||||
}
|
||||
// one manager for all proxy requests
|
||||
I2PSocketManager sm = smp.getManager("--proxy--");
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
private Destination findProxy() {
|
||||
//FIXME!
|
||||
return NamingService.getInstance().lookup("squid.i2p");
|
||||
//FIXME!
|
||||
return _context.namingService().lookup("squid.i2p");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -11,99 +12,105 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class RootHandler {
|
||||
|
||||
private static final Log _log = new Log(RootHandler.class);
|
||||
private static final Log _log = new Log(RootHandler.class); /* UNUSED */
|
||||
|
||||
private RootHandler() {
|
||||
errorHandler=new ErrorHandler();
|
||||
localHandler=new LocalHandler();
|
||||
proxyHandler=new ProxyHandler(errorHandler);
|
||||
eepHandler=new EepHandler(errorHandler);
|
||||
errorHandler = new ErrorHandler();
|
||||
localHandler = new LocalHandler();
|
||||
proxyHandler = new ProxyHandler(errorHandler);
|
||||
eepHandler = new EepHandler(errorHandler);
|
||||
}
|
||||
|
||||
private ErrorHandler errorHandler;
|
||||
private ProxyHandler proxyHandler;
|
||||
private LocalHandler localHandler;
|
||||
private EepHandler eepHandler;
|
||||
|
||||
|
||||
private static RootHandler instance;
|
||||
|
||||
/**
|
||||
* Singleton stuff
|
||||
* @return the one and only instance, yay!
|
||||
*/
|
||||
public static synchronized RootHandler getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RootHandler();
|
||||
}
|
||||
return instance;
|
||||
if (instance == null) {
|
||||
instance = new RootHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void handle(Request req, HTTPListener httpl,
|
||||
OutputStream out) throws IOException {
|
||||
String url=req.getURL();
|
||||
System.out.println(url);
|
||||
boolean byProxy = false;
|
||||
int pos;
|
||||
if (url.startsWith("http://")) { // access via proxy
|
||||
byProxy=true;
|
||||
if (httpl.firstProxyUse()) {
|
||||
localHandler.handleProxyConfWarning(req,httpl,out);
|
||||
return;
|
||||
}
|
||||
url = url.substring(7);
|
||||
pos = url.indexOf("/");
|
||||
String host;
|
||||
String rest;
|
||||
if (pos == -1) {
|
||||
errorHandler.handle(req, httpl, out, "No host end in URL");
|
||||
return;
|
||||
} else {
|
||||
host = url.substring(0,pos);
|
||||
url = url.substring(pos);
|
||||
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
|
||||
// normal request; go on below...
|
||||
} else if (host.endsWith(".i2p")) {
|
||||
// "old" service request, send a redirect...
|
||||
out.write(("HTTP/1.1 302 Moved\r\nLocation: "+
|
||||
"http://i2p.i2p/"+host+url+
|
||||
"\r\n\r\n").getBytes("ISO-8859-1"));
|
||||
return;
|
||||
} else {
|
||||
// this is for proxying to the real web
|
||||
proxyHandler.handle(req, httpl, out, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (url.equals("/")) { // main page
|
||||
url="/_/local/index";
|
||||
} else if (!url.startsWith("/")) {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"No leading slash in URL: "+url);
|
||||
return;
|
||||
}
|
||||
String dest;
|
||||
url=url.substring(1);
|
||||
pos = url.indexOf("/");
|
||||
if (pos == -1) {
|
||||
dest=url;
|
||||
url="/";
|
||||
} else {
|
||||
dest = url.substring(0,pos);
|
||||
url=url.substring(pos);
|
||||
}
|
||||
req.setURL(url);
|
||||
if (dest.equals("_")) { // no eepsite
|
||||
if (url.startsWith("/local/")) { // local request
|
||||
req.setURL(url.substring(6));
|
||||
localHandler.handle(req, httpl, out, byProxy);
|
||||
} else if (url.startsWith("/http/")) { // http warning
|
||||
localHandler.handleHTTPWarning(req, httpl, out, byProxy);
|
||||
} else if (url.startsWith("/proxy/")) { // http proxying
|
||||
req.setURL("http://"+url.substring(7));
|
||||
proxyHandler.handle(req, httpl, out, byProxy);
|
||||
} else {
|
||||
errorHandler.handle(req, httpl, out,
|
||||
"No local handler for this URL: "+url);
|
||||
}
|
||||
} else {
|
||||
eepHandler.handle(req, httpl, out, byProxy, dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* The _ROOT_ handler: it passes its workload off to the other handlers.
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
String url = req.getURL();
|
||||
System.out.println(url);
|
||||
/* boolean byProxy = false; */
|
||||
int pos;
|
||||
if (url.startsWith("http://")) { // access via proxy
|
||||
/* byProxy=true; */
|
||||
if (httpl.firstProxyUse()) {
|
||||
localHandler.handleProxyConfWarning(req, httpl, out);
|
||||
return;
|
||||
}
|
||||
url = url.substring(7);
|
||||
pos = url.indexOf("/");
|
||||
String host;
|
||||
|
||||
if (pos == -1) {
|
||||
errorHandler.handle(req, httpl, out, "No host end in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
host = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
|
||||
// normal request; go on below...
|
||||
} else if (host.endsWith(".i2p")) {
|
||||
// "old" service request, send a redirect...
|
||||
out.write(("HTTP/1.1 302 Moved\r\nLocation: " + "http://i2p.i2p/" + host + url + "\r\n\r\n").getBytes("ISO-8859-1"));
|
||||
return;
|
||||
} else {
|
||||
// this is for proxying to the real web
|
||||
proxyHandler.handle(req, httpl, out /*, true */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (url.equals("/")) { // main page
|
||||
url = "/_/local/index";
|
||||
} else if (!url.startsWith("/")) {
|
||||
errorHandler.handle(req, httpl, out, "No leading slash in URL: " + url);
|
||||
return;
|
||||
}
|
||||
String dest;
|
||||
url = url.substring(1);
|
||||
pos = url.indexOf("/");
|
||||
if (pos == -1) {
|
||||
dest = url;
|
||||
url = "/";
|
||||
} else {
|
||||
dest = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
}
|
||||
req.setURL(url);
|
||||
if (dest.equals("_")) { // no eepsite
|
||||
if (url.startsWith("/local/")) { // local request
|
||||
req.setURL(url.substring(6));
|
||||
localHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/http/")) { // http warning
|
||||
localHandler.handleHTTPWarning(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/proxy/")) { // http proxying
|
||||
req.setURL("http://" + url.substring(7));
|
||||
proxyHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else {
|
||||
errorHandler.handle(req, httpl, out, "No local handler for this URL: " + url);
|
||||
}
|
||||
} else {
|
||||
eepHandler.handle(req, httpl, out, /* byProxy, */dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
340
apps/i2psnark/COPYING
Normal file
340
apps/i2psnark/COPYING
Normal file
@@ -0,0 +1,340 @@
|
||||
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.
|
||||
24
apps/i2psnark/TODO
Normal file
24
apps/i2psnark/TODO
Normal file
@@ -0,0 +1,24 @@
|
||||
- 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.
|
||||
1
apps/i2psnark/authors.snark
Normal file
1
apps/i2psnark/authors.snark
Normal file
@@ -0,0 +1 @@
|
||||
Mark Wielaard <mark@klomp.org>
|
||||
487
apps/i2psnark/changelog.snark
Normal file
487
apps/i2psnark/changelog.snark
Normal file
@@ -0,0 +1,487 @@
|
||||
2003-06-27 14:24 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Update version number and explain new features.
|
||||
|
||||
2003-06-27 13:51 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/GnomeInfoWindow.java,
|
||||
org/klomp/snark/GnomePeerList.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add GnomeInfoWindow.
|
||||
|
||||
2003-06-27 00:37 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Implement 'info' and 'list' commands.
|
||||
|
||||
2003-06-27 00:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/GnomePeerList.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add GnomePeerList to show state of
|
||||
connected peers.
|
||||
|
||||
2003-06-27 00:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerID.java: Make Comparable.
|
||||
|
||||
2003-06-23 23:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerMonitorTask.java: Correctly update
|
||||
lastDownloaded and lastUploaded.
|
||||
|
||||
2003-06-23 23:20 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: When checking storage use the
|
||||
MetaInfo from the storage.
|
||||
|
||||
2003-06-23 21:47 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Fill piece hashes, not info hashes.
|
||||
|
||||
2003-06-23 21:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/MetaInfo.java: New package private
|
||||
getPieceHashes() method.
|
||||
|
||||
2003-06-22 19:49 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README, TODO, org/klomp/snark/Snark.java: Add new command line
|
||||
switch --no-commands. Don't read interactive commands or show
|
||||
usage info.
|
||||
|
||||
2003-06-22 19:26 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/PeerCheckerTask.java,
|
||||
org/klomp/snark/PeerMonitorTask.java, org/klomp/snark/Snark.java:
|
||||
Split peer statistic reporting from PeerCheckerTask into
|
||||
PeerMonitorTask. Use new task in Snark text ui.
|
||||
|
||||
2003-06-22 18:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Only print peer id when debug level
|
||||
is INFO or higher.
|
||||
|
||||
2003-06-22 18:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/ShutdownListener.java: Add new ShutdownListener
|
||||
interface.
|
||||
|
||||
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Text UI item to not read from stdin.
|
||||
|
||||
2003-06-22 17:18 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* snark-gnome.sh: kaffe java-gnome support (but crashes hard at the
|
||||
moment).
|
||||
|
||||
2003-06-22 14:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/CoordinatorListener.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/ProgressListener.java, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkGnome.java,
|
||||
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||
org/klomp/snark/StorageListener.java: Split ProgressListener into
|
||||
Storage, Coordinator and Shutdown listener.
|
||||
|
||||
2003-06-20 19:06 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCoordinator.java, Snark.java,
|
||||
SnarkGnome.java, Storage.java: Progress listeners for both Storage
|
||||
and PeerCoordinator.
|
||||
|
||||
2003-06-20 14:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/ProgressListener.java,
|
||||
org/klomp/snark/SnarkGnome.java: Add ProgressListener.
|
||||
|
||||
2003-06-20 13:22 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/SnarkGnome.java: Add Pieces collected field.
|
||||
|
||||
2003-06-20 12:26 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCoordinator.java, PeerListener.java,
|
||||
PeerState.java: Add PeerListener.downloaded() which gets called on
|
||||
chunk updates. Keep PeerCoordinator.downloaded up to date using
|
||||
this remove adjusting in gotPiece() except when we receive a bad
|
||||
piece.
|
||||
|
||||
2003-06-16 00:27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, snark-gnome.sh, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkGnome.java: Start of a Gnome GUI.
|
||||
|
||||
2003-06-05 13:19 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: Don't remove a BAD piece
|
||||
from the wantedPieces list. Revert to synchronizing on
|
||||
wantedPieces for all relevant sections.
|
||||
|
||||
2003-06-03 21:09 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Only call readLine() when !quit.
|
||||
Always print exception when fatal() is called.
|
||||
|
||||
2003-06-01 23:12 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set release version to 0.4.
|
||||
|
||||
2003-06-01 22:59 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionIn.java: Handle negative length
|
||||
prefixes (terminates connection).
|
||||
|
||||
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Snark.java, SnarkShutdown.java: Implement
|
||||
correct shutdown and read commands from stdin.
|
||||
|
||||
2003-06-01 21:34 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerInfo.java: Check that interval and peers
|
||||
list actually exist.
|
||||
|
||||
2003-06-01 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Implement close().
|
||||
|
||||
2003-06-01 21:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Fix debug logging.
|
||||
|
||||
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: Implement halt().
|
||||
|
||||
2003-06-01 20:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/ConnectionAcceptor.java: Rename stop() to halt().
|
||||
|
||||
2003-06-01 17:35 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Drop lock on this when calling
|
||||
addRequest() from havePiece().
|
||||
|
||||
2003-06-01 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README, org/klomp/snark/ConnectionAcceptor.java,
|
||||
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||
org/klomp/snark/PeerCheckerTask.java,
|
||||
org/klomp/snark/PeerConnectionIn.java,
|
||||
org/klomp/snark/PeerConnectionOut.java,
|
||||
org/klomp/snark/PeerCoordinator.java,
|
||||
org/klomp/snark/PeerState.java, org/klomp/snark/Snark.java,
|
||||
org/klomp/snark/SnarkShutdown.java, org/klomp/snark/Storage.java,
|
||||
org/klomp/snark/Tracker.java, org/klomp/snark/TrackerClient.java:
|
||||
Add debug/log level.
|
||||
|
||||
2003-05-31 23:04 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java: Use
|
||||
just one lock (peers) for all synchronization (even for
|
||||
wantedPieces). Let PeerChecker handle real disconnect and keep
|
||||
count of uploaders.
|
||||
|
||||
2003-05-31 22:29 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerConnectionIn.java: Set state to
|
||||
null on first disconnect() call. So always check whether it might
|
||||
already be null. Helps disconnect check.
|
||||
|
||||
2003-05-31 22:27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Don't explicitly close
|
||||
the DataOutputStream (if another thread is using it libgcj seems to
|
||||
not like it very much).
|
||||
|
||||
2003-05-30 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Cancel
|
||||
(un)interested/(un)choke when (inverse) is still in send queue.
|
||||
Remove pieces from send queue when choke message is actaully send.
|
||||
|
||||
2003-05-30 19:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Make sure listener.wantPiece(int)
|
||||
is never called while lock on this is held.
|
||||
|
||||
2003-05-30 19:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Indentation cleanup.
|
||||
|
||||
2003-05-30 17:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Storage.java: Only synchronize on bitfield as
|
||||
long as necessary.
|
||||
|
||||
2003-05-30 17:43 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Tracker.java: Identing cleanup.
|
||||
|
||||
2003-05-30 16:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Better error message.
|
||||
|
||||
2003-05-30 15:11 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Make sure not to hold the lock on
|
||||
this when calling the listener to prevent deadlocks. Implement
|
||||
handling and sending of cancel messages.
|
||||
|
||||
2003-05-30 14:50 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: First check if we still
|
||||
want a piece before trying to add it to the Storage.
|
||||
|
||||
2003-05-30 14:49 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Implement
|
||||
sendCancel(Request). Add cancelRequest(int, int, int).
|
||||
|
||||
2003-05-30 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Request.java: Add hashCode() and equals(Object)
|
||||
methods.
|
||||
|
||||
2003-05-30 14:45 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Peer.java: Fix wheter -> whether javadoc
|
||||
comments. Mark state null immediatly after calling
|
||||
listener.disconnected(). Call PeerState.havePiece() not
|
||||
PeerConnectionOut.sendHave() directly.
|
||||
|
||||
2003-05-25 19:23 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Add PeerCoordinator TODO for connecting to seeds.
|
||||
|
||||
2003-05-23 12:12 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: Create class files with jikes again.
|
||||
|
||||
2003-05-18 22:01 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||
Prefer to (optimistically) unchoke first those peers that unchoked
|
||||
us. And make sure to not unchoke a peer that we just choked.
|
||||
|
||||
2003-05-18 21:48 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Peer.java: Fix isChoked() to not always return
|
||||
true.
|
||||
|
||||
2003-05-18 14:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, PeerState.java: Remove separate Peer
|
||||
downloading/uploading states. Keep choke and interest always up to
|
||||
date. Uploading is now just when we are not choking the peer.
|
||||
Downloading is now defined as being unchoked and interesting.
|
||||
CHECK_PERIOD is now 20 seconds. MAX_CONNECTIONS is now 24.
|
||||
MAX_DOWNLOADERS doesn't exists anymore. We download whenever we can
|
||||
from peers.
|
||||
|
||||
2003-05-18 13:57 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Remove piece messages
|
||||
from queue when we are choking. (They will have to be rerequested
|
||||
when we unchoke the peer again.)
|
||||
|
||||
2003-05-15 00:08 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Ignore missed chunk requests,
|
||||
don't requeue them.
|
||||
|
||||
2003-05-15 00:06 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Request.java: Add sanity check
|
||||
|
||||
2003-05-10 15:47 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Add extra '(' to usage message.
|
||||
|
||||
2003-05-10 15:22 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set version to 0.3 (The Bakers Tale).
|
||||
|
||||
2003-05-10 15:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Mention received piece in warning
|
||||
message.
|
||||
|
||||
2003-05-10 03:20 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerConnectionIn.java, PeerState.java,
|
||||
Request.java: Remove currentRequest and handle all piece messages
|
||||
from the lastRequested list.
|
||||
|
||||
2003-05-09 20:02 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Fix nothing requested warning
|
||||
message.
|
||||
|
||||
2003-05-09 19:59 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionOut.java: Piece messages are big.
|
||||
So if there are other (control) messages make sure they are send
|
||||
first. Also remove request messages from the queue if we are
|
||||
currently being choked to prevent them from being send even if we
|
||||
get unchoked a little later. (Since we will resent them anyway in
|
||||
that case.)
|
||||
|
||||
2003-05-09 18:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, PeerID.java: New definition of PeerID.equals
|
||||
(port + address + id) and new method PeerID.sameID (only id). These
|
||||
are used to really see if we already have a connection to a certain
|
||||
peer (active setup vs passive setup).
|
||||
|
||||
2003-05-08 03:05 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Use Snark.debug() not
|
||||
System.out.println().
|
||||
|
||||
2003-05-06 20:29 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: s/noting/nothing/
|
||||
|
||||
2003-05-06 20:28 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: s/lagacy/legacy/
|
||||
|
||||
2003-05-05 23:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* README: Set version to 0.2, explain new functionality and add
|
||||
examples.
|
||||
|
||||
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* .cvsignore, Makefile, org/klomp/snark/StaticSnark.java: Enable
|
||||
-static binary creation.
|
||||
|
||||
2003-05-05 22:42 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Tracker.java: Disable --ip support.
|
||||
|
||||
2003-05-05 21:02 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: HttpAcceptor.java, PeerCheckerTask.java,
|
||||
PeerCoordinator.java, TrackerClient.java: Use Snark.debug() not
|
||||
System.out.println().
|
||||
|
||||
2003-05-05 21:01 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerConnectionIn.java: Be prepared to handle the
|
||||
case where currentRequest is null.
|
||||
|
||||
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Improve argument parsing errors.
|
||||
|
||||
2003-05-05 21:00 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile: Use gcj -C again for creating the class files.
|
||||
|
||||
2003-05-05 09:24 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Just clear outstandingRequests,
|
||||
never make it null.
|
||||
|
||||
2003-05-05 02:55 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Always retry both first
|
||||
started event and every other event as long the TrackerClient is
|
||||
not stopped.
|
||||
|
||||
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Remove double assignment port.
|
||||
|
||||
2003-05-05 02:54 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: Add Tracker TODO item.
|
||||
|
||||
2003-05-04 23:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: ConnectionAcceptor.java, MetaInfo.java,
|
||||
Snark.java, Storage.java, Tracker.java: Add info hash calcultation
|
||||
to MetaInfo. Add torrent creation to Storage. Add ip parameter
|
||||
handling to Tracker. Make ConnectionAcceptor handle
|
||||
null/non-existing HttpAcceptors. Add debug output, --ip handling
|
||||
and all the above to Snark.
|
||||
|
||||
2003-05-04 23:36 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Handle all failing requests
|
||||
the same (print a warning).
|
||||
|
||||
2003-05-03 15:46 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: Peer.java, PeerID.java, TrackerInfo.java: Split
|
||||
Peer and PeerID a little more.
|
||||
|
||||
2003-05-03 15:44 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/MetaInfo.java: Add reannounce() and
|
||||
getTorrentData().
|
||||
|
||||
2003-05-03 15:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/: PeerCheckerTask.java, PeerCoordinator.java:
|
||||
More concise verbose/debug output. Always use addUpDownloader() to
|
||||
set peers upload or download state to true.
|
||||
|
||||
2003-05-03 13:38 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Compile fixes.
|
||||
|
||||
2003-05-03 13:32 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/TrackerClient.java: Only generate fatal() call on
|
||||
first Tracker access. Otherwise just print a warning error message.
|
||||
|
||||
2003-05-03 03:10 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerState.java: Better handle resending
|
||||
outstanding pieces and try to recover better from unrequested
|
||||
pieces.
|
||||
|
||||
2003-05-02 21:33 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/HttpAcceptor.java,
|
||||
org/klomp/snark/MetaInfo.java, org/klomp/snark/PeerID.java,
|
||||
org/klomp/snark/Snark.java, org/klomp/snark/Tracker.java,
|
||||
org/klomp/snark/TrackerClient.java,
|
||||
org/klomp/snark/bencode/BEncoder.java: Add Tracker, PeerID and
|
||||
BEncoder.
|
||||
|
||||
2003-05-01 20:17 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* Makefile, org/klomp/snark/ConnectionAcceptor.java,
|
||||
org/klomp/snark/HttpAcceptor.java, org/klomp/snark/Peer.java,
|
||||
org/klomp/snark/PeerAcceptor.java, org/klomp/snark/Snark.java: Add
|
||||
ConnectionAcceptor that handles both PeerAcceptor and HttpAcceptor.
|
||||
|
||||
2003-05-01 18:39 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/PeerCoordinator.java: connected() synchronize on
|
||||
peers.
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/SnarkShutdown.java: Wait some time before
|
||||
returning...
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* TODO: More items.
|
||||
|
||||
2003-04-28 02:56 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* org/klomp/snark/Snark.java: Calculate real random ID.
|
||||
|
||||
2003-04-27 Mark Wielaard <mark@klomp.org>
|
||||
|
||||
* snark: Initial (0.1) version.
|
||||
35
apps/i2psnark/java/build.xml
Normal file
35
apps/i2psnark/java/build.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2psnark">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
131
apps/i2psnark/java/src/org/klomp/snark/BitField.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/* BitField - Container of a byte array representing set and unset bits.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
*/
|
||||
public class BitField
|
||||
{
|
||||
|
||||
private final byte[] bitfield;
|
||||
private final int size;
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> unset bits.
|
||||
*/
|
||||
public BitField(int size)
|
||||
{
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
bitfield = new byte[arraysize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> bits
|
||||
* as set by the given byte array. This will make a copy of the array.
|
||||
* Extra bytes will be ignored.
|
||||
*
|
||||
* @exception ArrayOutOfBoundsException if give byte array is not large
|
||||
* enough.
|
||||
*/
|
||||
public BitField(byte[] bitfield, int size)
|
||||
{
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
this.bitfield = new byte[arraysize];
|
||||
|
||||
// XXX - More correct would be to check that unused bits are
|
||||
// cleared or clear them explicitly ourselves.
|
||||
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the actual byte array used. Changes to this array
|
||||
* effect 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.
|
||||
*/
|
||||
public byte[] getFieldBytes()
|
||||
{
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the BitField. The returned value is one bigger
|
||||
* then the last valid bit number (since bit numbers are counted
|
||||
* from zero).
|
||||
*/
|
||||
public int size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to true.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
*/
|
||||
public void set(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
bitfield[index] |= mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bit is set or false if it is not.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
*/
|
||||
public boolean get(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
return (bitfield[index] & mask) != 0;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
// Not very efficient
|
||||
StringBuffer sb = new StringBuffer("BitField[");
|
||||
for (int i = 0; i < size; i++)
|
||||
if (get(i))
|
||||
{
|
||||
sb.append(' ');
|
||||
sb.append(i);
|
||||
}
|
||||
sb.append(" ]");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
143
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
143
apps/i2psnark/java/src/org/klomp/snark/ConnectionAcceptor.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/* ConnectionAcceptor - Accepts connections and routes them to sub-acceptors.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
|
||||
/**
|
||||
* Accepts connections on a TCP port and routes them to sub-acceptors.
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private final I2PServerSocket serverSocket;
|
||||
private final PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
|
||||
private boolean stop;
|
||||
|
||||
public ConnectionAcceptor(I2PServerSocket serverSocket,
|
||||
PeerAcceptor peeracceptor)
|
||||
{
|
||||
this.serverSocket = serverSocket;
|
||||
this.peeracceptor = peeracceptor;
|
||||
|
||||
stop = false;
|
||||
thread = new Thread(this);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void halt()
|
||||
{
|
||||
stop = true;
|
||||
|
||||
I2PServerSocket ss = serverSocket;
|
||||
if (ss != null)
|
||||
try
|
||||
{
|
||||
ss.close();
|
||||
}
|
||||
catch(I2PException ioe) { }
|
||||
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return 6881; // serverSocket.getLocalPort();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
{
|
||||
final I2PSocket socket = serverSocket.accept();
|
||||
Thread t = new Thread("Connection-" + socket)
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
BufferedInputStream bis = new BufferedInputStream(in);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(out);
|
||||
|
||||
// See what kind of connection it is.
|
||||
/*
|
||||
if (httpacceptor != null)
|
||||
{
|
||||
byte[] scratch = new byte[4];
|
||||
bis.mark(4);
|
||||
int len = bis.read(scratch);
|
||||
if (len != 4)
|
||||
throw new IOException("Need at least 4 bytes");
|
||||
bis.reset();
|
||||
if (scratch[0] == 19 && scratch[1] == 'B'
|
||||
&& scratch[2] == 'i' && scratch[3] == 't')
|
||||
peeracceptor.connection(socket, bis, bos);
|
||||
else if (scratch[0] == 'G' && scratch[1] == 'E'
|
||||
&& scratch[2] == 'T' && scratch[3] == ' ')
|
||||
httpacceptor.connection(socket, bis, bos);
|
||||
}
|
||||
else
|
||||
*/
|
||||
peeracceptor.connection(socket, bis, bos);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
}
|
||||
catch (I2PException ioe)
|
||||
{
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* CoordinatorListener.java - Callback when a peer changes state
|
||||
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used when some peer changes state.
|
||||
*/
|
||||
public interface CoordinatorListener
|
||||
{
|
||||
/**
|
||||
* Called when the PeerCoordinator notices a change in the state of a peer.
|
||||
*/
|
||||
void peerChange(PeerCoordinator coordinator, Peer peer);
|
||||
}
|
||||
165
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
165
apps/i2psnark/java/src/org/klomp/snark/I2PSnarkUtil.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
*/
|
||||
public class I2PSnarkUtil {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private static I2PSnarkUtil _instance = new I2PSnarkUtil();
|
||||
public static I2PSnarkUtil instance() { return _instance; }
|
||||
|
||||
private boolean _shouldProxy;
|
||||
private String _proxyHost;
|
||||
private int _proxyPort;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private Properties _opts;
|
||||
private I2PSocketManager _manager;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify what HTTP proxy tracker requests should go through (specify a null
|
||||
* host for no proxying)
|
||||
*
|
||||
*/
|
||||
public void setProxy(String host, int port) {
|
||||
if ( (host != null) && (port > 0) ) {
|
||||
_shouldProxy = true;
|
||||
_proxyHost = host;
|
||||
_proxyPort = port;
|
||||
} else {
|
||||
_shouldProxy = false;
|
||||
_proxyHost = null;
|
||||
_proxyPort = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Properties opts) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
if (opts != null)
|
||||
_opts = opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
boolean connect() {
|
||||
if (_manager == null) {
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, _opts);
|
||||
}
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
try {
|
||||
return _manager.connect(peer.getAddress());
|
||||
} catch (I2PException ie) {
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the given URL, returning the file it is stored in, or null on error
|
||||
*/
|
||||
File get(String url) {
|
||||
File out = null;
|
||||
try {
|
||||
out = File.createTempFile("i2psnark", "url");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), url);
|
||||
if (get.fetch()) {
|
||||
return out;
|
||||
} else {
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
I2PServerSocket getServerSocket() {
|
||||
return _manager.getServerSocket();
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
return _manager.getSession().getMyDestination().toBase64();
|
||||
}
|
||||
Destination getDestination(String ip) {
|
||||
if (ip == null) return null;
|
||||
if (ip.endsWith(".i2p")) {
|
||||
Destination dest = _context.namingService().lookup(ip);
|
||||
if (dest != null) {
|
||||
return dest;
|
||||
} else {
|
||||
try {
|
||||
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return new Destination(ip);
|
||||
} catch (DataFormatException dfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given http://blah.i2p/foo/announce turn it into http://i2p/blah/foo/announce
|
||||
*/
|
||||
String rewriteAnnounce(String origAnnounce) {
|
||||
int destStart = "http://".length();
|
||||
int destEnd = origAnnounce.indexOf(".i2p");
|
||||
int pathStart = origAnnounce.indexOf('/', destEnd);
|
||||
return "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||
}
|
||||
|
||||
/** hook between snark's logger and an i2p log */
|
||||
void debug(String msg, int snarkDebugLevel, Throwable t) {
|
||||
switch (snarkDebugLevel) {
|
||||
case 0:
|
||||
case 1:
|
||||
_log.error(msg, t);
|
||||
break;
|
||||
case 2:
|
||||
_log.warn(msg, t);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
_log.info(msg, t);
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
default:
|
||||
_log.debug(msg, t);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
137
apps/i2psnark/java/src/org/klomp/snark/Message.java
Normal file
@@ -0,0 +1,137 @@
|
||||
/* Message - A protocol message which can be send through a DataOutputStream.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
// Used to queue outgoing connections
|
||||
// sendMessage() should be used to translate them to wire format.
|
||||
class Message
|
||||
{
|
||||
final static byte KEEP_ALIVE = -1;
|
||||
final static byte CHOKE = 0;
|
||||
final static byte UNCHOKE = 1;
|
||||
final static byte INTERESTED = 2;
|
||||
final static byte UNINTERESTED = 3;
|
||||
final static byte HAVE = 4;
|
||||
final static byte BITFIELD = 5;
|
||||
final static byte REQUEST = 6;
|
||||
final static byte PIECE = 7;
|
||||
final static byte CANCEL = 8;
|
||||
|
||||
// Not all fields are used for every message.
|
||||
// KEEP_ALIVE doesn't have a real wire representation
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
// Used for PIECE and BITFIELD messages
|
||||
byte[] data;
|
||||
int off;
|
||||
int len;
|
||||
|
||||
/** Utility method for sending a message through a DataStream. */
|
||||
void sendMessage(DataOutputStream dos) throws IOException
|
||||
{
|
||||
// KEEP_ALIVE is special.
|
||||
if (type == KEEP_ALIVE)
|
||||
{
|
||||
dos.writeInt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the total length in bytes
|
||||
|
||||
// Type is one byte.
|
||||
int datalen = 1;
|
||||
|
||||
// piece is 4 bytes.
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// begin/offset is 4 bytes
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// length is 4 bytes
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
datalen += 4;
|
||||
|
||||
// add length of data for piece or bitfield array.
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
datalen += len;
|
||||
|
||||
// Send length
|
||||
dos.writeInt(datalen);
|
||||
dos.writeByte(type & 0xFF);
|
||||
|
||||
// Send additional info (piece number)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
dos.writeInt(piece);
|
||||
|
||||
// Send additional info (begin/offset)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
dos.writeInt(begin);
|
||||
|
||||
// Send additional info (length); for PIECE this is implicit.
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
dos.writeInt(length);
|
||||
|
||||
// Send actual data
|
||||
if (type == BITFIELD || type == PIECE)
|
||||
dos.write(data, off, len);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case KEEP_ALIVE:
|
||||
return "KEEP_ALIVE";
|
||||
case CHOKE:
|
||||
return "CHOKE";
|
||||
case UNCHOKE:
|
||||
return "UNCHOKE";
|
||||
case INTERESTED:
|
||||
return "INTERESTED";
|
||||
case UNINTERESTED:
|
||||
return "UNINTERESTED";
|
||||
case HAVE:
|
||||
return "HAVE(" + piece + ")";
|
||||
case BITFIELD:
|
||||
return "BITFIELD";
|
||||
case REQUEST:
|
||||
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||
case PIECE:
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
}
|
||||
}
|
||||
}
|
||||
382
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
382
apps/i2psnark/java/src/org/klomp/snark/MetaInfo.java
Normal file
@@ -0,0 +1,382 @@
|
||||
/* MetaInfo - Holds all information gotten from a torrent file.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.File;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
public class MetaInfo
|
||||
{
|
||||
private final String announce;
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
private final List files;
|
||||
private final List lengths;
|
||||
private final int piece_length;
|
||||
private final byte[] piece_hashes;
|
||||
private final long length;
|
||||
|
||||
private byte[] torrentdata;
|
||||
|
||||
MetaInfo(String announce, String name, List files, List lengths,
|
||||
int piece_length, byte[] piece_hashes, long length)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
this.files = files;
|
||||
this.lengths = lengths;
|
||||
this.piece_length = piece_length;
|
||||
this.piece_hashes = piece_hashes;
|
||||
this.length = length;
|
||||
|
||||
this.info_hash = calculateInfoHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from the given InputStream. The
|
||||
* InputStream must start with a correctly bencoded dictonary
|
||||
* describing the torrent.
|
||||
*/
|
||||
public MetaInfo(InputStream in) throws IOException
|
||||
{
|
||||
this(new BDecoder(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from the given BDecoder. The BDecoder
|
||||
* must have a complete dictionary describing the torrent.
|
||||
*/
|
||||
public MetaInfo(BDecoder be) throws IOException
|
||||
{
|
||||
// Note that evaluation order matters here...
|
||||
this(be.bdecodeMap().getMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MetaInfo from a Map of BEValues and the SHA1 over
|
||||
* the original bencoded info dictonary (this is a hack, we could
|
||||
* reconstruct the bencoded stream and recalculate the hash). Will
|
||||
* throw a InvalidBEncodingException if the given map does not
|
||||
* contain a valid announce string or info dictonary.
|
||||
*/
|
||||
public MetaInfo(Map m) throws InvalidBEncodingException
|
||||
{
|
||||
BEValue val = (BEValue)m.get("announce");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing announce string");
|
||||
this.announce = val.getString();
|
||||
|
||||
val = (BEValue)m.get("info");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing info map");
|
||||
Map info = val.getMap();
|
||||
|
||||
val = (BEValue)info.get("name");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing name string");
|
||||
name = val.getString();
|
||||
|
||||
val = (BEValue)info.get("piece length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing piece length number");
|
||||
piece_length = val.getInt();
|
||||
|
||||
val = (BEValue)info.get("pieces");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing piece bytes");
|
||||
piece_hashes = val.getBytes();
|
||||
|
||||
val = (BEValue)info.get("length");
|
||||
if (val != null)
|
||||
{
|
||||
// Single file case.
|
||||
length = val.getLong();
|
||||
files = null;
|
||||
lengths = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi file case.
|
||||
val = (BEValue)info.get("files");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException
|
||||
("Missing length number and/or files list");
|
||||
|
||||
List list = val.getList();
|
||||
int size = list.size();
|
||||
if (size == 0)
|
||||
throw new InvalidBEncodingException("zero size files list");
|
||||
|
||||
files = new ArrayList(size);
|
||||
lengths = new ArrayList(size);
|
||||
long l = 0;
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
{
|
||||
Map desc = ((BEValue)list.get(i)).getMap();
|
||||
val = (BEValue)desc.get("length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing length number");
|
||||
long len = val.getLong();
|
||||
lengths.add(new Long(len));
|
||||
l += len;
|
||||
|
||||
val = (BEValue)desc.get("path");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing path list");
|
||||
List path_list = val.getList();
|
||||
int path_length = path_list.size();
|
||||
if (path_length == 0)
|
||||
throw new InvalidBEncodingException("zero size file path list");
|
||||
|
||||
List file = new ArrayList(path_length);
|
||||
Iterator it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
|
||||
files.add(file);
|
||||
}
|
||||
length = l;
|
||||
}
|
||||
|
||||
info_hash = calculateInfoHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representing the URL of the tracker for this torrent.
|
||||
*/
|
||||
public String getAnnounce()
|
||||
{
|
||||
return announce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original 20 byte SHA1 hash over the bencoded info map.
|
||||
*/
|
||||
public byte[] getInfoHash()
|
||||
{
|
||||
// XXX - Should we return a clone, just to be sure?
|
||||
return info_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the piece hashes. Only used by storage so package local.
|
||||
*/
|
||||
byte[] getPieceHashes()
|
||||
{
|
||||
return piece_hashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested name for the file or toplevel directory.
|
||||
* If it is a toplevel directory name getFiles() will return a
|
||||
* non-null List of file name hierarchy name.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of lists of file name hierarchies or null if it is
|
||||
* a single name. It has the same size as the list returned by
|
||||
* getLengths().
|
||||
*/
|
||||
public List getFiles()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of Longs indication the size of the individual
|
||||
* files, or null if it is a single file. It has the same size as
|
||||
* the list returned by getFiles().
|
||||
*/
|
||||
public List getLengths()
|
||||
{
|
||||
// XXX - Immutable?
|
||||
return lengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of pieces.
|
||||
*/
|
||||
public int getPieces()
|
||||
{
|
||||
return piece_hashes.length/20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of a piece. All pieces are of equal length
|
||||
* except for the last one (<code>getPieces()-1</code>).
|
||||
*
|
||||
* @exception IndexOutOfBoundsException when piece is equal to or
|
||||
* greater then the number of pieces in the torrent.
|
||||
*/
|
||||
public int getPieceLength(int piece)
|
||||
{
|
||||
int pieces = getPieces();
|
||||
if (piece >= 0 && piece < pieces -1)
|
||||
return piece_length;
|
||||
else if (piece == pieces -1)
|
||||
return (int)(length - piece * piece_length);
|
||||
else
|
||||
throw new IndexOutOfBoundsException("no piece: " + piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the given piece has the same SHA1 hash as the given
|
||||
* byte array. Returns random results or IndexOutOfBoundsExceptions
|
||||
* when the piece number is unknown.
|
||||
*/
|
||||
public boolean checkPiece(int piece, byte[] bs, int off, int length)
|
||||
{
|
||||
// Check digest
|
||||
MessageDigest sha1;
|
||||
try
|
||||
{
|
||||
sha1 = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch (NoSuchAlgorithmException nsae)
|
||||
{
|
||||
throw new InternalError("No SHA digest available: " + nsae);
|
||||
}
|
||||
|
||||
sha1.update(bs, off, length);
|
||||
byte[] hash = sha1.digest();
|
||||
for (int i = 0; i < 20; i++)
|
||||
if (hash[i] != piece_hashes[20 * piece + i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total length of the torrent in bytes.
|
||||
*/
|
||||
public long getTotalLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "MetaInfo[info_hash='" + hexencode(info_hash)
|
||||
+ "', announce='" + announce
|
||||
+ "', name='" + name
|
||||
+ "', files=" + files
|
||||
+ ", #pieces='" + piece_hashes.length/20
|
||||
+ "', piece_length='" + piece_length
|
||||
+ "', length='" + length
|
||||
+ "']";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a byte array as a hex encoded string.
|
||||
*/
|
||||
private static String hexencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of this MetaInfo that shares everything except the
|
||||
* announce URL.
|
||||
*/
|
||||
public MetaInfo reannounce(String announce)
|
||||
{
|
||||
return new MetaInfo(announce, name, files,
|
||||
lengths, piece_length,
|
||||
piece_hashes, length);
|
||||
}
|
||||
|
||||
public byte[] getTorrentData()
|
||||
{
|
||||
if (torrentdata == null)
|
||||
{
|
||||
Map m = new HashMap();
|
||||
m.put("announce", announce);
|
||||
Map info = createInfoMap();
|
||||
m.put("info", info);
|
||||
torrentdata = BEncoder.bencode(m);
|
||||
}
|
||||
return torrentdata;
|
||||
}
|
||||
|
||||
private Map createInfoMap()
|
||||
{
|
||||
Map info = new HashMap();
|
||||
info.put("name", name);
|
||||
info.put("piece length", new Integer(piece_length));
|
||||
info.put("pieces", piece_hashes);
|
||||
if (files == null)
|
||||
info.put("length", new Long(length));
|
||||
else
|
||||
{
|
||||
List l = new ArrayList();
|
||||
for (int i = 0; i < files.size(); i++)
|
||||
{
|
||||
Map file = new HashMap();
|
||||
file.put("path", files.get(i));
|
||||
file.put("length", lengths.get(i));
|
||||
l.add(file);
|
||||
}
|
||||
info.put("files", l);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private byte[] calculateInfoHash()
|
||||
{
|
||||
Map info = createInfoMap();
|
||||
byte[] infoBytes = BEncoder.bencode(info);
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA");
|
||||
return digest.digest(infoBytes);
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
388
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
388
apps/i2psnark/java/src/org/klomp/snark/Peer.java
Normal file
@@ -0,0 +1,388 @@
|
||||
/* Peer - All public information concerning a peer.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
|
||||
public class Peer implements Comparable
|
||||
{
|
||||
// Identifying property, the peer id of the other side.
|
||||
private final PeerID peerID;
|
||||
|
||||
private final byte[] my_id;
|
||||
private final MetaInfo metainfo;
|
||||
|
||||
// The data in/output streams set during the handshake and used by
|
||||
// the actual connections.
|
||||
private DataInputStream din;
|
||||
private DataOutputStream dout;
|
||||
|
||||
// Keeps state for in/out connections. Non-null when the handshake
|
||||
// was successful, the connection setup and runs
|
||||
PeerState state;
|
||||
|
||||
private boolean deregister = true;
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
* relevant MetaInfo.
|
||||
*/
|
||||
public Peer(PeerID peerID, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this.peerID = peerID;
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a unconnected peer from the input and output stream got
|
||||
* from the socket. Note that the complete handshake (which can take
|
||||
* some time or block indefinitely) is done in the calling Thread to
|
||||
* get the remote peer id. To completely start the connection call
|
||||
* the connect() method.
|
||||
*
|
||||
* @exception IOException when an error occurred during the handshake.
|
||||
*/
|
||||
public Peer(final I2PSocket sock, BufferedInputStream bis,
|
||||
BufferedOutputStream bos, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
|
||||
byte[] id = handshake(bis, bos);
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the peer.
|
||||
*/
|
||||
public PeerID getPeerID()
|
||||
{
|
||||
return peerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String representation of the peerID.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return peerID.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a Peer is the hash code of the peerID.
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return peerID.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Two Peers are equal when they have the same PeerID.
|
||||
* All other properties are ignored.
|
||||
*/
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof Peer)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
return peerID.equals(p.peerID);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the PeerIDs.
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
return peerID.compareTo(p.peerID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the connection to the other peer. This method does not
|
||||
* return until the connection is terminated.
|
||||
*
|
||||
* When the connection is correctly started the connected() method
|
||||
* of the given PeerListener is called. If the connection ends or
|
||||
* the connection could not be setup correctly the disconnected()
|
||||
* method is called.
|
||||
*
|
||||
* If the given BitField is non-null it is send to the peer as first
|
||||
* message.
|
||||
*/
|
||||
public void runConnection(PeerListener listener, BitField bitfield)
|
||||
{
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
|
||||
try
|
||||
{
|
||||
// Do we need to handshake?
|
||||
if (din == null)
|
||||
{
|
||||
I2PSocket sock = I2PSnarkUtil.instance().connect(peerID);
|
||||
BufferedInputStream bis
|
||||
= new BufferedInputStream(sock.getInputStream());
|
||||
BufferedOutputStream bos
|
||||
= new BufferedOutputStream(sock.getOutputStream());
|
||||
byte [] id = handshake(bis, bos);
|
||||
byte [] expected_id = peerID.getID();
|
||||
if (!Arrays.equals(expected_id, id))
|
||||
throw new IOException("Unexpected peerID '"
|
||||
+ PeerID.idencode(id)
|
||||
+ "' expected '"
|
||||
+ PeerID.idencode(expected_id) + "'");
|
||||
}
|
||||
|
||||
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||
PeerConnectionOut out = new PeerConnectionOut(this, dout);
|
||||
PeerState s = new PeerState(this, listener, metainfo, in, out);
|
||||
|
||||
// Send our bitmap
|
||||
if (bitfield != null)
|
||||
s.out.sendBitfield(bitfield);
|
||||
|
||||
// We are up and running!
|
||||
state = s;
|
||||
listener.connected(this);
|
||||
|
||||
// Use this thread for running the incomming connection.
|
||||
// The outgoing connection has created its own Thread.
|
||||
s.in.run();
|
||||
}
|
||||
catch(IOException eofe)
|
||||
{
|
||||
// Ignore, probably just the other side closing the connection.
|
||||
// Or refusing the connection, timing out, etc.
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
Snark.debug(this + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (deregister) listener.disconnected(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets DataIn/OutputStreams, does the handshake and returns the id
|
||||
* reported by the other side.
|
||||
*/
|
||||
private byte[] handshake(BufferedInputStream bis, BufferedOutputStream bos)
|
||||
throws IOException
|
||||
{
|
||||
din = new DataInputStream(bis);
|
||||
dout = new DataOutputStream(bos);
|
||||
|
||||
// Handshake write - header
|
||||
dout.write(19);
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - zeros
|
||||
byte[] zeros = new byte[8];
|
||||
dout.write(zeros);
|
||||
// Handshake write - metainfo hash
|
||||
byte[] shared_hash = metainfo.getInfoHash();
|
||||
dout.write(shared_hash);
|
||||
// Handshake write - peer id
|
||||
dout.write(my_id);
|
||||
dout.flush();
|
||||
|
||||
// Handshake read - header
|
||||
byte b = din.readByte();
|
||||
if (b != 19)
|
||||
throw new IOException("Handshake failure, expected 19, got "
|
||||
+ (b & 0xff));
|
||||
|
||||
byte[] bs = new byte[19];
|
||||
din.readFully(bs);
|
||||
String bittorrentProtocol = new String(bs, "UTF-8");
|
||||
if (!"BitTorrent protocol".equals(bittorrentProtocol))
|
||||
throw new IOException("Handshake failure, expected "
|
||||
+ "'Bittorrent protocol', got '"
|
||||
+ bittorrentProtocol + "'");
|
||||
|
||||
// Handshake read - zeros
|
||||
din.readFully(zeros);
|
||||
|
||||
// Handshake read - metainfo hash
|
||||
bs = new byte[20];
|
||||
din.readFully(bs);
|
||||
if (!Arrays.equals(shared_hash, bs))
|
||||
throw new IOException("Unexpected MetaInfo hash");
|
||||
|
||||
// Handshake read - peer id
|
||||
din.readFully(bs);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return state != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects this peer if it was connected. If deregister is
|
||||
* true, PeerListener.disconnected() will be called when the
|
||||
* connection is completely terminated. Otherwise the connection is
|
||||
* silently terminated.
|
||||
*/
|
||||
public void disconnect(boolean deregister)
|
||||
{
|
||||
// Both in and out connection will call this.
|
||||
this.deregister = deregister;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
state = null;
|
||||
|
||||
PeerConnectionIn in = s.in;
|
||||
if (in != null)
|
||||
in.disconnect();
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
out.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the peer we have another piece.
|
||||
*/
|
||||
public void have(int piece)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.havePiece(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer is interested in pieces we have. Returns
|
||||
* false if not connected.
|
||||
*/
|
||||
public boolean isInterested()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) && s.interested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we are interested in pieces from this peer.
|
||||
* Defaults to false. When interest is true and this peer unchokes
|
||||
* us then we start downloading from it. Has no effect when not connected.
|
||||
*/
|
||||
public void setInteresting(boolean interest)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.setInteresting(interest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer has pieces we want from it. Returns false
|
||||
* if not connected.
|
||||
*/
|
||||
public boolean isInteresting()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) && s.interesting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we are choking the peer. Defaults to
|
||||
* true. When choke is false and the peer requests some pieces we
|
||||
* upload them, otherwise requests of this peer are ignored.
|
||||
*/
|
||||
public void setChoking(boolean choke)
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.setChoking(choke);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we are choking the peer. Returns true when not connected.
|
||||
*/
|
||||
public boolean isChoking()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s == null) || s.choking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the peer choked us. Returns true when not connected.
|
||||
*/
|
||||
public boolean isChoked()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s == null) || s.choked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been downloaded.
|
||||
* Can be reset to zero with <code>resetCounters()</code>/
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) ? s.downloaded : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been uploaded.
|
||||
* Can be reset to zero with <code>resetCounters()</code>/
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
PeerState s = state;
|
||||
return (s != null) ? s.uploaded : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the downloaded and uploaded counters to zero.
|
||||
*/
|
||||
public void resetCounters()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
s.downloaded = 0;
|
||||
s.uploaded = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
62
apps/i2psnark/java/src/org/klomp/snark/PeerAcceptor.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/* PeerAcceptor - Accepts incomming connections from peers.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
|
||||
/**
|
||||
* Accepts incomming connections from peers. The ConnectionAcceptor
|
||||
* will call the connection() method when it detects an incomming BT
|
||||
* protocol connection. The PeerAcceptor will then create a new peer
|
||||
* if the PeerCoordinator wants more peers.
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
{
|
||||
private final PeerCoordinator coordinator;
|
||||
|
||||
public PeerAcceptor(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
public void connection(I2PSocket socket,
|
||||
BufferedInputStream bis, BufferedOutputStream bos)
|
||||
throws IOException
|
||||
{
|
||||
if (coordinator.needPeers())
|
||||
{
|
||||
// XXX: inside this Peer constructor's handshake is where you'd deal with the other
|
||||
// side saying they want to communicate with another torrent - aka multitorrent
|
||||
// support. you'd then want to grab the meta info /they/ want, look that up in
|
||||
// our own list of active torrents, and put it on the right coordinator for it.
|
||||
// this currently, however, throws an IOException if the metainfo doesn't match
|
||||
// coodinator.getMetaInfo (Peer.java:242)
|
||||
Peer peer = new Peer(socket, bis, bos, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
coordinator.addPeer(peer);
|
||||
}
|
||||
else
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
198
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
198
apps/i2psnark/java/src/org/klomp/snark/PeerCheckerTask.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/* PeerCheckTasks - TimerTask that checks for good/bad up/downloaders.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TimerTask that checks for good/bad up/downloader. Works together
|
||||
* with the PeerCoordinator to select which Peers get (un)choked.
|
||||
*/
|
||||
class PeerCheckerTask extends TimerTask
|
||||
{
|
||||
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
|
||||
PeerCheckerTask(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
// Calculate total uploading and worst downloader.
|
||||
long worstdownload = Long.MAX_VALUE;
|
||||
Peer worstDownloader = null;
|
||||
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
|
||||
long uploaded = 0;
|
||||
long downloaded = 0;
|
||||
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List removed = new ArrayList();
|
||||
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
|
||||
// Remove dying peers
|
||||
if (!peer.isConnected())
|
||||
{
|
||||
it.remove();
|
||||
coordinator.removePeerFromPieces(peer);
|
||||
continue;
|
||||
}
|
||||
|
||||
peers++;
|
||||
|
||||
if (!peer.isChoking())
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
|
||||
// XXX - We should calculate the up/download rate a bit
|
||||
// more intelligently
|
||||
long upload = peer.getUploaded();
|
||||
uploaded += upload;
|
||||
long download = peer.getDownloaded();
|
||||
downloaded += download;
|
||||
peer.resetCounters();
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
{
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
}
|
||||
|
||||
// If we are at our max uploaders and we have lots of other
|
||||
// interested peers try to make some room.
|
||||
// (Note use of coordinator.uploaders)
|
||||
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& !peer.isChoking())
|
||||
{
|
||||
// Check if it still wants pieces from us.
|
||||
if (!peer.isInterested())
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isInteresting()
|
||||
&& !peer.isChoked()
|
||||
&& download == 0)
|
||||
{
|
||||
// We are downloading but didn't receive anything...
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isChoking() && download < worstdownload)
|
||||
{
|
||||
// Make sure download is good if we are uploading
|
||||
worstdownload = download;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
// (can shift a bit by disconnecting peers)
|
||||
coordinator.uploaders = uploaders;
|
||||
|
||||
// Remove the worst downloader if needed.
|
||||
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
coordinator.uploaders--;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
removed.add(worstDownloader);
|
||||
}
|
||||
|
||||
// Optimistically unchoke a peer
|
||||
coordinator.unchokePeer();
|
||||
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
156
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
156
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionIn.java
Normal file
@@ -0,0 +1,156 @@
|
||||
/* PeerConnectionIn - Handles incomming messages and hands them to PeerState.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
class PeerConnectionIn implements Runnable
|
||||
{
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
|
||||
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.din = din;
|
||||
quit = false;
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (quit == true)
|
||||
return;
|
||||
|
||||
quit = true;
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
thread = Thread.currentThread();
|
||||
try
|
||||
{
|
||||
PeerState ps = peer.state;
|
||||
while (!quit && ps != null)
|
||||
{
|
||||
// Common variables used for some messages.
|
||||
int piece;
|
||||
int begin;
|
||||
int len;
|
||||
|
||||
// Wait till we hear something...
|
||||
// The length of a complete message in bytes.
|
||||
int i = din.readInt();
|
||||
if (i < 0)
|
||||
throw new IOException("Unexpected length prefix: " + i);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
ps.keepAliveMessage();
|
||||
continue;
|
||||
}
|
||||
|
||||
byte b = din.readByte();
|
||||
Message m = new Message();
|
||||
m.type = b;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
ps.chokeMessage(true);
|
||||
break;
|
||||
case 1:
|
||||
ps.chokeMessage(false);
|
||||
break;
|
||||
case 2:
|
||||
ps.interestedMessage(true);
|
||||
break;
|
||||
case 3:
|
||||
ps.interestedMessage(false);
|
||||
break;
|
||||
case 4:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
break;
|
||||
case 5:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.requestMessage(piece, begin, len);
|
||||
break;
|
||||
case 7:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
Request req = ps.getOutstandingRequest(piece, begin, len);
|
||||
byte[] piece_bytes;
|
||||
if (req != null)
|
||||
{
|
||||
piece_bytes = req.bs;
|
||||
din.readFully(piece_bytes, begin, len);
|
||||
ps.pieceMessage(req);
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX - Consume but throw away afterwards.
|
||||
piece_bytes = new byte[len];
|
||||
din.readFully(piece_bytes);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.cancelMessage(piece, begin, len);
|
||||
break;
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
ps.unknownMessage(b, bs);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably the other side closed connection.
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
342
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
342
apps/i2psnark/java/src/org/klomp/snark/PeerConnectionOut.java
Normal file
@@ -0,0 +1,342 @@
|
||||
/* PeerConnectionOut - Keeps a queue of outgoing messages and delivers them.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
{
|
||||
private final Peer peer;
|
||||
private final DataOutputStream dout;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private List sendQueue = new ArrayList();
|
||||
|
||||
public PeerConnectionOut(Peer peer, DataOutputStream dout)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.dout = dout;
|
||||
|
||||
quit = false;
|
||||
thread = new Thread(this);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Continuesly monitors for more outgoing messages that have to be send.
|
||||
* Stops if quit is true of an IOException occurs.
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!quit)
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && sendQueue.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make sure everything will reach the other side.
|
||||
dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait();
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
state = peer.state;
|
||||
if (!quit && state != null)
|
||||
{
|
||||
// Piece messages are big. So if there are other
|
||||
// (control) messages make sure they are send first.
|
||||
// Also remove request messages from the queue if
|
||||
// we are currently being choked to prevent them from
|
||||
// being send even if we get unchoked a little later.
|
||||
// (Since we will resent them anyway in that case.)
|
||||
// And remove piece messages if we are choking.
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (m == null && it.hasNext())
|
||||
{
|
||||
Message nm = (Message)it.next();
|
||||
if (nm.type == Message.PIECE)
|
||||
{
|
||||
if (state.choking)
|
||||
it.remove();
|
||||
nm = null;
|
||||
}
|
||||
else if (nm.type == Message.REQUEST && state.choked)
|
||||
{
|
||||
it.remove();
|
||||
nm = null;
|
||||
}
|
||||
|
||||
if (m == null && nm != null)
|
||||
{
|
||||
m = nm;
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && sendQueue.size() > 0)
|
||||
m = (Message)sendQueue.remove(0);
|
||||
}
|
||||
}
|
||||
if (m != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.ALL)
|
||||
Snark.debug("Send " + peer + ": " + m, Snark.ALL);
|
||||
m.sendMessage(dout);
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
if (m.type == Message.CHOKE)
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
// XXX - Should also register overhead...
|
||||
if (m.type == Message.PIECE)
|
||||
state.uploaded(m.len);
|
||||
|
||||
m = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably other side closed connection.
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
quit = true;
|
||||
peer.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect()
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if (quit == true)
|
||||
return;
|
||||
|
||||
quit = true;
|
||||
thread.interrupt();
|
||||
|
||||
sendQueue.clear();
|
||||
sendQueue.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the sendQueue and notifies the method waiting
|
||||
* on the sendQueue to change.
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.notify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a particular message type from the queue.
|
||||
*
|
||||
* @param type the Message type to remove.
|
||||
* @returns true when a message of the given type was removed, false
|
||||
* otherwise.
|
||||
*/
|
||||
private boolean removeMessage(int type)
|
||||
{
|
||||
boolean removed = false;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
it.remove();
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void sendAlive()
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.KEEP_ALIVE;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendChoke(boolean choke)
|
||||
{
|
||||
// We cancel the (un)choke but keep PIECE messages.
|
||||
// PIECE messages are purged if a choke is actually send.
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
int inverseType = choke ? Message.UNCHOKE
|
||||
: Message.CHOKE;
|
||||
if (!removeMessage(inverseType))
|
||||
{
|
||||
Message m = new Message();
|
||||
if (choke)
|
||||
m.type = Message.CHOKE;
|
||||
else
|
||||
m.type = Message.UNCHOKE;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendInterest(boolean interest)
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
int inverseType = interest ? Message.UNINTERESTED
|
||||
: Message.INTERESTED;
|
||||
if (!removeMessage(inverseType))
|
||||
{
|
||||
Message m = new Message();
|
||||
if (interest)
|
||||
m.type = Message.INTERESTED;
|
||||
else
|
||||
m.type = Message.UNINTERESTED;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendHave(int piece)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequest(Request req)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.PIECE;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.data = bytes;
|
||||
m.off = begin;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
void sendCancel(Request req)
|
||||
{
|
||||
// See if it is still in our send queue
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST
|
||||
&& m.piece == req.piece
|
||||
&& m.begin == req.off
|
||||
&& m.length == req.len)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Always send, just to be sure it it is really canceled.
|
||||
Message m = new Message();
|
||||
m.type = Message.CANCEL;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
// 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.
|
||||
void cancelRequest(int piece, int begin, int length)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.PIECE
|
||||
&& m.piece == piece
|
||||
&& m.begin == begin
|
||||
&& m.length == length)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
534
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
534
apps/i2psnark/java/src/org/klomp/snark/PeerCoordinator.java
Normal file
@@ -0,0 +1,534 @@
|
||||
/* PeerCoordinator - Coordinates which peers do what (up and downloading).
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Coordinates what peer does what.
|
||||
*/
|
||||
public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
final MetaInfo metainfo;
|
||||
final Storage storage;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
final static int MAX_CONNECTIONS = 24;
|
||||
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
// Resynced by PeerChecker once in a while.
|
||||
int uploaders = 0;
|
||||
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
|
||||
/** Timer to handle all periodical tasks. */
|
||||
private final Timer timer = new Timer(true);
|
||||
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private final List wantedPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
|
||||
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
|
||||
CoordinatorListener listener)
|
||||
{
|
||||
this.id = id;
|
||||
this.metainfo = metainfo;
|
||||
this.storage = storage;
|
||||
this.listener = listener;
|
||||
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
for(int i = 0; i < metainfo.getPieces(); i++)
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean completed()
|
||||
{
|
||||
return storage.complete();
|
||||
}
|
||||
|
||||
|
||||
public int getPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return peers.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many bytes are still needed to get the complete file.
|
||||
*/
|
||||
public long getLeft()
|
||||
{
|
||||
// XXX - Only an approximation.
|
||||
return storage.needed() * metainfo.getPieceLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of uploaded bytes of all peers.
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of downloaded bytes of all peers.
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
public boolean needPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return !halted && peers.size() < MAX_CONNECTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
public void halt()
|
||||
{
|
||||
halted = true;
|
||||
synchronized(peers)
|
||||
{
|
||||
// Stop peer checker task.
|
||||
timer.cancel();
|
||||
|
||||
// Stop peers.
|
||||
Iterator it = peers.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
peer.disconnect();
|
||||
it.remove();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized(peers)
|
||||
{
|
||||
if (peerIDInList(peer.getPeerID(), peers))
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Already connected to: " + peer, Snark.INFO);
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("New connection to peer: " + peer, Snark.INFO);
|
||||
|
||||
// Add it to the beginning of the list.
|
||||
// And try to optimistically make it a uploader.
|
||||
peers.add(0, peer);
|
||||
unchokePeer();
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean peerIDInList(PeerID pid, List peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
if (pid.sameID(((Peer)it.next()).getPeerID()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean need_more;
|
||||
synchronized(peers)
|
||||
{
|
||||
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
|
||||
}
|
||||
|
||||
if (need_more)
|
||||
{
|
||||
// Run the peer with us as listener and the current bitfield.
|
||||
final PeerListener listener = this;
|
||||
final BitField bitfield = storage.getBitField();
|
||||
Runnable r = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
peer.runConnection(listener, bitfield);
|
||||
}
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new Thread(r, threadName).start();
|
||||
}
|
||||
else
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
if (peer.isConnected())
|
||||
Snark.debug("Add peer already connected: " + peer, Snark.INFO);
|
||||
else
|
||||
Snark.debug("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer, Snark.INFO);
|
||||
}
|
||||
|
||||
|
||||
// (Optimistically) unchoke. Should be called with peers synchronized
|
||||
void unchokePeer()
|
||||
{
|
||||
// linked list will contain all interested peers that we choke.
|
||||
// At the start are the peers that have us unchoked at the end the
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getBitMap()
|
||||
{
|
||||
return storage.getBitField().getFieldBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we don't have the given piece yet.
|
||||
*/
|
||||
public boolean gotHave(Peer peer, int piece)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
return wantedPieces.contains(new Piece(piece));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given bitfield contains at least one piece we
|
||||
* are interested in.
|
||||
*/
|
||||
public boolean gotBitField(Peer peer, BitField bitfield)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
p.addPeer(peer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
*/
|
||||
public int wantPiece(Peer peer, BitField havePieces)
|
||||
{
|
||||
if (halted)
|
||||
return -1;
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece piece = null;
|
||||
Collections.sort(wantedPieces); // Sort in order of rarest first.
|
||||
List requested = new ArrayList();
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (piece == null && it.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
if (havePieces.get(p.getId()) && !p.isRequested())
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
else if (p.isRequested())
|
||||
{
|
||||
requested.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
Piece p = (Piece)it2.next();
|
||||
if (havePieces.get(p.getId()))
|
||||
{
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
if (piece == null) return -1; //If we still can't find a piece we want, so be it.
|
||||
}
|
||||
piece.setRequested(true);
|
||||
return piece.getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*/
|
||||
public byte[] gotRequest(Peer peer, int piece)
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return storage.getPiece(piece);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.fatal("Error reading storage", ioe);
|
||||
return null; // Never reached.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a peer has uploaded some bytes of a piece.
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a peer has downloaded some bytes of a piece.
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the piece is no good (according to the hash).
|
||||
* In that case the peer that supplied the piece should probably be
|
||||
* blacklisted.
|
||||
*/
|
||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||
{
|
||||
if (halted)
|
||||
return true; // We don't actually care anymore.
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece p = new Piece(piece);
|
||||
if (!wantedPieces.contains(p))
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug(peer + " piece " + piece + " no longer needed",
|
||||
Snark.INFO);
|
||||
|
||||
// No need to announce have piece to peers.
|
||||
// Assume we got a good piece, we don't really care anymore.
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Recv p" + piece + " " + peer, Snark.INFO);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
if (Snark.debug >= Snark.NOTICE)
|
||||
Snark.debug("Got BAD piece " + piece + " from " + peer,
|
||||
Snark.NOTICE);
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.fatal("Error writing storage", ioe);
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
}
|
||||
|
||||
// Announce to the world we have it!
|
||||
synchronized(peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.isConnected())
|
||||
p.have(piece);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void gotChoke(Peer peer, boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got choke(" + choke + "): " + peer, Snark.INFO);
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
public void gotInterest(Peer peer, boolean interest)
|
||||
{
|
||||
if (interest)
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
{
|
||||
if(peer.isChoking())
|
||||
{
|
||||
uploaders++;
|
||||
peer.setChoking(false);
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
public void disconnected(Peer peer)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Disconnected " + peer, Snark.INFO);
|
||||
|
||||
synchronized(peers)
|
||||
{
|
||||
// Make sure it is no longer in our lists
|
||||
if (peers.remove(peer))
|
||||
{
|
||||
// Unchoke some random other peer
|
||||
unchokePeer();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
|
||||
/** Called when a peer is removed, to prevent it from being used in
|
||||
* rarest-first calculations.
|
||||
*/
|
||||
public void removePeerFromPieces(Peer peer) {
|
||||
synchronized(wantedPieces) {
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
piece.removePeer(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
208
apps/i2psnark/java/src/org/klomp/snark/PeerID.java
Normal file
@@ -0,0 +1,208 @@
|
||||
/* PeerID - All public information concerning a peer.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
public class PeerID implements Comparable
|
||||
{
|
||||
private final byte[] id;
|
||||
private final Destination address;
|
||||
private final int port;
|
||||
|
||||
private final int hash;
|
||||
|
||||
public PeerID(byte[] id, Destination address)
|
||||
{
|
||||
this.id = id;
|
||||
this.address = address;
|
||||
this.port = 6881;
|
||||
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PeerID from a BDecoder.
|
||||
*/
|
||||
public PeerID(BDecoder be)
|
||||
throws IOException
|
||||
{
|
||||
this(be.bdecodeMap().getMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PeerID from a Map containing BEncoded peer id, ip and
|
||||
* port.
|
||||
*/
|
||||
public PeerID(Map m)
|
||||
throws InvalidBEncodingException, UnknownHostException
|
||||
{
|
||||
BEValue bevalue = (BEValue)m.get("peer id");
|
||||
if (bevalue == null)
|
||||
throw new InvalidBEncodingException("peer id missing");
|
||||
id = bevalue.getBytes();
|
||||
|
||||
bevalue = (BEValue)m.get("ip");
|
||||
if (bevalue == null)
|
||||
throw new InvalidBEncodingException("ip missing");
|
||||
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
|
||||
if (address == null)
|
||||
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");
|
||||
|
||||
port = 6881;
|
||||
|
||||
hash = calculateHash();
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Destination getAddress()
|
||||
{
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
private int calculateHash()
|
||||
{
|
||||
int b = 0;
|
||||
for (int i = 0; i < id.length; i++)
|
||||
b ^= id[i];
|
||||
return (b ^ address.hashCode()) ^ port;
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash code of a PeerID is the exclusive or of all id bytes.
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if and only if this peerID and the given peerID have
|
||||
* the same 20 bytes as ID.
|
||||
*/
|
||||
public boolean sameID(PeerID pid)
|
||||
{
|
||||
boolean equal = true;
|
||||
for (int i = 0; equal && i < id.length; i++)
|
||||
equal = id[i] == pid.id[i];
|
||||
return equal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Two PeerIDs are equal when they have the same id, address and port.
|
||||
*/
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof PeerID)
|
||||
{
|
||||
PeerID pid = (PeerID)o;
|
||||
|
||||
return port == pid.port
|
||||
&& address.equals(pid.address)
|
||||
&& sameID(pid);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares port, address and id.
|
||||
*/
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
PeerID pid = (PeerID)o;
|
||||
|
||||
int result = port - pid.port;
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
result = address.hashCode() - pid.address.hashCode();
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
||||
for (int i = 0; i < id.length; i++)
|
||||
{
|
||||
result = id[i] - pid.id[i];
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String "id@address" where id is the base64 encoded id.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
int nonZero = 0;
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] != 0) {
|
||||
nonZero = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Base64.encode(id, nonZero, id.length-nonZero).substring(0,4) + "@" + address.calculateHash().toBase64().substring(0,6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an id as a hex encoded string and remove leading zeros.
|
||||
*/
|
||||
public static String idencode(byte[] bs)
|
||||
{
|
||||
boolean leading_zeros = true;
|
||||
|
||||
StringBuffer sb = new StringBuffer(bs.length*2);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
if (leading_zeros && c == 0)
|
||||
continue;
|
||||
else
|
||||
leading_zeros = false;
|
||||
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
145
apps/i2psnark/java/src/org/klomp/snark/PeerListener.java
Normal file
@@ -0,0 +1,145 @@
|
||||
/* PeerListener - Interface for listening to peer events.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Listener for Peer events.
|
||||
*/
|
||||
public interface PeerListener
|
||||
{
|
||||
/**
|
||||
* Called when the connection to the peer has started and the
|
||||
* handshake was successfull.
|
||||
*
|
||||
* @param peer the Peer that just got connected.
|
||||
*/
|
||||
void connected(Peer peer);
|
||||
|
||||
/**
|
||||
* Called when the connection to the peer was terminated or the
|
||||
* connection handshake failed.
|
||||
*
|
||||
* @param peer the Peer that just got disconnected.
|
||||
*/
|
||||
void disconnected(Peer peer);
|
||||
|
||||
/**
|
||||
* Called when a choke message is received.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param choke true when the peer got a choke message, false when
|
||||
* the peer got an unchoke message.
|
||||
*/
|
||||
void gotChoke(Peer peer, boolean choke);
|
||||
|
||||
/**
|
||||
* Called when an interested message is received.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param interest true when the peer got a interested message, false when
|
||||
* the peer got an uninterested message.
|
||||
*/
|
||||
void gotInterest(Peer peer, boolean interest);
|
||||
|
||||
/**
|
||||
* Called when a have piece message is received. If the method
|
||||
* returns true and the peer has not yet received a interested
|
||||
* message or we indicated earlier to be not interested then an
|
||||
* interested message will be send.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param piece the piece number that the per just got.
|
||||
*
|
||||
* @return true when it is a piece that we want, false if the piece is
|
||||
* already known.
|
||||
*/
|
||||
boolean gotHave(Peer peer, int piece);
|
||||
|
||||
/**
|
||||
* Called when a bitmap message is received. If this method returns
|
||||
* true a interested message will be send back to the peer.
|
||||
*
|
||||
* @param peer the Peer that got the message.
|
||||
* @param bitfield a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return true when the BitField contains pieces we want, false if
|
||||
* the piece is already known.
|
||||
*/
|
||||
boolean gotBitField(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when a piece is received from the peer. The piece must be
|
||||
* requested by Peer.request() first. If this method returns false
|
||||
* that means the Peer provided a corrupted piece and the connection
|
||||
* will be closed.
|
||||
*
|
||||
* @param peer the Peer that got the piece.
|
||||
* @param piece the piece number received.
|
||||
* @param bs the byte array containing the piece.
|
||||
*
|
||||
* @return true when the bytes represent the piece, false otherwise.
|
||||
*/
|
||||
boolean gotPiece(Peer peer, int piece, byte[] bs);
|
||||
|
||||
/**
|
||||
* Called when the peer wants (part of) a piece from us. Only called
|
||||
* when the peer is not choked by us (<code>peer.choke(false)</code>
|
||||
* was called).
|
||||
*
|
||||
* @param peer the Peer that wants the piece.
|
||||
* @param piece the piece number requested.
|
||||
*
|
||||
* @return a byte array containing the piece or null when the piece
|
||||
* is not available (which is a protocol error).
|
||||
*/
|
||||
byte[] gotRequest(Peer peer, int piece);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been downloaded from the peer.
|
||||
*
|
||||
* @param peer the Peer from which size bytes where downloaded.
|
||||
* @param size the number of bytes that where downloaded.
|
||||
*/
|
||||
void downloaded(Peer peer, int size);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been uploaded to the peer.
|
||||
*
|
||||
* @param peer the Peer to which size bytes where uploaded.
|
||||
* @param size the number of bytes that where uploaded.
|
||||
*/
|
||||
void uploaded(Peer peer, int size);
|
||||
|
||||
/**
|
||||
* Called when we are downloading from the peer and need to ask for
|
||||
* a new piece. Might be called multiple times before
|
||||
* <code>gotPiece()</code> is called.
|
||||
*
|
||||
* @param peer the Peer that will be asked to provide the piece.
|
||||
* @param bitfield a BitField containing the pieces that the other
|
||||
* side has.
|
||||
*
|
||||
* @return one of the pieces from the bitfield that we want or -1 if
|
||||
* we are no longer interested in the peer.
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
}
|
||||
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/PeerMonitorTask.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/* PeerMonitorTasks - TimerTask that monitors the peers and total up/down speed
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TimerTask that monitors the peers and total up/download speeds.
|
||||
* Works together with the main Snark class to report periodical statistics.
|
||||
*/
|
||||
class PeerMonitorTask extends TimerTask
|
||||
{
|
||||
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.
|
||||
private final long KILOPERSECOND = 1024 * (MONITOR_PERIOD / 1000);
|
||||
|
||||
private final PeerCoordinator coordinator;
|
||||
|
||||
private long lastDownloaded = 0;
|
||||
private long lastUploaded = 0;
|
||||
|
||||
PeerMonitorTask(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// Get some statistics
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
|
||||
// Don't list dying peers
|
||||
if (!peer.isConnected())
|
||||
continue;
|
||||
|
||||
peers++;
|
||||
|
||||
if (!peer.isChoking())
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
}
|
||||
}
|
||||
|
||||
// Print some statistics
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
String totalDown;
|
||||
if (downloaded >= 10 * 1024 * 1024)
|
||||
totalDown = (downloaded / (1024 * 1024)) + "MB";
|
||||
else
|
||||
totalDown = (downloaded / 1024 )+ "KB";
|
||||
long uploaded = coordinator.getUploaded();
|
||||
String totalUp;
|
||||
if (uploaded >= 10 * 1024 * 1024)
|
||||
totalUp = (uploaded / (1024 * 1024)) + "MB";
|
||||
else
|
||||
totalUp = (uploaded / 1024) + "KB";
|
||||
|
||||
int needP = coordinator.storage.needed();
|
||||
long needMB
|
||||
= needP * coordinator.metainfo.getPieceLength(0) / (1024 * 1024);
|
||||
int totalP = coordinator.metainfo.getPieces();
|
||||
long totalMB = coordinator.metainfo.getTotalLength() / (1024 * 1024);
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Down: "
|
||||
+ (downloaded - lastDownloaded) / KILOPERSECOND
|
||||
+ "KB/s"
|
||||
+ " (" + totalDown + ")"
|
||||
+ " Up: "
|
||||
+ (uploaded - lastUploaded) / KILOPERSECOND
|
||||
+ "KB/s"
|
||||
+ " (" + totalUp + ")"
|
||||
+ " Need " + needP
|
||||
+ " (" + needMB + "MB)"
|
||||
+ " of " + totalP
|
||||
+ " (" + totalMB + "MB)"
|
||||
+ " pieces");
|
||||
System.out.println(peers + ": Download #" + downloaders
|
||||
+ " Upload #" + uploaders
|
||||
+ " Interested #" + interested
|
||||
+ " Interesting #" + interesting
|
||||
+ " Choking #" + choking
|
||||
+ " Choked #" + choked);
|
||||
System.out.println();
|
||||
|
||||
lastDownloaded = downloaded;
|
||||
lastUploaded = uploaded;
|
||||
}
|
||||
}
|
||||
539
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
539
apps/i2psnark/java/src/org/klomp/snark/PeerState.java
Normal file
@@ -0,0 +1,539 @@
|
||||
/* PeerState - Keeps track of the Peer state through connection callbacks.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
class PeerState
|
||||
{
|
||||
final Peer peer;
|
||||
final PeerListener listener;
|
||||
final MetaInfo metainfo;
|
||||
|
||||
// Interesting and choking describes whether we are interested in or
|
||||
// are choking the other side.
|
||||
boolean interesting = false;
|
||||
boolean choking = true;
|
||||
|
||||
// Interested and choked describes whether the other side is
|
||||
// interested in us or choked us.
|
||||
boolean interested = false;
|
||||
boolean choked = true;
|
||||
|
||||
// Package local for use by Peer.
|
||||
long downloaded;
|
||||
long uploaded;
|
||||
|
||||
BitField bitfield;
|
||||
|
||||
// Package local for use by Peer.
|
||||
final PeerConnectionIn in;
|
||||
final PeerConnectionOut out;
|
||||
|
||||
// Outstanding request
|
||||
private final List outstandingRequests = new ArrayList();
|
||||
private Request lastRequest = null;
|
||||
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 5;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
PeerConnectionIn in, PeerConnectionOut out)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.listener = listener;
|
||||
this.metainfo = metainfo;
|
||||
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
// NOTE Methods that inspect or change the state synchronize (on this).
|
||||
|
||||
void keepAliveMessage()
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv alive", Snark.DEBUG);
|
||||
/* XXX - ignored */
|
||||
}
|
||||
|
||||
void chokeMessage(boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv " + (choke ? "" : "un") + "choked",
|
||||
Snark.DEBUG);
|
||||
|
||||
choked = choke;
|
||||
if (choked)
|
||||
resend = true;
|
||||
|
||||
listener.gotChoke(peer, choke);
|
||||
|
||||
if (!choked && interesting)
|
||||
request();
|
||||
}
|
||||
|
||||
void interestedMessage(boolean interest)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv " + (interest ? "" : "un")
|
||||
+ "interested", Snark.DEBUG);
|
||||
interested = interest;
|
||||
listener.gotInterest(peer, interest);
|
||||
}
|
||||
|
||||
void haveMessage(int piece)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv have(" + piece + ")", Snark.DEBUG);
|
||||
// Sanity check
|
||||
if (piece < 0 || piece >= metainfo.getPieces())
|
||||
{
|
||||
// XXX disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got strange 'have: " + piece + "' message from " + peer,
|
||||
+ Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Can happen if the other side never send a bitfield message.
|
||||
if (bitfield == null)
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
|
||||
bitfield.set(piece);
|
||||
}
|
||||
|
||||
if (listener.gotHave(peer, piece))
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
void bitfieldMessage(byte[] bitmap)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv bitfield", Snark.DEBUG);
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you except?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got unexpected bitfield message from " + peer,
|
||||
Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
}
|
||||
setInteresting(listener.gotBitField(peer, bitfield));
|
||||
}
|
||||
|
||||
void requestMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv request("
|
||||
+ piece + ", " + begin + ", " + length + ") ",
|
||||
Snark.DEBUG);
|
||||
if (choking)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Request received, but choking " + peer, Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (piece < 0
|
||||
|| piece >= metainfo.getPieces()
|
||||
|| begin < 0
|
||||
|| begin > metainfo.getPieceLength(piece)
|
||||
|| length <= 0
|
||||
|| length > 4*PARTSIZE)
|
||||
{
|
||||
// XXX - Protocol error -> disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got strange 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer,
|
||||
Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got request for unknown piece: " + piece, Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got out of range 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer,
|
||||
Snark.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer, Snark.DEBUG);
|
||||
out.sendPiece(piece, begin, length, pieceBytes);
|
||||
|
||||
// Tell about last subpiece delivery.
|
||||
if (begin + length == pieceBytes.length)
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Send p" + piece + " " + peer,
|
||||
Snark.DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when some bytes have left the outgoing connection.
|
||||
* XXX - Should indicate whether it was a real piece or overhead.
|
||||
*/
|
||||
void uploaded(int size)
|
||||
{
|
||||
uploaded += size;
|
||||
listener.uploaded(peer, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* PeerConnectionIn.
|
||||
*/
|
||||
void pieceMessage(Request req)
|
||||
{
|
||||
int size = req.len;
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got " + req.piece + ": " + peer, Snark.DEBUG);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got BAD " + req.piece + " from " + peer,
|
||||
Snark.DEBUG);
|
||||
// XXX ARGH What now !?!
|
||||
downloaded = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private int getFirstOutstandingRequest(int piece)
|
||||
{
|
||||
for (int i = 0; i < outstandingRequests.size(); i++)
|
||||
if (((Request)outstandingRequests.get(i)).piece == piece)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a piece message is being processed by the incoming
|
||||
* connection. Returns null when there was no such request. It also
|
||||
* requeues/sends requests when it thinks that they must have been
|
||||
* lost.
|
||||
*/
|
||||
Request getOutstandingRequest(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("getChunk("
|
||||
+ piece + "," + begin + "," + length + ") "
|
||||
+ peer, Snark.DEBUG);
|
||||
|
||||
int r = getFirstOutstandingRequest(piece);
|
||||
|
||||
// Unrequested piece number?
|
||||
if (r == -1)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unrequested 'piece: " + piece + ", "
|
||||
+ begin + ", " + length + "' received from "
|
||||
+ peer,
|
||||
Snark.INFO);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lookup the correct piece chunk request from the list.
|
||||
Request req;
|
||||
synchronized(this)
|
||||
{
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
while (req.piece == piece && req.off != begin
|
||||
&& r < outstandingRequests.size() - 1)
|
||||
{
|
||||
r++;
|
||||
req = (Request)outstandingRequests.get(r);
|
||||
}
|
||||
|
||||
// Something wrong?
|
||||
if (req.piece != piece || req.off != begin || req.len != length)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unrequested or unneeded 'piece: "
|
||||
+ piece + ", "
|
||||
+ begin + ", "
|
||||
+ length + "' received from "
|
||||
+ peer,
|
||||
Snark.INFO);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
|
||||
// Report missing requests.
|
||||
if (r != 0)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.print("Some requests dropped, got " + req
|
||||
+ ", wanted:");
|
||||
for (int i = 0; i < r; i++)
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
// We used to rerequest the missing chunks but that mostly
|
||||
// just confuses the other side. So now we just keep
|
||||
// waiting for them. They will be rerequested when we get
|
||||
// choked/unchoked again.
|
||||
/*
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.print(" " + dropReq);
|
||||
}
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.println(" " + peer);
|
||||
}
|
||||
outstandingRequests.remove(0);
|
||||
}
|
||||
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got cancel message ("
|
||||
+ piece + ", " + begin + ", " + length + ")",
|
||||
Snark.DEBUG);
|
||||
out.cancelRequest(piece, begin, length);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (Snark.debug >= Snark.WARNING)
|
||||
Snark.debug("Warning: Ignoring unknown message type: " + type
|
||||
+ " length: " + bs.length, Snark.WARNING);
|
||||
}
|
||||
|
||||
void havePiece(int piece)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Tell " + peer + " havePiece(" + piece + ")", Snark.DEBUG);
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Tell the other side that we are no longer interested in any of
|
||||
// the outstanding requests for this piece.
|
||||
if (lastRequest != null && lastRequest.piece == piece)
|
||||
lastRequest = null;
|
||||
|
||||
Iterator it = outstandingRequests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if (req.piece == piece)
|
||||
{
|
||||
it.remove();
|
||||
// Send cancel even when we are choked to make sure that it is
|
||||
// really never ever send.
|
||||
out.sendCancel(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the other side that we really have this piece.
|
||||
out.sendHave(piece);
|
||||
|
||||
// Request something else if necessary.
|
||||
addRequest();
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Is the peer still interesting?
|
||||
if (lastRequest == null)
|
||||
setInteresting(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Starts or resumes requesting pieces.
|
||||
private void request()
|
||||
{
|
||||
// Are there outstanding requests that have to be resend?
|
||||
if (resend)
|
||||
{
|
||||
out.sendRequests(outstandingRequests);
|
||||
resend = false;
|
||||
}
|
||||
|
||||
// Add/Send some more requests if necessary.
|
||||
addRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
*/
|
||||
private void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
}
|
||||
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
more_pieces = requestNextPiece();
|
||||
else if (more_pieces) // We want something
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
synchronized(this)
|
||||
{
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
}
|
||||
|
||||
// Last part of a piece?
|
||||
if (isLastChunk)
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
int maxLength = pieceLength - nextBegin;
|
||||
int nextLength = maxLength > PARTSIZE ? PARTSIZE
|
||||
: maxLength;
|
||||
Request req
|
||||
= new Request(nextPiece, bs, nextBegin, nextLength);
|
||||
outstandingRequests.add(req);
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " requests " + outstandingRequests, Snark.DEBUG);
|
||||
}
|
||||
|
||||
// Starts requesting first chunk of next piece. Returns true if
|
||||
// something has been added to the requests, false otherwise.
|
||||
private boolean requestNextPiece()
|
||||
{
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " want piece " + nextPiece, Snark.DEBUG);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
byte[] bs = new byte[piece_length];
|
||||
|
||||
int length = Math.min(piece_length, PARTSIZE);
|
||||
Request req = new Request(nextPiece, bs, 0, length);
|
||||
outstandingRequests.add(req);
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized void setInteresting(boolean interest)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " setInteresting(" + interest + ")", Snark.DEBUG);
|
||||
|
||||
if (interest != interesting)
|
||||
{
|
||||
interesting = interest;
|
||||
out.sendInterest(interest);
|
||||
|
||||
if (interesting && !choked)
|
||||
request();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void setChoking(boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " setChoking(" + choke + ")", Snark.DEBUG);
|
||||
|
||||
if (choking != choke)
|
||||
{
|
||||
choking = choke;
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
38
apps/i2psnark/java/src/org/klomp/snark/Piece.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Piece implements Comparable {
|
||||
|
||||
private int id;
|
||||
private Set peers;
|
||||
private boolean requested;
|
||||
|
||||
public Piece(int id) {
|
||||
this.id = id;
|
||||
this.peers = Collections.synchronizedSet(new HashSet());
|
||||
this.requested = false;
|
||||
}
|
||||
|
||||
public int compareTo(Object o) throws ClassCastException {
|
||||
return this.peers.size() - ((Piece)o).peers.size();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) return false;
|
||||
try {
|
||||
return this.id == ((Piece)o).id;
|
||||
} catch (ClassCastException cce) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int getId() { return this.id; }
|
||||
public Set getPeers() { return this.peers; }
|
||||
public boolean addPeer(Peer peer) { return this.peers.add(peer.getPeerID()); }
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
}
|
||||
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
73
apps/i2psnark/java/src/org/klomp/snark/Request.java
Normal file
@@ -0,0 +1,73 @@
|
||||
/* Request - Holds all information needed for a (partial) piece request.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Holds all information needed for a partial piece request.
|
||||
*/
|
||||
class Request
|
||||
{
|
||||
final int piece;
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
*
|
||||
* @param piece Piece number requested.
|
||||
* @param bs byte array where response should be stored.
|
||||
* @param off the offset in the array.
|
||||
* @param len the number of bytes requested.
|
||||
*/
|
||||
Request(int piece, byte[] bs, int off, int len)
|
||||
{
|
||||
this.piece = piece;
|
||||
this.bs = bs;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
|
||||
// Sanity check
|
||||
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
}
|
||||
|
||||
public int hashCode()
|
||||
{
|
||||
return piece ^ off ^ len;
|
||||
}
|
||||
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (o instanceof Request)
|
||||
{
|
||||
Request req = (Request)o;
|
||||
return req.piece == piece && req.off == off && req.len == len;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "(" + piece + "," + off + "," + len + ")";
|
||||
}
|
||||
}
|
||||
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
34
apps/i2psnark/java/src/org/klomp/snark/ShutdownListener.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/* ShutdownListener - Callback for end of shutdown sequence
|
||||
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback for end of shutdown sequence.
|
||||
*/
|
||||
interface ShutdownListener
|
||||
{
|
||||
/**
|
||||
* Called when the SnarkShutdown hook has finished shutting down all
|
||||
* subcomponents.
|
||||
*/
|
||||
void shutdown();
|
||||
}
|
||||
598
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
598
apps/i2psnark/java/src/org/klomp/snark/Snark.java
Normal file
@@ -0,0 +1,598 @@
|
||||
/* Snark - Main snark program startup class.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class Snark
|
||||
implements StorageListener, CoordinatorListener, ShutdownListener
|
||||
{
|
||||
private final static int MIN_PORT = 6881;
|
||||
private final static int MAX_PORT = 6889;
|
||||
|
||||
// Error messages (non-fatal)
|
||||
public final static int ERROR = 1;
|
||||
|
||||
// Warning messages
|
||||
public final static int WARNING = 2;
|
||||
|
||||
// Notices (peer level)
|
||||
public final static int NOTICE = 3;
|
||||
|
||||
// Info messages (protocol policy level)
|
||||
public final static int INFO = 4;
|
||||
|
||||
// Debug info (protocol level)
|
||||
public final static int DEBUG = 5;
|
||||
|
||||
// Very low level stuff (network level)
|
||||
public final static int ALL = 6;
|
||||
|
||||
/**
|
||||
* What level of debug info to show.
|
||||
*/
|
||||
public static int debug = NOTICE;
|
||||
|
||||
// Whether or not to ask the user for commands while sharing
|
||||
private static boolean command_interpreter = true;
|
||||
|
||||
private static final String newline = System.getProperty("line.separator");
|
||||
|
||||
private static final String copyright =
|
||||
"The Hunting of the Snark Project - Copyright (C) 2003 Mark J. Wielaard"
|
||||
+ newline + newline
|
||||
+ "Snark comes with ABSOLUTELY NO WARRANTY. This is free software, and"
|
||||
+ newline
|
||||
+ "you are welcome to redistribute it under certain conditions; read the"
|
||||
+ newline
|
||||
+ "COPYING file for details." + newline + newline
|
||||
+ "This is the I2P port, allowing anonymous bittorrent (http://www.i2p.net/)" + newline
|
||||
+ "It will not work with normal torrents, so don't even try ;)";
|
||||
|
||||
private static final String usage =
|
||||
"Press return for help. Type \"quit\" and return to stop.";
|
||||
private static final String help =
|
||||
"Commands: 'info', 'list', 'quit'.";
|
||||
|
||||
// String indicating main activity
|
||||
static String activity = "Not started";
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.println(copyright);
|
||||
System.out.println();
|
||||
|
||||
// Parse debug, share/ip and torrent file options.
|
||||
Snark snark = parseArguments(args);
|
||||
|
||||
SnarkShutdown snarkhook
|
||||
= new SnarkShutdown(snark.storage,
|
||||
snark.coordinator,
|
||||
snark.acceptor,
|
||||
snark.trackerclient,
|
||||
snark);
|
||||
Runtime.getRuntime().addShutdownHook(snarkhook);
|
||||
|
||||
Timer timer = new Timer(true);
|
||||
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
|
||||
timer.schedule(monitor,
|
||||
PeerMonitorTask.MONITOR_PERIOD,
|
||||
PeerMonitorTask.MONITOR_PERIOD);
|
||||
|
||||
// Start command interpreter
|
||||
if (Snark.command_interpreter)
|
||||
{
|
||||
boolean quit = false;
|
||||
|
||||
System.out.println();
|
||||
System.out.println(usage);
|
||||
System.out.println();
|
||||
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader
|
||||
(new InputStreamReader(System.in));
|
||||
String line = br.readLine();
|
||||
while(!quit && line != null)
|
||||
{
|
||||
line = line.toLowerCase();
|
||||
if ("quit".equals(line))
|
||||
quit = true;
|
||||
else if ("list".equals(line))
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
System.out.println(coordinator.peers.size()
|
||||
+ " peers -"
|
||||
+ " (i)nterested,"
|
||||
+ " (I)nteresting,"
|
||||
+ " (c)hoking,"
|
||||
+ " (C)hoked:");
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
System.out.println(peer);
|
||||
System.out.println("\ti: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ("info".equals(line))
|
||||
{
|
||||
System.out.println("Name: " + meta.getName());
|
||||
System.out.println("Torrent: " + torrent);
|
||||
System.out.println("Tracker: " + meta.getAnnounce());
|
||||
List files = meta.getFiles();
|
||||
System.out.println("Files: "
|
||||
+ ((files == null) ? 1 : files.size()));
|
||||
System.out.println("Pieces: " + meta.getPieces());
|
||||
System.out.println("Piece size: "
|
||||
+ meta.getPieceLength(0) / 1024
|
||||
+ " KB");
|
||||
System.out.println("Total size: "
|
||||
+ meta.getTotalLength() / (1024 * 1024)
|
||||
+ " MB");
|
||||
}
|
||||
else if ("".equals(line) || "help".equals(line))
|
||||
{
|
||||
System.out.println(usage);
|
||||
System.out.println(help);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Unknown command: " + line);
|
||||
System.out.println(usage);
|
||||
}
|
||||
|
||||
if (!quit)
|
||||
{
|
||||
System.out.println();
|
||||
line = br.readLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
debug("ERROR while reading stdin: " + ioe, ERROR);
|
||||
}
|
||||
|
||||
// Explicit shutdown.
|
||||
Runtime.getRuntime().removeShutdownHook(snarkhook);
|
||||
snarkhook.start();
|
||||
}
|
||||
}
|
||||
|
||||
static String torrent;
|
||||
static MetaInfo meta;
|
||||
static Storage storage;
|
||||
static PeerCoordinator coordinator;
|
||||
static ConnectionAcceptor acceptor;
|
||||
static TrackerClient trackerclient;
|
||||
|
||||
private Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
|
||||
if (clistener == null)
|
||||
clistener = this;
|
||||
|
||||
this.torrent = torrent;
|
||||
|
||||
activity = "Network setup";
|
||||
|
||||
// "Taking Three as the subject to reason about--
|
||||
// A convenient number to state--
|
||||
// We add Seven, and Ten, and then multiply out
|
||||
// By One Thousand diminished by Eight.
|
||||
//
|
||||
// "The result we proceed to divide, as you see,
|
||||
// By Nine Hundred and Ninety Two:
|
||||
// Then subtract Seventeen, and the answer must be
|
||||
// Exactly and perfectly true.
|
||||
|
||||
// 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.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] id = new byte[20];
|
||||
Random random = new Random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
id[i] = 0;
|
||||
id[i++] = snark;
|
||||
id[i++] = snark;
|
||||
id[i++] = snark;
|
||||
while (i < 20)
|
||||
id[i++] = (byte)random.nextInt(256);
|
||||
|
||||
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else
|
||||
debug("Listening on I2P destination " + serversocket.getManager().getSession().getMyDestination().toBase64(), NOTICE);
|
||||
|
||||
// Figure out what the torrent argument represents.
|
||||
meta = null;
|
||||
File f = null;
|
||||
try
|
||||
{
|
||||
InputStream in = null;
|
||||
f = new File(torrent);
|
||||
if (f.exists())
|
||||
in = new FileInputStream(f);
|
||||
else
|
||||
{
|
||||
activity = "Getting torrent";
|
||||
File torrentFile = I2PSnarkUtil.instance().get(torrent);
|
||||
if (torrentFile == null) {
|
||||
fatal("Unable to fetch " + torrent);
|
||||
if (false) return; // never reached - fatal(..) throws
|
||||
} else {
|
||||
torrentFile.deleteOnExit();
|
||||
in = new FileInputStream(torrentFile);
|
||||
}
|
||||
}
|
||||
meta = new MetaInfo(new BDecoder(in));
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
// OK, so it wasn't a torrent metainfo file.
|
||||
if (f != null && f.exists())
|
||||
if (ip == null)
|
||||
fatal("'" + torrent + "' exists,"
|
||||
+ " but is not a valid torrent metainfo file."
|
||||
+ System.getProperty("line.separator"), ioe);
|
||||
else
|
||||
fatal("I2PSnark does not support creating and tracking a torrent at the moment");
|
||||
/*
|
||||
{
|
||||
// Try to create a new metainfo file
|
||||
Snark.debug
|
||||
("Trying to create metainfo torrent for '" + torrent + "'",
|
||||
NOTICE);
|
||||
try
|
||||
{
|
||||
activity = "Creating torrent";
|
||||
storage = new Storage
|
||||
(f, "http://" + ip + ":" + port + "/announce", slistener);
|
||||
storage.create();
|
||||
meta = storage.getMetaInfo();
|
||||
}
|
||||
catch (IOException ioe2)
|
||||
{
|
||||
fatal("Could not create torrent for '" + torrent + "'", ioe2);
|
||||
}
|
||||
}
|
||||
*/
|
||||
else
|
||||
fatal("Cannot open '" + torrent + "'", ioe);
|
||||
}
|
||||
|
||||
debug(meta.toString(), INFO);
|
||||
|
||||
// When the metainfo torrent was created from an existing file/dir
|
||||
// it already exists.
|
||||
if (storage == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
fatal("Could not create storage", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener);
|
||||
PeerAcceptor peeracceptor = new PeerAcceptor(coordinator);
|
||||
ConnectionAcceptor acceptor = new ConnectionAcceptor(serversocket,
|
||||
peeracceptor);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
trackerclient.start();
|
||||
|
||||
}
|
||||
|
||||
static Snark parseArguments(String[] args)
|
||||
{
|
||||
return parseArguments(args, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets debug, ip and torrent variables then creates a Snark
|
||||
* instance. Calls usage(), which terminates the program, if
|
||||
* non-valid argument list. The given listeners will be
|
||||
* passed to all components that take one.
|
||||
*/
|
||||
static Snark parseArguments(String[] args,
|
||||
StorageListener slistener,
|
||||
CoordinatorListener clistener)
|
||||
{
|
||||
int user_port = -1;
|
||||
String ip = null;
|
||||
String torrent = null;
|
||||
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
debug = INFO;
|
||||
i++;
|
||||
|
||||
// Try if there is an level argument.
|
||||
if (i < args.length)
|
||||
{
|
||||
try
|
||||
{
|
||||
int level = Integer.parseInt(args[i]);
|
||||
if (level >= 0)
|
||||
{
|
||||
debug = level;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException nfe) { }
|
||||
}
|
||||
}
|
||||
else if (args[i].equals("--port"))
|
||||
{
|
||||
if (args.length - 1 < i + 1)
|
||||
usage("--port needs port number to listen on");
|
||||
try
|
||||
{
|
||||
user_port = Integer.parseInt(args[i + 1]);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
usage("--port argument must be a number (" + nfe + ")");
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else if (args[i].equals("--no-commands"))
|
||||
{
|
||||
command_interpreter = false;
|
||||
i++;
|
||||
}
|
||||
else if (args[i].equals("--eepproxy"))
|
||||
{
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
else if (args[i].equals("--i2cp"))
|
||||
{
|
||||
String i2cpHost = args[i+1];
|
||||
String i2cpPort = args[i+2];
|
||||
Properties opts = null;
|
||||
if (i+3 < args.length) {
|
||||
if (!args[i+3].startsWith("--")) {
|
||||
opts = new Properties();
|
||||
StringTokenizer tok = new StringTokenizer(args[i+3], " \t");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String str = tok.nextToken();
|
||||
int split = str.indexOf('=');
|
||||
if (split > 0) {
|
||||
opts.setProperty(str.substring(0, split), str.substring(split+1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
i += 3 + (opts != null ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
torrent = args[i];
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent == null || i != args.length)
|
||||
if (torrent != null && torrent.startsWith("-"))
|
||||
usage("Unknow option '" + torrent + "'.");
|
||||
else
|
||||
usage("Need exactly one <url>, <file> or <dir>.");
|
||||
|
||||
return new Snark(torrent, ip, user_port, slistener, clistener);
|
||||
}
|
||||
|
||||
private static void usage(String s)
|
||||
{
|
||||
System.out.println("snark: " + s);
|
||||
usage();
|
||||
}
|
||||
|
||||
private static void usage()
|
||||
{
|
||||
System.out.println
|
||||
("Usage: snark [--debug [level]] [--no-commands] [--port <port>]");
|
||||
System.out.println
|
||||
(" [--eepproxy hostname portnum]");
|
||||
System.out.println
|
||||
(" [--i2cp routerHost routerPort ['name=val name=val name=val']]");
|
||||
System.out.println
|
||||
(" (<url>|<file>)");
|
||||
System.out.println
|
||||
(" --debug\tShows some extra info and stacktraces");
|
||||
System.out.println
|
||||
(" level\tHow much debug details to show");
|
||||
System.out.println
|
||||
(" \t(defaults to "
|
||||
+ NOTICE + ", with --debug to "
|
||||
+ INFO + ", highest level is "
|
||||
+ ALL + ").");
|
||||
System.out.println
|
||||
(" --no-commands\tDon't read interactive commands or show usage info.");
|
||||
System.out.println
|
||||
(" --port\tThe port to listen on for incomming connections");
|
||||
System.out.println
|
||||
(" \t(if not given defaults to first free port between "
|
||||
+ MIN_PORT + "-" + MAX_PORT + ").");
|
||||
System.out.println
|
||||
(" --share\tStart torrent tracker on <ip> address or <host> name.");
|
||||
System.out.println
|
||||
(" --eepproxy\thttp proxy to use (default of 127.0.0.1 port 4444)");
|
||||
System.out.println
|
||||
(" --i2cp\tlocation of your I2P router (default of 127.0.0.1 port 7654)");
|
||||
System.out.println
|
||||
(" \toptional settings may be included, such as");
|
||||
System.out.println
|
||||
(" \tinbound.length=2 outbound.length=2 inbound.lengthVariance=-1 ");
|
||||
System.out.println
|
||||
(" <url> \tURL pointing to .torrent metainfo file to download/share.");
|
||||
System.out.println
|
||||
(" <file> \tEither a local .torrent metainfo file to download");
|
||||
System.out.println
|
||||
(" \tor (with --share) a file to share.");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public static void fatal(String s)
|
||||
{
|
||||
fatal(s, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public static void fatal(String s, Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, ERROR, t);
|
||||
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
throw new RuntimeException("die bart die");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show debug info if debug is true.
|
||||
*/
|
||||
public static void debug(String s, int level)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, level, null);
|
||||
//if (debug >= level)
|
||||
// System.out.println(s);
|
||||
}
|
||||
|
||||
public void peerChange(PeerCoordinator coordinator, Peer peer)
|
||||
{
|
||||
// System.out.println(peer.toString());
|
||||
}
|
||||
|
||||
boolean allocating = false;
|
||||
public void storageCreateFile(Storage storage, String name, long length)
|
||||
{
|
||||
if (allocating)
|
||||
System.out.println(); // Done with last file.
|
||||
|
||||
System.out.print("Creating file '" + name
|
||||
+ "' of length " + length + ": ");
|
||||
allocating = true;
|
||||
}
|
||||
|
||||
// How much storage space has been allocated
|
||||
private long allocated = 0;
|
||||
|
||||
public void storageAllocated(Storage storage, long length)
|
||||
{
|
||||
allocating = true;
|
||||
System.out.print(".");
|
||||
allocated += length;
|
||||
if (allocated == meta.getTotalLength())
|
||||
System.out.println(); // We have all the disk space we need.
|
||||
}
|
||||
|
||||
boolean allChecked = false;
|
||||
boolean checking = false;
|
||||
boolean prechecking = true;
|
||||
public void storageChecked(Storage storage, int num, boolean checked)
|
||||
{
|
||||
allocating = false;
|
||||
if (!allChecked && !checking)
|
||||
{
|
||||
// Use the MetaInfo from the storage since our own might not
|
||||
// yet be setup correctly.
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
if (meta != null)
|
||||
System.out.print("Checking existing "
|
||||
+ meta.getPieces()
|
||||
+ " pieces: ");
|
||||
checking = true;
|
||||
}
|
||||
if (checking)
|
||||
if (checked)
|
||||
System.out.print("+");
|
||||
else
|
||||
System.out.print("-");
|
||||
else
|
||||
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||
Snark.INFO);
|
||||
}
|
||||
|
||||
public void storageAllChecked(Storage storage)
|
||||
{
|
||||
if (checking)
|
||||
System.out.println();
|
||||
|
||||
allChecked = true;
|
||||
checking = false;
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
// have died. But in reality this does not always happen.
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
89
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
89
apps/i2psnark/java/src/org/klomp/snark/SnarkShutdown.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/* TrackerShutdown - Makes sure everything ends correctly when shutting down.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Makes sure everything ends correctly when shutting down.
|
||||
*/
|
||||
public class SnarkShutdown extends Thread
|
||||
{
|
||||
private final Storage storage;
|
||||
private final PeerCoordinator coordinator;
|
||||
private final ConnectionAcceptor acceptor;
|
||||
private final TrackerClient trackerclient;
|
||||
|
||||
private final ShutdownListener listener;
|
||||
|
||||
public SnarkShutdown(Storage storage,
|
||||
PeerCoordinator coordinator,
|
||||
ConnectionAcceptor acceptor,
|
||||
TrackerClient trackerclient,
|
||||
ShutdownListener listener)
|
||||
{
|
||||
this.storage = storage;
|
||||
this.coordinator = coordinator;
|
||||
this.acceptor = acceptor;
|
||||
this.trackerclient = trackerclient;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
Snark.debug("Shutting down...", Snark.NOTICE);
|
||||
|
||||
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
|
||||
if (acceptor != null)
|
||||
acceptor.halt();
|
||||
|
||||
Snark.debug("Halting TrackerClient...", Snark.INFO);
|
||||
if (trackerclient != null)
|
||||
trackerclient.halt();
|
||||
|
||||
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
|
||||
if (coordinator != null)
|
||||
coordinator.halt();
|
||||
|
||||
Snark.debug("Closing Storage...", Snark.INFO);
|
||||
if (storage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
storage.close();
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
Snark.fatal("Couldn't properly close storage", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX - Should actually wait till done...
|
||||
try
|
||||
{
|
||||
Snark.debug("Waiting 5 seconds...", Snark.INFO);
|
||||
Thread.sleep(5*1000);
|
||||
}
|
||||
catch (InterruptedException ie) { /* ignored */ }
|
||||
|
||||
listener.shutdown();
|
||||
}
|
||||
}
|
||||
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
47
apps/i2psnark/java/src/org/klomp/snark/StaticSnark.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/* StaticSnark - Main snark startup class for staticly linking with gcj.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
/**
|
||||
* Main snark startup class for staticly linking with gcj.
|
||||
* It references somee necessary classes that are normally loaded through
|
||||
* reflection.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class StaticSnark
|
||||
{
|
||||
public static void main(String[] args)
|
||||
{
|
||||
// The GNU security provider is needed for SHA-1 MessageDigest checking.
|
||||
// So make sure it is available as a security provider.
|
||||
//Provider gnu = new gnu.java.security.provider.Gnu();
|
||||
//Security.addProvider(gnu);
|
||||
|
||||
// And finally call the normal starting point.
|
||||
Snark.main(args);
|
||||
}
|
||||
}
|
||||
525
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
525
apps/i2psnark/java/src/org/klomp/snark/Storage.java
Normal file
@@ -0,0 +1,525 @@
|
||||
/* Storage - Class used to store and retrieve pieces.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
public class Storage
|
||||
{
|
||||
private MetaInfo metainfo;
|
||||
private long[] lengths;
|
||||
private RandomAccessFile[] rafs;
|
||||
private String[] names;
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield;
|
||||
private int needed;
|
||||
|
||||
// XXX - Not always set correctly
|
||||
int piece_size;
|
||||
int pieces;
|
||||
|
||||
/** The default piece size. */
|
||||
private static int MIN_PIECE_SIZE = 256*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
private static long MAX_PIECES = 100*1024/20;
|
||||
|
||||
/**
|
||||
* Creates a new storage based on the supplied MetaInfo. This will
|
||||
* try to create and/or check all needed files in the MetaInfo.
|
||||
*
|
||||
* @exception IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(MetaInfo metainfo, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
this.metainfo = metainfo;
|
||||
this.listener = listener;
|
||||
needed = metainfo.getPieces();
|
||||
bitfield = new BitField(needed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a storage from the existing file or directory together
|
||||
* with an appropriate MetaInfo file as can be announced on the
|
||||
* given announce String location.
|
||||
*/
|
||||
public Storage(File baseFile, String announce, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
this.listener = listener;
|
||||
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
|
||||
long total = 0;
|
||||
ArrayList lengthsList = new ArrayList();
|
||||
for (int i = 0; i < lengths.length; i++)
|
||||
{
|
||||
long length = lengths[i];
|
||||
total += length;
|
||||
lengthsList.add(new Long(length));
|
||||
}
|
||||
|
||||
piece_size = MIN_PIECE_SIZE;
|
||||
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||
while (pieces > MAX_PIECES)
|
||||
{
|
||||
piece_size = piece_size*2;
|
||||
pieces = (int) ((total - 1)/piece_size) +1;
|
||||
}
|
||||
|
||||
// Note that piece_hashes and the bitfield will be filled after
|
||||
// the MetaInfo is created.
|
||||
byte[] piece_hashes = new byte[20*pieces];
|
||||
bitfield = new BitField(pieces);
|
||||
needed = 0;
|
||||
|
||||
List files = new ArrayList();
|
||||
for (int i = 0; i < names.length; i++)
|
||||
{
|
||||
List file = new ArrayList();
|
||||
StringTokenizer st = new StringTokenizer(names[i], File.separator);
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
String part = st.nextToken();
|
||||
file.add(part);
|
||||
}
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
String name = baseFile.getName();
|
||||
if (files.size() == 1)
|
||||
{
|
||||
files = null;
|
||||
lengthsList = null;
|
||||
}
|
||||
|
||||
// Note that the piece_hashes are not correctly setup yet.
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), files,
|
||||
lengthsList, piece_size, piece_hashes, total);
|
||||
|
||||
}
|
||||
|
||||
// Creates piece hases for a new storage.
|
||||
public void create() throws IOException
|
||||
{
|
||||
// Calculate piece_hashes
|
||||
MessageDigest digest = null;
|
||||
try
|
||||
{
|
||||
digest = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
|
||||
byte[] piece_hashes = metainfo.getPieceHashes();
|
||||
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
piece_hashes[20 * i + j] = hash[j];
|
||||
|
||||
bitfield.set(i);
|
||||
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, i, true);
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageAllChecked(this);
|
||||
|
||||
// Reannounce to force recalculating the info_hash.
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
|
||||
private void getFiles(File base) throws IOException
|
||||
{
|
||||
ArrayList files = new ArrayList();
|
||||
addFiles(files, base);
|
||||
|
||||
int size = files.size();
|
||||
names = new String[size];
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
|
||||
int i = 0;
|
||||
Iterator it = files.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
File f = (File)it.next();
|
||||
names[i] = f.getPath();
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFiles(List l, File f)
|
||||
{
|
||||
if (!f.isDirectory())
|
||||
l.add(f);
|
||||
else
|
||||
{
|
||||
File[] files = f.listFiles();
|
||||
if (files == null)
|
||||
{
|
||||
Snark.debug("WARNING: Skipping '" + f
|
||||
+ "' not a normal file.", Snark.WARNING);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < files.length; i++)
|
||||
addFiles(l, files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MetaInfo associated with this Storage.
|
||||
*/
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many pieces are still missing from this storage.
|
||||
*/
|
||||
public int needed()
|
||||
{
|
||||
return needed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this storage contains all pieces if the MetaInfo.
|
||||
*/
|
||||
public boolean complete()
|
||||
{
|
||||
return needed == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
*/
|
||||
public BitField getBitField()
|
||||
{
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
public void check() throws IOException
|
||||
{
|
||||
File base = new File(filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Create base as file.
|
||||
Snark.debug("Creating/Checking file: " + base, Snark.NOTICE);
|
||||
if (!base.createNewFile() && !base.exists())
|
||||
throw new IOException("Could not create file " + base);
|
||||
|
||||
lengths = new long[1];
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
names[0] = base.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create base as dir.
|
||||
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
|
||||
if (!base.mkdir() && !base.isDirectory())
|
||||
throw new IOException("Could not create directory " + base);
|
||||
|
||||
List ls = metainfo.getLengths();
|
||||
int size = files.size();
|
||||
long total = 0;
|
||||
lengths = new long[size];
|
||||
rafs = new RandomAccessFile[size];
|
||||
names = new String[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
total += lengths[i];
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
names[i] = f.getName();
|
||||
}
|
||||
|
||||
// Sanity check for metainfo file.
|
||||
long metalength = metainfo.getTotalLength();
|
||||
if (total != metalength)
|
||||
throw new IOException("File lengths do not add up "
|
||||
+ total + " != " + metalength);
|
||||
}
|
||||
checkCreateFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
*/
|
||||
private String filterName(String name)
|
||||
{
|
||||
// XXX - Is this enough?
|
||||
return name.replace(File.separatorChar, '_');
|
||||
}
|
||||
|
||||
private File createFileFromNames(File base, List names) throws IOException
|
||||
{
|
||||
File f = null;
|
||||
Iterator it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName((String)it.next());
|
||||
if (it.hasNext())
|
||||
{
|
||||
// Another dir in the hierarchy.
|
||||
f = new File(base, name);
|
||||
if (!f.mkdir() && !f.isDirectory())
|
||||
throw new IOException("Could not create directory " + f);
|
||||
base = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The final element (file) in the hierarchy.
|
||||
f = new File(base, name);
|
||||
if (!f.createNewFile() && !f.exists())
|
||||
throw new IOException("Could not create file " + f);
|
||||
}
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
private void checkCreateFiles() throws IOException
|
||||
{
|
||||
// Whether we are resuming or not,
|
||||
// if any of the files already exists we assume we are resuming.
|
||||
boolean resume = false;
|
||||
|
||||
// Make sure all files are available and of correct length
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
long length = rafs[i].length();
|
||||
if(length == lengths[i])
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
resume = true; // XXX Could dynamicly check
|
||||
}
|
||||
else if (length == 0)
|
||||
allocateFile(i);
|
||||
else
|
||||
throw new IOException("File '" + names[i]
|
||||
+ "' exists, but has wrong length");
|
||||
}
|
||||
|
||||
// Check which pieces match and which don't
|
||||
if (resume)
|
||||
{
|
||||
pieces = metainfo.getPieces();
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
if (correctHash)
|
||||
{
|
||||
bitfield.set(i);
|
||||
needed--;
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, i, correctHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageAllChecked(this);
|
||||
}
|
||||
|
||||
private void allocateFile(int nr) throws IOException
|
||||
{
|
||||
// XXX - Is this the best way to make sure we have enough space for
|
||||
// the whole file?
|
||||
listener.storageCreateFile(this, names[nr], lengths[nr]);
|
||||
final int ZEROBLOCKSIZE = metainfo.getPieceLength(0);
|
||||
byte[] zeros = new byte[ZEROBLOCKSIZE];
|
||||
int i;
|
||||
for (i = 0; i < lengths[nr]/ZEROBLOCKSIZE; i++)
|
||||
{
|
||||
rafs[nr].write(zeros);
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, ZEROBLOCKSIZE);
|
||||
}
|
||||
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
|
||||
rafs[nr].write(zeros, 0, size);
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, size);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the Storage and makes sure that all RandomAccessFiles are
|
||||
* closed. The Storage is unusable after this.
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null if
|
||||
* the storage doesn't contain the piece yet.
|
||||
*/
|
||||
public byte[] getPiece(int piece) throws IOException
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
return null;
|
||||
|
||||
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||
getUncheckedPiece(piece, bs, 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the piece in the Storage if it is correct.
|
||||
*
|
||||
* @return true if the piece was correct (sha metainfo hash
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// If we were paranoia we could copy the array first.
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, piece, correctHash);
|
||||
if (!correctHash)
|
||||
return false;
|
||||
|
||||
boolean complete;
|
||||
synchronized(bitfield)
|
||||
{
|
||||
if (bitfield.get(piece))
|
||||
return true; // No need to store twice.
|
||||
else
|
||||
{
|
||||
bitfield.set(piece);
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
}
|
||||
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
|
||||
int written = 0;
|
||||
int off = 0;
|
||||
while (written < length)
|
||||
{
|
||||
int need = length - written;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].write(bs, off + written, len);
|
||||
}
|
||||
written += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||
throws IOException
|
||||
{
|
||||
// XXX - copy/paste code from putPiece().
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
{
|
||||
i++;
|
||||
start -= raflen;
|
||||
raflen = lengths[i];
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
while (read < length)
|
||||
{
|
||||
int need = length - read;
|
||||
int len = (start + need < raflen) ? need : (int)(raflen - start);
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, off + read, len);
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
{
|
||||
i++;
|
||||
raflen = lengths[i];
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
52
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
52
apps/i2psnark/java/src/org/klomp/snark/StorageListener.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/* StorageListener.java - Interface used as callback when storage changes.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
/**
|
||||
* Callback used when Storage changes.
|
||||
*/
|
||||
public interface StorageListener
|
||||
{
|
||||
/**
|
||||
* Called when the storage creates a new file of a given length.
|
||||
*/
|
||||
void storageCreateFile(Storage storage, String name, long length);
|
||||
|
||||
/**
|
||||
* Called to indicate that length bytes have been allocated.
|
||||
*/
|
||||
void storageAllocated(Storage storage, long length);
|
||||
|
||||
/**
|
||||
* Called when storage is being checked and the num piece of that
|
||||
* total pieces has been checked. When the piece hash matches the
|
||||
* expected piece hash checked will be true, otherwise it will be
|
||||
* false.
|
||||
*/
|
||||
void storageChecked(Storage storage, int num, boolean checked);
|
||||
|
||||
/**
|
||||
* Called when all pieces in the storage have been checked. Does not
|
||||
* mean that the storage is complete, just that the state of the
|
||||
* storage is known.
|
||||
*/
|
||||
void storageAllChecked(Storage storage);
|
||||
}
|
||||
254
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
254
apps/i2psnark/java/src/org/klomp/snark/TrackerClient.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/* TrackerClient - Class that informs a tracker and gets new peers.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
/**
|
||||
* Informs metainfo tracker of events and gets new peers for peer
|
||||
* coordinator.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class TrackerClient extends Thread
|
||||
{
|
||||
private static final String NO_EVENT = "";
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
private final int port;
|
||||
|
||||
private boolean stop;
|
||||
|
||||
private long interval;
|
||||
private long lastRequestTime;
|
||||
|
||||
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
|
||||
{
|
||||
// Set unique name.
|
||||
super("TrackerClient-" + urlencode(coordinator.getID()));
|
||||
this.meta = meta;
|
||||
this.coordinator = coordinator;
|
||||
|
||||
this.port = 6881; //(port == -1) ? 9 : port;
|
||||
|
||||
stop = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrupts this Thread to stop it.
|
||||
*/
|
||||
public void halt()
|
||||
{
|
||||
stop = true;
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// XXX - Support other IPs
|
||||
String announce = I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||
String infoHash = urlencode(meta.getInfoHash());
|
||||
String peerID = urlencode(coordinator.getID());
|
||||
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long left = coordinator.getLeft();
|
||||
|
||||
boolean completed = (left == 0);
|
||||
|
||||
try
|
||||
{
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send start.
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
STARTED_EVENT);
|
||||
Iterator it = info.getPeers().iterator();
|
||||
while (it.hasNext())
|
||||
coordinator.addPeer((Peer)it.next());
|
||||
started = true;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
{
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
try
|
||||
{
|
||||
// Sleep one minutes...
|
||||
Thread.sleep(60*1000);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
Thread.sleep(SLEEP*60*1000);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
uploaded = coordinator.getUploaded();
|
||||
downloaded = coordinator.getDownloaded();
|
||||
left = coordinator.getLeft();
|
||||
|
||||
// First time we got a complete download?
|
||||
String event;
|
||||
if (!completed && left == 0)
|
||||
{
|
||||
completed = true;
|
||||
event = COMPLETED_EVENT;
|
||||
}
|
||||
else
|
||||
event = NO_EVENT;
|
||||
|
||||
// Only do a request when necessary.
|
||||
if (event == COMPLETED_EVENT
|
||||
|| coordinator.needPeers()
|
||||
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||
{
|
||||
try
|
||||
{
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
Iterator it = info.getPeers().iterator();
|
||||
while (it.hasNext())
|
||||
coordinator.addPeer((Peer)it.next());
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Probably not fatal (if it doesn't last to long...)
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug("TrackerClient: " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
|
||||
downloaded, left, STOPPED_EVENT);
|
||||
}
|
||||
catch(IOException ioe) { /* ignored */ }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private TrackerInfo doRequest(String announce, String infoHash,
|
||||
String peerID, long uploaded,
|
||||
long downloaded, long left, String event)
|
||||
throws IOException
|
||||
{
|
||||
String s = announce
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
+ "&port=" + port
|
||||
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString()
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
File fetched = I2PSnarkUtil.instance().get(s);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
|
||||
fetched.deleteOnExit();
|
||||
InputStream in = new FileInputStream(fetched);
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
throw new IOException(failure);
|
||||
|
||||
interval = info.getInterval() * 1000;
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Very lazy byte[] to URL encoder. Just encodes everything, even
|
||||
* "normal" chars.
|
||||
*/
|
||||
static String urlencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*3);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
{
|
||||
int c = bs[i] & 0xFF;
|
||||
sb.append('%');
|
||||
if (c < 16)
|
||||
sb.append('0');
|
||||
sb.append(Integer.toHexString(c));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
128
apps/i2psnark/java/src/org/klomp/snark/TrackerInfo.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/* TrackerInfo - Holds information returned by a tracker, mainly the peer list.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
public class TrackerInfo
|
||||
{
|
||||
private final String failure_reason;
|
||||
private final int interval;
|
||||
private final Set peers;
|
||||
|
||||
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(new BDecoder(in), my_id, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this(be.bdecodeMap().getMap(), my_id, metainfo);
|
||||
}
|
||||
|
||||
public TrackerInfo(Map m, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
BEValue reason = (BEValue)m.get("failure reason");
|
||||
if (reason != null)
|
||||
{
|
||||
failure_reason = reason.getString();
|
||||
interval = -1;
|
||||
peers = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
failure_reason = null;
|
||||
BEValue beInterval = (BEValue)m.get("interval");
|
||||
if (beInterval == null)
|
||||
throw new InvalidBEncodingException("No interval given");
|
||||
else
|
||||
interval = beInterval.getInt();
|
||||
BEValue bePeers = (BEValue)m.get("peers");
|
||||
if (bePeers == null)
|
||||
throw new InvalidBEncodingException("No peer list");
|
||||
else
|
||||
peers = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set getPeers(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(new BDecoder(in), my_id, metainfo);
|
||||
}
|
||||
|
||||
public static Set getPeers(BDecoder be, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
return getPeers(be.bdecodeList().getList(), my_id, metainfo);
|
||||
}
|
||||
|
||||
public static Set getPeers(List l, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
Set peers = new HashSet(l.size());
|
||||
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
PeerID peerID = new PeerID(((BEValue)it.next()).getMap());
|
||||
peers.add(new Peer(peerID, my_id, metainfo));
|
||||
}
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
public Set getPeers()
|
||||
{
|
||||
return peers;
|
||||
}
|
||||
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
}
|
||||
|
||||
public int getInterval()
|
||||
{
|
||||
return interval;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
if (failure_reason != null)
|
||||
return "TrackerInfo[FAILED: " + failure_reason + "]";
|
||||
else
|
||||
return "TrackerInfo[interval=" + interval
|
||||
+ ", peers=" + peers + "]";
|
||||
}
|
||||
}
|
||||
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
355
apps/i2psnark/java/src/org/klomp/snark/bencode/BDecoder.java
Normal file
@@ -0,0 +1,355 @@
|
||||
/* BDecoder - Converts an InputStream to BEValues.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* Decodes a bencoded stream to <code>BEValue</code>s.
|
||||
*
|
||||
* A bencoded byte stream can represent byte arrays, numbers, lists and
|
||||
* maps (dictionaries).
|
||||
*
|
||||
* It currently contains a hack to indicate a name of a dictionary of
|
||||
* which a SHA-1 digest hash should be calculated (the hash over the
|
||||
* original bencoded bytes).
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org).
|
||||
*/
|
||||
public class BDecoder
|
||||
{
|
||||
// The InputStream to BDecode.
|
||||
private final InputStream in;
|
||||
|
||||
// The last indicator read.
|
||||
// Zero if unknown.
|
||||
// '0'..'9' indicates a byte[].
|
||||
// 'i' indicates an Number.
|
||||
// 'l' indicates a List.
|
||||
// 'd' indicates a Map.
|
||||
// 'e' indicates end of Number, List or Map (only used internally).
|
||||
// -1 indicates end of stream.
|
||||
// Call getNextIndicator to get the current value (will never return zero).
|
||||
private int indicator = 0;
|
||||
|
||||
// Used for ugly hack to get SHA hash over the metainfo info map
|
||||
private String special_map = "info";
|
||||
private boolean in_special_map = false;
|
||||
private final MessageDigest sha_digest;
|
||||
|
||||
// Ugly hack. Return the SHA has over bytes that make up the special map.
|
||||
public byte[] get_special_map_digest()
|
||||
{
|
||||
byte[] result = sha_digest.digest();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ugly hack. Name defaults to "info".
|
||||
public void set_special_map_name(String name)
|
||||
{
|
||||
special_map = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes a new BDecoder. Nothing is read from the given
|
||||
* <code>InputStream</code> yet.
|
||||
*/
|
||||
public BDecoder(InputStream in)
|
||||
{
|
||||
this.in = in;
|
||||
// XXX - Used for ugly hack.
|
||||
try
|
||||
{
|
||||
sha_digest = MessageDigest.getInstance("SHA");
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
throw new InternalError(nsa.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new BDecoder and immediatly decodes the first value it
|
||||
* sees.
|
||||
*
|
||||
* @return The first BEValue on the stream or null when the stream
|
||||
* has ended.
|
||||
*
|
||||
* @exception InvalidBEncoding when the stream doesn't start with a
|
||||
* bencoded value or the stream isn't a bencoded stream at all.
|
||||
* @exception IOException when somthing bad happens with the stream
|
||||
* to read from.
|
||||
*/
|
||||
public static BEValue bdecode(InputStream in) throws IOException
|
||||
{
|
||||
return new BDecoder(in).bdecode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns what the next bencoded object will be on the stream or -1
|
||||
* when the end of stream has been reached. Can return something
|
||||
* unexpected (not '0' .. '9', 'i', 'l' or 'd') when the stream
|
||||
* isn't bencoded.
|
||||
*
|
||||
* This might or might not read one extra byte from the stream.
|
||||
*/
|
||||
public int getNextIndicator() throws IOException
|
||||
{
|
||||
if (indicator == 0)
|
||||
{
|
||||
indicator = in.read();
|
||||
// XXX - Used for ugly hack
|
||||
if (in_special_map) sha_digest.update((byte)indicator);
|
||||
}
|
||||
return indicator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next indicator and returns either null when the stream
|
||||
* has ended or bdecodes the rest of the stream and returns the
|
||||
* appropriate BEValue encoded object.
|
||||
*/
|
||||
public BEValue bdecode() throws IOException
|
||||
{
|
||||
indicator = getNextIndicator();
|
||||
if (indicator == -1)
|
||||
return null;
|
||||
|
||||
if (indicator >= '0' && indicator <= '9')
|
||||
return bdecodeBytes();
|
||||
else if (indicator == 'i')
|
||||
return bdecodeNumber();
|
||||
else if (indicator == 'l')
|
||||
return bdecodeList();
|
||||
else if (indicator == 'd')
|
||||
return bdecodeMap();
|
||||
else
|
||||
throw new InvalidBEncodingException
|
||||
("Unknown indicator '" + indicator + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a byte array. If it is not a bencoded byte array it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeBytes() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
int num = c - '0';
|
||||
if (num < 0 || num > 9)
|
||||
throw new InvalidBEncodingException("Number expected, not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
c = read();
|
||||
int i = c - '0';
|
||||
while (i >= 0 && i <= 9)
|
||||
{
|
||||
// XXX - This can overflow!
|
||||
num = num*10 + i;
|
||||
c = read();
|
||||
i = c - '0';
|
||||
}
|
||||
|
||||
if (c != ':')
|
||||
throw new InvalidBEncodingException("Colon expected, not '"
|
||||
+ (char)c + "'");
|
||||
|
||||
return new BEValue(read(num));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a number. If it is not a number it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeNumber() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'i')
|
||||
throw new InvalidBEncodingException("Expected 'i', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
c = read();
|
||||
if (c == '0')
|
||||
{
|
||||
c = read();
|
||||
if (c == 'e')
|
||||
return new BEValue(BigInteger.ZERO);
|
||||
else
|
||||
throw new InvalidBEncodingException("'e' expected after zero,"
|
||||
+ " not '" + (char)c + "'");
|
||||
}
|
||||
|
||||
// XXX - We don't support more the 255 char big integers
|
||||
char[] chars = new char[256];
|
||||
int off = 0;
|
||||
|
||||
if (c == '-')
|
||||
{
|
||||
c = read();
|
||||
if (c == '0')
|
||||
throw new InvalidBEncodingException("Negative zero not allowed");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
}
|
||||
|
||||
if (c < '1' || c > '9')
|
||||
throw new InvalidBEncodingException("Invalid Integer start '"
|
||||
+ (char)c + "'");
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
|
||||
c = read();
|
||||
int i = c - '0';
|
||||
while(i >= 0 && i <= 9)
|
||||
{
|
||||
chars[off] = (char)c;
|
||||
off++;
|
||||
c = read();
|
||||
i = c - '0';
|
||||
}
|
||||
|
||||
if (c != 'e')
|
||||
throw new InvalidBEncodingException("Integer should end with 'e'");
|
||||
|
||||
String s = new String(chars, 0, off);
|
||||
return new BEValue(new BigInteger(s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a list. If it is not a list it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeList() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'l')
|
||||
throw new InvalidBEncodingException("Expected 'l', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
List result = new ArrayList();
|
||||
c = getNextIndicator();
|
||||
while (c != 'e')
|
||||
{
|
||||
result.add(bdecode());
|
||||
c = getNextIndicator();
|
||||
}
|
||||
indicator = 0;
|
||||
|
||||
return new BEValue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next bencoded value on the stream and makes sure it
|
||||
* is a map (dictonary). If it is not a map it will throw
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public BEValue bdecodeMap() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'd')
|
||||
throw new InvalidBEncodingException("Expected 'd', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
||||
Map result = new HashMap();
|
||||
c = getNextIndicator();
|
||||
while (c != 'e')
|
||||
{
|
||||
// Dictonary keys are always strings.
|
||||
String key = bdecode().getString();
|
||||
|
||||
// XXX ugly hack
|
||||
boolean special = special_map.equals(key);
|
||||
if (special)
|
||||
in_special_map = true;
|
||||
|
||||
BEValue value = bdecode();
|
||||
result.put(key, value);
|
||||
|
||||
// XXX ugly hack continued
|
||||
if (special)
|
||||
in_special_map = false;
|
||||
|
||||
c = getNextIndicator();
|
||||
}
|
||||
indicator = 0;
|
||||
|
||||
return new BEValue(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next byte read from the InputStream (as int).
|
||||
* Throws EOFException if InputStream.read() returned -1.
|
||||
*/
|
||||
private int read() throws IOException
|
||||
{
|
||||
int c = in.read();
|
||||
if (c == -1)
|
||||
throw new EOFException();
|
||||
if (in_special_map) sha_digest.update((byte)c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte[] containing length valid bytes starting at offset
|
||||
* zero. Throws EOFException if InputStream.read() returned -1
|
||||
* before all requested bytes could be read. Note that the byte[]
|
||||
* returned might be bigger then requested but will only contain
|
||||
* length valid bytes. The returned byte[] will be reused when this
|
||||
* method is called again.
|
||||
*/
|
||||
private byte[] read(int length) throws IOException
|
||||
{
|
||||
byte[] result = new byte[length];
|
||||
|
||||
int read = 0;
|
||||
while (read < length)
|
||||
{
|
||||
int i = in.read(result, read, length - read);
|
||||
if (i == -1)
|
||||
throw new EOFException();
|
||||
read += i;
|
||||
}
|
||||
|
||||
if (in_special_map) sha_digest.update(result, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
190
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
190
apps/i2psnark/java/src/org/klomp/snark/bencode/BEValue.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/* BEValue - Holds different types that a bencoded byte array can represent.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
* You need to call the correct get method to get the correct java
|
||||
* type object. If the BEValue wasn't actually of the requested type
|
||||
* you will get a InvalidBEncodingException.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class BEValue
|
||||
{
|
||||
// This is either a byte[], Number, List or Map.
|
||||
private final Object value;
|
||||
|
||||
public BEValue(byte[] value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(Number value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(List value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BEValue(Map value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a String. This operation only succeeds
|
||||
* when the BEValue is a byte[], otherwise it will throw a
|
||||
* InvalidBEncodingException. The byte[] will be interpreted as
|
||||
* UTF-8 encoded characters.
|
||||
*/
|
||||
public String getString() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return new String(getBytes(), "UTF-8");
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
throw new InternalError(uee.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a byte[]. This operation only succeeds
|
||||
* when the BEValue is actually a byte[], otherwise it will throw a
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public byte[] getBytes() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (byte[])value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a Number. This operation only succeeds
|
||||
* when the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException.
|
||||
*/
|
||||
public Number getNumber() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Number)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as int. This operation only succeeds when
|
||||
* the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException. The returned int is the result of
|
||||
* <code>Number.intValue()</code>.
|
||||
*/
|
||||
public int getInt() throws InvalidBEncodingException
|
||||
{
|
||||
return getNumber().intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as long. This operation only succeeds when
|
||||
* the BEValue is actually a Number, otherwise it will throw a
|
||||
* InvalidBEncodingException. The returned long is the result of
|
||||
* <code>Number.longValue()</code>.
|
||||
*/
|
||||
public long getLong() throws InvalidBEncodingException
|
||||
{
|
||||
return getNumber().longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a List of BEValues. This operation only
|
||||
* succeeds when the BEValue is actually a List, otherwise it will
|
||||
* throw a InvalidBEncodingException.
|
||||
*/
|
||||
public List getList() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (List)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this BEValue as a Map of BEValue keys and BEValue
|
||||
* values. This operation only succeeds when the BEValue is actually
|
||||
* a Map, otherwise it will throw a InvalidBEncodingException.
|
||||
*/
|
||||
public Map getMap() throws InvalidBEncodingException
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Map)value;
|
||||
}
|
||||
catch (ClassCastException cce)
|
||||
{
|
||||
throw new InvalidBEncodingException(cce.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
String valueString;
|
||||
if (value instanceof byte[])
|
||||
{
|
||||
byte[] bs = (byte[])value;
|
||||
// XXX - Stupid heuristic...
|
||||
if (bs.length <= 12)
|
||||
valueString = new String(bs);
|
||||
else
|
||||
valueString = "bytes:" + bs.length;
|
||||
}
|
||||
else
|
||||
valueString = value.toString();
|
||||
|
||||
return "BEValue[" + valueString + "]";
|
||||
}
|
||||
}
|
||||
191
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
191
apps/i2psnark/java/src/org/klomp/snark/bencode/BEncoder.java
Normal file
@@ -0,0 +1,191 @@
|
||||
/* BDecoder - Converts an InputStream to BEValues.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BEncoder
|
||||
{
|
||||
|
||||
public static byte[] bencode(Object o) throws IllegalArgumentException
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(o, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Object o, OutputStream out)
|
||||
throws IOException, IllegalArgumentException
|
||||
{
|
||||
if (o instanceof String)
|
||||
bencode((String)o, out);
|
||||
else if (o instanceof byte[])
|
||||
bencode((byte[])o, out);
|
||||
else if (o instanceof Number)
|
||||
bencode((Number)o, out);
|
||||
else if (o instanceof List)
|
||||
bencode((List)o, out);
|
||||
else if (o instanceof Map)
|
||||
bencode((Map)o, out);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot bencode: " + o.getClass());
|
||||
}
|
||||
|
||||
public static byte[] bencode(String s)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(s, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(String s, OutputStream out) throws IOException
|
||||
{
|
||||
byte[] bs = s.getBytes("UTF-8");
|
||||
bencode(bs, out);
|
||||
}
|
||||
|
||||
public static byte[] bencode(Number n)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(n, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Number n, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('i');
|
||||
String s = n.toString();
|
||||
out.write(s.getBytes("UTF-8"));
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
public static byte[] bencode(List l)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(l, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(List l, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('l');
|
||||
Iterator it = l.iterator();
|
||||
while (it.hasNext())
|
||||
bencode(it.next(), out);
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
public static byte[] bencode(byte[] bs)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(bs, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(byte[] bs, OutputStream out) throws IOException
|
||||
{
|
||||
String l = Integer.toString(bs.length);
|
||||
out.write(l.getBytes("UTF-8"));
|
||||
out.write(':');
|
||||
out.write(bs);
|
||||
}
|
||||
|
||||
public static byte[] bencode(Map m)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bencode(m, baos);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
throw new InternalError(ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void bencode(Map m, OutputStream out) throws IOException
|
||||
{
|
||||
out.write('d');
|
||||
|
||||
// Keys must be sorted. XXX - But is this the correct order?
|
||||
Set s = m.keySet();
|
||||
List l = new ArrayList(s);
|
||||
Collections.sort(l);
|
||||
|
||||
Iterator it = l.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
// Keys must be Strings.
|
||||
String key = (String)it.next();
|
||||
Object value = m.get(key);
|
||||
bencode(key, out);
|
||||
bencode(value, out);
|
||||
}
|
||||
|
||||
out.write('e');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/* InvalidBEncodingException - Thrown when a bencoded stream is corrupted.
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
|
||||
This file is part of Snark.
|
||||
|
||||
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, 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.
|
||||
*/
|
||||
|
||||
package org.klomp.snark.bencode;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Exception thrown when a bencoded stream is corrupted.
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class InvalidBEncodingException extends IOException
|
||||
{
|
||||
public InvalidBEncodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user