forked from I2P_Developers/i2p.i2p
Compare commits
261 Commits
i2p-1.5.0
...
i2p-jpacka
Author | SHA1 | Date | |
---|---|---|---|
fadf25dc8f | |||
3b9c26fe8a | |||
961936f8d5 | |||
7ea31835c2 | |||
bf1f2e4635 | |||
198008472a | |||
91e9d95df7 | |||
1b5feda517 | |||
29f74ba72a | |||
d6684403a2 | |||
388aa233e0 | |||
ea92b79340 | |||
3ba754d723 | |||
d651d25de6 | |||
00ea32938e | |||
37002822b3 | |||
50d56ccbe8 | |||
48cb6f79ef | |||
a325e63426 | |||
309e306337 | |||
2ba56a5e17 | |||
c949ad5205 | |||
5621b4bf97 | |||
dbfe8d24a8 | |||
548c0994a7 | |||
d0ca1d38ca | |||
96560e8590 | |||
19712cfd95 | |||
3dcc954341 | |||
568b5e303f | |||
73d90ed5c4 | |||
b2fe36b0d3 | |||
632a1578a2 | |||
e28f4be46b | |||
73e34b3941 | |||
b4e2366458 | |||
1389e89f6d | |||
c3abe7b3d4 | |||
042c1e88aa | |||
899ce0f959 | |||
5dd8139aad | |||
13ee324d36 | |||
afa7278080 | |||
b6be2d7e65 | |||
78ba3d1f68 | |||
d930f0a64a | |||
c1dc3c8275 | |||
8bf87da4b1 | |||
8812e822f9 | |||
f17cd24dc8 | |||
b9f53069bb | |||
21f5f7c148 | |||
3057103875 | |||
2752015b6e | |||
f2e0aacbf0 | |||
3e8f8a2bd3 | |||
77e30e246d | |||
5e7a00ede4 | |||
ba55ec09ed | |||
0b058c0ffd | |||
61422d9f7f | |||
b63a2e41be | |||
606961c788 | |||
9573c6ed0f | |||
70bb63e8ab | |||
b96255a65b | |||
e15dae5c5f | |||
695cf8796d | |||
175f043819 | |||
662ea995c1 | |||
b8670e1e5b | |||
7f4441078d | |||
150248d8d7 | |||
aaa1da4c66 | |||
8167f5184d | |||
034a5acd37 | |||
7249f21602 | |||
d1192f74f2 | |||
13f910be70 | |||
2d42541b79 | |||
131da9bdb9 | |||
bc97e955e2 | |||
faa1bf117a | |||
aa386f3bdc | |||
f1170b948f | |||
59ab40779c | |||
85b9862b64 | |||
132d76a06b | |||
c4b4b2d4b2 | |||
db6914f555 | |||
bef729463d | |||
d0e72aca66 | |||
46ee8be9c6 | |||
cc3d2cf67b | |||
57c997730f | |||
0826f431ef | |||
c63cb378e8 | |||
242dc92397 | |||
26f34d6985 | |||
e002d3f558 | |||
3d5dd639e3 | |||
2bfedfbc74 | |||
70131c6b25 | |||
e946040ddd | |||
bab37e57fe | |||
70e06de846 | |||
11f60a7192 | |||
6282c365bb | |||
621ea49621 | |||
e51738d180 | |||
811442f9cb | |||
464a39b939 | |||
1a05083ed0 | |||
937b6120ff | |||
2a451cdb97 | |||
ccba4a197d | |||
feaff690a3 | |||
098ef9a0ff | |||
f317d29dd5 | |||
f17b568f19 | |||
5029516087 | |||
69699638ae | |||
e6c76fa5ae | |||
b8435f5e9e | |||
5995b0b7a7 | |||
80237a57bd | |||
c4cfe420a6 | |||
14c5d54f0e | |||
b1a4a8517e | |||
22ff40bc84 | |||
b5d7dffb08 | |||
a59cad0066 | |||
cbb6a6db65 | |||
730b9790d9 | |||
ebf6ca5b34 | |||
0422134a86 | |||
1a77352fa7 | |||
cc971eb34f | |||
fa0e59435e | |||
962cc31f31 | |||
87362fd68b | |||
51f6bef5dc | |||
a1ea48e2b6 | |||
e9aa3a55cc | |||
d03c690724 | |||
2a900a8c5b | |||
de995761db | |||
cfbdf8385d | |||
83b959c4a1 | |||
e66ec208a8 | |||
cf22186182 | |||
890a8927a5 | |||
dd439bc9be | |||
537a8bf19b | |||
bd0c696b84 | |||
5c56884d7f | |||
b53707074f | |||
6cb8d2eeb7 | |||
3895cd1068 | |||
5b2fbc4ec4 | |||
87654e2f4f | |||
9c29f8c8ff | |||
619c36d18d | |||
cf10a2d5b6 | |||
56fdc244d4 | |||
adc69c0d9a | |||
dd9a5548a8 | |||
ab88f86954 | |||
9466b225b6 | |||
ef1e2b02de | |||
ee68aec647 | |||
3dfeb92b63 | |||
ee5288ebb1 | |||
488acdfd98 | |||
ee2e7ec30d | |||
40466bc602 | |||
d8d6954ef0 | |||
0aa4550bbe | |||
ad82946fd3 | |||
77b48a48ab | |||
d948fa8db3 | |||
31393c2bef | |||
e3fc34ef1f | |||
d7fdd6d9dc | |||
5a3a7b843a | |||
e06f8961b4 | |||
9d1aa5b762 | |||
2e71a0b36a | |||
b072f40ed1 | |||
35d2f118ce | |||
3f7f315951 | |||
6ef4c74d97 | |||
0e4d684e7d | |||
e3be6b50ce | |||
dad2bed334 | |||
bbe66f0e18 | |||
27bf65c1a4 | |||
9c7b415d62 | |||
78e4572a8c | |||
4507ecd5f2 | |||
721d39c01d | |||
427fc1c1ca | |||
33f1b3be87 | |||
7e1c8c7983 | |||
aa6b27d829 | |||
999e2615c3 | |||
807b7d672f | |||
685a2f1e39 | |||
4217a05ae9 | |||
1e70849bde | |||
1ab3e9b310 | |||
fd2cf972bf | |||
d9eed6446e | |||
6b823e6381 | |||
917b7e615e | |||
af97381461 | |||
4975bb1482 | |||
83e2246195 | |||
3632070e3f | |||
0cb30a085c | |||
a7a59a2b1b | |||
bf7155b935 | |||
62fb294f54 | |||
b7e710b28f | |||
4a8534e4e6 | |||
aa4e2f5c95 | |||
fe4fbce7bd | |||
33374eacaa | |||
cea76ed9d5 | |||
f41db2685e | |||
95bf068b0a | |||
e2caa246f2 | |||
bed013d858 | |||
282460cb3f | |||
f015d1f490 | |||
f0758ee36f | |||
c77e9537ae | |||
b7de63e922 | |||
13ade14289 | |||
2b43e4e4b5 | |||
571986a78b | |||
d7c89be9a2 | |||
d466fd6799 | |||
116ec88f56 | |||
346372e002 | |||
f14b7d53a3 | |||
3355daa334 | |||
67fea26638 | |||
b1c367777d | |||
3917dc6d2f | |||
2d239edf34 | |||
1fbe084b74 | |||
7a37f09334 | |||
1ae05103e4 | |||
a66422fa3c | |||
dabc29f8a5 | |||
132da4a35a | |||
ea1eac2343 | |||
569e035bfd | |||
8b1b5d4eb3 | |||
33f64f7913 |
34
.github/workflows/ant.yml
vendored
Normal file
34
.github/workflows/ant.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# Mostly copied from https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-ant
|
||||
# zlatinb
|
||||
|
||||
name: Java CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: GetText
|
||||
run: sudo apt install gettext
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 8
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'temurin'
|
||||
- name : Generate override.properties
|
||||
run: |
|
||||
rm -f override.properties
|
||||
echo "build.built-by=GitHub Actions" >> override.properties
|
||||
echo "noExe=true" >> override.properties
|
||||
- name: build with Ant
|
||||
run: ant distclean pkg
|
||||
- name: Upload installer.jar
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: I2P-install.jar-${{ github.sha }}
|
||||
path: install.jar
|
||||
|
||||
|
11
.tx/config
11
.tx/config
@ -96,6 +96,7 @@ trans.pt = core/locale/messages_pt.po
|
||||
trans.pt_BR = core/locale/messages_pt_BR.po
|
||||
trans.ro = core/locale/messages_ro.po
|
||||
trans.ru_RU = core/locale/messages_ru.po
|
||||
trans.sl = core/locale/messages_sl.po
|
||||
trans.sv_SE = core/locale/messages_sv.po
|
||||
trans.tk = core/locale/messages_tk.po
|
||||
trans.tr_TR = core/locale/messages_tr.po
|
||||
@ -133,6 +134,7 @@ trans.pt = router/locale/messages_pt.po
|
||||
trans.pt_BR = router/locale/messages_pt_BR.po
|
||||
trans.ro = router/locale/messages_ro.po
|
||||
trans.ru_RU = router/locale/messages_ru.po
|
||||
trans.sl = router/locale/messages_sl.po
|
||||
trans.sv_SE = router/locale/messages_sv.po
|
||||
trans.tk = router/locale/messages_tk.po
|
||||
trans.tr_TR = router/locale/messages_tr.po
|
||||
@ -206,6 +208,7 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-news/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-news/messages_sk.po
|
||||
trans.sl = apps/routerconsole/locale-news/messages_sl.po
|
||||
trans.sq = apps/routerconsole/locale-news/messages_sq.po
|
||||
trans.sr = apps/routerconsole/locale-news/messages_sr.po
|
||||
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
|
||||
@ -253,6 +256,7 @@ trans.pt_BR = apps/routerconsole/locale-countries/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-countries/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-countries/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-countries/messages_sk.po
|
||||
trans.sl = apps/routerconsole/locale-countries/messages_sl.po
|
||||
trans.sq = apps/routerconsole/locale-countries/messages_sq.po
|
||||
trans.sr = apps/routerconsole/locale-countries/messages_sr.po
|
||||
trans.sv_SE = apps/routerconsole/locale-countries/messages_sv.po
|
||||
@ -361,6 +365,7 @@ trans.pt_BR = apps/desktopgui/locale/messages_pt_BR.po
|
||||
trans.ro = apps/desktopgui/locale/messages_ro.po
|
||||
trans.ru_RU = apps/desktopgui/locale/messages_ru.po
|
||||
trans.sk = apps/desktopgui/locale/messages_sk.po
|
||||
trans.sl = apps/desktopgui/locale/messages_sl.po
|
||||
trans.sr = apps/desktopgui/locale/messages_sr.po
|
||||
trans.sv_SE = apps/desktopgui/locale/messages_sv.po
|
||||
trans.sq = apps/desktopgui/locale/messages_sq.po
|
||||
@ -439,6 +444,7 @@ trans.pt_BR = debian/po/pt_BR.po
|
||||
trans.ro = debian/po/ro.po
|
||||
trans.ru_RU = debian/po/ru.po
|
||||
trans.sk = debian/po/sk.po
|
||||
trans.sl = debian/po/sl.po
|
||||
trans.sq = debian/po/sq.po
|
||||
trans.sv_SE = debian/po/sv.po
|
||||
trans.tk = debian/po/tk.po
|
||||
@ -517,6 +523,7 @@ trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
|
||||
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
|
||||
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
|
||||
trans.sl = core/java/src/gnu/getopt/MessagesBundle_sl.properties
|
||||
trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
|
||||
trans.sr = core/java/src/gnu/getopt/MessagesBundle_sr.properties
|
||||
trans.sv_SE = core/java/src/gnu/getopt/MessagesBundle_sv.properties
|
||||
@ -550,6 +557,7 @@ trans.pt = apps/ministreaming/locale/messages_pt.po
|
||||
trans.pt_BR = apps/ministreaming/locale/messages_pt_BR.po
|
||||
trans.ro = apps/ministreaming/locale/messages_ro.po
|
||||
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
|
||||
trans.sl = apps/ministreaming/locale/messages_sl.po
|
||||
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
|
||||
trans.tk = apps/ministreaming/locale/messages_tk.po
|
||||
trans.tr_TR = apps/ministreaming/locale/messages_tr.po
|
||||
@ -570,8 +578,10 @@ trans.de = installer/resources/locale-man/man_de.po
|
||||
trans.es = installer/resources/locale-man/man_es.po
|
||||
trans.fi = installer/resources/locale-man/man_fi.po
|
||||
trans.fr = installer/resources/locale-man/man_fr.po
|
||||
trans.hu = installer/resources/locale-man/man_hu.po
|
||||
trans.id = installer/resources/locale-man/man_id.po
|
||||
trans.it = installer/resources/locale-man/man_it.po
|
||||
trans.ja = installer/resources/locale-man/man_ja.po
|
||||
trans.ko = installer/resources/locale-man/man_ko.po
|
||||
trans.nl = installer/resources/locale-man/man_nl.po
|
||||
trans.pl = installer/resources/locale-man/man_pl.po
|
||||
@ -636,6 +646,7 @@ trans.pl = apps/routerconsole/resources/docs/readme_pl.html
|
||||
trans.pt = apps/routerconsole/resources/docs/readme_pt.html
|
||||
trans.ro = apps/routerconsole/resources/docs/readme_ro.html
|
||||
trans.ru_RU = apps/routerconsole/resources/docs/readme_ru.html
|
||||
trans.sl = apps/routerconsole/resources/docs/readme_sl.html
|
||||
trans.tr_TR = apps/routerconsole/resources/docs/readme_tr.html
|
||||
trans.uk_UA = apps/routerconsole/resources/docs/readme_uk.html
|
||||
trans.zh_CN = apps/routerconsole/resources/docs/readme_zh.html
|
||||
|
49
Docker.md
49
Docker.md
@ -1,5 +1,25 @@
|
||||
# I2P in Docker
|
||||
|
||||
### Very quick start
|
||||
If you just want to give I2P a quick try or are using it on a home network, follow these steps:
|
||||
|
||||
1. Create two directories `i2pconfig` and `i2ptorrents`
|
||||
2. Copy the following text and save it in a file `docker-compose.yml`
|
||||
```
|
||||
version: "3.5"
|
||||
services:
|
||||
i2p:
|
||||
image: geti2p/i2p
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./i2pconfig:/i2p/.i2p
|
||||
- ./i2ptorrents:/i2psnark
|
||||
```
|
||||
3. Execute `docker-compose up`
|
||||
4. Start a browser and go to `http://127.0.0.1:7657` to complete the setup wizard.
|
||||
|
||||
Note that this quick-start approach is not recommended for production deployments on remote servers. Please read the rest of this document for more information.
|
||||
|
||||
### Building an image
|
||||
There is an i2P image available over at [DockerHub](https://hub.docker.com). If you do not want to use that one, you can build one yourself:
|
||||
```
|
||||
@ -17,21 +37,26 @@ By the default the image limits the memory available to the Java heap to 512MB.
|
||||
#### Ports
|
||||
There are several ports which are exposed by the image. You can choose which ones to publish depending on your specific needs.
|
||||
|
||||
|Port|Description|TCP/UDP|
|
||||
|---|---|---|
|
||||
|4444|HTTP Proxy|TCP|
|
||||
|4445|HTTPS Proxy|TCP|
|
||||
|6668|IRC Proxy|TCP|
|
||||
|7654|I2CP Protocol|TCP|
|
||||
|7656|SAM Bridge TCP|TCP|
|
||||
|7657|Router console|TCP|
|
||||
|7658|I2P Site|TCP|
|
||||
|7659|SMTP Proxy|TCP|
|
||||
|7660|POP Proxy|TCP|
|
||||
|12345|I2NP Protocol|TCP and UDP|
|
||||
|Port|Interface|Description|TCP/UDP|
|
||||
|---|---|---|---|
|
||||
|4444|127.0.0.1|HTTP Proxy|TCP|
|
||||
|4445|127.0.0.1|HTTPS Proxy|TCP|
|
||||
|6668|127.0.0.1|IRC Proxy|TCP|
|
||||
|7654|127.0.0.1|I2CP Protocol|TCP|
|
||||
|7656|127.0.0.1|SAM Bridge TCP|TCP|
|
||||
|7657|127.0.0.1|Router console|TCP|
|
||||
|7658|127.0.0.1|I2P Site|TCP|
|
||||
|7659|127.0.0.1|SMTP Proxy|TCP|
|
||||
|7660|127.0.0.1|POP Proxy|TCP|
|
||||
|7652|LAN interface|UPnP|TCP|
|
||||
|7653|LAN interface|UPnP|UDP|
|
||||
|12345|0.0.0.0|I2NP Protocol|TCP and UDP|
|
||||
|
||||
You probably want at least the Router Console (7657) and the HTTP Proxy (4444). If you want I2P to be able to receive incoming connections from the internet, and hence not think it's firewalled, publish the I2NP Protocol port (12345) - but make sure you publish to a different random port, otherwise others may be able to guess you're running I2P in a Docker image.
|
||||
|
||||
#### Networking
|
||||
A best-practices guide for cloud deployments is beyond the scope of this document, but in general you should try to minimize the number of published ports, while exposing only the `I2NP` ports to the internet. That means that the services in the list above which are bound to `127.0.0.1` (which include the router console) will need to be accessed via other methods like ssh tunneling or be manually configured to bind to a different interface.
|
||||
|
||||
#### Example
|
||||
Here is an example container that mounts `i2phome` as home directory, `i2ptorrents` for torrents, and opens HTTP Proxy, IRC, Router Console and I2NP Protocols. It also limits the memory available to the JVM to 256MB.
|
||||
```
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM jlesage/baseimage:alpine-3.10-glibc as builder
|
||||
FROM jlesage/baseimage:alpine-3.15-glibc as builder
|
||||
|
||||
ENV APP_HOME="/i2p"
|
||||
|
||||
@ -10,7 +10,7 @@ RUN add-pkg --virtual build-base gettext tar bzip2 apache-ant openjdk8 \
|
||||
&& rm -rf pkg-temp/osid pkg-temp/lib/wrapper pkg-temp/lib/wrapper.* \
|
||||
&& del-pkg build-base gettext tar bzip2 apache-ant openjdk8
|
||||
|
||||
FROM jlesage/baseimage:alpine-3.10-glibc
|
||||
FROM jlesage/baseimage:alpine-3.15-glibc
|
||||
ENV APP_HOME="/i2p"
|
||||
|
||||
RUN add-pkg openjdk8-jre
|
||||
|
@ -264,7 +264,7 @@ Applications:
|
||||
Zxing 3.4.1:
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
Jetty 9.3.29.v20201019 (jetty-*.jar, org.mortbay.*.jar):
|
||||
Jetty 9.3.30.v20211001 (jetty-*.jar, org.mortbay.*.jar):
|
||||
(not included in most distribution packages, except for jetty-i2p.jar)
|
||||
See licenses/ABOUT-Jetty.html
|
||||
See licenses/NOTICE-Jetty.html
|
||||
@ -339,9 +339,9 @@ Applications:
|
||||
Systray (systray.jar):
|
||||
Public domain.
|
||||
|
||||
Tomcat 9.0.45 (jasper-runtime.jar):
|
||||
Tomcat 9.0.54 (jasper-runtime.jar):
|
||||
(not included in most distribution packages)
|
||||
Copyright 1999-2020 The Apache Software Foundation
|
||||
Copyright 1999-2021 The Apache Software Foundation
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Tomcat.txt
|
||||
|
||||
|
@ -64,13 +64,16 @@ your `~/.gradle/gradle.properties`:
|
||||
systemProp.socksProxyHost=localhost
|
||||
systemProp.socksProxyPort=9150
|
||||
|
||||
### Development builds
|
||||
Automatic CI builds are available at the [continuous integration](https://github.com/i2p/i2p.i2p/actions/workflows/ant.yml) page.
|
||||
|
||||
### Docker
|
||||
For more information how to run I2P in Docker, see [Docker.md](Docker.md)
|
||||
## Contact info
|
||||
|
||||
Need help? See the IRC channel #i2p on irc.freenode.net
|
||||
|
||||
Bug reports: [https://trac.i2p2.de/report/1](https://trac.i2p2.de/report/1)
|
||||
Bug reports: [https://i2pgit.org/i2p-hackers/i2p.i2p/-/issues](https://i2pgit.org/i2p-hackers/i2p.i2p/-/issues) [http://git.idk.i2p/i2p-hackers/i2p.i2p/-/issues](http://git.idk.i2p/i2p-hackers/i2p.i2p/-/issues)
|
||||
|
||||
Contact information, security issues, press inquiries: [https://geti2p.net/en/contact](https://geti2p.net/en/contact)
|
||||
|
||||
|
@ -47,7 +47,8 @@ Need help?
|
||||
IRC irc.freenode.net #i2p
|
||||
|
||||
Bug reports:
|
||||
https://trac.i2p2.de/report/1
|
||||
https://i2pgit.org/i2p-hackers/i2p.i2p/-/issues
|
||||
http://git.idk.i2p/i2p-hackers/i2p.i2p/-/issues
|
||||
|
||||
Contact information, security issues, press inquiries:
|
||||
https://geti2p.net/en/contact
|
||||
|
@ -10,6 +10,7 @@
|
||||
<property name="javac.version" value="1.8" />
|
||||
<property name="javac.release" value="8" />
|
||||
<property name="require.gettext" value="true" />
|
||||
<property name="manifest.classpath.name" value="Class-Path" />
|
||||
|
||||
<condition property="no.bundle">
|
||||
<isfalse value="${require.gettext}" />
|
||||
@ -86,6 +87,7 @@
|
||||
<jar basedir="${build}" excludes="messages-src/**" destfile="${dist}/${jar}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.desktopgui.Main"/>
|
||||
<attribute name="${manifest.classpath.name}" value="i2p.jar router.jar" />
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
<attribute name="Build-Date" value="${build.timestamp}" />
|
||||
|
@ -10,9 +10,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2017-09-20 20:31+0000\n"
|
||||
"Last-Translator: Vitaly Zdorovenko <stenliterziev@gmail.com>\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-09 19:23+0000\n"
|
||||
"Last-Translator: zzzi2p\n"
|
||||
"Language-Team: Bulgarian (http://www.transifex.com/otf/I2P/language/bg/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -27,69 +27,81 @@ msgstr "Стартиране на I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P е стартиран!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "Стартиране"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:207
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Стартиране на I2P Браузер"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "Деактивиране"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "Рестартиране на I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:110
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:262
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "Спиране на I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:126
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:278
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "Рестартирайте Незабавно"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:143
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:295
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "Изключване в {0}"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr ""
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:370
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr ""
|
||||
msgstr "Мрежа"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr ""
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P desktopgui\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-08-11 15:33+0000\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2010-06-15 14:09+0100\n"
|
||||
"Last-Translator: duck <duck@mail.i2p>\n"
|
||||
"Language-Team: duck <duck@mail.i2p>\n"
|
||||
@ -25,69 +25,81 @@ msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:54
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:206
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:75
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:227
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
msgid "Disable"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:92
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:244
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:109
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:261
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:125
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:277
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:142
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:294
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:156
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:308
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:362
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:364
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr ""
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:369
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr ""
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr ""
|
||||
|
@ -4,15 +4,16 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# タカハシ <indexial@outlook.jp>, 2013
|
||||
# daingewuvzeevisiddfddd, 2022
|
||||
# タカハシ, 2013
|
||||
# Masayuki Hatta <mhatta@mhatta.org>, 2018
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2018-08-17 22:08+0000\n"
|
||||
"Last-Translator: Masayuki Hatta <mhatta@mhatta.org>\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-13 03:11+0000\n"
|
||||
"Last-Translator: daingewuvzeevisiddfddd\n"
|
||||
"Language-Team: Japanese (http://www.transifex.com/otf/I2P/language/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -27,69 +28,81 @@ msgstr "I2P を開始"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P を起動中!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "起動中"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:207
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "I2P ブラウザを起動"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr "I2P システムトレイを設定"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "無効"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr "通知を有効化"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr "通知を無効化"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr "システムトレイを無効化"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "I2P を再起動"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:110
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:262
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "I2P を停止"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:126
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:278
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "すぐに I2P を再起動"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:143
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:295
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr "すぐに I2P を停止"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "I2P のシャットダウンを中止"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "{0} でシャットダウン"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr "即時シャットダウン"
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:370
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr "ネットワーク"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr "I2P: 右クリックでメニュー"
|
||||
|
@ -4,14 +4,15 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Zagros <zagros21@cmail.nu>, 2020
|
||||
# Zagros, 2021
|
||||
# Zagros, 2020
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2020-09-23 21:02+0000\n"
|
||||
"Last-Translator: Zagros <zagros21@cmail.nu>\n"
|
||||
"PO-Revision-Date: 2021-08-30 21:05+0000\n"
|
||||
"Last-Translator: Zagros\n"
|
||||
"Language-Team: Kurdish (http://www.transifex.com/otf/I2P/language/ku/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -42,7 +43,7 @@ msgstr "وێبگەڕی I2P بکەوە"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr ""
|
||||
msgstr "دەستکاری سیستەمی ئاگەدارکردنەوەی I2P بکە"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
@ -72,7 +73,7 @@ msgstr "دەستبەجێ I2P بوەستێنە"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "کووژاندنەوە I2P هەڵبوەشێنەوە"
|
||||
msgstr "دەستهەڵگرتن لە کووژاندنەوەی I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#, java-format
|
||||
|
106
apps/desktopgui/locale/messages_sl.po
Normal file
106
apps/desktopgui/locale/messages_sl.po
Normal file
@ -0,0 +1,106 @@
|
||||
# I2P
|
||||
# Copyright (C) 2009 The I2P Project
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Žan Šadl-Ferš, 2021
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-09 19:23+0000\n"
|
||||
"Last-Translator: zzzi2p\n"
|
||||
"Language-Team: Slovenian (http://www.transifex.com/otf/I2P/language/sl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: sl\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:31
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:59
|
||||
msgid "Start I2P"
|
||||
msgstr "Zaženi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P se zaganja!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "Zaganja"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Zaženi I2P brskalnik"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr "Konfiguriraj I2P opravilno vrstico"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr ""
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "Ponovno zaženi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "Ustavi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "Nemudoma ponovno zaženi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr "Nemudoma ustavi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "Prekliči zaustavitev od I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "Zaustavi v {0}"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr "Zaustavitev je neizbežna"
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr "Omrežje"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr "I2P: Pritisnite na desno tipko miške za meni"
|
@ -4,15 +4,16 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Besnik <besnik@programeshqip.org>, 2016,2019
|
||||
# Besnik Bleta <besnik@programeshqip.org>, 2022
|
||||
# Besnik Bleta <besnik@programeshqip.org>, 2016,2019
|
||||
# Shpetim <shpetim@privacysolutions.no>, 2014
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2019-01-10 14:28+0000\n"
|
||||
"Last-Translator: Besnik <besnik@programeshqip.org>\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-10 10:34+0000\n"
|
||||
"Last-Translator: Besnik Bleta <besnik@programeshqip.org>\n"
|
||||
"Language-Team: Albanian (http://www.transifex.com/otf/I2P/language/sq/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -27,69 +28,81 @@ msgstr "Nise I2P-në"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P po niset!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "Po niset"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:207
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Nis Shfletuesin I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr "Formësoni Panel Sistemi I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "Çaktivizoje"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr "Aktivizoni njoftimet"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr "Çaktivizoni njoftimet"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr "Çaktivizo panel sistemi"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "Rinise I2P-në"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:110
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:262
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "Ndale I2P-në"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:126
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:278
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "Rinise I2P-në Menjëherë"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:143
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:295
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr "Ndale I2P-në Menjëherë"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "Anuloje Mbylljen e I2P-së"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "Mbylle për {0}"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr "Mbyllje shumë shpejt"
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:370
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr "Rrjet"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr "I2P: Djathtasklikoni për menu"
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2021-05-28 01:02+0000\n"
|
||||
"PO-Revision-Date: 2021-10-02 16:29+0000\n"
|
||||
"Last-Translator: Jonatan Nyberg <jonatan@autistici.org>\n"
|
||||
"Language-Team: Swedish (Sweden) (http://www.transifex.com/otf/I2P/language/sv_SE/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -51,7 +51,7 @@ msgstr "Konfigurera I2P-meddelandefältet"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "Avaktivera"
|
||||
msgstr "Inaktivera"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
|
@ -4,20 +4,20 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Kaya Zeren <kayazeren@gmail.com>, 2013,2016
|
||||
# Kaya Zeren <kayazeren@gmail.com>, 2013,2016,2022
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2017-06-30 21:32+0000\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-10 04:40+0000\n"
|
||||
"Last-Translator: Kaya Zeren <kayazeren@gmail.com>\n"
|
||||
"Language-Team: Turkish (Turkey) (http://www.transifex.com/otf/I2P/language/tr_TR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: tr_TR\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:31
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:59
|
||||
@ -26,69 +26,81 @@ msgstr "I2P başlasın"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P başlatılıyor!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "Başlatılıyor"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:207
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "I2P Tarayıcısını Açın"
|
||||
msgstr "I2P tarayıcısını aç"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr "I2P Sistem Tepsisi Ayarları"
|
||||
msgstr "I2P sistem tepsisi ayarları"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "Devre Dışı"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr "Bildirimleri etkinleştir"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr "Bildirimleri devre dışı bırak"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr "Sistem tepsisini devre dışı bırak"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "I2P Yeniden Başlasın"
|
||||
msgstr "I2P yeniden başlasın"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:110
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:262
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "I2P Durdurulsun"
|
||||
msgstr "I2P durdurulsun"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:126
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:278
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "I2P Hemen Yeniden Başlatılsın"
|
||||
msgstr "I2P hemen yeniden başlatılsın"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:143
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:295
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr "I2P Hemen Durdurulsun"
|
||||
msgstr "I2P hemen durdurulsun"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "I2P Kapatmayı İptal Et"
|
||||
msgstr "I2P kapatmayı iptal et"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "{0} içinde kapat"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr "Kapatılmak üzere"
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:370
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr "Ağ"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr "I2P: Menüde sağ tık"
|
||||
|
@ -5,15 +5,15 @@
|
||||
#
|
||||
# Translators:
|
||||
# ducki2p <ducki2p@gmail.com>, 2011
|
||||
# Scott Rhodes <starring169@gmail.com>, 2021
|
||||
# Scott Rhodes <starring169@gmail.com>, 2021-2022
|
||||
# walking <walking@i2pmail.org>, 2011
|
||||
# YFdyh000 <yfdyh000@gmail.com>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-25 12:29+0000\n"
|
||||
"PO-Revision-Date: 2021-03-07 07:58+0000\n"
|
||||
"POT-Creation-Date: 2022-02-09 19:13+0000\n"
|
||||
"PO-Revision-Date: 2022-02-16 16:05+0000\n"
|
||||
"Last-Translator: Scott Rhodes <starring169@gmail.com>\n"
|
||||
"Language-Team: Chinese (China) (http://www.transifex.com/otf/I2P/language/zh_CN/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -29,69 +29,81 @@ msgstr "启动 I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "I2P is starting!"
|
||||
msgstr " I2P 正在启动!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:44
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:72
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
msgid "Starting"
|
||||
msgstr "正在启动"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:55
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:207
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:65
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:249
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "启动 I2P 浏览器"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:76
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:228
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:86
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:270
|
||||
msgid "Configure I2P System Tray"
|
||||
msgstr "配置 I2P 系统托盘"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:77
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:229
|
||||
msgid "Disable"
|
||||
msgstr "禁用"
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:87
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:271
|
||||
msgid "Enable notifications"
|
||||
msgstr "启用通知"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:93
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:245
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:101
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:285
|
||||
msgid "Disable notifications"
|
||||
msgstr "禁用通知"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:115
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:299
|
||||
msgid "Disable system tray"
|
||||
msgstr "禁用系统托盘"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:131
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:315
|
||||
msgid "Restart I2P"
|
||||
msgstr "重启 I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:110
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:262
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:148
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:332
|
||||
msgid "Stop I2P"
|
||||
msgstr "停止 I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:126
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:278
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:164
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:348
|
||||
msgid "Restart I2P Immediately"
|
||||
msgstr "立即重启 I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:143
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:295
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:181
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
msgid "Stop I2P Immediately"
|
||||
msgstr "立即停止 I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:157
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:309
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:195
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:379
|
||||
msgid "Cancel I2P Shutdown"
|
||||
msgstr "取消 I2P 关闭"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:363
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:437
|
||||
#, java-format
|
||||
msgid "Shutdown in {0}"
|
||||
msgstr "{0} 后关闭"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:365
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:439
|
||||
msgid "Shutdown imminent"
|
||||
msgstr "立即关闭"
|
||||
|
||||
#. status translations are in the console bundle
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:370
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:444
|
||||
msgid "Network"
|
||||
msgstr "网络"
|
||||
|
||||
#. Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:63
|
||||
#: src/net/i2p/desktopgui/TrayManager.java:73
|
||||
msgid "I2P: Right-click for menu"
|
||||
msgstr "I2P:右击获得菜单"
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.desktopgui;
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Desktop.Action;
|
||||
import java.awt.MenuItem;
|
||||
@ -30,9 +31,11 @@ class InternalTrayManager extends TrayManager {
|
||||
private final RouterContext _context;
|
||||
private final Log log;
|
||||
private MenuItem _statusItem, _browserItem, _configItem, _restartItem, _stopItem,
|
||||
_restartHardItem, _stopHardItem, _cancelItem;
|
||||
_restartHardItem, _stopHardItem, _cancelItem,
|
||||
_notificationItem1, _notificationItem2;
|
||||
private JMenuItem _jstatusItem, _jbrowserItem, _jconfigItem, _jrestartItem, _jstopItem,
|
||||
_jrestartHardItem, _jstopHardItem, _jcancelItem;
|
||||
_jrestartHardItem, _jstopHardItem, _jcancelItem,
|
||||
_jnotificationItem1, _jnotificationItem2;
|
||||
|
||||
private static final boolean CONSOLE_ENABLED = Desktop.isDesktopSupported() &&
|
||||
Desktop.getDesktop().isSupported(Action.BROWSE);
|
||||
@ -44,6 +47,14 @@ class InternalTrayManager extends TrayManager {
|
||||
log = ctx.logManager().getLog(InternalTrayManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void startManager() throws AWTException {
|
||||
super.startManager();
|
||||
displayMessage(Log.INFO, _t("Starting"), _t("I2P is starting!"), null);
|
||||
}
|
||||
|
||||
public synchronized PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
|
||||
@ -73,7 +84,35 @@ class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
PopupMenu desktopguiConfigurationLauncher = new PopupMenu(_t("Configure I2P System Tray"));
|
||||
MenuItem configSubmenu = new MenuItem(_t("Disable"));
|
||||
final MenuItem notificationItem2 = new MenuItem(_t("Enable notifications"));
|
||||
notificationItem2.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
configureNotifications(true);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
final MenuItem notificationItem1 = new MenuItem(_t("Disable notifications"));
|
||||
notificationItem1.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
configureNotifications(false);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
MenuItem configSubmenu = new MenuItem(_t("Disable system tray"));
|
||||
configSubmenu.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
@ -173,6 +212,8 @@ class InternalTrayManager extends TrayManager {
|
||||
popup.add(browserLauncher);
|
||||
popup.addSeparator();
|
||||
}
|
||||
desktopguiConfigurationLauncher.add(notificationItem2);
|
||||
desktopguiConfigurationLauncher.add(notificationItem1);
|
||||
desktopguiConfigurationLauncher.add(configSubmenu);
|
||||
popup.add(desktopguiConfigurationLauncher);
|
||||
popup.addSeparator();
|
||||
@ -187,6 +228,8 @@ class InternalTrayManager extends TrayManager {
|
||||
_statusItem = statusItem;
|
||||
_browserItem = browserLauncher;
|
||||
_configItem = desktopguiConfigurationLauncher;
|
||||
_notificationItem1 = notificationItem1;
|
||||
_notificationItem2 = notificationItem2;
|
||||
_restartItem = restartItem;
|
||||
_stopItem = stopItem;
|
||||
_restartHardItem = restartItem2;
|
||||
@ -225,7 +268,35 @@ class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
JMenu desktopguiConfigurationLauncher = new JMenu(_t("Configure I2P System Tray"));
|
||||
JMenuItem configSubmenu = new JMenuItem(_t("Disable"));
|
||||
final JMenuItem notificationItem2 = new JMenuItem(_t("Enable notifications"));
|
||||
notificationItem2.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
configureNotifications(true);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
final JMenuItem notificationItem1 = new JMenuItem(_t("Disable notifications"));
|
||||
notificationItem1.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
configureNotifications(false);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem configSubmenu = new JMenuItem(_t("Disable system tray"));
|
||||
configSubmenu.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
@ -325,6 +396,8 @@ class InternalTrayManager extends TrayManager {
|
||||
popup.add(browserLauncher);
|
||||
popup.addSeparator();
|
||||
}
|
||||
desktopguiConfigurationLauncher.add(notificationItem2);
|
||||
desktopguiConfigurationLauncher.add(notificationItem1);
|
||||
desktopguiConfigurationLauncher.add(configSubmenu);
|
||||
popup.add(desktopguiConfigurationLauncher);
|
||||
popup.addSeparator();
|
||||
@ -339,6 +412,8 @@ class InternalTrayManager extends TrayManager {
|
||||
_jstatusItem = statusItem;
|
||||
_jbrowserItem = browserLauncher;
|
||||
_jconfigItem = desktopguiConfigurationLauncher;
|
||||
_jnotificationItem1 = notificationItem1;
|
||||
_jnotificationItem2 = notificationItem2;
|
||||
_jrestartItem = restartItem;
|
||||
_jstopItem = stopItem;
|
||||
_jrestartHardItem = restartItem2;
|
||||
@ -390,6 +465,10 @@ class InternalTrayManager extends TrayManager {
|
||||
_stopHardItem.setEnabled(!imminent);
|
||||
if (_cancelItem != null)
|
||||
_cancelItem.setEnabled(x && !imminent);
|
||||
if (_notificationItem1 != null)
|
||||
_notificationItem1.setEnabled(_showNotifications);
|
||||
if (_notificationItem2 != null)
|
||||
_notificationItem2.setEnabled(!_showNotifications);
|
||||
|
||||
if (_jstatusItem != null)
|
||||
_jstatusItem.setText(status);
|
||||
@ -407,6 +486,10 @@ class InternalTrayManager extends TrayManager {
|
||||
_jstopHardItem.setVisible(!imminent);
|
||||
if (_jcancelItem != null)
|
||||
_jcancelItem.setVisible(x && !imminent);
|
||||
if (_jnotificationItem1 != null)
|
||||
_jnotificationItem1.setVisible(_showNotifications);
|
||||
if (_jnotificationItem2 != null)
|
||||
_jnotificationItem2.setVisible(!_showNotifications);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -415,18 +498,24 @@ class InternalTrayManager extends TrayManager {
|
||||
private void configureDesktopgui(boolean enable) {
|
||||
String property = Main.PROP_ENABLE;
|
||||
String value = Boolean.toString(enable);
|
||||
try {
|
||||
|
||||
_context.router().saveConfig(property, value);
|
||||
if (!enable) {
|
||||
// TODO popup that explains how to re-enable in console
|
||||
_main.shutdown(null);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Error saving config", ex);
|
||||
if (!_context.router().saveConfig(property, value))
|
||||
log.error("Error saving config");
|
||||
if (!enable) {
|
||||
// TODO popup that explains how to re-enable in console
|
||||
_main.shutdown(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private void configureNotifications(boolean enable) {
|
||||
_showNotifications = enable;
|
||||
String value = Boolean.toString(enable);
|
||||
if (!_context.router().saveConfig(PROP_NOTIFICATIONS, value))
|
||||
log.error("Error saving config");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the console URL with info from the port mapper,
|
||||
* and launch the browser at it.
|
||||
|
@ -5,6 +5,7 @@ package net.i2p.desktopgui;
|
||||
*/
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
@ -16,6 +17,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import static net.i2p.app.ClientAppState.*;
|
||||
import net.i2p.app.NotificationService;
|
||||
import net.i2p.desktopgui.router.RouterManager;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.app.RouterApp;
|
||||
@ -27,7 +29,7 @@ import net.i2p.util.I2PProperties.I2PPropertyCallback;
|
||||
/**
|
||||
* The main class of the application.
|
||||
*/
|
||||
public class Main implements RouterApp {
|
||||
public class Main implements RouterApp, NotificationService {
|
||||
|
||||
// non-null
|
||||
private final I2PAppContext _appContext;
|
||||
@ -95,6 +97,11 @@ public class Main implements RouterApp {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// early check so we can bail out when started via CLI
|
||||
if (!SystemTray.isSupported()) {
|
||||
System.err.println("SystemTray not supported");
|
||||
return;
|
||||
}
|
||||
Main main = new Main();
|
||||
main.beginStartup(args);
|
||||
}
|
||||
@ -198,6 +205,46 @@ public class Main implements RouterApp {
|
||||
t.start();
|
||||
}
|
||||
|
||||
/////// NotificationService methods
|
||||
|
||||
/**
|
||||
* Send a notification to the user.
|
||||
*
|
||||
* @param source unsupported
|
||||
* @param category unsupported
|
||||
* @param priority unsupported
|
||||
* @param title for the popup, translated
|
||||
* @param message translated
|
||||
* @param path unsupported
|
||||
* @return 0, or -1 on failure
|
||||
*/
|
||||
public int notify(String source, String category, int priority, String title, String message, String path) {
|
||||
TrayManager tm = _trayManager;
|
||||
if (tm == null)
|
||||
return -1;
|
||||
return tm.displayMessage(priority, title, message, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a notification if possible.
|
||||
* Unsupported.
|
||||
*
|
||||
* @return false always
|
||||
*/
|
||||
public boolean cancel(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the text of a notification if possible.
|
||||
* Unsupported.
|
||||
*
|
||||
* @return false always
|
||||
*/
|
||||
public boolean update(int id, String title, String message, String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/////// ClientApp methods
|
||||
|
||||
/** @since 0.9.26 */
|
||||
|
@ -8,21 +8,27 @@ import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.event.MenuKeyEvent;
|
||||
import javax.swing.event.MenuKeyListener;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.apps.systray.UrlLauncher;
|
||||
import net.i2p.desktopgui.i18n.DesktopguiTranslator;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
@ -37,11 +43,14 @@ abstract class TrayManager {
|
||||
protected SystemTray tray;
|
||||
///Our tray icon, or null if unsupported
|
||||
protected TrayIcon trayIcon;
|
||||
protected volatile boolean _showNotifications;
|
||||
|
||||
private static final String PNG_DIR = "/desktopgui/resources/images/";
|
||||
private static final String MAC_ICON = "itoopie_black_24.png";
|
||||
private static final String WIN_ICON = "itoopie_white_24.png";
|
||||
private static final String WIN_ICON_LIGHT = "itoopie_white_24.png";
|
||||
private static final String WIN_ICON_DARK = "itoopie_black_24.png";
|
||||
private static final String LIN_ICON = "logo.png";
|
||||
protected static final String PROP_NOTIFICATIONS = "desktopgui.showNotifications";
|
||||
|
||||
/**
|
||||
* Instantiate tray manager.
|
||||
@ -58,6 +67,7 @@ abstract class TrayManager {
|
||||
public synchronized void startManager() throws AWTException {
|
||||
if (!SystemTray.isSupported())
|
||||
throw new AWTException("SystemTray not supported");
|
||||
_showNotifications = _appContext.getBooleanPropertyDefaultTrue(PROP_NOTIFICATIONS);
|
||||
tray = SystemTray.getSystemTray();
|
||||
// Windows typically has tooltips; Linux (at least Ubuntu) doesn't
|
||||
String tooltip = SystemVersion.isWindows() ? _t("I2P: Right-click for menu") : null;
|
||||
@ -191,12 +201,20 @@ abstract class TrayManager {
|
||||
*/
|
||||
private Image getTrayImage() throws AWTException {
|
||||
String img;
|
||||
if (SystemVersion.isWindows())
|
||||
img = WIN_ICON;
|
||||
else if (SystemVersion.isMac())
|
||||
if (SystemVersion.isWindows()) {
|
||||
// too hard to get the theme out of the registry,
|
||||
// use our console theme as a best guess
|
||||
// so we have a contrasting icon
|
||||
String theme = _appContext.getProperty("routerconsole.theme", "light");
|
||||
if (theme.equals("dark"))
|
||||
img = WIN_ICON_LIGHT;
|
||||
else
|
||||
img = WIN_ICON_DARK;
|
||||
} else if (SystemVersion.isMac()) {
|
||||
img = MAC_ICON;
|
||||
else
|
||||
} else {
|
||||
img = LIN_ICON;
|
||||
}
|
||||
URL url = getClass().getResource(PNG_DIR + img);
|
||||
if (url == null)
|
||||
throw new AWTException("cannot load tray image " + img);
|
||||
@ -204,6 +222,74 @@ abstract class TrayManager {
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a notification to the user.
|
||||
*
|
||||
* @param title for the popup, translated
|
||||
* @param message translated
|
||||
* @param path unsupported
|
||||
* @return 0, or -1 on failure
|
||||
*/
|
||||
public int displayMessage(int priority, String title, String message, String path) {
|
||||
if (!_showNotifications)
|
||||
return -1;
|
||||
final TrayIcon ti = trayIcon;
|
||||
if (ti == null)
|
||||
return -1;
|
||||
TrayIcon.MessageType type;
|
||||
if (priority <= Log.DEBUG)
|
||||
type = TrayIcon.MessageType.NONE;
|
||||
else if (priority <= Log.INFO)
|
||||
type = TrayIcon.MessageType.INFO;
|
||||
else if (priority <= Log.WARN)
|
||||
type = TrayIcon.MessageType.WARNING;
|
||||
else
|
||||
type = TrayIcon.MessageType.ERROR;
|
||||
ti.displayMessage(title, message, type);
|
||||
/*
|
||||
* There's apparently no way to bind a particular message to an action
|
||||
that comes back. We can't keep a queue because we don't get
|
||||
an action back when the message is removed via timeout or user x-out.
|
||||
On OSX, new messages dismiss previous ones.
|
||||
On LXDE (and Gnome?), new messages go under previous ones. Timeout is only 10 seconds.
|
||||
Message timeout is platform-dependent.
|
||||
So the order of events is unknowable.
|
||||
This only works if there is only one message ever.
|
||||
|
||||
if (path != null && path.length() > 0) {
|
||||
if (path.charAt(0) == '/');
|
||||
path = path.substring(1);
|
||||
final String url = _appContext.portMapper().getConsoleURL() + path;
|
||||
ti.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent arg0) {
|
||||
ti.removeActionListener(this);
|
||||
new SwingWorker<Object, Object>() {
|
||||
@Override
|
||||
protected Object doInBackground() throws Exception {
|
||||
System.out.println("DIB " + arg0);
|
||||
UrlLauncher launcher = new UrlLauncher(_appContext, null, null);
|
||||
try {
|
||||
launcher.openUrl(url);
|
||||
System.out.println("DIB success " + url);
|
||||
} catch (IOException e1) {
|
||||
System.out.println("DIB fail " + url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
System.out.println("done " + arg0);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected String _t(String s) {
|
||||
return DesktopguiTranslator._t(_appContext, s);
|
||||
}
|
||||
|
@ -156,11 +156,11 @@ public class JSONRPC2Servlet extends HttpServlet {
|
||||
} else {
|
||||
out.println("<p>Current API password:<input name=\"password\" type=\"password\">");
|
||||
}
|
||||
out.println("<p>New API password (twice):<input name=\"password2\" type=\"password\">" +
|
||||
"<input name=\"password3\" type=\"password\">" +
|
||||
out.println("<p>New API password (twice): <input name=\"password2\" type=\"password\"> " +
|
||||
"<input name=\"password3\" type=\"password\"> " +
|
||||
"<input name=\"save\" type=\"submit\" value=\"Change API Password\">" +
|
||||
"<p>If you forget the API password, stop i2pcontrol, delete the file <tt>" + _conf.getConfFile() +
|
||||
"</tt>, and restart i2pcontrol.");
|
||||
"<p>If you forget the API password, <a href=\"/configwebapps\">stop jsonrpc</a>, delete the file <tt>" + _conf.getConfFile() +
|
||||
"</tt>, and <a href=\"/configwebapps\">restart jsonrpc</a>.");
|
||||
out.println("</form>");
|
||||
} else {
|
||||
out.println("<p><a href=\"password\">Change API Password</a>");
|
||||
|
@ -96,7 +96,7 @@ public class RouterInfoHandler implements RequestHandler {
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.status")) {
|
||||
outParams.put("i2p.router.status", _context.throttle().getTunnelStatus());
|
||||
outParams.put("i2p.router.status", _context.throttle().getLocalizedTunnelStatus());
|
||||
}
|
||||
|
||||
if (inParams.containsKey("i2p.router.net.status")) {
|
||||
@ -179,25 +179,44 @@ public class RouterInfoHandler implements RequestHandler {
|
||||
|
||||
int status = _context.commSystem().getStatus().getCode();
|
||||
switch (status) {
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
RouterAddress ra = _context.router().getRouterInfo().getTargetAddress("NTCP");
|
||||
|
||||
case CommSystemFacade.STATUS_OK:
|
||||
case CommSystemFacade.STATUS_IPV4_OK_IPV6_UNKNOWN:
|
||||
case CommSystemFacade.STATUS_IPV4_OK_IPV6_FIREWALLED:
|
||||
case CommSystemFacade.STATUS_IPV4_FIREWALLED_IPV6_OK:
|
||||
case CommSystemFacade.STATUS_IPV4_DISABLED_IPV6_OK:
|
||||
RouterAddress ra = _context.router().getRouterInfo().getTargetAddress("NTCP2");
|
||||
if (ra == null || TransportUtil.isPubliclyRoutable(ra.getIP(), true))
|
||||
return NETWORK_STATUS.OK;
|
||||
return NETWORK_STATUS.ERROR_PRIVATE_TCP_ADDRESS;
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
|
||||
case CommSystemFacade.STATUS_DIFFERENT:
|
||||
case CommSystemFacade.STATUS_IPV4_SNAT_IPV6_OK:
|
||||
case CommSystemFacade.STATUS_IPV4_SNAT_IPV6_UNKNOWN:
|
||||
return NETWORK_STATUS.ERROR_SYMMETRIC_NAT;
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
if (_context.router().getRouterInfo().getTargetAddress("NTCP") != null)
|
||||
|
||||
case CommSystemFacade.STATUS_REJECT_UNSOLICITED:
|
||||
case CommSystemFacade.STATUS_IPV4_FIREWALLED_IPV6_UNKNOWN:
|
||||
case CommSystemFacade.STATUS_IPV4_DISABLED_IPV6_FIREWALLED:
|
||||
if (_context.router().getRouterInfo().getTargetAddress("NTCP2") != null)
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_WITH_INBOUND_TCP;
|
||||
if (((FloodfillNetworkDatabaseFacade) _context.netDb()).floodfillEnabled())
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_AND_FLOODFILL;
|
||||
if (_context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0)
|
||||
return NETWORK_STATUS.WARN_FIREWALLED_AND_FAST;
|
||||
return NETWORK_STATUS.FIREWALLED;
|
||||
case CommSystemFacade.STATUS_HOSED:
|
||||
|
||||
case CommSystemFacade.STATUS_HOSED:
|
||||
return NETWORK_STATUS.ERROR_UDP_PORT_IN_USE;
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
default:
|
||||
|
||||
case CommSystemFacade.STATUS_DISCONNECTED:
|
||||
return NETWORK_STATUS.ERROR_NO_ACTIVE_PEERS_CHECK_CONNECTION_AND_FIREWALL;
|
||||
|
||||
case CommSystemFacade.STATUS_UNKNOWN: // fallthrough
|
||||
case CommSystemFacade.STATUS_IPV4_UNKNOWN_IPV6_OK:
|
||||
case CommSystemFacade.STATUS_IPV4_UNKNOWN_IPV6_FIREWALLED:
|
||||
case CommSystemFacade.STATUS_IPV4_DISABLED_IPV6_UNKNOWN:
|
||||
default:
|
||||
ra = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
if (ra == null && _context.router().getUptime() > 5 * 60 * 1000) {
|
||||
if (_context.commSystem().countActivePeers() <= 0)
|
||||
|
@ -2,3 +2,7 @@
|
||||
# This is for app context configuration of standalone i2psnark.
|
||||
# Almost all configuration settings are in i2psnark.config.d/i2psnark.config
|
||||
#
|
||||
# disable browser launch on startup
|
||||
#routerconsole.browser=/bin/false
|
||||
# change browser
|
||||
#routerconsole.browser=firefox
|
||||
|
@ -197,9 +197,16 @@
|
||||
</target>
|
||||
|
||||
<target name="standalone" depends="standalone_prep">
|
||||
<!-- doesn't support file permissions
|
||||
<zip destfile="i2psnark-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="i2psnark/" />
|
||||
<zipfileset dir="./i2psnark/" />
|
||||
</zip>
|
||||
-->
|
||||
<exec executable="zip" failifexecutionfails="true" failonerror="true" >
|
||||
<arg value="-r" />
|
||||
<arg value="i2psnark-standalone.zip" />
|
||||
<arg value="i2psnark" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- make a fat jar for standalone -->
|
||||
@ -228,6 +235,7 @@
|
||||
<zipfileset src="../../ministreaming/java/build/mstreaming.jar" />
|
||||
<zipfileset src="../../streaming/java/build/streaming.jar" />
|
||||
<zipfileset src="../../systray/java/build/systray.jar" />
|
||||
<zipfileset src="../../../build/jbigi.jar" />
|
||||
<!-- Countries translations. The i2psnark translations are in the war but it's easier to put these here -->
|
||||
<!-- 300KB just to translate "Brazil", but why not... -->
|
||||
<!--
|
||||
@ -306,33 +314,34 @@
|
||||
</target>
|
||||
|
||||
<target name="standalone_prep" depends="standalone_jar, standalone_war">
|
||||
<delete dir="./dist" />
|
||||
<mkdir dir="./dist" />
|
||||
<copy file="../launch-i2psnark" todir="./dist/" />
|
||||
<copy file="../launch-i2psnark.bat" todir="./dist/" />
|
||||
<mkdir dir="./dist/contexts" />
|
||||
<copy file="../standalone-context.xml" tofile="./dist/contexts/context.xml" />
|
||||
<mkdir dir="./dist/docroot" />
|
||||
<copy file="../standalone-index.html" tofile="./dist/docroot/index.html" />
|
||||
<mkdir dir="./dist/webapps" />
|
||||
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
|
||||
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
|
||||
<copy file="../i2psnark-appctx.config" tofile="./dist/i2psnark-appctx.config" />
|
||||
<copy file="./build/i2psnark-standalone.jar" tofile="./dist/i2psnark.jar" />
|
||||
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
|
||||
<delete dir="./i2psnark" />
|
||||
<mkdir dir="./i2psnark" />
|
||||
<copy file="../launch-i2psnark" todir="./i2psnark/" />
|
||||
<chmod type="file" file="./i2psnark/launch-i2psnark" perm="+x" />
|
||||
<copy file="../launch-i2psnark.bat" todir="./i2psnark/" />
|
||||
<mkdir dir="./i2psnark/contexts" />
|
||||
<copy file="../standalone-context.xml" tofile="./i2psnark/contexts/context.xml" />
|
||||
<mkdir dir="./i2psnark/docroot" />
|
||||
<copy file="../standalone-index.html" tofile="./i2psnark/docroot/index.html" />
|
||||
<mkdir dir="./i2psnark/webapps" />
|
||||
<copy file="../i2psnark.war" tofile="./i2psnark/webapps/i2psnark.war" />
|
||||
<copy file="../jetty-i2psnark.xml" tofile="./i2psnark/jetty-i2psnark.xml" />
|
||||
<copy file="../i2psnark-appctx.config" tofile="./i2psnark/i2psnark-appctx.config" />
|
||||
<copy file="./build/i2psnark-standalone.jar" tofile="./i2psnark/i2psnark.jar" />
|
||||
<copy file="../readme-standalone.txt" tofile="./i2psnark/readme.txt" />
|
||||
<!-- temp so announces work -->
|
||||
<copy file="../../../installer/resources/hosts.txt" tofile="./dist/hosts.txt" />
|
||||
<copy todir="./dist/licenses" >
|
||||
<copy file="../../../installer/resources/hosts.txt" tofile="./i2psnark/hosts.txt" />
|
||||
<copy todir="./i2psnark/licenses" >
|
||||
<fileset dir="../../../licenses" includes="LICENSE-GPLv2.txt, ABOUT-Jetty.html" />
|
||||
</copy>
|
||||
<mkdir dir="./dist/logs" />
|
||||
<mkdir dir="./i2psnark/logs" />
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
<delete file="../i2psnark.war" />
|
||||
<delete file="./i2psnark-standalone.zip" />
|
||||
<delete dir="./dist" />
|
||||
<delete dir="./i2psnark" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
</target>
|
||||
|
@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketEepGet;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base32;
|
||||
@ -47,7 +48,7 @@ import org.klomp.snark.dht.KRPC;
|
||||
* so we can run multiple instances of single Snarks
|
||||
* (but not multiple SnarkManagers, it is still static)
|
||||
*/
|
||||
public class I2PSnarkUtil {
|
||||
public class I2PSnarkUtil implements DisconnectListener {
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
private final String _baseName;
|
||||
@ -75,6 +76,7 @@ public class I2PSnarkUtil {
|
||||
private List<String> _openTrackers;
|
||||
private DHT _dht;
|
||||
private long _startedTime;
|
||||
private final DisconnectListener _discon;
|
||||
|
||||
private static final int EEPGET_CONNECT_TIMEOUT = 45*1000;
|
||||
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
|
||||
@ -91,17 +93,18 @@ public class I2PSnarkUtil {
|
||||
|
||||
|
||||
public I2PSnarkUtil(I2PAppContext ctx) {
|
||||
this(ctx, "i2psnark");
|
||||
this(ctx, "i2psnark", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseName generally "i2psnark"
|
||||
* @since Jetty 7
|
||||
*/
|
||||
public I2PSnarkUtil(I2PAppContext ctx, String baseName) {
|
||||
public I2PSnarkUtil(I2PAppContext ctx, String baseName, DisconnectListener discon) {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_baseName = baseName;
|
||||
_discon = discon;
|
||||
_opts = new HashMap<String, String>();
|
||||
//setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", I2PClient.DEFAULT_LISTEN_PORT, null);
|
||||
@ -324,8 +327,11 @@ public class I2PSnarkUtil {
|
||||
if (opts.getProperty(I2PClient.PROP_GZIP) == null)
|
||||
opts.setProperty(I2PClient.PROP_GZIP, "false");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
if (_manager != null)
|
||||
if (_manager != null) {
|
||||
_startedTime = _context.clock().now();
|
||||
if (_discon != null)
|
||||
_manager.addDisconnectListener(this);
|
||||
}
|
||||
_connecting = false;
|
||||
}
|
||||
if (_shouldUseDHT && _manager != null && _dht == null)
|
||||
@ -333,6 +339,19 @@ public class I2PSnarkUtil {
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* DisconnectListener interface
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void sessionDisconnected() {
|
||||
synchronized(this) {
|
||||
_manager = null;
|
||||
_connecting = false;
|
||||
}
|
||||
if (_discon != null)
|
||||
_discon.sessionDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if disabled or not started
|
||||
* @since 0.8.4
|
||||
|
@ -82,8 +82,9 @@ public class MetaInfo
|
||||
* @param created_by may be null
|
||||
* @param url_list may be null
|
||||
* @param comment may be null
|
||||
* @since public since 0.9.53, was package private
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
public MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
|
||||
List<List<String>> announce_list, String created_by, List<String> url_list, String comment)
|
||||
{
|
||||
@ -442,9 +443,12 @@ public class MetaInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the piece hashes. Only used by storage so package local.
|
||||
* Returns the piece hashes.
|
||||
*
|
||||
* @return not a copy, do not modify
|
||||
* @since public since 0.9.53, was package private
|
||||
*/
|
||||
byte[] getPieceHashes()
|
||||
public byte[] getPieceHashes()
|
||||
{
|
||||
return piece_hashes;
|
||||
}
|
||||
|
@ -590,8 +590,12 @@ public class Snark
|
||||
|
||||
private void x_startTorrent() {
|
||||
boolean ok = _util.connect();
|
||||
if (!ok)
|
||||
fatalRouter("Unable to connect to I2P", null);
|
||||
if (!ok) {
|
||||
if (_util.getContext().isRouterContext())
|
||||
fatalRouter(_util.getString("Unable to connect to I2P"), null);
|
||||
else
|
||||
fatalRouter(_util.getString("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort(), null);
|
||||
}
|
||||
if (coordinator == null) {
|
||||
I2PServerSocket serversocket = _util.getServerSocket();
|
||||
if (serversocket == null)
|
||||
@ -1255,7 +1259,7 @@ public class Snark
|
||||
*/
|
||||
private void fatalRouter(String s, Throwable t) throws RouterException {
|
||||
_log.error(s, t);
|
||||
stopTorrent();
|
||||
stopTorrent(true);
|
||||
if (completeListener != null)
|
||||
completeListener.fatal(this, s);
|
||||
throw new RouterException(s, t);
|
||||
@ -1321,6 +1325,15 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call after editing torrent.
|
||||
* Caller must ensure infohash, files, etc. did not change.
|
||||
*
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void replaceMetaInfo(MetaInfo metainfo) {
|
||||
meta = metainfo;
|
||||
}
|
||||
|
||||
///////////// Begin StorageListener methods
|
||||
|
||||
|
@ -29,7 +29,9 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientApp;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.app.ClientAppState;
|
||||
import net.i2p.app.NotificationService;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base64;
|
||||
@ -57,7 +59,7 @@ import org.klomp.snark.dht.KRPC;
|
||||
/**
|
||||
* Manage multiple snarks
|
||||
*/
|
||||
public class SnarkManager implements CompleteListener, ClientApp {
|
||||
public class SnarkManager implements CompleteListener, ClientApp, DisconnectListener {
|
||||
|
||||
/**
|
||||
* Map of (canonical) filename of the .torrent file to Snark instance.
|
||||
@ -130,7 +132,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
public static final String PROP_FILES_PUBLIC = "i2psnark.filesPublic";
|
||||
public static final String PROP_OLD_AUTO_START = "i2snark.autoStart"; // oops
|
||||
public static final String PROP_AUTO_START = "i2psnark.autoStart"; // convert in migration to new config file
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
private final boolean DEFAULT_AUTO_START;
|
||||
//public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
|
||||
//public static final String DEFAULT_LINK_PREFIX = "file:///";
|
||||
public static final String PROP_STARTUP_DELAY = "i2psnark.startupDelay";
|
||||
@ -140,7 +142,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
public static final String RC_PROP_UNIVERSAL_THEMING = "routerconsole.universal.theme";
|
||||
public static final String PROP_THEME = "i2psnark.theme";
|
||||
public static final String DEFAULT_THEME = "ubergine";
|
||||
private static final String[] THEMES = new String[] { "dark", "light", "ubergine", "vanilla" };
|
||||
// Translators: Translate "ubergine" as "aubergine" or "eggplant" or "purple"
|
||||
private static final String[] THEMES = new String[] { _x("ubergine"), _x("dark"), _x("light"), _x("vanilla") };
|
||||
/** From CSSHelper */
|
||||
private static final String PROP_DISABLE_OLD = "routerconsole.disableOldThemes";
|
||||
private static final boolean DEFAULT_DISABLE_OLD = true;
|
||||
@ -267,7 +270,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
_contextName = ctxName;
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new UIMessages(MAX_MESSAGES);
|
||||
_util = new I2PSnarkUtil(_context, ctxName);
|
||||
_util = new I2PSnarkUtil(_context, ctxName, this);
|
||||
DEFAULT_AUTO_START = !ctx.isRouterContext();
|
||||
String cfile = ctxName + CONFIG_FILE_SUFFIX;
|
||||
File configFile = new File(cfile);
|
||||
if (!configFile.isAbsolute())
|
||||
@ -342,6 +346,18 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DisconnectListener interface
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void sessionDisconnected() {
|
||||
if (!_context.isRouterContext()) {
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
stopAllTorrents(true);
|
||||
_stopping = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by the webapp at Jetty shutdown.
|
||||
* Stops all torrents. Does not close the tunnel, so the announces have a chance.
|
||||
@ -463,7 +479,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.parseBoolean(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START));
|
||||
return Boolean.parseBoolean(_config.getProperty(PROP_AUTO_START, Boolean.toString(DEFAULT_AUTO_START)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -814,7 +830,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, _contextName);
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
|
||||
_config.setProperty(PROP_AUTO_START, Boolean.toString(DEFAULT_AUTO_START));
|
||||
if (!_config.containsKey(PROP_REFRESH_DELAY))
|
||||
_config.setProperty(PROP_REFRESH_DELAY, Integer.toString(DEFAULT_REFRESH_DELAY_SECS));
|
||||
if (!_config.containsKey(PROP_STARTUP_DELAY))
|
||||
@ -847,7 +863,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
|
||||
/**
|
||||
* Get current theme.
|
||||
* @return String -- the current theme
|
||||
* @return String -- the current theme, untranslated
|
||||
*/
|
||||
public String getTheme() {
|
||||
String theme;
|
||||
@ -912,20 +928,29 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
|
||||
/**
|
||||
* Get all themes
|
||||
* @return String[] -- Array of all the themes found, non-null, unsorted
|
||||
* @return Array of all the themes found, non-null, unsorted, untranslated. Not a copy, do not modify.
|
||||
*/
|
||||
public static String[] getThemes() {
|
||||
return THEMES;
|
||||
}
|
||||
|
||||
|
||||
/** call from DirMonitor since loadConfig() is called before router I2CP is up */
|
||||
private void getBWLimit() {
|
||||
if (!_config.containsKey(PROP_UPBW_MAX)) {
|
||||
/**
|
||||
* Call from DirMonitor since loadConfig() is called before router I2CP is up.
|
||||
* We also use this as a test that the router is there for standalone.
|
||||
*
|
||||
* @return true if we got a response from the router
|
||||
*/
|
||||
private boolean getBWLimit() {
|
||||
boolean shouldSet = !_config.containsKey(PROP_UPBW_MAX);
|
||||
if (shouldSet || !_context.isRouterContext()) {
|
||||
int[] limits = BWLimits.getBWLimits(_util.getI2CPHost(), _util.getI2CPPort());
|
||||
if (limits != null && limits[1] > 0)
|
||||
if (limits == null)
|
||||
return false;
|
||||
if (shouldSet && limits[1] > 0)
|
||||
_util.setMaxUpBW(limits[1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
@ -1576,7 +1601,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
}
|
||||
if (dataDir == null)
|
||||
dataDir = getDataDir();
|
||||
Snark torrent = null;
|
||||
Snark torrent;
|
||||
synchronized (_snarks) {
|
||||
torrent = _snarks.get(filename);
|
||||
}
|
||||
@ -1696,7 +1721,10 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
addMessage(_t("Connecting to I2P"));
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!"));
|
||||
if (_context.isRouterContext())
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
else
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort());
|
||||
// this would rename the torrent to .BAD
|
||||
//return false;
|
||||
}
|
||||
@ -2492,6 +2520,13 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
addMessage(_t("Torrent removed: \"{0}\"", torrent.getBaseName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This calls monitorTorrents() once a minute.
|
||||
* It also gets the bandwidth limits and loads magnets on first run.
|
||||
* For standalone, it also handles checking that the external router is there,
|
||||
* and restarting torrents once the router appears.
|
||||
*
|
||||
*/
|
||||
private class DirMonitor implements Runnable {
|
||||
public void run() {
|
||||
// don't bother delaying if auto start is false
|
||||
@ -2506,17 +2541,65 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
|
||||
// here because we need to delay until I2CP is up
|
||||
// although the user will see the default until then
|
||||
getBWLimit();
|
||||
boolean routerOK = false;
|
||||
boolean doMagnets = true;
|
||||
while (_running) {
|
||||
File dir = getDataDir();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
|
||||
if (routerOK &&
|
||||
(_context.isRouterContext() || _util.connected() || _util.isConnecting())) {
|
||||
autostart = shouldAutoStart();
|
||||
} else {
|
||||
// Test if the router is there
|
||||
// For standalone, this will probe the router every 60 seconds if not connected
|
||||
boolean oldOK = routerOK;
|
||||
routerOK = getBWLimit();
|
||||
if (routerOK) {
|
||||
autostart = shouldAutoStart();
|
||||
if (autostart && !oldOK && !doMagnets && !_snarks.isEmpty()) {
|
||||
// Start previously added torrents
|
||||
for (Snark snark : _snarks.values()) {
|
||||
Properties config = getConfig(snark);
|
||||
String prop = config.getProperty(PROP_META_RUNNING);
|
||||
if (prop == null || Boolean.parseBoolean(prop)) {
|
||||
if (!_util.connected()) {
|
||||
addMessage(_t("Connecting to I2P"));
|
||||
// getBWLimit() was successful so this should work
|
||||
boolean ok = _util.connect();
|
||||
if (!ok) {
|
||||
if (_context.isRouterContext())
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
else
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort());
|
||||
routerOK = false;
|
||||
autostart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
addMessageNoEscape(_t("Starting up torrent {0}", linkify(snark)));
|
||||
try {
|
||||
snark.startTorrent();
|
||||
} catch (Snark.RouterException re) {
|
||||
// Snark.fatal() will log and call fatal() here for user message before throwing
|
||||
break;
|
||||
} catch (RuntimeException re) {
|
||||
// Snark.fatal() will log and call fatal() here for user message before throwing
|
||||
}
|
||||
}
|
||||
}
|
||||
if (routerOK)
|
||||
addMessage(_t("Up bandwidth limit is {0} KBps", _util.getMaxUpBW()));
|
||||
}
|
||||
} else {
|
||||
autostart = false;
|
||||
}
|
||||
}
|
||||
boolean ok;
|
||||
try {
|
||||
// Don't let this interfere with .torrent files being added or deleted
|
||||
synchronized (_snarks) {
|
||||
ok = monitorTorrents(dir);
|
||||
ok = monitorTorrents(dir, autostart);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
@ -2530,7 +2613,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
} catch (RuntimeException e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
if (!_snarks.isEmpty())
|
||||
if (routerOK && !_snarks.isEmpty())
|
||||
addMessage(_t("Up bandwidth limit is {0} KBps", _util.getMaxUpBW()));
|
||||
// To fix bug where files were left behind,
|
||||
// but also good for when user removes snarks when i2p is not running
|
||||
@ -2539,6 +2622,12 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
// time i2psnark starts. See ticket #1658.
|
||||
if (ok)
|
||||
cleanupTorrentStatus();
|
||||
if (!routerOK) {
|
||||
if (_context.isRouterContext())
|
||||
addMessage(_t("Unable to connect to I2P"));
|
||||
else
|
||||
addMessage(_t("Error connecting to I2P - check your I2CP settings!") + ' ' + _util.getI2CPHost() + ':' + _util.getI2CPPort());
|
||||
}
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
@ -2555,8 +2644,18 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
Storage storage = snark.getStorage();
|
||||
if (meta == null || storage == null)
|
||||
return;
|
||||
if (snark.getDownloaded() > 0)
|
||||
if (snark.getDownloaded() > 0) {
|
||||
addMessageNoEscape(_t("Download finished: {0}", linkify(snark)));
|
||||
ClientAppManager cmgr = _context.clientAppManager();
|
||||
if (cmgr != null) {
|
||||
NotificationService ns = (NotificationService) cmgr.getRegisteredApp("desktopgui");
|
||||
if (ns != null) {
|
||||
ns.notify("I2PSnark", null, Log.INFO, _t("I2PSnark"),
|
||||
_t("Download finished: {0}", snark.getName()),
|
||||
"/i2psnark/" + linkify(snark));
|
||||
}
|
||||
}
|
||||
}
|
||||
updateStatus(snark);
|
||||
}
|
||||
|
||||
@ -2704,9 +2803,10 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
/**
|
||||
* caller must synchronize on _snarks
|
||||
*
|
||||
* @param shouldStart should we autostart the torrents
|
||||
* @return success, false if an error adding any torrent.
|
||||
*/
|
||||
private boolean monitorTorrents(File dir) {
|
||||
private boolean monitorTorrents(File dir, boolean shouldStart) {
|
||||
boolean rv = true;
|
||||
File files[] = dir.listFiles(new FileSuffixFilter(".torrent"));
|
||||
List<String> foundNames = new ArrayList<String>(0);
|
||||
@ -2726,7 +2826,6 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("DirMon found: " + DataHelper.toString(foundNames) + " existing: " + DataHelper.toString(existingNames));
|
||||
// lets find new ones first...
|
||||
boolean shouldStart = shouldAutoStart();
|
||||
for (String name : foundNames) {
|
||||
if (existingNames.contains(name)) {
|
||||
// already known. noop
|
||||
@ -2791,6 +2890,14 @@ public class SnarkManager implements CompleteListener, ClientApp {
|
||||
return _util.getString(s, o, o2);
|
||||
}
|
||||
|
||||
/**
|
||||
* mark for translation, does not translate
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private static String _x(String s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsorted map of name to Tracker object
|
||||
* Modifiable, not a copy
|
||||
|
753
apps/i2psnark/java/src/org/klomp/snark/UDPTrackerClient.java
Normal file
753
apps/i2psnark/java/src/org/klomp/snark/UDPTrackerClient.java
Normal file
@ -0,0 +1,753 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionMuxedListener;
|
||||
import net.i2p.client.SendMessageOptions;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
import net.i2p.client.datagram.I2PInvalidDatagramException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
* One of these for all trackers and info hashes.
|
||||
* Ref: BEP 15, proposal 160
|
||||
*
|
||||
* The main difference from BEP 15 is that the announce response
|
||||
* contains a 32-byte hash instead of a 4-byte IP and a 2-byte port.
|
||||
*
|
||||
* This implements only "fast mode".
|
||||
* We send only repliable datagrams, and
|
||||
* receive only raw datagrams, as follows:
|
||||
*
|
||||
*<pre>
|
||||
* client tracker type
|
||||
* ------ ------- ----
|
||||
* announce --> repliable
|
||||
* <-- ann resp raw
|
||||
*</pre>
|
||||
*
|
||||
* @since 0.9.53, enabled in 0.9.54
|
||||
*/
|
||||
class UDPTrackerClient implements I2PSessionMuxedListener {
|
||||
|
||||
private final I2PAppContext _context;
|
||||
private final Log _log;
|
||||
/** hook to inject and receive datagrams */
|
||||
private final I2PSession _session;
|
||||
private final I2PSnarkUtil _util;
|
||||
private final Hash _myHash;
|
||||
/** unsigned dgrams */
|
||||
private final int _rPort;
|
||||
/** dest and port to tracker data */
|
||||
private final ConcurrentHashMap<HostPort, Tracker> _trackers;
|
||||
/** our TID to tracker */
|
||||
private final Map<Integer, ReplyWaiter> _sentQueries;
|
||||
private boolean _isRunning;
|
||||
|
||||
public static final int EVENT_NONE = 0;
|
||||
public static final int EVENT_COMPLETED = 1;
|
||||
public static final int EVENT_STARTED = 2;
|
||||
public static final int EVENT_STOPPED = 3;
|
||||
|
||||
private static final int ACTION_CONNECT = 0;
|
||||
private static final int ACTION_ANNOUNCE = 1;
|
||||
private static final int ACTION_SCRAPE = 2;
|
||||
private static final int ACTION_ERROR = 3;
|
||||
|
||||
private static final int SEND_CRYPTO_TAGS = 8;
|
||||
private static final int LOW_CRYPTO_TAGS = 4;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 15*1000;
|
||||
private static final long DEFAULT_QUERY_TIMEOUT = 60*1000;
|
||||
private static final long CLEAN_TIME = 163*1000;
|
||||
|
||||
/** in seconds */
|
||||
private static final int DEFAULT_INTERVAL = 60*60;
|
||||
private static final int MIN_INTERVAL = 15*60;
|
||||
private static final int MAX_INTERVAL = 8*60*60;
|
||||
|
||||
private enum WaitState { INIT, SUCCESS, TIMEOUT, FAIL }
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public UDPTrackerClient(I2PAppContext ctx, I2PSession session, I2PSnarkUtil util) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_util = util;
|
||||
_log = ctx.logManager().getLog(UDPTrackerClient.class);
|
||||
_rPort = TrackerClient.PORT - 1;
|
||||
_myHash = session.getMyDestination().calculateHash();
|
||||
_trackers = new ConcurrentHashMap<HostPort, Tracker>(8);
|
||||
_sentQueries = new ConcurrentHashMap<Integer, ReplyWaiter>(32);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Can't be restarted after stopping?
|
||||
*/
|
||||
public synchronized void start() {
|
||||
if (_isRunning)
|
||||
return;
|
||||
_session.addMuxedSessionListener(this, I2PSession.PROTO_DATAGRAM_RAW, _rPort);
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop everything.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
if (!_isRunning)
|
||||
return;
|
||||
_isRunning = false;
|
||||
_session.removeListener(I2PSession.PROTO_DATAGRAM_RAW, _rPort);
|
||||
_trackers.clear();
|
||||
for (ReplyWaiter w : _sentQueries.values()) {
|
||||
w.cancel();
|
||||
}
|
||||
_sentQueries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Announce and get peers for a torrent.
|
||||
* Blocking!
|
||||
* Caller should run in a thread.
|
||||
*
|
||||
* @param ih the Info Hash (torrent)
|
||||
* @param max maximum number of peers to return
|
||||
* @param maxWait the maximum time to wait (ms) must be > 0
|
||||
* @param fast if true, don't wait for dest, no retx, ...
|
||||
* @return null on fail or if fast is true
|
||||
*/
|
||||
public TrackerResponse announce(byte[] ih, byte[] peerID, int max, long maxWait,
|
||||
String toHost, int toPort,
|
||||
long downloaded, long left, long uploaded,
|
||||
int event, boolean fast) {
|
||||
long now = _context.clock().now();
|
||||
long end = now + maxWait;
|
||||
if (toPort < 0)
|
||||
throw new IllegalArgumentException();
|
||||
Tracker tr = getTracker(toHost, toPort);
|
||||
if (tr.getDest(fast) == null) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("cannot resolve " + tr);
|
||||
return null;
|
||||
}
|
||||
long toWait = end - now;
|
||||
if (!fast)
|
||||
toWait = toWait * 3 / 4;
|
||||
if (toWait < 1000) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("out of time after resolving: " + tr);
|
||||
return null;
|
||||
}
|
||||
if (fast) {
|
||||
toWait = 0;
|
||||
} else {
|
||||
toWait = end - now;
|
||||
if (toWait < 1000) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("out of time after getting conn: " + tr);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ReplyWaiter w = sendAnnounce(tr, 0, ih, peerID,
|
||||
downloaded, left, uploaded, event, max, toWait);
|
||||
if (fast)
|
||||
return null;
|
||||
if (w == null) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("initial announce failed: " + tr);
|
||||
return null;
|
||||
}
|
||||
boolean success = waitAndRetransmit(w, end);
|
||||
_sentQueries.remove(w.getID());
|
||||
if (success)
|
||||
return w.getReplyObject();
|
||||
if (_log.shouldInfo())
|
||||
_log.info("announce failed after retx: " + tr);
|
||||
return null;
|
||||
}
|
||||
|
||||
//////// private below here
|
||||
|
||||
/**
|
||||
* @return non-null
|
||||
*/
|
||||
private Tracker getTracker(String host, int port) {
|
||||
Tracker ndp = new Tracker(host, port);
|
||||
Tracker odp = _trackers.putIfAbsent(ndp, ndp);
|
||||
if (odp != null)
|
||||
ndp = odp;
|
||||
return ndp;
|
||||
}
|
||||
|
||||
///// Sending.....
|
||||
|
||||
/**
|
||||
* Send one time with a new tid
|
||||
* @param toWait if <= 0 does not register
|
||||
* @return null on failure or if toWait <= 0
|
||||
*/
|
||||
private ReplyWaiter sendAnnounce(Tracker tr, long connID,
|
||||
byte[] ih, byte[] id,
|
||||
long downloaded, long left, long uploaded,
|
||||
int event, int numWant, long toWait) {
|
||||
int tid = _context.random().nextInt();
|
||||
byte[] payload = sendAnnounce(tr, tid, connID, ih, id, downloaded, left, uploaded, event, numWant);
|
||||
if (payload != null) {
|
||||
if (toWait > 0) {
|
||||
ReplyWaiter rv = new ReplyWaiter(tid, tr, payload, toWait);
|
||||
_sentQueries.put(Integer.valueOf(tid), rv);
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Sent: " + rv + " timeout: " + toWait);
|
||||
return rv;
|
||||
}
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Sent annc " + event + " to " + tr + " no wait");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send one time with given tid
|
||||
* @return the payload or null on failure
|
||||
*/
|
||||
private byte[] sendAnnounce(Tracker tr, int tid, long connID,
|
||||
byte[] ih, byte[] id,
|
||||
long downloaded, long left, long uploaded,
|
||||
int event, int numWant) {
|
||||
byte[] payload = new byte[98];
|
||||
DataHelper.toLong8(payload, 0, connID);
|
||||
DataHelper.toLong(payload, 8, 4, ACTION_ANNOUNCE);
|
||||
DataHelper.toLong(payload, 12, 4, tid);
|
||||
System.arraycopy(ih, 0, payload, 16, 20);
|
||||
System.arraycopy(id, 0, payload, 36, 20);
|
||||
DataHelper.toLong(payload, 56, 8, downloaded);
|
||||
DataHelper.toLong(payload, 64, 8, left);
|
||||
DataHelper.toLong(payload, 72, 8, uploaded);
|
||||
DataHelper.toLong(payload, 80, 4, event);
|
||||
DataHelper.toLong(payload, 92, 4, numWant);
|
||||
DataHelper.toLong(payload, 96, 2, TrackerClient.PORT);
|
||||
boolean rv = sendMessage(tr.getDest(true), tr.getPort(), payload, true);
|
||||
return rv ? payload : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* wait after initial send, resend if necessary
|
||||
*/
|
||||
private boolean waitAndRetransmit(ReplyWaiter w, long untilTime) {
|
||||
synchronized(w) {
|
||||
while(true) {
|
||||
try {
|
||||
long toWait = untilTime - _context.clock().now();
|
||||
if (toWait <= 0)
|
||||
return false;
|
||||
w.wait(toWait);
|
||||
} catch (InterruptedException ie) {
|
||||
return false;
|
||||
}
|
||||
switch (w.getState()) {
|
||||
case INIT:
|
||||
continue;
|
||||
|
||||
case SUCCESS:
|
||||
return true;
|
||||
|
||||
case FAIL:
|
||||
return false;
|
||||
|
||||
case TIMEOUT:
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Timeout: " + w);
|
||||
long toWait = untilTime - _context.clock().now();
|
||||
if (toWait <= 1000)
|
||||
return false;
|
||||
boolean ok = resend(w, Math.min(toWait, w.getSentTo().getTimeout()));
|
||||
if (!ok)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend the stored payload
|
||||
* @return success
|
||||
*/
|
||||
private boolean resend(ReplyWaiter w, long toWait) {
|
||||
Tracker tr = w.getSentTo();
|
||||
int port = tr.getPort();
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Resending: " + w + " timeout: " + toWait);
|
||||
boolean rv = sendMessage(tr.getDest(true), port, w.getPayload(), true);
|
||||
if (rv) {
|
||||
_sentQueries.put(Integer.valueOf(w.getID()), w);
|
||||
w.schedule(toWait);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lowest-level send message call.
|
||||
* @param dest may be null, returns false
|
||||
* @param repliable true for conn request, false for announce
|
||||
* @return success
|
||||
*/
|
||||
private boolean sendMessage(Destination dest, int toPort, byte[] payload, boolean repliable) {
|
||||
if (!_isRunning) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("send failed, not running");
|
||||
return false;
|
||||
}
|
||||
if (dest == null) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("send failed, no dest");
|
||||
return false;
|
||||
}
|
||||
if (dest.calculateHash().equals(_myHash))
|
||||
throw new IllegalArgumentException("don't send to ourselves");
|
||||
|
||||
if (repliable) {
|
||||
I2PDatagramMaker dgMaker = new I2PDatagramMaker(_session);
|
||||
payload = dgMaker.makeI2PDatagram(payload);
|
||||
if (payload == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("DGM fail");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SendMessageOptions opts = new SendMessageOptions();
|
||||
opts.setDate(_context.clock().now() + 60*1000);
|
||||
opts.setTagsToSend(SEND_CRYPTO_TAGS);
|
||||
opts.setTagThreshold(LOW_CRYPTO_TAGS);
|
||||
if (!repliable)
|
||||
opts.setSendLeaseSet(false);
|
||||
try {
|
||||
boolean success = _session.sendMessage(dest, payload, 0, payload.length,
|
||||
repliable ? I2PSession.PROTO_DATAGRAM : I2PSession.PROTO_DATAGRAM_RAW,
|
||||
_rPort, toPort, opts);
|
||||
if (success) {
|
||||
// ...
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("sendMessage fail");
|
||||
}
|
||||
return success;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("sendMessage fail", ise);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
///// Reception.....
|
||||
|
||||
/**
|
||||
* @param from dest or null if it didn't come in on signed port
|
||||
*/
|
||||
private void receiveMessage(Destination from, int fromPort, byte[] payload) {
|
||||
if (payload.length < 8) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Got short message: " + payload.length + " bytes");
|
||||
return;
|
||||
}
|
||||
|
||||
int action = (int) DataHelper.fromLong(payload, 0, 4);
|
||||
int tid = (int) DataHelper.fromLong(payload, 4, 4);
|
||||
ReplyWaiter waiter = _sentQueries.remove(Integer.valueOf(tid));
|
||||
if (waiter == null) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Rcvd msg with no one waiting: " + tid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == ACTION_ANNOUNCE) {
|
||||
receiveAnnounce(waiter, payload);
|
||||
} else if (action == ACTION_ERROR) {
|
||||
receiveError(waiter, payload);
|
||||
} else {
|
||||
// includes ACTION_CONNECT
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Rcvd msg with unknown action: " + action + " for: " + waiter);
|
||||
waiter.gotReply(false);
|
||||
Tracker tr = waiter.getSentTo();
|
||||
tr.gotError();
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveAnnounce(ReplyWaiter waiter, byte[] payload) {
|
||||
Tracker tr = waiter.getSentTo();
|
||||
if (payload.length >= 22) {
|
||||
int interval = Math.min(MAX_INTERVAL, Math.max(MIN_INTERVAL,
|
||||
(int) DataHelper.fromLong(payload, 8, 4)));
|
||||
int leeches = (int) DataHelper.fromLong(payload, 12, 4);
|
||||
int seeds = (int) DataHelper.fromLong(payload, 16, 4);
|
||||
int peers = (int) DataHelper.fromLong(payload, 20, 2);
|
||||
if (22 + (peers * Hash.HASH_LENGTH) > payload.length) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Short reply");
|
||||
waiter.gotReply(false);
|
||||
tr.gotError();
|
||||
return;
|
||||
}
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Rcvd " + peers + " peers from " + tr);
|
||||
Set<Hash> hashes;
|
||||
if (peers > 0) {
|
||||
hashes = new HashSet<Hash>(peers);
|
||||
for (int off = 20; off < payload.length; off += Hash.HASH_LENGTH) {
|
||||
hashes.add(Hash.create(payload, off));
|
||||
}
|
||||
} else {
|
||||
hashes = Collections.emptySet();
|
||||
}
|
||||
TrackerResponse resp = new TrackerResponse(interval, seeds, leeches, hashes);
|
||||
waiter.gotResponse(resp);
|
||||
tr.setInterval(interval);
|
||||
} else {
|
||||
waiter.gotReply(false);
|
||||
tr.gotError();
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveError(ReplyWaiter waiter, byte[] payload) {
|
||||
String msg;
|
||||
if (payload.length > 8) {
|
||||
msg = DataHelper.getUTF8(payload, 8, payload.length - 8);
|
||||
} else {
|
||||
msg = "";
|
||||
}
|
||||
TrackerResponse resp = new TrackerResponse(msg);
|
||||
waiter.gotResponse(resp);
|
||||
Tracker tr = waiter.getSentTo();
|
||||
tr.gotError();
|
||||
}
|
||||
|
||||
// I2PSessionMuxedListener interface ----------------
|
||||
|
||||
/**
|
||||
* Instruct the client that the given session has received a message
|
||||
*
|
||||
* Will be called only if you register via addMuxedSessionListener().
|
||||
* Will be called only for the proto(s) and toPort(s) you register for.
|
||||
*
|
||||
* @param session session to notify
|
||||
* @param msgId message number available
|
||||
* @param size size of the message - why it's a long and not an int is a mystery
|
||||
* @param proto 1-254 or 0 for unspecified
|
||||
* @param fromPort 1-65535 or 0 for unspecified
|
||||
* @param toPort 1-65535 or 0 for unspecified
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromPort, int toPort) {
|
||||
// TODO throttle
|
||||
try {
|
||||
byte[] payload = session.receiveMessage(msgId);
|
||||
if (payload == null)
|
||||
return;
|
||||
if (toPort == _rPort) {
|
||||
// raw
|
||||
receiveMessage(null, fromPort, payload);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("msg on bad port");
|
||||
}
|
||||
} catch (I2PSessionException e) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("bad msg");
|
||||
}
|
||||
}
|
||||
|
||||
/** for non-muxed */
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {}
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("UDPTC disconnected");
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("UDPTC got error msg: ", error);
|
||||
}
|
||||
|
||||
public static class TrackerResponse {
|
||||
|
||||
private final int interval, complete, incomplete;
|
||||
private final String error;
|
||||
private final Set<Hash> peers;
|
||||
|
||||
/** success */
|
||||
public TrackerResponse(int interval, int seeds, int leeches, Set<Hash> peers) {
|
||||
this.interval = interval;
|
||||
complete = seeds;
|
||||
incomplete = leeches;
|
||||
this.peers = peers;
|
||||
error = null;
|
||||
}
|
||||
|
||||
/** failure */
|
||||
public TrackerResponse(String errorMsg) {
|
||||
interval = DEFAULT_INTERVAL;
|
||||
complete = 0;
|
||||
incomplete = 0;
|
||||
peers = null;
|
||||
error = errorMsg;
|
||||
}
|
||||
|
||||
public Set<Hash> getPeers() {
|
||||
return peers;
|
||||
}
|
||||
|
||||
public int getPeerCount() {
|
||||
int pc = peers == null ? 0 : peers.size();
|
||||
return Math.max(pc, complete + incomplete - 1);
|
||||
}
|
||||
|
||||
public int getSeedCount() {
|
||||
return complete;
|
||||
}
|
||||
|
||||
public int getLeechCount() {
|
||||
return incomplete;
|
||||
}
|
||||
|
||||
public String getFailureReason() {
|
||||
return error;
|
||||
}
|
||||
|
||||
/** in seconds */
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HostPort {
|
||||
|
||||
protected final String host;
|
||||
protected final int port;
|
||||
|
||||
/**
|
||||
* @param port the announce port
|
||||
*/
|
||||
public HostPort(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the announce port
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return host.hashCode() ^ port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || !(o instanceof HostPort))
|
||||
return false;
|
||||
HostPort dp = (HostPort) o;
|
||||
return port == dp.port && host.equals(dp.host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UDP Tracker " + host + ':' + port;
|
||||
}
|
||||
}
|
||||
|
||||
private class Tracker extends HostPort {
|
||||
|
||||
private final Object destLock = new Object();
|
||||
private Destination dest;
|
||||
private long expires;
|
||||
private long lastHeardFrom;
|
||||
private long lastFailed;
|
||||
private int consecFails;
|
||||
private int interval = DEFAULT_INTERVAL;
|
||||
|
||||
private static final long DELAY = 15*1000;
|
||||
|
||||
public Tracker(String host, int port) {
|
||||
super(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fast if true, do not lookup
|
||||
* @return dest or null
|
||||
*/
|
||||
public Destination getDest(boolean fast) {
|
||||
synchronized(destLock) {
|
||||
if (dest == null && !fast)
|
||||
dest = _util.getDestination(host);
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
/** does not change state */
|
||||
public synchronized void replyTimeout() {
|
||||
consecFails++;
|
||||
lastFailed = _context.clock().now();
|
||||
}
|
||||
|
||||
public synchronized int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/** sets heardFrom; calls notify */
|
||||
public synchronized void setInterval(int interval) {
|
||||
long now = _context.clock().now();
|
||||
lastHeardFrom = now;
|
||||
consecFails = 0;
|
||||
this.interval = interval;
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
/** sets heardFrom; calls notify */
|
||||
public synchronized void gotError() {
|
||||
long now = _context.clock().now();
|
||||
lastHeardFrom = now;
|
||||
consecFails = 0;
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
/** doubled for each consecutive failure */
|
||||
public synchronized long getTimeout() {
|
||||
return DEFAULT_TIMEOUT << Math.min(consecFails, 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UDP Tracker " + host + ':' + port + " hasDest? " + (dest != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for replies
|
||||
*/
|
||||
private class ReplyWaiter extends SimpleTimer2.TimedEvent {
|
||||
private final int tid;
|
||||
private final Tracker sentTo;
|
||||
private final byte[] data;
|
||||
private TrackerResponse replyObject;
|
||||
private WaitState state = WaitState.INIT;
|
||||
|
||||
/**
|
||||
* Either wait on this object with a timeout, or use non-null Runnables.
|
||||
* Any sent data to be remembered may be stored by setSentObject().
|
||||
* Reply object may be in getReplyObject().
|
||||
*/
|
||||
public ReplyWaiter(int tid, Tracker tracker, byte[] payload, long toWait) {
|
||||
super(SimpleTimer2.getInstance(), toWait);
|
||||
this.tid = tid;
|
||||
sentTo = tracker;
|
||||
data = payload;
|
||||
}
|
||||
|
||||
public int getID() {
|
||||
return tid;
|
||||
}
|
||||
|
||||
public Tracker getSentTo() {
|
||||
return sentTo;
|
||||
}
|
||||
|
||||
public byte[] getPayload() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return may be null depending on what happened. Cast to expected type.
|
||||
*/
|
||||
public synchronized TrackerResponse getReplyObject() {
|
||||
return replyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, we got a reply, and getReplyObject() may contain something.
|
||||
*/
|
||||
public synchronized WaitState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will notify this.
|
||||
* Also removes from _sentQueries and calls heardFrom().
|
||||
* Sets state to SUCCESS or FAIL.
|
||||
*/
|
||||
public synchronized void gotReply(boolean success) {
|
||||
cancel();
|
||||
_sentQueries.remove(Integer.valueOf(tid));
|
||||
setState(success ? WaitState.SUCCESS : WaitState.FAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will notify this and run onReply.
|
||||
* Also removes from _sentQueries and calls heardFrom().
|
||||
*/
|
||||
private synchronized void setState(WaitState state) {
|
||||
this.state = state;
|
||||
this.notifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will notify this.
|
||||
* Also removes from _sentQueries and calls heardFrom().
|
||||
* Sets state to SUCCESS.
|
||||
*/
|
||||
public synchronized void gotResponse(TrackerResponse resp) {
|
||||
replyObject = resp;
|
||||
gotReply(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets state to INIT.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void schedule(long toWait) {
|
||||
state = WaitState.INIT;
|
||||
super.schedule(toWait);
|
||||
}
|
||||
|
||||
/** timer callback on timeout */
|
||||
public synchronized void timeReached() {
|
||||
// don't trump success or failure
|
||||
if (state != WaitState.INIT)
|
||||
return;
|
||||
//if (action == ACTION_CONNECT)
|
||||
// sentTo.connFailed();
|
||||
//else
|
||||
sentTo.replyTimeout();
|
||||
setState(WaitState.TIMEOUT);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("timeout waiting for reply from " + sentTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Waiting for ID: " + tid + " to: " + sentTo + " state: " + state;
|
||||
}
|
||||
}
|
||||
}
|
@ -32,14 +32,15 @@ public class ConfigUIHelper {
|
||||
{ "cs", "cz", "Čeština", null },
|
||||
{ "zh", "cn", "Chinese 中文", null },
|
||||
//{ "zh_TW", "tw", "Chinese 中文", "Taiwan" },
|
||||
//{ "da", "dk", "Dansk", null },
|
||||
{ "da", "dk", "Dansk", null },
|
||||
{ "de", "de", "Deutsch", null },
|
||||
//{ "et", "ee", "Eesti", null },
|
||||
{ "en", "us", "English", null },
|
||||
{ "es", "es", "Español", null },
|
||||
{ "fa", "ir", "Persian فارسی", null },
|
||||
{ "fr", "fr", "Français", null },
|
||||
//{ "gl", "lang_gl", "Galego", null },
|
||||
//{ "el", "gr", "Greek Ελληνικά", null },
|
||||
{ "el", "gr", "Greek Ελληνικά", null },
|
||||
{ "in", "id", "bahasa Indonesia", null },
|
||||
{ "it", "it", "Italiano", null },
|
||||
{ "ja", "jp", "Japanese 日本語", null },
|
||||
|
@ -4,9 +4,12 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.apps.systray.UrlLauncher;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.jetty.I2PLogger;
|
||||
import net.i2p.jetty.JettyStart;
|
||||
|
||||
/**
|
||||
@ -29,6 +32,13 @@ public class RunStandalone {
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
_context = new I2PAppContext(p);
|
||||
// Do this after we have a context
|
||||
// To take effect, must be set before any Jetty classes are loaded
|
||||
try {
|
||||
Log.setLog(new I2PLogger(_context));
|
||||
} catch (Throwable t) {
|
||||
System.err.println("INFO: I2P Jetty logging class not found, logging to stdout");
|
||||
}
|
||||
File base = _context.getBaseDir();
|
||||
File xml = new File(base, "jetty-i2psnark.xml");
|
||||
_jettyStart = new JettyStart(_context, null, new String[] { xml.getAbsolutePath() } );
|
||||
|
@ -3,12 +3,14 @@ package org.klomp.snark.web;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.Collator;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -22,6 +24,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
@ -35,8 +38,10 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.servlet.util.ServletUtil;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
import net.i2p.util.Translate;
|
||||
import net.i2p.util.UIMessages;
|
||||
@ -251,11 +256,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
} else {
|
||||
String base = addPaths(req.getRequestURI(), "/");
|
||||
boolean showEdit = req.getParameter("showEdit") != null;
|
||||
String listing = getListHTML(resource, base, true, method.equals("POST") ? req.getParameterMap() : null,
|
||||
req.getParameter("sort"));
|
||||
req.getParameter("sort"), showEdit);
|
||||
if (method.equals("POST")) {
|
||||
// P-R-G
|
||||
sendRedirect(req, resp, "");
|
||||
sendRedirect(req, resp, showEdit ? "?showEdit" : "");
|
||||
} else if (listing != null) {
|
||||
setHTMLHeaders(resp, cspNonce, true);
|
||||
resp.getWriter().write(listing);
|
||||
@ -343,25 +349,24 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("</head>\n" +
|
||||
"<body>" +
|
||||
"<center>");
|
||||
List<Tracker> sortedTrackers = null;
|
||||
if (isConfigure) {
|
||||
out.write("<div class=\"snarknavbar\"><a href=\"" + _contextPath + "/\" title=\"");
|
||||
out.write(_t("Torrents"));
|
||||
out.write("\" class=\"snarkNav nav_main\">");
|
||||
if (_contextName.equals(DEFAULT_NAME))
|
||||
out.write(_t("I2PSnark"));
|
||||
else
|
||||
out.write(_contextName);
|
||||
out.write("</a>");
|
||||
} else {
|
||||
out.write("<div class=\"snarknavbar\"><a href=\"" + _contextPath + '/' + peerString + "\" title=\"");
|
||||
out.write(_t("Refresh page"));
|
||||
out.write("\" class=\"snarkNav nav_main\">");
|
||||
if (_contextName.equals(DEFAULT_NAME))
|
||||
out.write(_t("I2PSnark"));
|
||||
else
|
||||
out.write(_contextName);
|
||||
out.write("</a>\n");
|
||||
}
|
||||
out.write("\" class=\"snarkNav nav_main\">");
|
||||
if (_contextName.equals(DEFAULT_NAME))
|
||||
out.write(_t("I2PSnark"));
|
||||
else
|
||||
out.write(_contextName);
|
||||
if (!_context.isRouterContext()) {
|
||||
out.write(' ' + CoreVersion.VERSION);
|
||||
}
|
||||
out.write("</a>");
|
||||
List<Tracker> sortedTrackers = null;
|
||||
if (!isConfigure) {
|
||||
sortedTrackers = _manager.getSortedTrackers();
|
||||
if (_context.isRouterContext() && _manager.hasModifiedTrackers()) {
|
||||
for (Tracker t : sortedTrackers) {
|
||||
@ -2511,12 +2516,18 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("<select name='theme'>");
|
||||
String theme = _manager.getTheme();
|
||||
String[] themes = _manager.getThemes();
|
||||
Arrays.sort(themes);
|
||||
// translated sort
|
||||
Map<String, String> tmap = new TreeMap<String, String>(Collator.getInstance());
|
||||
for (int i = 0; i < themes.length; i++) {
|
||||
if(themes[i].equals(theme))
|
||||
out.write("\n<OPTION value=\"" + themes[i] + "\" SELECTED>" + themes[i]);
|
||||
tmap.put(_t(themes[i]), themes[i]);
|
||||
}
|
||||
for (Map.Entry<String, String> e : tmap.entrySet()) {
|
||||
String tr = e.getKey();
|
||||
String opt = e.getValue();
|
||||
if(opt.equals(theme))
|
||||
out.write("\n<option value=\"" + opt + "\" SELECTED>" + tr + "</option>");
|
||||
else
|
||||
out.write("\n<OPTION value=\"" + themes[i] + "\">" + themes[i]);
|
||||
out.write("\n<option value=\"" + opt + "\">" + tr + "</option>");
|
||||
}
|
||||
out.write("</select>\n");
|
||||
}
|
||||
@ -2984,8 +2995,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
* @return String of HTML or null if postParams != null
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams, String sortParam)
|
||||
throws IOException
|
||||
private String getListHTML(File xxxr, String base, boolean parent, Map<String, String[]> postParams,
|
||||
String sortParam, boolean showEdit) throws IOException
|
||||
{
|
||||
String decodedBase = decodePath(base);
|
||||
String title = decodedBase;
|
||||
@ -3027,6 +3038,10 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
_manager.startTorrent(snark);
|
||||
} else if (postParams.get("recheck") != null) {
|
||||
_manager.recheckTorrent(snark);
|
||||
} else if (postParams.get("editTorrent") != null) {
|
||||
saveTorrentEdit(snark, postParams);
|
||||
} else if (postParams.get("showEdit") != null) {
|
||||
// P-R-G only
|
||||
} else {
|
||||
_manager.addMessage("Unknown command");
|
||||
}
|
||||
@ -3055,7 +3070,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
r = new File("");
|
||||
}
|
||||
|
||||
boolean showStopStart = snark != null;
|
||||
boolean showStopStart = snark != null && !showEdit;
|
||||
Storage storage = snark != null ? snark.getStorage() : null;
|
||||
boolean showPriority = storage != null && !storage.complete() &&
|
||||
r.isDirectory();
|
||||
@ -3093,7 +3108,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
final boolean includeForm = showStopStart || showPriority || er || ec;
|
||||
if (includeForm) {
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n" +
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\">\n");
|
||||
if (sortParam != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"sort\" value=\"")
|
||||
.append(DataHelper.stripHTML(sortParam)).append("\" >\n");
|
||||
@ -3103,7 +3118,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// first table - torrent info
|
||||
buf.append("<table class=\"snarkTorrentInfo\">\n" +
|
||||
"<tr><th></th><th><b>")
|
||||
.append(_t("Torrent"))
|
||||
.append(showEdit ? _t("Edit Torrent") : _t("Torrent"))
|
||||
.append("</b></th><th>")
|
||||
.append(DataHelper.escapeHTML(snark.getBaseName()))
|
||||
.append("</th></tr>\n");
|
||||
@ -3117,7 +3132,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append("</b></td><td><a href=\"").append(_contextPath).append('/').append(baseName).append("\">")
|
||||
.append(DataHelper.escapeHTML(fullPath))
|
||||
.append("</a></td></tr>\n");
|
||||
if (storage != null) {
|
||||
if (storage != null && !showEdit) {
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "file");
|
||||
buf.append("</td><td><b>")
|
||||
@ -3127,17 +3142,19 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
String hex = I2PSnarkUtil.toHex(snark.getInfoHash());
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Info hash"))
|
||||
.append("</b></td><td><span id=\"infohash\">")
|
||||
.append(hex.toUpperCase(Locale.US))
|
||||
.append("</span></td></tr>\n");
|
||||
if (!showEdit) {
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Info hash"))
|
||||
.append("</b></td><td><span id=\"infohash\">")
|
||||
.append(hex.toUpperCase(Locale.US))
|
||||
.append("</span></td></tr>\n");
|
||||
}
|
||||
|
||||
String announce = null;
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta != null) {
|
||||
if (meta != null && !showEdit) {
|
||||
announce = meta.getAnnounce();
|
||||
if (announce == null)
|
||||
announce = snark.getTrackerURL();
|
||||
@ -3218,7 +3235,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Comment")).append("</b></td><td>")
|
||||
.append(DataHelper.stripHTML(com))
|
||||
.append(DataHelper.escapeHTML(com).replace("\r\n", "<br>").replace("\n", "<br>"))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
long dat = meta.getCreationDate();
|
||||
@ -3239,7 +3256,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Created By")).append("</b></td><td>")
|
||||
.append(DataHelper.stripHTML(cby))
|
||||
.append(DataHelper.escapeHTML(cby))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
long[] dates = _manager.getSavedAddedAndCompleted(snark);
|
||||
@ -3275,6 +3292,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
}
|
||||
|
||||
if (!showEdit) { // don't bother to reindent
|
||||
if (meta == null || !meta.isPrivate()) {
|
||||
buf.append("<tr><td><a href=\"")
|
||||
.append(MagnetURI.MAGNET_FULL).append(hex);
|
||||
@ -3377,6 +3395,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append(":</b> ")
|
||||
.append(formatSize(snark.getPieceLength(0)))
|
||||
.append("</span></td></tr>\n");
|
||||
} // !showEdit
|
||||
|
||||
// buttons
|
||||
if (showStopStart) {
|
||||
@ -3390,7 +3409,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("<b>").append(_t("Starting")).append("…</b>");
|
||||
} else if (snark.isAllocating()) {
|
||||
buf.append("<b>").append(_t("Allocating")).append("…</b>");
|
||||
} else {
|
||||
} else if (isTopLevel && !showEdit) {
|
||||
boolean isRunning = !snark.isStopped();
|
||||
buf.append("<input type=\"submit\" value=\"");
|
||||
if (isRunning)
|
||||
@ -3398,14 +3417,23 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
else
|
||||
buf.append(_t("Start")).append("\" name=\"start\" class=\"starttorrent\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"recheck\" value=\"").append(_t("Force Recheck"));
|
||||
if (isRunning)
|
||||
if (isRunning) {
|
||||
buf.append("\" class=\"disabled\" disabled=\"disabled\" title=\"")
|
||||
.append(_t("Stop the torrent in order to check file integrity"))
|
||||
.append("\">\n");
|
||||
else
|
||||
.append(_t("Torrent must be stopped"));
|
||||
} else {
|
||||
buf.append("\" class=\"reload\" title=\"")
|
||||
.append(_t("Check integrity of the downloaded files"))
|
||||
.append("\">\n");
|
||||
.append(_t("Check integrity of the downloaded files"));
|
||||
}
|
||||
buf.append("\">\n" +
|
||||
"<input type=\"submit\" name=\"showEdit\" value=\"").append(_t("Edit Torrent"));
|
||||
if (isRunning) {
|
||||
buf.append("\" class=\"disabled\" disabled=\"disabled\" title=\"")
|
||||
.append(_t("Torrent must be stopped"));
|
||||
} else {
|
||||
buf.append("\" class=\"reload\" title=\"")
|
||||
.append(_t("Add or remove trackers"));
|
||||
}
|
||||
buf.append("\">\n");
|
||||
}
|
||||
boolean showInOrder = storage != null && !storage.complete() &&
|
||||
meta != null;
|
||||
@ -3439,6 +3467,13 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
|
||||
if (snark != null && isTopLevel && showEdit) {
|
||||
// Edit torrent. Show edit section only.
|
||||
displayTorrentEdit(snark, base, buf);
|
||||
buf.append("</form></div></div></center></body></html>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
if (snark != null && !r.exists()) {
|
||||
// fixup TODO
|
||||
buf.append("<table class=\"resourceError\" id=\"DoesNotExist\"><tr><th colspan=\"2\">")
|
||||
@ -3481,7 +3516,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
displayComments(snark, er, ec, esc, buf);
|
||||
if (includeForm)
|
||||
buf.append("</form>");
|
||||
buf.append("</div></div></body></html>");
|
||||
buf.append("</div></div></center></body></html>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@ -3778,7 +3813,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// for stop/start/check
|
||||
if (includeForm)
|
||||
buf.append("</form>");
|
||||
buf.append("</div></div></body></html>");
|
||||
buf.append("</div></div></center></body></html>");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
@ -4150,6 +4185,136 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("</div>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param snark non-null
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private void displayTorrentEdit(Snark snark, String base, StringBuilder buf) {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null)
|
||||
return;
|
||||
buf.append("<div id=\"snarkCommentSection\"><table class=\"snarkTorrentInfo\">\n");
|
||||
//.append("<tr><th colspan=\"5\">")
|
||||
//.append(_t("Edit Torrent"))
|
||||
//.append("</th>")
|
||||
//.append("</tr>");
|
||||
boolean isRunning = !snark.isStopped();
|
||||
if (isRunning) {
|
||||
// shouldn't happen
|
||||
buf.append("<tr><td colspan=\"5\">")
|
||||
.append(_t("Torrent must be stopped"))
|
||||
.append("</td></tr></table></div></form>");
|
||||
return;
|
||||
}
|
||||
String announce = meta.getAnnounce();
|
||||
if (announce == null)
|
||||
announce = snark.getTrackerURL();
|
||||
if (announce != null) {
|
||||
// strip non-i2p trackers
|
||||
if (!isI2PTracker(announce))
|
||||
announce = null;
|
||||
}
|
||||
List<List<String>> alist = meta.getAnnounceList();
|
||||
Set<String> annlist = new TreeSet<String>();
|
||||
if (alist != null && !alist.isEmpty()) {
|
||||
// strip non-i2p trackers
|
||||
for (List<String> alist2 : alist) {
|
||||
for (String s : alist2) {
|
||||
if (isI2PTracker(s))
|
||||
annlist.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (announce != null)
|
||||
annlist.add(announce);
|
||||
if (!annlist.isEmpty()) {
|
||||
buf.append("<tr><td colspan=\"3\"></td><td>").append("Primary").append("</td><td>")
|
||||
.append("Delete").append("</td></tr>");
|
||||
for (String s : annlist) {
|
||||
int hc = s.hashCode();
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Tracker")).append("</b></td><td>");
|
||||
s = DataHelper.stripHTML(s);
|
||||
buf.append("<span class=\"info_tracker\">");
|
||||
buf.append(getShortTrackerLink(s, snark.getInfoHash()));
|
||||
buf.append("</span> ");
|
||||
//buf.append(s);
|
||||
buf.append("</td><td>");
|
||||
buf.append("<input type=\"radio\" class=\"optbox\" name=\"primary\" ");
|
||||
if (s.equals(announce))
|
||||
buf.append("checked=\"checked\" ");
|
||||
buf.append("value=\"").append(hc);
|
||||
buf.append("\"></td><td>");
|
||||
buf.append("<input type=\"checkbox\" class=\"optbox\" name=\"trdelete.")
|
||||
.append(hc).append("\" title=\"").append(_t("Mark for deletion")).append("\">");
|
||||
buf.append("</td></tr>\n");
|
||||
}
|
||||
}
|
||||
|
||||
List<Tracker> newTrackers = _manager.getSortedTrackers();
|
||||
for (Iterator<Tracker> iter = newTrackers.iterator(); iter.hasNext(); ) {
|
||||
Tracker t = iter.next();
|
||||
String announceURL = t.announceURL.replace("=", "=");
|
||||
if (announceURL.equals(announce) || annlist.contains(announceURL))
|
||||
iter.remove();
|
||||
}
|
||||
if (!newTrackers.isEmpty()) {
|
||||
buf.append("<tr><td colspan=\"3\"></td><td>").append("Primary").append("</td><td>")
|
||||
.append("Add").append("</td></tr>");
|
||||
for (Tracker t : newTrackers) {
|
||||
String name = t.name;
|
||||
int hc = t.announceURL.hashCode();
|
||||
String announceURL = t.announceURL.replace("=", "=");
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Add Tracker")).append("</b></td><td>");
|
||||
buf.append(name);
|
||||
buf.append("</td><td><input type=\"radio\" class=\"optbox\" name=\"primary\" value=\"");
|
||||
buf.append(hc);
|
||||
buf.append("\"></td><td>");
|
||||
buf.append("<input type=\"checkbox\" class=\"optbox\" id=\"").append(name).append("\" name=\"tradd.")
|
||||
.append(hc).append("\" title=\"").append(_t("Add tracker")).append("\"> ")
|
||||
.append("</td><td></td></tr>\n");
|
||||
}
|
||||
}
|
||||
|
||||
String com = meta.getComment();
|
||||
if (com == null) {
|
||||
com = "";
|
||||
} else if (com.length() > 0) {
|
||||
com = DataHelper.escapeHTML(com);
|
||||
}
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Comment")).append("</b></td>");
|
||||
buf.append("<td colspan=\"2\" id=\"addCommentText\"><textarea name=\"nofilter_newTorrentComment\" cols=\"88\" rows=\"4\">")
|
||||
.append(com).append("</textarea></td><td></td>");
|
||||
buf.append("</tr>\n");
|
||||
|
||||
String cb = meta.getCreatedBy();
|
||||
if (cb == null) {
|
||||
cb = "";
|
||||
} else if (cb.length() > 0) {
|
||||
cb = DataHelper.escapeHTML(cb);
|
||||
}
|
||||
buf.append("<tr><td>");
|
||||
toThemeImg(buf, "details");
|
||||
buf.append("</td><td><b>")
|
||||
.append(_t("Created By")).append("</b></td>");
|
||||
buf.append("<td id=\"editTorrentCreatedBy\"><input type=\"text\" name=\"nofilter_newTorrentCreatedBy\" cols=\"44\" rows=\"1\" value=\"")
|
||||
.append(cb).append("\"></td></tr>");
|
||||
|
||||
buf.append("<tr id=\"torrentInfoControl\"><td colspan=\"5\">");
|
||||
buf.append("<input type=\"submit\" name=\"editTorrent\" value=\"");
|
||||
buf.append(_t("Save Changes"));
|
||||
buf.append("\" class=\"accept\"></td></tr>\n");
|
||||
buf.append("</table></div>");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param so null ok
|
||||
* @return query string or ""
|
||||
@ -4370,6 +4535,147 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
_manager.setSavedCommentsEnabled(snark, yes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
private void saveTorrentEdit(Snark snark, Map<String, String[]> postParams) {
|
||||
if (!snark.isStopped()) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage(_t("Torrent must be stopped"));
|
||||
return;
|
||||
}
|
||||
List<Integer> toAdd = new ArrayList<Integer>();
|
||||
List<Integer> toDel = new ArrayList<Integer>();
|
||||
Integer primary = null;
|
||||
String newComment = "";
|
||||
String newCreatedBy = "";
|
||||
for (Map.Entry<String, String[]> entry : postParams.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String val = entry.getValue()[0]; // jetty arrays
|
||||
if (key.startsWith("tradd.")) {
|
||||
try {
|
||||
toAdd.add(Integer.parseInt(key.substring(6)));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.startsWith("trdelete.")) {
|
||||
try {
|
||||
toDel.add(Integer.parseInt(key.substring(9)));
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.equals("primary")) {
|
||||
try {
|
||||
primary = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
} else if (key.equals("nofilter_newTorrentComment")) {
|
||||
newComment = val.trim();
|
||||
} else if (key.equals("nofilter_newTorrentCreatedBy")) {
|
||||
newCreatedBy = val.trim();
|
||||
}
|
||||
}
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage("Can't edit magnet");
|
||||
return;
|
||||
}
|
||||
String oldPrimary = meta.getAnnounce();
|
||||
String oldComment = meta.getComment();
|
||||
if (oldComment == null)
|
||||
oldComment = "";
|
||||
String oldCreatedBy = meta.getCreatedBy();
|
||||
if (oldCreatedBy == null)
|
||||
oldCreatedBy = "";
|
||||
if (toAdd.isEmpty() && toDel.isEmpty() &&
|
||||
(primary == null || primary.equals(oldPrimary)) &&
|
||||
oldComment.equals(newComment) &&
|
||||
oldCreatedBy.equals(newCreatedBy)) {
|
||||
_manager.addMessage("No changes to torrent, not saved");
|
||||
return;
|
||||
}
|
||||
List<List<String>> alist = meta.getAnnounceList();
|
||||
Set<String> annlist = new TreeSet<String>();
|
||||
if (alist != null && !alist.isEmpty()) {
|
||||
// strip non-i2p trackers
|
||||
for (List<String> alist2 : alist) {
|
||||
for (String s : alist2) {
|
||||
if (isI2PTracker(s))
|
||||
annlist.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldPrimary != null)
|
||||
annlist.add(oldPrimary);
|
||||
List<Tracker> newTrackers = _manager.getSortedTrackers();
|
||||
for (Integer i : toDel) {
|
||||
int hc = i.intValue();
|
||||
for (Iterator<String> iter = annlist.iterator(); iter.hasNext(); ) {
|
||||
String s = iter.next();
|
||||
if (s.hashCode() == hc)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
for (Integer i : toAdd) {
|
||||
int hc = i.intValue();
|
||||
for (Tracker t : newTrackers) {
|
||||
if (t.announceURL.hashCode() == hc) {
|
||||
annlist.add(t.announceURL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
String thePrimary = oldPrimary;
|
||||
if (primary != null) {
|
||||
int hc = primary.intValue();
|
||||
for (String s : annlist) {
|
||||
if (s.hashCode() == hc) {
|
||||
thePrimary = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
List<List<String>> newAnnList;
|
||||
if (annlist.isEmpty()) {
|
||||
newAnnList = null;
|
||||
thePrimary = null;
|
||||
} else {
|
||||
List<String> aalist = new ArrayList<String>(annlist);
|
||||
newAnnList = Collections.singletonList(aalist);
|
||||
if (!aalist.contains(thePrimary))
|
||||
thePrimary = aalist.get(0);
|
||||
}
|
||||
if (newComment.equals(""))
|
||||
newComment = null;
|
||||
if (newCreatedBy.equals(""))
|
||||
newCreatedBy = null;
|
||||
MetaInfo newMeta = new MetaInfo(thePrimary, meta.getName(), null, meta.getFiles(), meta.getLengths(),
|
||||
meta.getPieceLength(0), meta.getPieceHashes(), meta.getTotalLength(), meta.isPrivate(),
|
||||
newAnnList, newCreatedBy, meta.getWebSeedURLs(), newComment);
|
||||
if (!DataHelper.eq(meta.getInfoHash(), newMeta.getInfoHash())) {
|
||||
// shouldn't happen
|
||||
_manager.addMessage("Torrent edit failed, infohash mismatch");
|
||||
return;
|
||||
}
|
||||
File f = new File(_manager.util().getTempDir(), "edit-" + _manager.util().getContext().random().nextLong() + ".torrent");
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new SecureFileOutputStream(f);
|
||||
out.write(newMeta.getTorrentData());
|
||||
out.close();
|
||||
boolean ok = FileUtil.rename(f, new File(snark.getName()));
|
||||
if (!ok) {
|
||||
_manager.addMessage("Save edit changes failed");
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
try { if (out != null) out.close(); } catch (IOException ioe2) {}
|
||||
_manager.addMessage("Save edit changes failed: " + ioe);
|
||||
return;
|
||||
} finally {
|
||||
f.delete();
|
||||
}
|
||||
snark.replaceMetaInfo(newMeta);
|
||||
_manager.addMessage("Torrent changes saved");
|
||||
}
|
||||
|
||||
|
||||
/** @since 0.9.32 */
|
||||
private static boolean noCollapsePanels(HttpServletRequest req) {
|
||||
// check for user agents that can't toggle the collapsible panels...
|
||||
|
@ -4,5 +4,33 @@
|
||||
# The file jetty-i2psnark.xml must be present in the current directory.
|
||||
# i2psnark will be accessed at http://127.0.0.1:8002/
|
||||
#
|
||||
|
||||
# Raise the soft open files soft ulimit to this value, if able
|
||||
OPEN_FILES_ULIMIT=2048
|
||||
|
||||
raiseopenfilesulimit() {
|
||||
OPEN_FILES_SOFT=`ulimit -S -n` 2> /dev/null || return
|
||||
if [ "$OPEN_FILES_SOFT" != "unlimited" ]
|
||||
then
|
||||
if [ "$OPEN_FILES_ULIMIT" -gt "$OPEN_FILES_SOFT" ]
|
||||
then
|
||||
OPEN_FILES_HARD=`ulimit -H -n` 2> /dev/null || return
|
||||
if [ "$OPEN_FILES_HARD" != "unlimited" ]
|
||||
then
|
||||
if [ "$OPEN_FILES_ULIMIT" -gt "$OPEN_FILES_HARD" ]
|
||||
then
|
||||
OPEN_FILES_ULIMIT="$OPEN_FILES_HARD"
|
||||
fi
|
||||
fi
|
||||
if [ "$OPEN_FILES_ULIMIT" -gt "$OPEN_FILES_SOFT" ]
|
||||
then
|
||||
ulimit -S -n "$OPEN_FILES_ULIMIT" > /dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
raiseopenfilesulimit
|
||||
|
||||
I2P="."
|
||||
java -jar "$I2P/i2psnark.jar"
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
3gpp = video/3gpp
|
||||
7z = application/x-7z-compressed
|
||||
ape = audio/x-monkeys-audio
|
||||
avif = image/avif
|
||||
bz2 = application/x-bzip2
|
||||
cue = application/x-cue
|
||||
dff = audio/x-dsd
|
||||
|
@ -4,6 +4,10 @@ java -jar i2psnark.jar
|
||||
|
||||
I2PSnark web ui will be at http://127.0.0.1:8002/i2psnark/
|
||||
|
||||
To change or disable browser launch at startup, edit i2psnark-appctx.config.
|
||||
To change the port, edit jetty-i2psnark.xml.
|
||||
|
||||
|
||||
I2PSnark is GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
|
||||
(https://geti2p.net/) within a webserver (such as the bundled Jetty from
|
||||
https://www.eclipse.org/jetty/). For more information about I2PSnark, get in touch
|
||||
@ -19,7 +23,7 @@ To add RPC support:
|
||||
to the webapps/ directory in your standalone install.
|
||||
|
||||
2b) If you do not have the i2psnark-rpc plugin installed, get the i2p.plugins.i2psnark-rpc
|
||||
branch out of monotone, build with 'ant war', and copy the file src/build/transmission.war.jar
|
||||
branch out of git, build with 'ant war', and copy the file src/build/transmission.war.jar
|
||||
to the file webapps/transmission.war in your standalone install.
|
||||
|
||||
3) Start i2psnark standalone as usual. The transmission web interface will be at
|
||||
|
@ -354,8 +354,9 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
|
||||
|
||||
/**
|
||||
* @return A copy, unmodifiable, non-null
|
||||
* @since public since 0.9.53 for advanced plugin usage, was package private
|
||||
*/
|
||||
List<I2PSession> getSessions() {
|
||||
public List<I2PSession> getSessions() {
|
||||
if (_sessions.isEmpty())
|
||||
return Collections.emptyList();
|
||||
return new ArrayList<I2PSession>(_sessions);
|
||||
|
@ -148,6 +148,9 @@ public class TunnelController implements Logging {
|
||||
|
||||
private static final String OPT_ENCTYPE = PFX_OPTION + "i2cp.leaseSetEncType";
|
||||
|
||||
/** @since 0.9.53 */
|
||||
private static final String OPT_PRIORITY = PFX_OPTION + "outbound.priority";
|
||||
|
||||
/** all of these @since 0.9.14 */
|
||||
public static final String TYPE_CONNECT = "connectclient";
|
||||
public static final String TYPE_HTTP_BIDIR_SERVER = "httpbidirserver";
|
||||
@ -231,6 +234,15 @@ public class TunnelController implements Logging {
|
||||
_state = keyOK && getStartOnLoad() ? TunnelState.START_ON_LOAD : TunnelState.STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The I2PTunnel
|
||||
*
|
||||
* @since 0.9.53 for advanced plugin usage
|
||||
*/
|
||||
public I2PTunnel getTunnel() {
|
||||
return _tunnel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
*/
|
||||
@ -902,6 +914,12 @@ public class TunnelController implements Logging {
|
||||
if (!_config.containsKey(OPT_SIG_TYPE))
|
||||
_config.setProperty(OPT_SIG_TYPE, PREFERRED_SIGTYPE.name());
|
||||
}
|
||||
if (type.equals(TYPE_IRC_CLIENT) || type.equals(TYPE_STD_CLIENT) ||
|
||||
type.equals(TYPE_IRC_SERVER) || type.equals(TYPE_STD_SERVER) ||
|
||||
type.equals(TYPE_SOCKS_IRC)) {
|
||||
if (!_config.containsKey(OPT_PRIORITY))
|
||||
_config.setProperty(OPT_PRIORITY, "10");
|
||||
}
|
||||
if (!isClient(type)) {
|
||||
_tunnel.filterDefinition = _config.getProperty(PROP_FILTER);
|
||||
|
||||
|
@ -122,10 +122,11 @@ class AccessFilter implements StatefulConnectionFilter {
|
||||
|
||||
synchronized(knownDests) {
|
||||
knownDests.keySet().retainAll(tmp.keySet());
|
||||
for (Hash newHash : tmp.keySet()) {
|
||||
for (Map.Entry<Hash, DestTracker> e : tmp.entrySet()) {
|
||||
Hash newHash = e.getKey();
|
||||
if (knownDests.containsKey(newHash))
|
||||
continue;
|
||||
knownDests.put(newHash, tmp.get(newHash));
|
||||
knownDests.put(newHash, e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.i2p.i2ptunnel.access;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import java.io.File;
|
||||
@ -18,6 +19,7 @@ import net.i2p.data.Hash;
|
||||
class FileFilterDefinitionElement extends FilterDefinitionElement {
|
||||
|
||||
private final File file;
|
||||
private final Map<Hash, DestTracker> lastLoaded = new HashMap<>();
|
||||
private volatile long lastLoading;
|
||||
|
||||
/**
|
||||
@ -31,18 +33,36 @@ class FileFilterDefinitionElement extends FilterDefinitionElement {
|
||||
|
||||
@Override
|
||||
public void update(Map<Hash, DestTracker> map) throws IOException {
|
||||
if (!(file.exists() && file.isFile() && file.lastModified() > lastLoading))
|
||||
if (!(file.exists() && file.isFile()))
|
||||
return;
|
||||
if (file.lastModified() <= lastLoading) {
|
||||
synchronized (lastLoaded) {
|
||||
for (Map.Entry<Hash, DestTracker> entry : lastLoaded.entrySet()) {
|
||||
if (!map.containsKey(entry.getKey()))
|
||||
map.put(entry.getKey(),entry.getValue());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastLoading = System.currentTimeMillis();
|
||||
BufferedReader reader = null;
|
||||
synchronized (lastLoaded) {
|
||||
lastLoaded.clear();
|
||||
}
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new FileReader(file));
|
||||
reader = new BufferedReader(new FileReader(file));
|
||||
String b32;
|
||||
while((b32 = reader.readLine()) != null) {
|
||||
Hash hash = fromBase32(b32);
|
||||
if (map.containsKey(hash))
|
||||
continue;
|
||||
map.put(hash, new DestTracker(hash, threshold));
|
||||
DestTracker newTracker = new DestTracker(hash, threshold);
|
||||
map.put(hash, newTracker);
|
||||
synchronized (lastLoaded) {
|
||||
lastLoaded.put(hash, newTracker);
|
||||
}
|
||||
}
|
||||
} catch (InvalidDefinitionException bad32) {
|
||||
throw new IOException("invalid access list entry", bad32);
|
||||
|
@ -59,13 +59,17 @@ abstract class IRCFilter {
|
||||
*/
|
||||
public static String inboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
|
||||
|
||||
String field[] = DataHelper.split(s, " ", 4);
|
||||
String field[] = DataHelper.split(s, " ", 5);
|
||||
String command;
|
||||
int idx=0;
|
||||
|
||||
|
||||
try {
|
||||
if (field[0].charAt(0) == ':')
|
||||
// https://www.unrealircd.org/docs/Message_tags
|
||||
// https://ircv3.net/specs/extensions/message-tags.html
|
||||
if (field[0].charAt(0) == '@')
|
||||
idx++;
|
||||
if (field[idx].charAt(0) == ':')
|
||||
idx++;
|
||||
command = field[idx++].toUpperCase(Locale.US);
|
||||
} catch (IndexOutOfBoundsException ioobe) {
|
||||
@ -279,7 +283,7 @@ abstract class IRCFilter {
|
||||
*/
|
||||
public static String outboundFilter(String s, StringBuffer expectedPong, DCCHelper helper) {
|
||||
|
||||
String field[] = DataHelper.split(s, " ",3);
|
||||
String field[] = DataHelper.split(s, " ", 4);
|
||||
|
||||
if(field[0].length()==0)
|
||||
return null; // W T F?
|
||||
@ -288,7 +292,12 @@ abstract class IRCFilter {
|
||||
if(field[0].charAt(0)==':')
|
||||
return null; // ???
|
||||
|
||||
String command = field[0].toUpperCase(Locale.US);
|
||||
int idx = 0;
|
||||
// https://www.unrealircd.org/docs/Message_tags
|
||||
// https://ircv3.net/specs/extensions/message-tags.html
|
||||
if (field[0].charAt(0) == '@')
|
||||
idx++;
|
||||
String command = field[idx++].toUpperCase(Locale.US);
|
||||
|
||||
if ("PING".equals(command)) {
|
||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||
@ -304,17 +313,17 @@ abstract class IRCFilter {
|
||||
|
||||
String rv = null;
|
||||
expectedPong.setLength(0);
|
||||
if (field.length == 1) { // PING
|
||||
if (field.length == idx) { // PING
|
||||
rv = "PING";
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG 127.0.0.1");
|
||||
} else if (field.length == 2) { // PING nonce
|
||||
rv = "PING " + field[1];
|
||||
} else if (field.length == idx + 1) { // PING nonce
|
||||
rv = "PING " + field[idx];
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG ").append(field[1]);
|
||||
} else if (field.length == 3) { // PING nonce serverLocation
|
||||
rv = "PING " + field[1];
|
||||
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
|
||||
} else if (field.length == idx + 2) { // PING nonce serverLocation
|
||||
rv = "PING " + field[idx];
|
||||
expectedPong.append("PONG ").append(field[idx + 1]).append(" :").append(field[idx]); // PONG serverLocation nonce
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.ERROR))
|
||||
// _log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
||||
@ -335,20 +344,20 @@ abstract class IRCFilter {
|
||||
// in addition to the CTCP version
|
||||
if("NOTICE".equals(command))
|
||||
{
|
||||
if (field.length < 3)
|
||||
if (field.length < idx + 2)
|
||||
return s; // invalid, allow server response
|
||||
String msg = field[2];
|
||||
String msg = field[idx + 1];
|
||||
if(msg.startsWith(":DCC "))
|
||||
return filterDCCOut(field[0] + ' ' + field[1] + " :DCC ", msg.substring(5), helper);
|
||||
return filterDCCOut(field[idx - 1] + ' ' + field[idx] + " :DCC ", msg.substring(5), helper);
|
||||
// fall through
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||
if("PRIVMSG".equals(command) || "NOTICE".equals(command))
|
||||
{
|
||||
if (field.length < 3)
|
||||
if (field.length < idx + 2)
|
||||
return s; // invalid, allow server response
|
||||
String msg = field[2];
|
||||
String msg = field[idx + 1];
|
||||
|
||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||
{
|
||||
@ -369,7 +378,7 @@ abstract class IRCFilter {
|
||||
return s;
|
||||
}
|
||||
if (msg.startsWith("DCC "))
|
||||
return filterDCCOut(field[0] + ' ' + field[1] + " :\001DCC ", msg.substring(4), helper);
|
||||
return filterDCCOut(field[idx - 1] + ' ' + field[idx] + " :\001DCC ", msg.substring(4), helper);
|
||||
// XDCC looks safe, ip/port happens over regular DCC
|
||||
// http://en.wikipedia.org/wiki/XDCC
|
||||
if (msg.toUpperCase(Locale.US).startsWith("XDCC ") && helper != null && helper.isEnabled())
|
||||
@ -382,19 +391,19 @@ abstract class IRCFilter {
|
||||
}
|
||||
|
||||
if("USER".equals(command)) {
|
||||
if (field.length < 3)
|
||||
if (field.length < idx + 2)
|
||||
return s; // invalid, allow server response
|
||||
int idx = field[2].lastIndexOf(':');
|
||||
if(idx<0)
|
||||
int cidx = field[idx + 1].lastIndexOf(':');
|
||||
if (cidx < 0)
|
||||
return "USER user hostname localhost :realname";
|
||||
String realname = field[2].substring(idx+1);
|
||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||
String realname = field[idx + 1].substring(cidx + 1);
|
||||
String ret = "USER " + field[idx] + " hostname localhost :" + realname;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ("PART".equals(command)) {
|
||||
// hide client message
|
||||
return "PART " + field[1] + " :leaving";
|
||||
return "PART " + field[idx] + " :leaving";
|
||||
}
|
||||
|
||||
if ("QUIT".equals(command)) {
|
||||
|
@ -8,12 +8,17 @@ import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Sends to one of many Sinks
|
||||
* Sends to one of many Sinks based on the toPort
|
||||
*
|
||||
* @author zzz modded from streamr/MultiSource
|
||||
*/
|
||||
public class MultiSink<S extends Sink> implements Source, Sink {
|
||||
private final Map<Integer, S> cache;
|
||||
|
||||
public MultiSink(Map<Destination, S> cache) {
|
||||
/**
|
||||
* @param cache map of toPort to Sink
|
||||
*/
|
||||
public MultiSink(Map<Integer, S> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@ -23,18 +28,32 @@ public class MultiSink<S extends Sink> implements Source, Sink {
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* Send to a single sink looked up by toPort
|
||||
*
|
||||
* May throw RuntimeException from underlying sinks
|
||||
*
|
||||
* @param from passed along
|
||||
* @param fromPort passed along
|
||||
* @param toPort passed along
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
Sink s = this.cache.get(from);
|
||||
public void send(Destination from, int fromPort, int toPort, byte[] data) {
|
||||
Sink s = cache.get(toPort);
|
||||
if (s == null && toPort == 0 && cache.size() == 1) {
|
||||
// for now, do the server a favor if the toPort isn't specified
|
||||
for (Sink ss : cache.values()) {
|
||||
s = ss;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s == null) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(MultiSink.class);
|
||||
log.error("No where to go for " + from.calculateHash().toBase64().substring(0, 6));
|
||||
String frm = (from != null) ? from.toBase32() : "raw";
|
||||
if (log.shouldWarn())
|
||||
log.warn("No where to go for " + frm + " port " + fromPort + " to port " + toPort);
|
||||
return;
|
||||
}
|
||||
s.send(from, data);
|
||||
s.send(from, fromPort, toPort, data);
|
||||
}
|
||||
|
||||
private Map<Destination, S> cache;
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Track who the reply goes to
|
||||
* @author zzz
|
||||
*/
|
||||
public class ReplyTracker<S extends Sink> implements Source, Sink {
|
||||
|
||||
public ReplyTracker(S reply, Map<Destination, S> cache) {
|
||||
this.reply = reply;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
this.cache.put(to, this.reply);
|
||||
this.sink.send(to, data);
|
||||
}
|
||||
|
||||
private S reply;
|
||||
private Map<Destination, S> cache;
|
||||
private Sink sink;
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.socks.SOCKS5Constants.AddressType;
|
||||
import net.i2p.util.Addresses;
|
||||
|
||||
/**
|
||||
* Save the SOCKS header from a datagram
|
||||
@ -11,9 +15,12 @@ import net.i2p.data.Destination;
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSHeader {
|
||||
private byte[] header;
|
||||
private static final byte[] beg = {0,0,0,3,60};
|
||||
|
||||
/**
|
||||
* @param data the whole packet
|
||||
* @throws IllegalArgumentException on bad socks format
|
||||
*/
|
||||
public SOCKSHeader(byte[] data) {
|
||||
if (data.length <= 8)
|
||||
@ -24,14 +31,12 @@ public class SOCKSHeader {
|
||||
throw new IllegalArgumentException("We can't handle fragments!");
|
||||
int headerlen = 0;
|
||||
int addressType = data[3];
|
||||
if (addressType == 1) {
|
||||
// this will fail in getDestination()
|
||||
if (addressType == AddressType.IPV4) {
|
||||
headerlen = 6 + 4;
|
||||
} else if (addressType == 3) {
|
||||
} else if (addressType == AddressType.DOMAINNAME) {
|
||||
headerlen = 6 + 1 + (data[4] & 0xff);
|
||||
} else if (addressType == 4) {
|
||||
// this will fail in getDestination()
|
||||
// but future garlicat partial hash lookup possible?
|
||||
} else if (addressType == AddressType.IPV6) {
|
||||
// future garlicat partial hash lookup possible?
|
||||
headerlen = 6 + 16;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown address type: " + addressType);
|
||||
@ -42,33 +47,62 @@ public class SOCKSHeader {
|
||||
this.header = new byte[headerlen];
|
||||
System.arraycopy(data, 0, this.header, 0, headerlen);
|
||||
}
|
||||
|
||||
private static final byte[] beg = {0,0,0,3,60};
|
||||
private static final byte[] end = {0,0};
|
||||
|
||||
/**
|
||||
* Make a dummy header from a dest,
|
||||
* for those cases where we want to receive unsolicited datagrams.
|
||||
* Unused for now.
|
||||
*
|
||||
* @param port I2CP port 0-65535
|
||||
* @since 0.9.53 add port param
|
||||
*/
|
||||
public SOCKSHeader(Destination dest) {
|
||||
this.header = new byte[beg.length + 60 + end.length];
|
||||
public SOCKSHeader(Destination dest, int port) {
|
||||
this.header = new byte[beg.length + 60 + 2];
|
||||
System.arraycopy(beg, 0, this.header, 0, beg.length);
|
||||
String b32 = dest.toBase32();
|
||||
System.arraycopy(DataHelper.getASCII(b32), 0, this.header, beg.length, 60);
|
||||
System.arraycopy(end, 0, this.header, beg.length + 60, end.length);
|
||||
DataHelper.toLong(header, beg.length + 60, 2, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* As of 0.9.53, returns IP address as a string for address types 1 and 4.
|
||||
*
|
||||
* @return hostname or null for unknown address type
|
||||
*/
|
||||
public String getHost() {
|
||||
int addressType = this.header[3];
|
||||
if (addressType != 3)
|
||||
return null;
|
||||
int namelen = (this.header[4] & 0xff);
|
||||
byte[] nameBytes = new byte[namelen];
|
||||
System.arraycopy(this.header, 5, nameBytes, 0, namelen);
|
||||
return DataHelper.getUTF8(nameBytes);
|
||||
if (addressType == AddressType.DOMAINNAME) {
|
||||
int namelen = (this.header[4] & 0xff);
|
||||
return DataHelper.getUTF8(header, 5, namelen);
|
||||
}
|
||||
if (addressType == AddressType.IPV4)
|
||||
return Addresses.toString(Arrays.copyOfRange(header, 4, 4));
|
||||
if (addressType == AddressType.IPV6)
|
||||
return Addresses.toString(Arrays.copyOfRange(header, 4, 16));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 0 - 65535
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public int getPort() {
|
||||
int namelen;
|
||||
int addressType = header[3];
|
||||
if (addressType == 3)
|
||||
namelen = 1 + (header[4] & 0xff);
|
||||
else if (addressType == 1)
|
||||
namelen = 4;
|
||||
else if (addressType == 4)
|
||||
namelen = 16;
|
||||
else
|
||||
return 0;
|
||||
return (int) DataHelper.fromLong(header, 4 + namelen, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return destination or null
|
||||
*/
|
||||
public Destination getDestination() {
|
||||
String name = getHost();
|
||||
if (name == null)
|
||||
@ -80,6 +114,4 @@ public class SOCKSHeader {
|
||||
public byte[] getBytes() {
|
||||
return header;
|
||||
}
|
||||
|
||||
private byte[] header;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.net.InetAddress;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketAddress;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
|
||||
@ -14,22 +15,26 @@ import net.i2p.i2ptunnel.udp.*;
|
||||
* ports, it happens outside of here.
|
||||
*
|
||||
* TX:
|
||||
* UDPSource -> SOCKSUDPUnwrapper -> ReplyTracker ( -> I2PSink in SOCKSUDPTunnel)
|
||||
* UDPSource -> SOCKSUDPUnwrapper -> (I2PSink in SOCKSUDPTunnel)
|
||||
*
|
||||
* RX:
|
||||
* UDPSink <- SOCKSUDPWrapper ( <- MultiSink <- I2PSource in SOCKSUDPTunnel)
|
||||
*
|
||||
* The Unwrapper passes headers to the Wrapper through a cache.
|
||||
* The ReplyTracker passes sinks to MultiSink through a cache.
|
||||
* MultiSink routes packets based on toPort.
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPPort implements Source, Sink {
|
||||
private final UDPSink udpsink;
|
||||
private final UDPSource udpsource;
|
||||
private final SOCKSUDPWrapper wrapper;
|
||||
private final SOCKSUDPUnwrapper unwrapper;
|
||||
|
||||
public SOCKSUDPPort(InetAddress host, int port, Map<Destination, SOCKSUDPPort> replyMap) {
|
||||
public SOCKSUDPPort(InetAddress host, int port, Map<Integer, SOCKSUDPPort> replyMap) {
|
||||
|
||||
// this passes the host and port from UDPUnwrapper to UDPWrapper
|
||||
Map<Destination, SOCKSHeader> cache = new ConcurrentHashMap<Destination, SOCKSHeader>(4);
|
||||
Map<I2PSocketAddress, SOCKSHeader> cache = new ConcurrentHashMap<I2PSocketAddress, SOCKSHeader>(4);
|
||||
|
||||
// rcv from I2P and send to a port
|
||||
this.wrapper = new SOCKSUDPWrapper(cache);
|
||||
@ -41,8 +46,6 @@ public class SOCKSUDPPort implements Source, Sink {
|
||||
this.udpsource = new UDPSource(sock);
|
||||
this.unwrapper = new SOCKSUDPUnwrapper(cache);
|
||||
this.udpsource.setSink(this.unwrapper);
|
||||
this.udptracker = new ReplyTracker<SOCKSUDPPort>(this, replyMap);
|
||||
this.unwrapper.setSink(this.udptracker);
|
||||
}
|
||||
|
||||
/** Socks passes this back to the client on the TCP connection */
|
||||
@ -51,7 +54,7 @@ public class SOCKSUDPPort implements Source, Sink {
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.udptracker.setSink(sink);
|
||||
this.unwrapper.setSink(sink);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@ -66,16 +69,14 @@ public class SOCKSUDPPort implements Source, Sink {
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sink
|
||||
|
||||
* @param from will be passed along
|
||||
* @param fromPort will be passed along
|
||||
* @param toPort will be passed along
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
this.wrapper.send(from, data);
|
||||
public void send(Destination from, int fromPort, int toPort, byte[] data) {
|
||||
this.wrapper.send(from, fromPort, toPort, data);
|
||||
}
|
||||
|
||||
|
||||
private UDPSink udpsink;
|
||||
private UDPSource udpsource;
|
||||
private SOCKSUDPWrapper wrapper;
|
||||
private SOCKSUDPUnwrapper unwrapper;
|
||||
private ReplyTracker<SOCKSUDPPort> udptracker;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import java.net.InetAddress;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
|
||||
@ -12,18 +13,24 @@ import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase;
|
||||
* A Datagram Tunnel that can have multiple bidirectional ports on the UDP side.
|
||||
*
|
||||
* TX:
|
||||
* (ReplyTracker in multiple SOCKSUDPPorts -> ) I2PSink
|
||||
* (multiple SOCKSUDPPorts -> ) I2PSink
|
||||
*
|
||||
* RX:
|
||||
* (SOCKSUDPWrapper in multiple SOCKSUDPPorts <- ) MultiSink <- I2PSource
|
||||
*
|
||||
* The reply from a dest goes to the last SOCKSUDPPort that sent to that dest.
|
||||
* If multiple ports are talking to a dest at the same time, this isn't
|
||||
* going to work very well.
|
||||
* The replies must be to the same I2CP toPort as the outbound fromPort.
|
||||
* If the server does not honor that, the replies will be dropped.
|
||||
*
|
||||
* The replies must be repliable. Raw datagrams are not supported, and would
|
||||
* require a unique source port for each target.
|
||||
*
|
||||
* Preliminary, untested, possibly incomplete.
|
||||
*
|
||||
* @author zzz modded from streamr/StreamrConsumer
|
||||
*/
|
||||
public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
|
||||
private final Map<Integer, SOCKSUDPPort> ports;
|
||||
private final MultiSink<SOCKSUDPPort> demuxer;
|
||||
|
||||
/**
|
||||
* Set up a tunnel with no UDP side yet.
|
||||
@ -33,15 +40,14 @@ public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
|
||||
super(null, tunnel, tunnel, tunnel);
|
||||
|
||||
this.ports = new ConcurrentHashMap<Integer, SOCKSUDPPort>(1);
|
||||
this.cache = new ConcurrentHashMap<Destination, SOCKSUDPPort>(1);
|
||||
this.demuxer = new MultiSink<SOCKSUDPPort>(this.cache);
|
||||
this.demuxer = new MultiSink<SOCKSUDPPort>(ports);
|
||||
setSink(this.demuxer);
|
||||
}
|
||||
|
||||
|
||||
/** @return the UDP port number */
|
||||
public int add(InetAddress host, int port) {
|
||||
SOCKSUDPPort sup = new SOCKSUDPPort(host, port, this.cache);
|
||||
SOCKSUDPPort sup = new SOCKSUDPPort(host, port, ports);
|
||||
this.ports.put(Integer.valueOf(sup.getPort()), sup);
|
||||
sup.setSink(this);
|
||||
sup.start();
|
||||
@ -52,11 +58,6 @@ public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
|
||||
SOCKSUDPPort sup = this.ports.remove(port);
|
||||
if (sup != null)
|
||||
sup.stop();
|
||||
for (Iterator<Map.Entry<Destination, SOCKSUDPPort>> iter = cache.entrySet().iterator(); iter.hasNext();) {
|
||||
Map.Entry<Destination, SOCKSUDPPort> e = iter.next();
|
||||
if (e.getValue() == sup)
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -81,12 +82,5 @@ public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase {
|
||||
sup.stop();
|
||||
}
|
||||
this.ports.clear();
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private Map<Integer, SOCKSUDPPort> ports;
|
||||
private Map<Destination, SOCKSUDPPort> cache;
|
||||
private MultiSink<SOCKSUDPPort> demuxer;
|
||||
}
|
||||
|
@ -5,20 +5,23 @@ import java.util.Map;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.client.streaming.I2PSocketAddress;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Strip a SOCKS header off a datagram, convert it to a Destination
|
||||
* Strip a SOCKS header off a datagram, convert it to a Destination and port
|
||||
* Ref: RFC 1928
|
||||
*
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
private Sink sink;
|
||||
private final Map<I2PSocketAddress, SOCKSHeader> cache;
|
||||
|
||||
/**
|
||||
* @param cache put headers here to pass to SOCKSUDPWrapper
|
||||
*/
|
||||
public SOCKSUDPUnwrapper(Map<Destination, SOCKSHeader> cache) {
|
||||
public SOCKSUDPUnwrapper(Map<I2PSocketAddress, SOCKSHeader> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@ -31,9 +34,13 @@ public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
/**
|
||||
*
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @param ignored_from ignored
|
||||
* @param fromPort will be passed along
|
||||
* @param toPort ignored
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
public void send(Destination ignored_from, int fromPort, int toPort, byte[] data) {
|
||||
SOCKSHeader h;
|
||||
try {
|
||||
h = new SOCKSHeader(data);
|
||||
@ -50,14 +57,14 @@ public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.put(dest, h);
|
||||
cache.put(new I2PSocketAddress(dest, toPort), h);
|
||||
|
||||
int headerlen = h.getBytes().length;
|
||||
byte unwrapped[] = new byte[data.length - headerlen];
|
||||
System.arraycopy(data, headerlen, unwrapped, 0, unwrapped.length);
|
||||
this.sink.send(dest, unwrapped);
|
||||
// We pass the local DatagramSocket's port through as the I2CP from port,
|
||||
// so that it will come back as the toPort in the reply,
|
||||
// and MultiSink will send it to the right SOCKSUDPWrapper/SOCKSUDPPort
|
||||
this.sink.send(dest, fromPort, h.getPort(), unwrapped);
|
||||
}
|
||||
|
||||
private Sink sink;
|
||||
private Map<Destination, SOCKSHeader> cache;
|
||||
}
|
||||
|
@ -2,8 +2,11 @@ package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketAddress;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.udp.*;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Put a SOCKS header on a datagram
|
||||
@ -12,7 +15,10 @@ import net.i2p.i2ptunnel.udp.*;
|
||||
* @author zzz
|
||||
*/
|
||||
public class SOCKSUDPWrapper implements Source, Sink {
|
||||
public SOCKSUDPWrapper(Map<Destination, SOCKSHeader> cache) {
|
||||
private Sink sink;
|
||||
private final Map<I2PSocketAddress, SOCKSHeader> cache;
|
||||
|
||||
public SOCKSUDPWrapper(Map<I2PSocketAddress, SOCKSHeader> cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@ -26,13 +32,23 @@ public class SOCKSUDPWrapper implements Source, Sink {
|
||||
* Use the cached header, which should have the host string and port
|
||||
*
|
||||
* May throw RuntimeException from underlying sink
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination from, byte[] data) {
|
||||
public void send(Destination from, int fromPort, int toPort, byte[] data) {
|
||||
if (this.sink == null)
|
||||
return;
|
||||
if (from == null) {
|
||||
// TODO to handle raw replies, SOCKSUDPWrapper would have to use a unique
|
||||
// fromPort for every target or request, and we would lookup the
|
||||
// destination by toPort
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SOCKSUDPWrapper.class);
|
||||
if (log.shouldWarn())
|
||||
log.warn("No support for raw datagrams, from port " + fromPort + " to port " + toPort);
|
||||
return;
|
||||
}
|
||||
|
||||
SOCKSHeader h = cache.get(from);
|
||||
SOCKSHeader h = cache.get(new I2PSocketAddress(from, fromPort));
|
||||
if (h == null) {
|
||||
// RFC 1928 says drop
|
||||
// h = new SOCKSHeader(from);
|
||||
@ -43,9 +59,6 @@ public class SOCKSUDPWrapper implements Source, Sink {
|
||||
byte wrapped[] = new byte[header.length + data.length];
|
||||
System.arraycopy(header, 0, wrapped, 0, header.length);
|
||||
System.arraycopy(data, 0, wrapped, header.length, data.length);
|
||||
this.sink.send(from, wrapped);
|
||||
this.sink.send(from, fromPort, toPort, wrapped);
|
||||
}
|
||||
|
||||
private Sink sink;
|
||||
private Map<Destination, SOCKSHeader> cache;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import java.net.Socket;
|
||||
import java.nio.channels.SelectableChannel;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketAddress;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
|
@ -14,10 +14,12 @@ import net.i2p.util.Log;
|
||||
* @author zzz modded for I2PTunnel
|
||||
*/
|
||||
public class MultiSource implements Source, Sink {
|
||||
private Sink sink;
|
||||
private final List<MSink> sinks;
|
||||
private final Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
|
||||
public MultiSource() {
|
||||
this.sinks = new CopyOnWriteArrayList<Destination>();
|
||||
this.sinks = new CopyOnWriteArrayList<MSink>();
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
@ -32,9 +34,10 @@ public class MultiSource implements Source, Sink {
|
||||
|
||||
/**
|
||||
* May throw RuntimeException from underlying sinks
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public void send(Destination ignored_from, byte[] data) {
|
||||
public void send(Destination ignored_from, int ignored_fromPort, int ignored_toPort, byte[] data) {
|
||||
if (sinks.isEmpty()) {
|
||||
if (log.shouldDebug())
|
||||
log.debug("No subscribers to send " + data.length + " bytes to");
|
||||
@ -43,19 +46,53 @@ public class MultiSource implements Source, Sink {
|
||||
if (log.shouldDebug())
|
||||
log.debug("Sending " + data.length + " bytes to " + sinks.size() + " subscribers");
|
||||
|
||||
for(Destination dest : this.sinks) {
|
||||
this.sink.send(dest, data);
|
||||
for(MSink ms : this.sinks) {
|
||||
this.sink.send(ms.dest, ms.fromPort, ms.toPort, data);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Destination sink) {
|
||||
this.sinks.add(sink);
|
||||
/**
|
||||
* @since 0.9.53 changed to MSink parameter
|
||||
*/
|
||||
public void add(MSink ms) {
|
||||
sinks.add(ms);
|
||||
}
|
||||
|
||||
public void remove(Destination sink) {
|
||||
this.sinks.remove(sink);
|
||||
/**
|
||||
* @since 0.9.53 changed to MSink parameter
|
||||
*/
|
||||
public void remove(MSink ms) {
|
||||
sinks.remove(ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
static class MSink {
|
||||
public final Destination dest;
|
||||
public final int fromPort, toPort;
|
||||
|
||||
public MSink(Destination dest, int fromPort, int toPort) {
|
||||
this.dest = dest; this.fromPort = fromPort; this.toPort = toPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return dest.hashCode() | fromPort | (toPort << 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof MSink))
|
||||
return false;
|
||||
MSink s = (MSink) o;
|
||||
return dest.equals(s.dest) && fromPort == s.fromPort && toPort == s.toPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "from port " + fromPort + " to " + dest.toBase32() + ':' + toPort;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Sink sink;
|
||||
private final List<Destination> sinks;
|
||||
}
|
||||
|
@ -10,10 +10,21 @@ import net.i2p.util.Log;
|
||||
* @author welterde/zzz
|
||||
*/
|
||||
public class Pinger implements Source, Runnable {
|
||||
private final Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
protected Sink sink;
|
||||
protected final Thread thread;
|
||||
private final Object waitlock = new Object();
|
||||
protected volatile boolean running;
|
||||
private final Log log;
|
||||
private final int fromPort;
|
||||
|
||||
public Pinger() {
|
||||
/**
|
||||
* @param fromPort the I2CP from port
|
||||
* @since 0.9.53 added ctx and fromPort params
|
||||
*/
|
||||
public Pinger(I2PAppContext ctx, int fromPort) {
|
||||
this.thread = new I2PAppThread(this);
|
||||
log = ctx.logManager().getLog(getClass());
|
||||
this.fromPort = fromPort;
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
@ -22,7 +33,6 @@ public class Pinger implements Source, Runnable {
|
||||
|
||||
public void start() {
|
||||
this.running = true;
|
||||
//this.waitlock = new Object();
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
@ -35,9 +45,9 @@ public class Pinger implements Source, Runnable {
|
||||
byte[] data = new byte[1];
|
||||
data[0] = 1;
|
||||
try {
|
||||
this.sink.send(null, data);
|
||||
this.sink.send(null, fromPort, 0, data);
|
||||
if (log.shouldDebug())
|
||||
log.debug("Sent unsubscribe");
|
||||
log.debug("Sent unsubscribe from port " + fromPort);
|
||||
} catch (RuntimeException re) {}
|
||||
}
|
||||
|
||||
@ -47,11 +57,10 @@ public class Pinger implements Source, Runnable {
|
||||
data[0] = 0;
|
||||
int i = 0;
|
||||
while(this.running) {
|
||||
//System.out.print("p");
|
||||
try {
|
||||
this.sink.send(null, data);
|
||||
this.sink.send(null, fromPort, 0, data);
|
||||
if (log.shouldDebug())
|
||||
log.debug("Sent subscribe");
|
||||
log.debug("Sent subscribe from port " + fromPort);
|
||||
} catch (RuntimeException re) {
|
||||
if (log.shouldWarn())
|
||||
log.warn("error sending", re);
|
||||
@ -71,9 +80,4 @@ public class Pinger implements Source, Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Sink sink;
|
||||
protected final Thread thread;
|
||||
private final Object waitlock = new Object();
|
||||
protected volatile boolean running;
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase {
|
||||
super(destination, l, notifyThis, tunnel);
|
||||
|
||||
// create udp-destination
|
||||
this.sink = new UDPSink(host, port);
|
||||
UDPSink udps = new UDPSink(host, port);
|
||||
int localPort = udps.getPort();
|
||||
this.sink = udps;
|
||||
setSink(this.sink);
|
||||
|
||||
// create pinger
|
||||
this.pinger = new Pinger();
|
||||
this.pinger = new Pinger(_context, localPort);
|
||||
this.pinger.setSink(this);
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,7 @@ public class StreamrProducer extends I2PTunnelUDPServerBase {
|
||||
public StreamrProducer(int port,
|
||||
File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
// verify subscription requests
|
||||
super(true, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
super(privkey, privkeyname, l, notifyThis, tunnel);
|
||||
|
||||
// The broadcaster
|
||||
this.multi = new MultiSource();
|
||||
|
@ -20,7 +20,7 @@ public class Subscriber implements Sink {
|
||||
|
||||
private final I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
private final Log log = ctx.logManager().getLog(getClass());
|
||||
private final Map<Destination, Long> subscriptions;
|
||||
private final Map<MultiSource.MSink, Long> subscriptions;
|
||||
private final MultiSource multi;
|
||||
private final SimpleTimer2.TimedEvent timer;
|
||||
private volatile boolean timerRunning;
|
||||
@ -31,7 +31,7 @@ public class Subscriber implements Sink {
|
||||
public Subscriber(MultiSource multi) {
|
||||
this.multi = multi;
|
||||
// subscriptions
|
||||
this.subscriptions = new ConcurrentHashMap<Destination, Long>();
|
||||
this.subscriptions = new ConcurrentHashMap<MultiSource.MSink, Long>();
|
||||
timer = new Expire();
|
||||
}
|
||||
|
||||
@ -40,44 +40,47 @@ public class Subscriber implements Sink {
|
||||
*
|
||||
* @param dest to subscribe or unsubscribe
|
||||
* @param data must be a single byte, 0 to subscribe, 1 to unsubscribe
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
*/
|
||||
public void send(Destination dest, byte[] data) {
|
||||
public void send(Destination dest, int fromPort, int toPort, byte[] data) {
|
||||
if(dest == null || data.length < 1) {
|
||||
// invalid packet
|
||||
if (log.shouldWarn())
|
||||
log.warn("bad subscription from " + dest);
|
||||
log.warn("bad subscription from " + dest.toBase32() + ':' + fromPort);
|
||||
} else {
|
||||
byte ctrl = data[0];
|
||||
// swap fromPort and toPort for the replies
|
||||
MultiSource.MSink ms = new MultiSource.MSink(dest, toPort, fromPort);
|
||||
int ctrl = data[0] & 0xff;
|
||||
if(ctrl == 0) {
|
||||
if (this.subscriptions.put(dest, Long.valueOf(ctx.clock().now())) == null) {
|
||||
if (this.subscriptions.put(ms, Long.valueOf(ctx.clock().now())) == null) {
|
||||
if (subscriptions.size() > MAX_SUBSCRIPTIONS) {
|
||||
subscriptions.remove(dest);
|
||||
if (log.shouldWarn())
|
||||
log.warn("Too many subscriptions, denying: " + dest.toBase32());
|
||||
log.warn("Too many subscriptions, denying: " + ms);
|
||||
return;
|
||||
}
|
||||
// subscribe
|
||||
if (log.shouldWarn())
|
||||
log.warn("Add subscription: " + dest.toBase32());
|
||||
this.multi.add(dest);
|
||||
log.warn("Add subscription: " + ms);
|
||||
this.multi.add(ms);
|
||||
if (!timerRunning) {
|
||||
timer.reschedule(EXPIRATION);
|
||||
timerRunning = true;
|
||||
}
|
||||
} else {
|
||||
if (log.shouldInfo())
|
||||
log.info("Continue subscription: " + dest.toBase32());
|
||||
log.info("Continue subscription: " + ms);
|
||||
}
|
||||
} else if(ctrl == 1) {
|
||||
// unsubscribe
|
||||
if (log.shouldWarn())
|
||||
log.warn("Remove subscription: " + dest.toBase32());
|
||||
if (subscriptions.remove(dest) != null)
|
||||
multi.remove(dest);
|
||||
log.warn("Remove subscription: " + ms);
|
||||
if (subscriptions.remove(ms) != null)
|
||||
multi.remove(ms);
|
||||
} else {
|
||||
// invalid packet
|
||||
if (log.shouldWarn())
|
||||
log.warn("bad subscription from " + dest);
|
||||
log.warn("bad subscription flag " + ctrl + " from " + ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -95,15 +98,15 @@ public class Subscriber implements Sink {
|
||||
return;
|
||||
}
|
||||
long exp = ctx.clock().now() - EXPIRATION;
|
||||
for (Iterator<Map.Entry<Destination, Long>> iter = subscriptions.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry<Destination, Long> e = iter.next();
|
||||
for (Iterator<Map.Entry<MultiSource.MSink, Long>> iter = subscriptions.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry<MultiSource.MSink, Long> e = iter.next();
|
||||
long then = e.getValue().longValue();
|
||||
if (then < exp) {
|
||||
Destination dest = e.getKey();
|
||||
MultiSource.MSink ms = e.getKey();
|
||||
iter.remove();
|
||||
multi.remove(dest);
|
||||
multi.remove(ms);
|
||||
if (log.shouldWarn())
|
||||
log.warn("Expired subscription: " + dest.toBase32());
|
||||
log.warn("Expired subscription: " + ms);
|
||||
}
|
||||
}
|
||||
if (!subscriptions.isEmpty()) {
|
||||
|
@ -14,14 +14,39 @@ import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
*/
|
||||
public class I2PSink implements Sink {
|
||||
|
||||
protected final boolean raw;
|
||||
protected final I2PSession sess;
|
||||
protected final Destination dest;
|
||||
protected final I2PDatagramMaker maker;
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
protected final int toPort;
|
||||
|
||||
/**
|
||||
* repliable (not raw)
|
||||
*/
|
||||
public I2PSink(I2PSession sess, Destination dest) {
|
||||
this(sess, dest, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param raw false for repliable
|
||||
*/
|
||||
public I2PSink(I2PSession sess, Destination dest, boolean raw) {
|
||||
this(sess, dest, raw, I2PSession.PORT_UNSPECIFIED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param raw false for repliable
|
||||
* @param toPort I2CP destination port, 0-65535
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public I2PSink(I2PSession sess, Destination dest, boolean raw, int toPort) {
|
||||
this.sess = sess;
|
||||
this.dest = dest;
|
||||
this.raw = raw;
|
||||
this.toPort = toPort;
|
||||
|
||||
// create maker
|
||||
if (raw) {
|
||||
@ -34,31 +59,30 @@ public class I2PSink implements Sink {
|
||||
|
||||
/**
|
||||
* @param src ignored
|
||||
* @param fromPort I2CP port
|
||||
* @param ign_toPort ignored
|
||||
* @since 0.9.53 added fromPort and toPort parameters, breaking change, sorry
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public synchronized void send(Destination src, byte[] data) {
|
||||
public synchronized void send(Destination src, int fromPort, int ign_toPort, byte[] data) {
|
||||
//System.out.print("w");
|
||||
// create payload
|
||||
byte[] payload;
|
||||
if(!this.raw) {
|
||||
if (!this.raw) {
|
||||
synchronized(this.maker) {
|
||||
payload = this.maker.makeI2PDatagram(data);
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
payload = data;
|
||||
}
|
||||
|
||||
// send message
|
||||
try {
|
||||
this.sess.sendMessage(this.dest, payload,
|
||||
(this.raw ? I2PSession.PROTO_DATAGRAM_RAW : I2PSession.PROTO_DATAGRAM),
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
fromPort, toPort);
|
||||
} catch (I2PSessionException ise) {
|
||||
throw new RuntimeException("failed to send data", ise);
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean raw;
|
||||
protected final I2PSession sess;
|
||||
protected final Destination dest;
|
||||
protected final I2PDatagramMaker maker;
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
*/
|
||||
public class I2PSinkAnywhere implements Sink {
|
||||
|
||||
protected final boolean raw;
|
||||
protected final I2PSession sess;
|
||||
protected final I2PDatagramMaker maker;
|
||||
|
||||
public I2PSinkAnywhere(I2PSession sess) {
|
||||
this(sess, false);
|
||||
}
|
||||
@ -35,27 +39,35 @@ public class I2PSinkAnywhere implements Sink {
|
||||
* @param to - where it's going
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public synchronized void send(Destination to, byte[] data) {
|
||||
public void send(Destination to, byte[] data) {
|
||||
send(to, I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param to - where it's going
|
||||
* @param fromPort I2CP port 0 - 65535
|
||||
* @param toPort I2CP port 0 - 65535
|
||||
* @since 0.9.53
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public synchronized void send(Destination to, int fromPort, int toPort, byte[] data) {
|
||||
// create payload
|
||||
byte[] payload;
|
||||
if(!this.raw) {
|
||||
synchronized(this.maker) {
|
||||
payload = this.maker.makeI2PDatagram(data);
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
payload = data;
|
||||
}
|
||||
|
||||
// send message
|
||||
try {
|
||||
this.sess.sendMessage(to, payload,
|
||||
(this.raw ? I2PSession.PROTO_DATAGRAM_RAW : I2PSession.PROTO_DATAGRAM),
|
||||
I2PSession.PORT_UNSPECIFIED, I2PSession.PORT_UNSPECIFIED);
|
||||
fromPort, toPort);
|
||||
} catch (I2PSessionException ise) {
|
||||
throw new RuntimeException("failed to send data", ise);
|
||||
}
|
||||
}
|
||||
|
||||
protected final boolean raw;
|
||||
protected final I2PSession sess;
|
||||
protected final I2PDatagramMaker maker;
|
||||
}
|
||||
|
@ -1,94 +1,105 @@
|
||||
package net.i2p.i2ptunnel.udp;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.I2PSessionMuxedListener;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Refactored in 0.9.53 to support I2CP protocols and ports
|
||||
*
|
||||
* @author welterde
|
||||
*/
|
||||
public class I2PSource implements Source, Runnable {
|
||||
public class I2PSource implements Source {
|
||||
|
||||
protected final I2PSession sess;
|
||||
protected Sink sink;
|
||||
private final Protocol protocol;
|
||||
private final int port;
|
||||
private final I2PDatagramDissector diss;
|
||||
private final Log log;
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public enum Protocol { REPLIABLE, RAW, BOTH }
|
||||
|
||||
/**
|
||||
* Handles both REPLIABLE and RAW on any port
|
||||
*/
|
||||
public I2PSource(I2PSession sess) {
|
||||
this(sess, true, false);
|
||||
this(sess, Protocol.BOTH);
|
||||
}
|
||||
|
||||
public I2PSource(I2PSession sess, boolean verify) {
|
||||
this(sess, verify, false);
|
||||
/**
|
||||
* Listen on all I2CP ports.
|
||||
* No support for arbitrary protocol numbers.
|
||||
*
|
||||
* @param protocol REPLIABLE, RAW, or BOTH
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public I2PSource(I2PSession sess, Protocol protocol) {
|
||||
this(sess, protocol, I2PSession.PORT_ANY);
|
||||
}
|
||||
|
||||
public I2PSource(I2PSession sess, boolean verify, boolean raw) {
|
||||
/**
|
||||
* @param port I2CP port or I2PSession.PORT_ANY
|
||||
* @param protocol REPLIABLE, RAW, or BOTH
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public I2PSource(I2PSession sess, Protocol protocol, int port) {
|
||||
this.sess = sess;
|
||||
this.verify = verify;
|
||||
this.raw = raw;
|
||||
|
||||
// create queue
|
||||
this.queue = new ArrayBlockingQueue<Integer>(256);
|
||||
|
||||
// create listener
|
||||
this.sess.setSessionListener(new Listener());
|
||||
|
||||
// create thread
|
||||
this.thread = new I2PAppThread(this);
|
||||
this.protocol = protocol;
|
||||
this.port = port;
|
||||
diss = (protocol != Protocol.RAW) ? new I2PDatagramDissector() : null;
|
||||
log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
}
|
||||
|
||||
public void setSink(Sink sink) {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
|
||||
public void start() {
|
||||
this.thread.start();
|
||||
// create listener
|
||||
Listener l = new Listener();
|
||||
if (protocol != Protocol.RAW)
|
||||
sess.addMuxedSessionListener(l, I2PSession.PROTO_DATAGRAM, port);
|
||||
if (protocol != Protocol.REPLIABLE)
|
||||
sess.addMuxedSessionListener(l, I2PSession.PROTO_DATAGRAM_RAW, port);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
// create dissector
|
||||
I2PDatagramDissector diss = new I2PDatagramDissector();
|
||||
_running = true;
|
||||
while (_running) {
|
||||
protected class Listener implements I2PSessionMuxedListener {
|
||||
|
||||
public void messageAvailable(I2PSession sess, int id, long size) {
|
||||
throw new IllegalStateException("muxed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int id, long size, int proto, int fromPort, int toPort) {
|
||||
if (log.shouldDebug())
|
||||
log.debug("Got " + size + " bytes, proto: " + proto + " from port: " + fromPort + " to port: " + toPort);
|
||||
try {
|
||||
// get id
|
||||
int id = this.queue.take();
|
||||
|
||||
// receive message
|
||||
byte[] msg = this.sess.receiveMessage(id);
|
||||
|
||||
if(!this.raw) {
|
||||
byte[] msg = session.receiveMessage(id);
|
||||
if (proto == I2PSession.PROTO_DATAGRAM) {
|
||||
// load datagram into it
|
||||
diss.loadI2PDatagram(msg);
|
||||
|
||||
// now call sink
|
||||
if(this.verify)
|
||||
this.sink.send(diss.getSender(), diss.getPayload());
|
||||
else
|
||||
this.sink.send(diss.extractSender(), diss.extractPayload());
|
||||
sink.send(diss.getSender(), fromPort, toPort, diss.getPayload());
|
||||
} else if (proto == I2PSession.PROTO_DATAGRAM_RAW) {
|
||||
sink.send(null, fromPort, toPort, msg);
|
||||
} else {
|
||||
// verify is ignored
|
||||
this.sink.send(null, msg);
|
||||
if (log.shouldWarn())
|
||||
log.warn("dropping message with unknown protocol " + proto);
|
||||
}
|
||||
//System.out.print("r");
|
||||
} catch(Exception e) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
if (log.shouldWarn())
|
||||
log.warn("error sending", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class Listener implements I2PSessionListener {
|
||||
|
||||
public void messageAvailable(I2PSession sess, int id, long size) {
|
||||
try {
|
||||
queue.put(id);
|
||||
} catch(Exception e) {
|
||||
// ignore
|
||||
log.warn("error receiving datagram", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,24 +108,11 @@ public class I2PSource implements Source, Runnable {
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession arg0) {
|
||||
_running = false;
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession arg0, String arg1, Throwable arg2) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
log.error(arg1, arg2);
|
||||
_running = false;
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final I2PSession sess;
|
||||
protected final BlockingQueue<Integer> queue;
|
||||
protected Sink sink;
|
||||
protected final Thread thread;
|
||||
protected final boolean verify;
|
||||
protected final boolean raw;
|
||||
private volatile boolean _running;
|
||||
}
|
||||
|
@ -8,8 +8,11 @@ import net.i2p.data.Destination;
|
||||
*/
|
||||
public interface Sink {
|
||||
/**
|
||||
* @param src some implementations may ignore
|
||||
* @param fromPort I2CP source port, 0-65535
|
||||
* @param toPort I2CP destination port, 0-65535
|
||||
* @param src some implementations may ignore, may be null in some implementations
|
||||
* @since 0.9.53 added fromPort and toPort parameters, breaking change, sorry
|
||||
* @throws RuntimeException in some implementations
|
||||
*/
|
||||
public void send(Destination src, byte[] data);
|
||||
public void send(Destination src, int fromPort, int toPort, byte[] data);
|
||||
}
|
||||
|
@ -13,7 +13,13 @@ import net.i2p.data.Destination;
|
||||
*/
|
||||
public class UDPSink implements Sink {
|
||||
|
||||
protected final DatagramSocket sock;
|
||||
protected final InetAddress remoteHost;
|
||||
protected final int remotePort;
|
||||
|
||||
/**
|
||||
* @param host where to send
|
||||
* @param port where to send
|
||||
* @throws IllegalArgumentException on DatagramSocket IOException
|
||||
*/
|
||||
public UDPSink(InetAddress host, int port) {
|
||||
@ -23,18 +29,31 @@ public class UDPSink implements Sink {
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("failed to open udp-socket", e);
|
||||
}
|
||||
|
||||
this.remoteHost = host;
|
||||
this.remotePort = port;
|
||||
}
|
||||
|
||||
// remote port
|
||||
|
||||
/**
|
||||
* @param socket existing socket
|
||||
* @param host where to send
|
||||
* @param port where to send
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public UDPSink(DatagramSocket socket, InetAddress host, int port) {
|
||||
sock = socket;
|
||||
this.remoteHost = host;
|
||||
this.remotePort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param src ignored
|
||||
* @param fromPort ignored
|
||||
* @param toPort ignored
|
||||
* @since 0.9.53 added fromPort and toPort parameters, breaking change, sorry
|
||||
* @throws RuntimeException on DatagramSocket IOException
|
||||
*/
|
||||
public void send(Destination src, byte[] data) {
|
||||
public void send(Destination src, int fromPort, int toPort, byte[] data) {
|
||||
// if data.length > this.sock.getSendBufferSize() ...
|
||||
|
||||
// create packet
|
||||
@ -48,6 +67,9 @@ public class UDPSink implements Sink {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the local port of the DatagramSocket we are sending from
|
||||
*/
|
||||
public int getPort() {
|
||||
return this.sock.getLocalPort();
|
||||
}
|
||||
@ -60,9 +82,4 @@ public class UDPSink implements Sink {
|
||||
public void stop() {
|
||||
this.sock.close();
|
||||
}
|
||||
|
||||
protected final DatagramSocket sock;
|
||||
protected final InetAddress remoteHost;
|
||||
protected final int remotePort;
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,10 @@ import net.i2p.util.Log;
|
||||
* @author welterde
|
||||
*/
|
||||
public class UDPSource implements Source, Runnable {
|
||||
protected final DatagramSocket sock;
|
||||
protected Sink sink;
|
||||
protected final Thread thread;
|
||||
private final int port;
|
||||
public static final int MAX_SIZE = 15360;
|
||||
|
||||
/**
|
||||
@ -25,7 +29,7 @@ public class UDPSource implements Source, Runnable {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("failed to listen...", e);
|
||||
}
|
||||
|
||||
this.port = port;
|
||||
// create thread
|
||||
this.thread = new I2PAppThread(this);
|
||||
}
|
||||
@ -33,6 +37,7 @@ public class UDPSource implements Source, Runnable {
|
||||
/** use socket from UDPSink */
|
||||
public UDPSource(DatagramSocket sock) {
|
||||
this.sock = sock;
|
||||
port = sock.getLocalPort();
|
||||
this.thread = new I2PAppThread(this);
|
||||
}
|
||||
|
||||
@ -60,7 +65,7 @@ public class UDPSource implements Source, Runnable {
|
||||
System.arraycopy(pack.getData(), 0, nbuf, 0, nbuf.length);
|
||||
|
||||
// transfer to sink
|
||||
this.sink.send(null, nbuf);
|
||||
this.sink.send(null, port, 0, nbuf);
|
||||
//System.out.print("i");
|
||||
} catch(Exception e) {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
|
||||
@ -71,11 +76,15 @@ public class UDPSource implements Source, Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the local port of the DatagramSocket we are receiving on
|
||||
* @since 0.9.53
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.sock.close();
|
||||
}
|
||||
|
||||
protected final DatagramSocket sock;
|
||||
protected Sink sink;
|
||||
protected final Thread thread;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PSocketAddress;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
@ -57,7 +58,6 @@ import net.i2p.util.EventDispatcher;
|
||||
private final I2PSession _session;
|
||||
private final Source _i2pSource;
|
||||
private final Sink _i2pSink;
|
||||
private final Destination _otherDest;
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
|
||||
@ -103,19 +103,19 @@ import net.i2p.util.EventDispatcher;
|
||||
throw new RuntimeException("failed to create session", exc);
|
||||
}
|
||||
|
||||
// Setup the source. Always expect raw unverified datagrams.
|
||||
_i2pSource = new I2PSource(_session, false, true);
|
||||
// Setup the source. Handle both repliable and raw datagrams, on all ports.
|
||||
_i2pSource = new I2PSource(_session, I2PSource.Protocol.BOTH);
|
||||
|
||||
// Setup the sink. Always send repliable datagrams.
|
||||
if (destination != null && destination.length() > 0) {
|
||||
_otherDest = _context.namingService().lookup(destination);
|
||||
if (_otherDest == null) {
|
||||
I2PSocketAddress addr = new I2PSocketAddress(destination);
|
||||
if (addr.isUnresolved()) {
|
||||
// unlike in I2PTunnelClient, we don't defer and retry resolution later
|
||||
l.log("Could not resolve " + destination);
|
||||
throw new RuntimeException("failed to create session - could not resolve " + destination);
|
||||
}
|
||||
_i2pSink = new I2PSink(_session, _otherDest, false);
|
||||
}
|
||||
_i2pSink = new I2PSink(_session, addr.getAddress(), false, addr.getPort());
|
||||
} else {
|
||||
_otherDest = null;
|
||||
_i2pSink = new I2PSinkAnywhere(_session, false);
|
||||
}
|
||||
}
|
||||
@ -178,10 +178,11 @@ import net.i2p.util.EventDispatcher;
|
||||
* Sink Methods
|
||||
*
|
||||
* @param to - ignored if configured for a single destination
|
||||
* (we use the dest specified in the constructor)
|
||||
* (we use the dest specified in the constructor)
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException if session is closed
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
_i2pSink.send(to, data);
|
||||
public void send(Destination to, int fromPort, int toPort, byte[] data) {
|
||||
_i2pSink.send(to, fromPort, toPort, data);
|
||||
}
|
||||
}
|
||||
|
@ -67,15 +67,14 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
* badly that we cant create a socketManager
|
||||
*
|
||||
*/
|
||||
|
||||
public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l,
|
||||
public I2PTunnelUDPServerBase(File privkey, String privkeyname, Logging l,
|
||||
EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super("UDPServer <- " + privkeyname, notifyThis, tunnel);
|
||||
_log = tunnel.getContext().logManager().getLog(I2PTunnelUDPServerBase.class);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(privkey);
|
||||
init(verify, fis, privkeyname, l);
|
||||
init(fis, privkeyname, l);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error starting server", ioe);
|
||||
notifyEvent("openServerResult", "error");
|
||||
@ -85,7 +84,7 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
}
|
||||
}
|
||||
|
||||
private void init(boolean verify, InputStream privData, String privkeyname, Logging l) {
|
||||
private void init(InputStream privData, String privkeyname, Logging l) {
|
||||
this.l = l;
|
||||
|
||||
// create i2pclient
|
||||
@ -99,8 +98,8 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
throw new RuntimeException("failed to create session", exc);
|
||||
}
|
||||
|
||||
// Setup the source. Always expect repliable datagrams, optionally verify
|
||||
_i2pSource = new I2PSource(_session, verify, false);
|
||||
// Setup the source. Always expect repliable datagrams, listen on all ports.
|
||||
_i2pSource = new I2PSource(_session, I2PSource.Protocol.REPLIABLE);
|
||||
|
||||
// Setup the sink. Always send raw datagrams.
|
||||
_i2pSink = new I2PSinkAnywhere(_session, true);
|
||||
@ -188,11 +187,12 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin
|
||||
* Sink Methods
|
||||
*
|
||||
* @param to
|
||||
* @since 0.9.53 added fromPort and toPort parameters
|
||||
* @throws RuntimeException if session is closed
|
||||
*
|
||||
*/
|
||||
public void send(Destination to, byte[] data) {
|
||||
_i2pSink.send(to, data);
|
||||
public void send(Destination to, int fromPort, int toPort, byte[] data) {
|
||||
_i2pSink.send(to, fromPort, toPort, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,9 @@ package net.i2p.i2ptunnel.ui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -14,6 +16,7 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -855,8 +858,35 @@ public class GeneralHelper {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return entries sorted, converted to b32, separated by newlines, or ""
|
||||
*/
|
||||
public String getAccessList(int tunnel) {
|
||||
return getProperty(tunnel, "i2cp.accessList", "").replace(",", "\n");
|
||||
String val = getProperty(tunnel, "i2cp.accessList", "");
|
||||
if (val.length() > 0) {
|
||||
// Convert B64 to B32 for display
|
||||
String[] vals = DataHelper.split(val, ",");
|
||||
for (int i = 0; i < vals.length; i++) {
|
||||
String v = vals[i];
|
||||
if (v.length() == 44) {
|
||||
byte[] b = Base64.decode(v);
|
||||
if (b != null)
|
||||
vals[i] = Base32.encode(b) + ".b32.i2p";
|
||||
}
|
||||
}
|
||||
Arrays.sort(vals, Collator.getInstance());
|
||||
StringBuilder buf = new StringBuilder(val.length() * 3 / 2);
|
||||
for (int i = 0; i < vals.length; i++) {
|
||||
String v = vals[i];
|
||||
if (v.length() == 0)
|
||||
continue;
|
||||
buf.append(vals[i]);
|
||||
if (i != vals.length - 1)
|
||||
buf.append('\n');
|
||||
}
|
||||
val = buf.toString();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,6 +19,7 @@ import net.i2p.crypto.EncType;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.KeyPair;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -466,8 +467,27 @@ public class TunnelConfig {
|
||||
}
|
||||
|
||||
public void setAccessList(String val) {
|
||||
if (val != null)
|
||||
_otherOptions.put("i2cp.accessList", val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ","));
|
||||
if (val != null) {
|
||||
val = val.trim().replace("\r\n", ",").replace("\n", ",").replace(" ", ",");
|
||||
// Convert to B64 to save space
|
||||
String[] vals = DataHelper.split(val, ",");
|
||||
StringBuilder buf = new StringBuilder(val.length());
|
||||
for (int i = 0; i < vals.length; i++) {
|
||||
String v = vals[i];
|
||||
int len = v.length();
|
||||
if (len == 0)
|
||||
continue;
|
||||
if (len == 60 && v.endsWith(".b32.i2p")) {
|
||||
byte[] b = Base32.decode(v.substring(0, 52));
|
||||
if (b != null)
|
||||
v = Base64.encode(b);
|
||||
}
|
||||
buf.append(v);
|
||||
if (i != vals.length - 1)
|
||||
buf.append(',');
|
||||
}
|
||||
_otherOptions.put("i2cp.accessList", buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void setJumpList(String val) {
|
||||
|
@ -609,12 +609,12 @@
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="0" type="radio" id="startOnLoad" name="encType" <%=(has0 ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
<label><input value="0" type="radio" id="startOnLoad" name="encType" <%=((has0 && !has4) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ElGamal-2048</label>
|
||||
</span>
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="4" type="radio" id="startOnLoad" name="encType" <%=(has4 ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ECIES-X25519 (<%=intl._t("Experts only!")%>)</label>
|
||||
<label><input value="4" type="radio" id="startOnLoad" name="encType" <%=((has4 && !has0) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ECIES-X25519</label>
|
||||
</span>
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="4,0" type="radio" id="startOnLoad" name="encType" <%=((has0 && has4) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
|
@ -701,12 +701,12 @@
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="0" type="radio" id="startOnLoad" name="encType" <%=(has0 ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
<label><input value="0" type="radio" id="startOnLoad" name="encType" <%=((has0 && !has4) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ElGamal-2048</label>
|
||||
</span>
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="4" type="radio" id="startOnLoad" name="encType" <%=(has4 ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ECIES-X25519 (<%=intl._t("Experts only!")%>)</label>
|
||||
<label><input value="4" type="radio" id="startOnLoad" name="encType" <%=((has4 && !has0) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
ECIES-X25519</label>
|
||||
</span>
|
||||
<span class="multiOption" <%=ehdisabled%>>
|
||||
<label><input value="4,0" type="radio" id="startOnLoad" name="encType" <%=((has0 && has4) ? " checked=\"checked\"" : edisabled)%> class="tickbox" />
|
||||
@ -911,7 +911,7 @@
|
||||
</th>
|
||||
</tr><tr>
|
||||
<th colspan="5">
|
||||
<%=intl._t("Inbound connection limits (0=unlimited)")%>
|
||||
<%=intl._t("Inbound connection limits")%>
|
||||
</th>
|
||||
</tr><tr>
|
||||
<td></td>
|
||||
@ -921,27 +921,30 @@
|
||||
<td class="blankColumn"></td>
|
||||
</tr><tr>
|
||||
<td><b><%=intl._t("Per Client")%></b></td>
|
||||
<%
|
||||
String unlimited = " (0 = " + intl._t("unlimited") + ')';
|
||||
%>
|
||||
<td>
|
||||
<input type="text" name="limitMinute" title="<%=intl._t("Maximum number of web page requests per minute for a unique client before access to the server is blocked")%>" value="<%=editBean.getLimitMinute(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="limitMinute" title="<%=intl._t("Maximum number of web page requests per minute for a unique client before access to the server is blocked") + unlimited %>" value="<%=editBean.getLimitMinute(curTunnel)%>" class="freetext" />
|
||||
</td><td>
|
||||
<input type="text" name="limitHour" title="<%=intl._t("Maximum number of web page requests per hour for a unique client before access to the server is blocked")%>" value="<%=editBean.getLimitHour(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="limitHour" title="<%=intl._t("Maximum number of web page requests per hour for a unique client before access to the server is blocked") + unlimited %>" value="<%=editBean.getLimitHour(curTunnel)%>" class="freetext" />
|
||||
</td><td>
|
||||
<input type="text" name="limitDay" title="<%=intl._t("Maximum number of web page requests per day for a unique client before access to the server is blocked")%>" value="<%=editBean.getLimitDay(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="limitDay" title="<%=intl._t("Maximum number of web page requests per day for a unique client before access to the server is blocked") + unlimited %>" value="<%=editBean.getLimitDay(curTunnel)%>" class="freetext" />
|
||||
</td><td class="blankColumn"></td>
|
||||
</tr><tr>
|
||||
<td><b><%=intl._t("Total")%></b></td>
|
||||
<td>
|
||||
<input type="text" name="totalMinute" title="<%=intl._t("Total number of web page requests per minute before access to the server is blocked")%>" value="<%=editBean.getTotalMinute(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="totalMinute" title="<%=intl._t("Total number of web page requests per minute before access to the server is blocked") + unlimited %>" value="<%=editBean.getTotalMinute(curTunnel)%>" class="freetext" />
|
||||
</td><td>
|
||||
<input type="text" name="totalHour" title="<%=intl._t("Total number of web page requests per hour before access to the server is blocked")%>" value="<%=editBean.getTotalHour(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="totalHour" title="<%=intl._t("Total number of web page requests per hour before access to the server is blocked") + unlimited %>" value="<%=editBean.getTotalHour(curTunnel)%>" class="freetext" />
|
||||
</td><td>
|
||||
<input type="text" name="totalDay" title="<%=intl._t("Total number of web page requests per day before access to the server is blocked")%>" value="<%=editBean.getTotalDay(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="totalDay" title="<%=intl._t("Total number of web page requests per day before access to the server is blocked") + unlimited %>" value="<%=editBean.getTotalDay(curTunnel)%>" class="freetext" />
|
||||
</td><td class="blankColumn"></td>
|
||||
</tr><tr>
|
||||
<th colspan="5"><%=intl._t("Max concurrent connections (0=unlimited)")%></th>
|
||||
<th colspan="5"><%=intl._t("Max concurrent connections")%></th>
|
||||
</tr><tr>
|
||||
<td></td><td>
|
||||
<input type="text" name="maxStreams" title="<%=intl._t("Maximum number of simultaneous client connections")%>" value="<%=editBean.getMaxStreams(curTunnel)%>" class="freetext" />
|
||||
<input type="text" name="maxStreams" title="<%=intl._t("Maximum number of simultaneous client connections") + unlimited %>" value="<%=editBean.getMaxStreams(curTunnel)%>" class="freetext" />
|
||||
</td><td></td><td></td><td class="blankColumn"></td>
|
||||
</tr>
|
||||
<%
|
||||
@ -949,7 +952,7 @@
|
||||
%>
|
||||
<tr>
|
||||
<th colspan="5">
|
||||
<%=intl._t("POST limits (0=unlimited)")%>
|
||||
<%=intl._t("POST limits")%>
|
||||
</th>
|
||||
</tr><tr>
|
||||
<td></td><td>
|
||||
@ -962,7 +965,7 @@
|
||||
<b><%=intl._t("Per Client")%>
|
||||
</b>
|
||||
</td><td>
|
||||
<input type="text" name="postMax" title="<%=intl._t("Maximum number of post requests permitted for a unique client for the configured time span")%>" value="<%=editBean.getPostMax(curTunnel)%>" class="freetext quantity"/>
|
||||
<input type="text" name="postMax" title="<%=intl._t("Maximum number of post requests permitted for a unique client for the configured time span") + unlimited %>" value="<%=editBean.getPostMax(curTunnel)%>" class="freetext quantity"/>
|
||||
</td><td colspan="2">
|
||||
<input type="text" name="postBanTime" title="<%=intl._t("If a client exceeds the maximum number of post requests per allocated period, enforce a ban for this number of minutes")%>" value="<%=editBean.getPostBanTime(curTunnel)%>" class="freetext period"/>
|
||||
<%=intl._t("minutes")%>
|
||||
@ -972,7 +975,7 @@
|
||||
<b><%=intl._t("Total")%>
|
||||
</b>
|
||||
</td><td>
|
||||
<input type="text" name="postTotalMax" title="<%=intl._t("Total number of post requests permitted for the configured time span")%>" value="<%=editBean.getPostTotalMax(curTunnel)%>" class="freetext quantity"/>
|
||||
<input type="text" name="postTotalMax" title="<%=intl._t("Total number of post requests permitted for the configured time span") + unlimited %>" value="<%=editBean.getPostTotalMax(curTunnel)%>" class="freetext quantity"/>
|
||||
</td><td colspan="2">
|
||||
<input type="text" name="postTotalBanTime" title="<%=intl._t("If the maximum number of post requests per allocated period is exceeded, enforce a global access ban for this number of minutes")%>" value="<%=editBean.getPostTotalBanTime(curTunnel)%>" class="freetext period"/>
|
||||
<%=intl._t("minutes")%>
|
||||
|
@ -47,7 +47,7 @@ form {
|
||||
}
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
margin: 5px 3px 5px 5px;
|
||||
margin: 4px 5px 6px;
|
||||
background: none;
|
||||
vertical-align: sub;
|
||||
min-width: 16px;
|
||||
@ -216,7 +216,7 @@ hr {
|
||||
box-sizing: border-box;
|
||||
margin: 2px 4px !important;
|
||||
min-width: 70px !important;
|
||||
padding: 7px 8px 3px;
|
||||
padding: 5px 8px 5px;
|
||||
}
|
||||
|
||||
.control:hover, .control:focus {
|
||||
|
@ -4,16 +4,17 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# タカハシ, 2022
|
||||
# Masayuki Hatta <mhatta@mhatta.org>, 2018
|
||||
# XMPPはいいぞ, 2021
|
||||
# XMPPはいいぞ, 2021
|
||||
# daingewuvzeevisiddfddd, 2021
|
||||
# daingewuvzeevisiddfddd, 2021
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-05 14:31+0000\n"
|
||||
"PO-Revision-Date: 2021-05-14 01:18+0000\n"
|
||||
"Last-Translator: XMPPはいいぞ\n"
|
||||
"PO-Revision-Date: 2022-02-14 11:11+0000\n"
|
||||
"Last-Translator: タカハシ\n"
|
||||
"Language-Team: Japanese (http://www.transifex.com/otf/I2P/language/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -554,7 +555,7 @@ msgid ""
|
||||
"This address will be saved to your Local address book. Select this option "
|
||||
"for addresses you wish to keep separate from the main router address book, "
|
||||
"but don't mind publishing."
|
||||
msgstr ""
|
||||
msgstr "このアドレスはローカルのアドレス帳に保存されます。メインのルーターアドレス帳とは別に保管したいが、公開しても構わないアドレスに対してこのオプションを選択してください。"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1478
|
||||
#, java-format
|
||||
@ -569,7 +570,7 @@ msgstr "このアドレスはプライベートなアドレス帳へ保存され
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1507
|
||||
msgid "Base32 address requires lookup password"
|
||||
msgstr ""
|
||||
msgstr "Base32のアドレスにはルックアップパスワードが必要です"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1509
|
||||
msgid "Base32 address requires encryption key"
|
||||
@ -577,7 +578,7 @@ msgstr "Base32 アドレスは暗号化鍵を要求します"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1511
|
||||
msgid "Base32 address requires encryption key and lookup password"
|
||||
msgstr ""
|
||||
msgstr "Base32のアドレスには暗号化キーとルックアップパスワードが必要です"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1513
|
||||
msgid "Base32 address decryption failure, check encryption key"
|
||||
@ -611,7 +612,7 @@ msgstr "新しいDH暗号化鍵を生成"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1541
|
||||
msgid "Lookup password"
|
||||
msgstr ""
|
||||
msgstr "ルックアップパスワード"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1542
|
||||
msgid "You must enter the password provided by the server operator."
|
||||
@ -628,7 +629,7 @@ msgstr "アドレスヘルパー経由で追加"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:235
|
||||
msgid "Missing lookup password"
|
||||
msgstr ""
|
||||
msgstr "ルックアップパスワードがありません"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:249
|
||||
msgid "Missing private key"
|
||||
|
@ -9,7 +9,7 @@
|
||||
# cacapo <handelsehorisont@gmail.com>, 2015-2016
|
||||
# hottuna <i2p@robertfoss.se>, 2013
|
||||
# hottuna <i2p@robertfoss.se>, 2012
|
||||
# Jonatan Nyberg <jonatan@autistici.org>, 2016-2017,2021
|
||||
# Jonatan Nyberg <jonatan@autistici.org>, 2016-2017,2021-2022
|
||||
# efef6ec5b435a041fce803c7f8af77d2_2341d43, 2019-2020
|
||||
# efef6ec5b435a041fce803c7f8af77d2_2341d43, 2017
|
||||
# WinterFairy <winterfairy@riseup.net>, 2014
|
||||
@ -18,7 +18,7 @@ msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-05 14:31+0000\n"
|
||||
"PO-Revision-Date: 2021-06-23 09:38+0000\n"
|
||||
"PO-Revision-Date: 2022-01-14 17:57+0000\n"
|
||||
"Last-Translator: Jonatan Nyberg <jonatan@autistici.org>\n"
|
||||
"Language-Team: Swedish (Sweden) (http://www.transifex.com/otf/I2P/language/sv_SE/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -116,7 +116,7 @@ msgstr "Webbplatsen var inte nåbar."
|
||||
msgid ""
|
||||
"The website is offline, there is network congestion, or your router is not "
|
||||
"yet well-integrated with peers."
|
||||
msgstr "Webbplatsen är frånkopplad, nätverket är under stor belastning eller så är din router inte välintegrerad med noder."
|
||||
msgstr "Webbplatsen är frånkopplad, nätverket är under stor belastning, eller din router är ännu inte välintegrerad med jämlikar."
|
||||
|
||||
#: ../java/build/Proxy.java:14 ../java/build/Proxy.java:58
|
||||
#: ../java/build/Proxy.java:109 ../java/build/Proxy.java:129
|
||||
@ -130,7 +130,7 @@ msgstr "Du kanske vill {0}försöka igen{1}."
|
||||
#: ../java/build/Proxy.java:118 ../java/build/Proxy.java:130
|
||||
#: ../java/build/Proxy.java:171 ../java/build/Proxy.java:196
|
||||
msgid "Could not find the following destination:"
|
||||
msgstr "Kunde inte hitta följande mål:"
|
||||
msgstr "Det gick inte att hitta följande destination:"
|
||||
|
||||
#: ../java/build/Proxy.java:16 ../java/build/Proxy.java:22
|
||||
#: ../java/build/Proxy.java:160 ../java/build/Proxy.java:166
|
||||
@ -205,7 +205,7 @@ msgstr "Varning: Ogiltigt Mål"
|
||||
|
||||
#: ../java/build/Proxy.java:47
|
||||
msgid "The Base32 address is invalid."
|
||||
msgstr ""
|
||||
msgstr "Base32-adressen är ogiltig."
|
||||
|
||||
#: ../java/build/Proxy.java:56
|
||||
msgid "The website was not reachable, because its lease set was not found."
|
||||
@ -315,7 +315,7 @@ msgstr ""
|
||||
msgid ""
|
||||
"The website destination specified was not valid, or was otherwise "
|
||||
"unreachable."
|
||||
msgstr "Webbplatsens angivna destination var inte giltig, eller på annat sätt onåbar. "
|
||||
msgstr "Den angivna webbplatsdestinationen var inte giltig eller på annat sätt onåbar."
|
||||
|
||||
#: ../java/build/Proxy.java:127
|
||||
msgid ""
|
||||
@ -335,7 +335,7 @@ msgstr "Varning: Ingen utproxy inställd"
|
||||
msgid ""
|
||||
"Your request was for a site outside of I2P, but you have no HTTP outproxy "
|
||||
"configured."
|
||||
msgstr "Din förfrågan var för en sida utanför I2P, men du har ingen HTTP utproxy inställd."
|
||||
msgstr "Din förfrågan gällde en webbplats utanför I2P, men du har ingen HTTP-utproxy konfigurerad."
|
||||
|
||||
#: ../java/build/Proxy.java:139
|
||||
msgid "Please configure an outproxy in I2PTunnel."
|
||||
@ -362,7 +362,7 @@ msgid ""
|
||||
"Resolve the conflict by deciding which key you trust, and then either ignore"
|
||||
" the address helper link, or delete the host entry from your address book "
|
||||
"and click the address helper link again."
|
||||
msgstr "Lös konflikten genom att välja vilken nyckel du litar på. Sedan kan du antingen ignorera adressens hjälplänk eller radera posten från din adressbok och klicka på adressens hjälplänk igen. "
|
||||
msgstr "Lös konflikten genom att bestämma vilken nyckel du litar på och ignorera sedan länken för adresshjälp eller ta bort värdposten från din adressbok och klicka på länken för adresshjälp igen."
|
||||
|
||||
#: ../java/build/Proxy.java:150 ../java/build/Proxy.java:156
|
||||
msgid "Warning: Bad Address Helper"
|
||||
@ -391,7 +391,7 @@ msgstr "HTTP-utproxyn hittades inte."
|
||||
msgid ""
|
||||
"It is offline, there is network congestion, or your router is not yet well-"
|
||||
"integrated with peers."
|
||||
msgstr "Den är antingen frånkopplad, nätverket är under stor belastning eller så är din router ännu inte väl integrerad med noder."
|
||||
msgstr "Den är antingen frånkopplad, nätverket är under stor belastning eller så är din router ännu inte väl integrerad med jämlikar."
|
||||
|
||||
#: ../java/build/Proxy.java:172 ../java/build/Proxy.java:178
|
||||
msgid "Warning: Request Denied"
|
||||
@ -399,7 +399,7 @@ msgstr "Varning: Förfrågan Nekad"
|
||||
|
||||
#: ../java/build/Proxy.java:179
|
||||
msgid "You attempted to connect to a non-I2P website or location."
|
||||
msgstr "Du försökte ansluta till en icke-I2P webbplats eller plats."
|
||||
msgstr "Du försökte ansluta till en icke-I2P-webbplats eller plats."
|
||||
|
||||
#: ../java/build/Proxy.java:180
|
||||
msgid "Proxy Authorization Required"
|
||||
@ -467,7 +467,7 @@ msgstr "HTTP-proxyn kunde inte nås eftersom den använder krypteringsinställni
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:692
|
||||
msgid "This seems to be a bad destination:"
|
||||
msgstr "Detta verkar vara ett felaktigt mål"
|
||||
msgstr "Det här verkar vara en dåligt destination:"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:693
|
||||
msgid "i2paddresshelper cannot help you with a destination like that!"
|
||||
@ -529,7 +529,7 @@ msgstr "Fortsätt utan att spara"
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1460
|
||||
#, java-format
|
||||
msgid "Save {0} to router address book and continue to website"
|
||||
msgstr "Spara {0} till routerns adressbok och fortsätt till webbplatsen."
|
||||
msgstr "Spara {0} i routerns adressbok och fortsätt till webbplatsen"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1461
|
||||
msgid ""
|
||||
@ -565,7 +565,7 @@ msgstr ""
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1478
|
||||
#, java-format
|
||||
msgid "Save {0} to private address book and continue to website"
|
||||
msgstr "Spara {0} till privata adressboken och fortsätt till webbplatsen."
|
||||
msgstr "Spara {0} i den privata adressboken och fortsätt till webbplatsen"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:1479
|
||||
msgid ""
|
||||
@ -626,7 +626,7 @@ msgstr ""
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:196
|
||||
#, java-format
|
||||
msgid "Added via address helper from {0}"
|
||||
msgstr "Tillagd via adresshjälpare från {0}"
|
||||
msgstr "Tillagd via adresshjälp från {0}"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:198
|
||||
msgid "Added via address helper"
|
||||
@ -701,7 +701,7 @@ msgstr ""
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:379
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:411
|
||||
msgid "Click here if you are not redirected automatically."
|
||||
msgstr "Klicka här om du inte omdirigeras automatiskt "
|
||||
msgstr "Klicka här om du inte omdirigeras automatiskt."
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/localServer/LocalHTTPServer.java:409
|
||||
#, java-format
|
||||
|
@ -4,7 +4,7 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Kaya Zeren <kayazeren@gmail.com>, 2016-2021
|
||||
# Kaya Zeren <kayazeren@gmail.com>, 2016-2022
|
||||
# Ozancan Karataş <ozancankaratas96@outlook.com>, 2015
|
||||
# zzzi2p, 2018
|
||||
msgid ""
|
||||
@ -12,7 +12,7 @@ msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-02-05 14:31+0000\n"
|
||||
"PO-Revision-Date: 2021-02-15 04:27+0000\n"
|
||||
"PO-Revision-Date: 2022-02-03 06:12+0000\n"
|
||||
"Last-Translator: Kaya Zeren <kayazeren@gmail.com>\n"
|
||||
"Language-Team: Turkish (Turkey) (http://www.transifex.com/otf/I2P/language/tr_TR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -443,7 +443,7 @@ msgstr "İstek kötü bir iletişim kuralı kullanıyor."
|
||||
|
||||
#: ../java/build/Proxy.java:205
|
||||
msgid "The I2P HTTP Proxy supports HTTP and HTTPS requests only."
|
||||
msgstr "I2P HTTP Vekil Sunucusu yalnız HTTP ve HTTPS isteklerini destekler."
|
||||
msgstr "I2P HTTP Vekil Sunucusu yalnızca HTTP ve HTTPS isteklerini destekler."
|
||||
|
||||
#: ../java/build/Proxy.java:206
|
||||
msgid "Other protocols such as FTP are not allowed."
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user