forked from I2P_Developers/i2p.i2p
propagate from branch 'i2p.i2p.zzz.gmp6-prop' (head 416ef26df4b91fb9de3e27623551c7f87ec2bfe0)
to branch 'i2p.i2p' (head 9466fdeae338d6b0bf049d86975db9b6ddbd3064)
This commit is contained in:
@@ -28,9 +28,9 @@ web-fragment.xml
|
||||
web-out.xml
|
||||
|
||||
# Temporary/build dirs
|
||||
^build
|
||||
^build$
|
||||
^pkg-temp
|
||||
/build
|
||||
/build$
|
||||
/classes
|
||||
/dist
|
||||
^installer/resources/locale/mo
|
||||
@@ -45,7 +45,6 @@ core/c/jcpuid/msvc/*.user
|
||||
|
||||
# Debian-related
|
||||
^debian/copyright
|
||||
^debian/changelog
|
||||
^.pc/
|
||||
|
||||
# Build property overrides
|
||||
|
||||
54
.tx/config
54
.tx/config
@@ -23,6 +23,7 @@ trans.sv_SE = apps/i2ptunnel/locale/messages_sv.po
|
||||
trans.uk_UA = apps/i2ptunnel/locale/messages_uk.po
|
||||
trans.vi = apps/i2ptunnel/locale/messages_vi.po
|
||||
trans.zh_CN = apps/i2ptunnel/locale/messages_zh.po
|
||||
trans.zh_TW = apps/i2ptunnel/locale/messages_zh_TW.po
|
||||
|
||||
[I2P.proxy]
|
||||
source_file = apps/i2ptunnel/locale-proxy/messages_en.po
|
||||
@@ -33,6 +34,8 @@ trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
|
||||
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
|
||||
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
|
||||
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/i2ptunnel/locale-proxy/messages_in.po
|
||||
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
|
||||
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
|
||||
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
|
||||
@@ -74,6 +77,7 @@ trans.tr_TR = apps/routerconsole/locale/messages_tr.po
|
||||
trans.uk_UA = apps/routerconsole/locale/messages_uk.po
|
||||
trans.vi = apps/routerconsole/locale/messages_vi.po
|
||||
trans.zh_CN = apps/routerconsole/locale/messages_zh.po
|
||||
trans.zh_TW = apps/routerconsole/locale/messages_zh_TW.po
|
||||
|
||||
[I2P.welcome]
|
||||
source_file = apps/routerconsole/locale-news/messages_en.po
|
||||
@@ -81,11 +85,15 @@ source_lang = en
|
||||
trans.ar = apps/routerconsole/locale-news/messages_ar.po
|
||||
trans.de = apps/routerconsole/locale-news/messages_de.po
|
||||
trans.es = apps/routerconsole/locale-news/messages_es.po
|
||||
trans.fi = apps/routerconsole/locale-news/messages_fi.po
|
||||
trans.fr = apps/routerconsole/locale-news/messages_fr.po
|
||||
trans.he = apps/routerconsole/locale-news/messages_he.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/routerconsole/locale-news/messages_in.po
|
||||
trans.it = apps/routerconsole/locale-news/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-news/messages_ja.po
|
||||
trans.ko = apps/routerconsole/locale-news/messages_ko.po
|
||||
trans.mg = apps/routerconsole/locale-news/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-news/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-news/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-news/messages_pl.po
|
||||
@@ -94,10 +102,12 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
|
||||
trans.ro = apps/routerconsole/locale-news/messages_ro.po
|
||||
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
|
||||
trans.sk = apps/routerconsole/locale-news/messages_sk.po
|
||||
trans.sq = apps/routerconsole/locale-news/messages_sq.po
|
||||
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
|
||||
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
|
||||
trans.uk_UA = apps/routerconsole/locale-news/messages_uk.po
|
||||
trans.zh_CN = apps/routerconsole/locale-news/messages_zh.po
|
||||
trans.zh_TW = apps/routerconsole/locale-news/messages_zh_TW.po
|
||||
|
||||
[I2P.countries]
|
||||
type = PO
|
||||
@@ -114,6 +124,7 @@ trans.fr = apps/routerconsole/locale-countries/messages_fr.po
|
||||
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
|
||||
trans.it = apps/routerconsole/locale-countries/messages_it.po
|
||||
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
|
||||
trans.mg = apps/routerconsole/locale-countries/messages_mg.po
|
||||
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
|
||||
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
|
||||
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
|
||||
@@ -128,6 +139,7 @@ trans.uk_UA = apps/routerconsole/locale-countries/messages_uk.po
|
||||
trans.tr_TR = apps/routerconsole/locale-countries/messages_tr.po
|
||||
trans.vi = apps/routerconsole/locale-countries/messages_vi.po
|
||||
trans.zh_CN = apps/routerconsole/locale-countries/messages_zh.po
|
||||
trans.zh_TW = apps/routerconsole/locale-countries/messages_zh_TW.po
|
||||
|
||||
[I2P.i2psnark]
|
||||
source_file = apps/i2psnark/locale/messages_en.po
|
||||
@@ -208,17 +220,22 @@ trans.cs = apps/susimail/locale/messages_cs.po
|
||||
trans.da = apps/susimail/locale/messages_da.po
|
||||
trans.de = apps/susimail/locale/messages_de.po
|
||||
trans.es = apps/susimail/locale/messages_es.po
|
||||
trans.fi = apps/susimail/locale/messages_fi.po
|
||||
trans.fr = apps/susimail/locale/messages_fr.po
|
||||
trans.hu = apps/susimail/locale/messages_hu.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/susimail/locale/messages_in.po
|
||||
trans.it = apps/susimail/locale/messages_it.po
|
||||
trans.ja = apps/susimail/locale/messages_ja.po
|
||||
trans.mg = apps/susimail/locale/messages_mg.po
|
||||
trans.nl = apps/susimail/locale/messages_nl.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.pl = apps/susimail/locale/messages_pl.po
|
||||
trans.pt = apps/susimail/locale/messages_pt.po
|
||||
trans.pt_BR = apps/susimail/locale/messages_pt_BR.po
|
||||
trans.ro = apps/susimail/locale/messages_ro.po
|
||||
trans.ru_RU = apps/susimail/locale/messages_ru.po
|
||||
trans.sq = apps/susimail/locale/messages_sq.po
|
||||
trans.sv_SE = apps/susimail/locale/messages_sv.po
|
||||
trans.uk_UA = apps/susimail/locale/messages_uk.po
|
||||
trans.vi = apps/susimail/locale/messages_vi.po
|
||||
trans.zh_CN = apps/susimail/locale/messages_zh.po
|
||||
@@ -230,16 +247,21 @@ trans.cs = debian/po/cs.po
|
||||
trans.de = debian/po/de.po
|
||||
trans.el = debian/po/el.po
|
||||
trans.es = debian/po/es.po
|
||||
trans.fi = debian/po/fi.po
|
||||
trans.fr = debian/po/fr.po
|
||||
trans.id = debian/po/id.po
|
||||
trans.it = debian/po/it.po
|
||||
trans.hu = debian/po/hu.po
|
||||
trans.ja = debian/po/ja.po
|
||||
trans.ko = debian/po/ko.po
|
||||
trans.nl = debian/po/nl.po
|
||||
trans.pl = debian/po/pl.po
|
||||
trans.pt = debian/po/pt.po
|
||||
trans.pt_BR = debian/po/pt_BR.po
|
||||
trans.ro = debian/po/ro.po
|
||||
trans.ru_RU = debian/po/ru.po
|
||||
trans.sk = debian/po/sk.po
|
||||
trans.sq = debian/po/sq.po
|
||||
trans.sv_SE = debian/po/sv.po
|
||||
trans.uk_UA = debian/po/uk.po
|
||||
trans.tr_TR = debian/po/tr.po
|
||||
@@ -248,12 +270,20 @@ trans.zh_CN = debian/po/zh.po
|
||||
[I2P.i2prouter-script]
|
||||
source_file = installer/resources/locale/po/messages_en.po
|
||||
source_lang = en
|
||||
;; currently fails check
|
||||
;;trans.ca = installer/resources/locale/po/messages_ca.po
|
||||
trans.de = installer/resources/locale/po/messages_de.po
|
||||
trans.es = installer/resources/locale/po/messages_es.po
|
||||
;; currently fails check
|
||||
;;trans.fi = installer/resources/locale/po/messages_fi.po
|
||||
trans.fr = installer/resources/locale/po/messages_fr.po
|
||||
trans.id = installer/resources/locale/po/messages_id.po
|
||||
trans.it = installer/resources/locale/po/messages_it.po
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.ja = installer/resources/locale/po/messages_ja.po
|
||||
;; currently fails check
|
||||
;;trans.ko = installer/resources/locale/po/messages_ko.po
|
||||
trans.nl = installer/resources/locale/po/messages_nl.po
|
||||
trans.pl = installer/resources/locale/po/messages_pl.po
|
||||
trans.pt = installer/resources/locale/po/messages_pt.po
|
||||
trans.pt_BR = installer/resources/locale/po/messages_pt_BR.po
|
||||
@@ -262,6 +292,8 @@ trans.ru_RU = installer/resources/locale/po/messages_ru.po
|
||||
trans.sk = installer/resources/locale/po/messages_sk.po
|
||||
trans.sv_SE = installer/resources/locale/po/messages_sv.po
|
||||
trans.tr_TR = installer/resources/locale/po/messages_tr.po
|
||||
;; currently fails check
|
||||
;;trans.uk_UA = installer/resources/locale/po/messages_uk.po
|
||||
trans.zh_CN = installer/resources/locale/po/messages_zh.po
|
||||
|
||||
[I2P.getopt]
|
||||
@@ -271,28 +303,44 @@ type = PROPERTIES
|
||||
trans.cs = core/java/src/gnu/getopt/MessagesBundle_cs.properties
|
||||
trans.de = core/java/src/gnu/getopt/MessagesBundle_de.properties
|
||||
trans.es = core/java/src/gnu/getopt/MessagesBundle_es.properties
|
||||
trans.fi = core/java/src/gnu/getopt/MessagesBundle_fi.properties
|
||||
trans.fr = core/java/src/gnu/getopt/MessagesBundle_fr.properties
|
||||
trans.hu = core/java/src/gnu/getopt/MessagesBundle_hu.properties
|
||||
;; Java converts id to in
|
||||
trans.id = core/java/src/gnu/getopt/MessagesBundle_in.properties
|
||||
trans.it = core/java/src/gnu/getopt/MessagesBundle_it.properties
|
||||
trans.ja = core/java/src/gnu/getopt/MessagesBundle_ja.properties
|
||||
trans.ko = core/java/src/gnu/getopt/MessagesBundle_ko.properties
|
||||
trans.nl = core/java/src/gnu/getopt/MessagesBundle_nl.properties
|
||||
trans.nb = core/java/src/gnu/getopt/MessagesBundle_nb.properties
|
||||
trans.pl = core/java/src/gnu/getopt/MessagesBundle_pl.properties
|
||||
trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt = core/java/src/gnu/getopt/MessagesBundle_pt.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
|
||||
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
|
||||
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
|
||||
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
|
||||
;; currently corrupt, non-UTF-8
|
||||
;;trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
|
||||
trans.uk_UA = core/java/src/gnu/getopt/MessagesBundle_uk.properties
|
||||
trans.zh_CN = core/java/src/gnu/getopt/MessagesBundle_zh.properties
|
||||
|
||||
[I2P.streaming]
|
||||
source_file = apps/ministreaming/locale/messages_en.po
|
||||
source_lang = en
|
||||
trans.ca = apps/ministreaming/locale/messages_ca.po
|
||||
trans.de = apps/ministreaming/locale/messages_de.po
|
||||
trans.es = apps/ministreaming/locale/messages_es.po
|
||||
trans.fr = apps/ministreaming/locale/messages_fr.po
|
||||
;; Java converts id to in
|
||||
trans.id = apps/ministreaming/locale/messages_in.po
|
||||
trans.it = apps/ministreaming/locale/messages_it.po
|
||||
trans.nb = apps/ministreaming/locale/messages_nb.po
|
||||
trans.pl = apps/ministreaming/locale/messages_pl.po
|
||||
trans.ro = apps/ministreaming/locale/messages_ro.po
|
||||
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
|
||||
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
|
||||
trans.uk_UA = apps/ministreaming/locale/messages_uk.po
|
||||
trans.zh_CN = apps/ministreaming/locale/messages_zh.po
|
||||
|
||||
|
||||
@@ -25,21 +25,22 @@ where there are comments labeled "PORTABLE". Do this before you
|
||||
run I2P for the first time.
|
||||
|
||||
To start I2P:
|
||||
(*nix): sh i2prouter start
|
||||
(*nix, BSD, Mac): sh i2prouter start
|
||||
(win*): I2P.exe
|
||||
(non-x86 platforms PPC, ARM, etc): sh runplain.sh
|
||||
(platforms without wrapper support): sh runplain.sh
|
||||
|
||||
To stop I2P (gracefully):
|
||||
lynx http://localhost:7657/summaryframe (click "Shutdown")
|
||||
or (*nix, BSD, Mac) sh i2prouter graceful
|
||||
|
||||
To stop I2P immediately:
|
||||
sh i2prouter stop
|
||||
(*nix, BSD, Mac) sh i2prouter stop
|
||||
|
||||
To uninstall I2P:
|
||||
rm -rf $I2PInstallDir ~/.i2p
|
||||
|
||||
Supported JVMs:
|
||||
All platforms: Java 1.6 or higher required; 1.7 or higher recommended
|
||||
All platforms: Java 1.7 or higher required
|
||||
Windows: OpenJDK or Oracle from http://java.com/download
|
||||
Linux: OpenJDK or Oracle from http://java.com/download
|
||||
FreeBSD: OpenJDK or Oracle from http://java.com/download
|
||||
|
||||
23
INSTALL.txt
23
INSTALL.txt
@@ -1,11 +1,13 @@
|
||||
I2P source installation instructions
|
||||
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
|
||||
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
|
||||
Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
|
||||
Apache Ant 1.7.0 or higher
|
||||
The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
Build environment must use a UTF-8 locale.
|
||||
|
||||
To build and install I2P from source, you must first build
|
||||
and package up the appropriate installer by running:
|
||||
@@ -40,29 +42,30 @@ or on Windows, just double-click on i2pinstall.exe.
|
||||
Or move the i2pupdate.zip file into an existing installation directory and restart.
|
||||
|
||||
To start I2P:
|
||||
(*nix): sh i2prouter start
|
||||
(*nix, BSD, Mac): sh i2prouter start
|
||||
(win*): I2P.exe or i2prouter.bat
|
||||
(non-x86 platforms PPC, ARM, etc): sh runplain.sh
|
||||
(platforms without wrapper support): sh runplain.sh
|
||||
|
||||
To install I2P as a system service:
|
||||
(*nix) sh i2prouter install
|
||||
(*nix, BSD, Mac) sh i2prouter install
|
||||
(win*) install_i2p_service_winnt.bat
|
||||
|
||||
To uninstall I2P as a system service:
|
||||
(*nix) sh i2prouter remove
|
||||
(*nix, BSD, Mac) sh i2prouter remove
|
||||
(win*) uninstall_i2p-service_winnt.bat
|
||||
|
||||
To stop I2P (gracefully):
|
||||
lynx http://localhost:7657/summaryframe (click "Shutdown")
|
||||
or (*nix, BSD, Mac) sh i2prouter graceful
|
||||
|
||||
To stop I2P immediately:
|
||||
sh i2prouter stop
|
||||
(*nix, BSD, Mac) sh i2prouter stop
|
||||
|
||||
To uninstall I2P:
|
||||
rm -rf $I2PInstallDir ~/.i2p
|
||||
|
||||
Supported JVMs:
|
||||
Windows: Latest available from http://java.com/download (1.5+ supported)
|
||||
Linux: Latest available from http://java.com/download (1.5+ supported)
|
||||
FreeBSD: 1.5-compatible (NIO required)
|
||||
Windows: Latest available from http://java.com/download (1.7+ supported)
|
||||
Linux: Latest available from http://java.com/download (1.7+ supported)
|
||||
FreeBSD: 1.7-compatible (NIO required)
|
||||
Other operating systems and JVMs: See http://trac.i2p2.de/wiki/java
|
||||
|
||||
32
LICENSE.txt
32
LICENSE.txt
@@ -40,6 +40,10 @@ Public domain except as listed below:
|
||||
Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
|
||||
See licenses/LICENSE-SHA256.txt
|
||||
|
||||
ElGamal:
|
||||
Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
|
||||
See licenses/LICENSE-SHA256.txt
|
||||
|
||||
AES code:
|
||||
Copyright (c) 1995-2005 The Cryptix Foundation Limited.
|
||||
See licenses/LICENSE-Cryptix.txt
|
||||
@@ -80,6 +84,10 @@ Public domain except as listed below:
|
||||
Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
HostnameVerifier:
|
||||
From Apache HttpClient 4.4.1 and HttpCore 4.4.1
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
|
||||
Router (router.jar):
|
||||
Public domain except as listed below:
|
||||
@@ -87,7 +95,7 @@ Public domain except as listed below:
|
||||
From freenet
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
UPnP subsystem (CyberLink) 2.1:
|
||||
UPnP subsystem (CyberLink) 3.0:
|
||||
Copyright (C) 2003-2010 Satoshi Konno
|
||||
See licenses/LICENSE-UPnP.txt
|
||||
|
||||
@@ -95,9 +103,14 @@ Public domain except as listed below:
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
||||
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com/
|
||||
|
||||
GeoIP API 1.3.1:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
|
||||
Installer:
|
||||
Launch4j 3.0.1:
|
||||
(Launch4j is only included in the upstream source package and Windows binaries.
|
||||
Not applicable for non-Windows binaries or Debian/Launchpad packages.)
|
||||
Copyright (c) 2004, 2008 Grzegorz Kowal
|
||||
See licenses/LICENSE-Launch4j.txt (in binary packages)
|
||||
See installer/lib/launch4j/LICENSE.txt (in source packages)
|
||||
@@ -182,7 +195,18 @@ Applications:
|
||||
By welterde.
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
|
||||
Jetty 8.1.16.v20140903:
|
||||
Imagegen:
|
||||
Identicon:
|
||||
Copyright (c) 2007-2014 Don Park <donpark@docuverse.com>
|
||||
See licenses/LICENSE-Identicon.txt
|
||||
RandomArt:
|
||||
Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
|
||||
Copyright (c) 2008 Alexander von Gernler. All rights reserved.
|
||||
See licenses/LICENSE-BSD.txt
|
||||
Zxing:
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
|
||||
Jetty 8.1.17.v20150415:
|
||||
See licenses/ABOUT-Jetty.html
|
||||
See licenses/NOTICE-Jetty.html
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
@@ -248,8 +272,8 @@ Applications:
|
||||
Bundles systray4j-2.4.1:
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
Tomcat 6.0.41:
|
||||
Copyright 1999-2014 The Apache Software Foundation
|
||||
Tomcat 6.0.44:
|
||||
Copyright 1999-2015 The Apache Software Foundation
|
||||
See licenses/LICENSE-Apache2.0.txt
|
||||
See licenses/NOTICE-Tomcat.txt
|
||||
|
||||
|
||||
16
README.txt
16
README.txt
@@ -1,9 +1,11 @@
|
||||
Prerequisites to build from source:
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
|
||||
Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
|
||||
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
|
||||
Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
|
||||
Apache Ant 1.7.0 or higher
|
||||
The xgettext, msgfmt, and msgmerge tools installed
|
||||
from the GNU gettext package http://www.gnu.org/software/gettext/
|
||||
Build environment must use a UTF-8 locale.
|
||||
|
||||
To build:
|
||||
On x86 systems do:
|
||||
@@ -19,7 +21,8 @@ To build:
|
||||
|
||||
Documentation:
|
||||
https://geti2p.net/how
|
||||
API: run 'ant javadoc' then start at build/javadoc/index.html
|
||||
API: http://docs.i2p-projekt.de/javadoc/
|
||||
or run 'ant javadoc' then start at build/javadoc/index.html
|
||||
|
||||
Latest release:
|
||||
https://geti2p.net/download
|
||||
@@ -34,6 +37,15 @@ Need help?
|
||||
IRC irc.freenode.net #i2p
|
||||
http://forum.i2p/
|
||||
|
||||
Bug reports:
|
||||
https://trac.i2p2.de/report/1
|
||||
|
||||
Contact information, security issues, press inquiries:
|
||||
https://geti2p.net/en/contact
|
||||
|
||||
Twitter:
|
||||
@i2p, @geti2p
|
||||
|
||||
Licenses:
|
||||
See LICENSE.txt
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public class Main {
|
||||
}
|
||||
|
||||
static void wrtxt(OutputStream CMDout, String s) throws IOException {
|
||||
CMDout.write(s.getBytes());
|
||||
CMDout.write(DataHelper.getUTF8(s));
|
||||
CMDout.write('\n');
|
||||
CMDout.flush();
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class Main {
|
||||
}
|
||||
|
||||
static void wrtxt(OutputStream CMDout, String s) throws IOException {
|
||||
CMDout.write(s.getBytes());
|
||||
CMDout.write(DataHelper.getUTF8(s));
|
||||
CMDout.write('\n');
|
||||
CMDout.flush();
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.*;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@@ -144,6 +143,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
* Stop BOB gracefully
|
||||
* @deprecated unused
|
||||
*/
|
||||
@Deprecated
|
||||
public synchronized static void stop() {
|
||||
if (_bob != null)
|
||||
_bob.shutdown(null);
|
||||
@@ -214,9 +214,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
// Re-reading the config file in each thread is pretty damn stupid.
|
||||
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
||||
// This is here just to ensure there is no interference with our threadgroups.
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
i = Y1.hashCode();
|
||||
i = Y2.hashCode();
|
||||
{
|
||||
File cfg = new File(configLocation);
|
||||
@@ -250,11 +248,11 @@ public class BOB implements Runnable, ClientApp {
|
||||
save = true;
|
||||
}
|
||||
if (!props.containsKey("inbound.length")) {
|
||||
props.setProperty("inbound.length", "1");
|
||||
props.setProperty("inbound.length", "3");
|
||||
save = true;
|
||||
}
|
||||
if (!props.containsKey("outbound.length")) {
|
||||
props.setProperty("outbound.length", "1");
|
||||
props.setProperty("outbound.length", "3");
|
||||
save = true;
|
||||
}
|
||||
if (!props.containsKey("inbound.lengthVariance")) {
|
||||
@@ -341,7 +339,7 @@ public class BOB implements Runnable, ClientApp {
|
||||
|
||||
if (g) {
|
||||
DoCMDS conn_c = new DoCMDS(spin, lock, server, props, database, _log);
|
||||
Thread t = new Thread(conn_c);
|
||||
Thread t = new I2PAppThread(conn_c);
|
||||
t.setName("BOB.DoCMDS " + i);
|
||||
t.start();
|
||||
i++;
|
||||
|
||||
@@ -25,12 +25,13 @@ import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
//import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
//import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
// needed only for debugging.
|
||||
// import java.util.logging.Level;
|
||||
// import java.util.logging.Logger;
|
||||
@@ -909,6 +910,8 @@ public class DoCMDS implements Runnable {
|
||||
|
||||
} else if (Command.equals(C_getnick)) {
|
||||
// Get the NamedDB to work with...
|
||||
boolean nsfail = false;
|
||||
|
||||
try {
|
||||
database.getReadLock();
|
||||
} catch (Exception ex) {
|
||||
@@ -919,6 +922,7 @@ public class DoCMDS implements Runnable {
|
||||
ns = true;
|
||||
} catch (RuntimeException b) {
|
||||
try {
|
||||
nsfail = true;
|
||||
nns(out);
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
@@ -931,7 +935,7 @@ public class DoCMDS implements Runnable {
|
||||
}
|
||||
|
||||
database.releaseReadLock();
|
||||
if (ns) {
|
||||
if (ns && !nsfail) {
|
||||
try {
|
||||
rlock();
|
||||
} catch (Exception e) {
|
||||
@@ -1307,7 +1311,7 @@ public class DoCMDS implements Runnable {
|
||||
// wait
|
||||
}
|
||||
tunnel = new MUXlisten(lock, database, nickinfo, _log);
|
||||
Thread t = new Thread(tunnel);
|
||||
Thread t = new I2PAppThread(tunnel);
|
||||
t.start();
|
||||
// try {
|
||||
// Thread.sleep(1000 * 10); // Slow down the startup.
|
||||
|
||||
@@ -18,10 +18,12 @@ package net.i2p.BOB;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Listen on I2P and connect to TCP
|
||||
@@ -30,16 +32,15 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class I2Plistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PServerSocket serverSocket;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param SS
|
||||
* @param S
|
||||
* @param S unused
|
||||
* @param info
|
||||
* @param database
|
||||
* @param _log
|
||||
@@ -48,7 +49,6 @@ public class I2Plistener implements Runnable {
|
||||
this.database = database;
|
||||
this.info = info;
|
||||
this._log = _log;
|
||||
this.socketManager = S;
|
||||
this.serverSocket = SS;
|
||||
this.lives = lives;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class I2Plistener implements Runnable {
|
||||
conn++;
|
||||
// toss the connection to a new thread.
|
||||
I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database, lives);
|
||||
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
|
||||
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
|
||||
t.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,10 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Process I2P->TCP
|
||||
@@ -104,15 +107,15 @@ public class I2PtoTCP implements Runnable {
|
||||
|
||||
if (tell) {
|
||||
// tell who is connecting
|
||||
out.write(I2P.getPeerDestination().toBase64().getBytes());
|
||||
out.write(DataHelper.getASCII(I2P.getPeerDestination().toBase64()));
|
||||
out.write(10); // nl
|
||||
out.flush(); // not really needed, but...
|
||||
}
|
||||
// setup to cross the streams
|
||||
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
|
||||
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
|
||||
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
// Fire!
|
||||
t.start();
|
||||
q.start();
|
||||
|
||||
@@ -21,11 +21,13 @@ import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -201,14 +203,14 @@ public class MUXlisten implements Runnable {
|
||||
// I2P -> TCP
|
||||
SS = socketManager.getServerSocket();
|
||||
I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log, lives);
|
||||
t = new Thread(tg, conn, "BOBI2Plistener " + N);
|
||||
t = new I2PAppThread(tg, conn, "BOBI2Plistener " + N);
|
||||
t.start();
|
||||
}
|
||||
|
||||
if (come_in) {
|
||||
// TCP -> I2P
|
||||
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log, lives);
|
||||
q = new Thread(tg, conn, "BOBTCPlistener " + N);
|
||||
q = new I2PAppThread(tg, conn, "BOBTCPlistener " + N);
|
||||
q.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package net.i2p.BOB;
|
||||
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
/**
|
||||
@@ -31,12 +30,10 @@ public class Main {
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// THINK THINK THINK THINK THINK THINK
|
||||
SimpleScheduler Y1 = SimpleScheduler.getInstance();
|
||||
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
|
||||
|
||||
BOB.main(args);
|
||||
|
||||
Y2.stop();
|
||||
Y1.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class NamedDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find objects in the array, returns it's index or throws exception
|
||||
* Find objects in the array, returns its index or throws exception
|
||||
* @param key
|
||||
* @return an objects index
|
||||
* @throws ArrayIndexOutOfBoundsException when key does not exist
|
||||
|
||||
@@ -20,8 +20,10 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* Listen on TCP port and connect to I2P
|
||||
@@ -30,12 +32,11 @@ import net.i2p.client.streaming.I2PSocketManager;
|
||||
*/
|
||||
public class TCPlistener implements Runnable {
|
||||
|
||||
private NamedDB info, database;
|
||||
private Logger _log;
|
||||
public I2PSocketManager socketManager;
|
||||
public I2PServerSocket serverSocket;
|
||||
private ServerSocket listener;
|
||||
private AtomicBoolean lives;
|
||||
private final NamedDB info, database;
|
||||
private final Logger _log;
|
||||
private final I2PSocketManager socketManager;
|
||||
private final ServerSocket listener;
|
||||
private final AtomicBoolean lives;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -76,7 +77,7 @@ public class TCPlistener implements Runnable {
|
||||
conn++;
|
||||
// toss the connection to a new thread.
|
||||
TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info, database, lives);
|
||||
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
|
||||
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
|
||||
t.start();
|
||||
g = false;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,14 @@ import java.net.NoRouteToHostException;
|
||||
import java.net.Socket;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
//import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -158,8 +159,8 @@ public class TCPtoI2P implements Runnable {
|
||||
// setup to cross the streams
|
||||
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
|
||||
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
|
||||
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
|
||||
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
|
||||
// Fire!
|
||||
t.start();
|
||||
q.start();
|
||||
|
||||
@@ -34,15 +34,18 @@ import net.i2p.util.Log;
|
||||
* The skeletal frame is here, just needs to be finished.
|
||||
*
|
||||
* @author sponge
|
||||
* @deprecated incomplete, unused
|
||||
*/
|
||||
@Deprecated
|
||||
public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
|
||||
private NamedDB info;
|
||||
private Log _log;
|
||||
private Socket socket;
|
||||
private final NamedDB info;
|
||||
private final Log _log;
|
||||
private final Socket socket;
|
||||
private DataInputStream in;
|
||||
private DataOutputStream out;
|
||||
private I2PSession _session;
|
||||
private final I2PSession _session;
|
||||
// FIXME never set
|
||||
private Destination _peerDestination;
|
||||
private boolean up;
|
||||
|
||||
@@ -58,7 +61,6 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
|
||||
this._log = _log;
|
||||
this.socket = socket;
|
||||
this._session = _session;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,15 +77,15 @@
|
||||
<!-- unused for now (except for Android), as we oddly ship addressbook as a .war -->
|
||||
<target name="jar" depends="compile, changes">
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes" value="" />
|
||||
<property name="workspace.changes.tr" value="" />
|
||||
<jar basedir="${build}" destfile="${dist}/${jar}">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="addressbook.Daemon"/>
|
||||
<attribute name="Main-Class" value="net.i2p.addressbook.Daemon"/>
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
<attribute name="Build-Date" value="${build.timestamp}" />
|
||||
<attribute name="Base-Revision" value="${workspace.version}" />
|
||||
<attribute name="Workspace-Changes" value="${workspace.changes}" />
|
||||
<attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
@@ -27,8 +27,10 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.SecureFile;
|
||||
|
||||
@@ -37,18 +39,40 @@ import net.i2p.util.SecureFile;
|
||||
* destinations. AddressBooks can be created from local and remote files, merged
|
||||
* together, and written out to local files.
|
||||
*
|
||||
* Methods are NOT thread-safe.
|
||||
*
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
class AddressBook {
|
||||
class AddressBook implements Iterable<Map.Entry<String, HostTxtEntry>> {
|
||||
|
||||
private final String location;
|
||||
/** either addresses or subFile will be non-null, but not both */
|
||||
private final Map<String, String> addresses;
|
||||
private final Map<String, HostTxtEntry> addresses;
|
||||
private final File subFile;
|
||||
private boolean modified;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final int MIN_DEST_LENGTH = 516;
|
||||
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||
|
||||
/**
|
||||
* 5-67 chars lower/upper case
|
||||
*/
|
||||
private static final Pattern HOST_PATTERN =
|
||||
Pattern.compile("^[0-9a-zA-Z\\.-]{5,67}$");
|
||||
|
||||
/**
|
||||
* 52 chars lower/upper case
|
||||
* Always ends in 'a' or 'q'
|
||||
*/
|
||||
private static final Pattern B32_PATTERN =
|
||||
Pattern.compile("^[2-7a-zA-Z]{51}[aAqQ]$");
|
||||
|
||||
/** not a complete qualification, just a quick check */
|
||||
private static final Pattern B64_PATTERN =
|
||||
Pattern.compile("^[0-9a-zA-Z~-]{" + MIN_DEST_LENGTH + ',' + MAX_DEST_LENGTH + "}={0,2}$");
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the contents of the Map addresses.
|
||||
*
|
||||
@@ -56,7 +80,7 @@ class AddressBook {
|
||||
* A Map containing human readable addresses as keys, mapped to
|
||||
* base64 i2p destinations.
|
||||
*/
|
||||
public AddressBook(Map<String, String> addresses) {
|
||||
public AddressBook(Map<String, HostTxtEntry> addresses) {
|
||||
this.addresses = addresses;
|
||||
this.subFile = null;
|
||||
this.location = null;
|
||||
@@ -86,7 +110,7 @@ class AddressBook {
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
*/
|
||||
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
|
||||
static final long MAX_SUB_SIZE = 5 * 1024 * 1024l; //about 8,000 hosts
|
||||
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
@@ -107,7 +131,7 @@ class AddressBook {
|
||||
* @param proxyPort port number of proxy
|
||||
*/
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
Map<String, String> a = null;
|
||||
Map<String, HostTxtEntry> a = null;
|
||||
File subf = null;
|
||||
try {
|
||||
File tmp = SecureFile.createTempFile("addressbook", null, I2PAppContext.getGlobalContext().getTempDir());
|
||||
@@ -144,23 +168,40 @@ class AddressBook {
|
||||
*/
|
||||
public AddressBook(File file) {
|
||||
this.location = file.toString();
|
||||
Map<String, String> a;
|
||||
Map<String, HostTxtEntry> a;
|
||||
try {
|
||||
a = ConfigParser.parse(file);
|
||||
a = HostTxtParser.parse(file);
|
||||
} catch (IOException exp) {
|
||||
a = new HashMap<String, String>();
|
||||
a = new HashMap<String, HostTxtEntry>();
|
||||
}
|
||||
this.addresses = a;
|
||||
this.subFile = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test only.
|
||||
*
|
||||
* @param testsubfile path to a file containing the simulated fetch of a subscription
|
||||
* @since 0.9.26
|
||||
*/
|
||||
public AddressBook(String testsubfile) {
|
||||
this.location = testsubfile;
|
||||
this.addresses = null;
|
||||
this.subFile = new File(testsubfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over the addresses in the AddressBook.
|
||||
* @since 0.8.7
|
||||
*/
|
||||
public Iterator<Map.Entry<String, String>> iterator() {
|
||||
if (this.subFile != null)
|
||||
return new ConfigIterator(this.subFile);
|
||||
public Iterator<Map.Entry<String, HostTxtEntry>> iterator() {
|
||||
if (this.subFile != null) {
|
||||
try {
|
||||
return new HostTxtIterator(this.subFile);
|
||||
} catch (IOException ioe) {
|
||||
return new HostTxtIterator();
|
||||
}
|
||||
}
|
||||
return this.addresses.entrySet().iterator();
|
||||
}
|
||||
|
||||
@@ -201,12 +242,9 @@ class AddressBook {
|
||||
return "Map containing " + this.addresses.size() + " entries";
|
||||
}
|
||||
|
||||
private static final int MIN_DEST_LENGTH = 516;
|
||||
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||
|
||||
/**
|
||||
* Do basic validation of the hostname
|
||||
* hostname was already converted to lower case by ConfigParser.parse()
|
||||
* hostname was already converted to lower case by HostTxtParser.parse()
|
||||
*/
|
||||
public static boolean isValidKey(String host) {
|
||||
return
|
||||
@@ -220,9 +258,10 @@ class AddressBook {
|
||||
host.indexOf("..") < 0 &&
|
||||
// IDN - basic check, not complete validation
|
||||
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
HOST_PATTERN.matcher(host).matches() &&
|
||||
// Base32 spoofing (52chars.i2p)
|
||||
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
|
||||
// We didn't do it this way, we use a .b32.i2p suffix, but let's prohibit it anyway
|
||||
(! (host.length() == 56 && B32_PATTERN.matcher(host.substring(0,52)).matches())) &&
|
||||
// ... or maybe we do Base32 this way ...
|
||||
(! host.equals("b32.i2p")) &&
|
||||
(! host.endsWith(".b32.i2p")) &&
|
||||
@@ -246,7 +285,7 @@ class AddressBook {
|
||||
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
|
||||
// B64 comes in groups of 2, 3, or 4 chars, but never 1
|
||||
((dest.length() % 4) != 1) &&
|
||||
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
||||
B64_PATTERN.matcher(dest).matches()
|
||||
;
|
||||
}
|
||||
|
||||
@@ -267,15 +306,15 @@ class AddressBook {
|
||||
public void merge(AddressBook other, boolean overwrite, Log log) {
|
||||
if (this.addresses == null)
|
||||
throw new IllegalStateException();
|
||||
for (Iterator<Map.Entry<String, String>> iter = other.iterator(); iter.hasNext(); ) {
|
||||
Map.Entry<String, String> entry = iter.next();
|
||||
for (Iterator<Map.Entry<String, HostTxtEntry>> iter = other.iterator(); iter.hasNext(); ) {
|
||||
Map.Entry<String, HostTxtEntry> entry = iter.next();
|
||||
String otherKey = entry.getKey();
|
||||
String otherValue = entry.getValue();
|
||||
HostTxtEntry otherValue = entry.getValue();
|
||||
|
||||
if (isValidKey(otherKey) && isValidDest(otherValue)) {
|
||||
if (isValidKey(otherKey) && isValidDest(otherValue.getDest())) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (DEBUG && log != null &&
|
||||
!this.addresses.get(otherKey).equals(otherValue)) {
|
||||
!this.addresses.get(otherKey).equals(otherValue.getDest())) {
|
||||
log.append("Conflict for " + otherKey + " from "
|
||||
+ other.location
|
||||
+ ". Destination in remote address book is "
|
||||
@@ -308,7 +347,7 @@ class AddressBook {
|
||||
throw new IllegalStateException();
|
||||
if (this.modified) {
|
||||
try {
|
||||
ConfigParser.write(this.addresses, file);
|
||||
HostTxtParser.write(this.addresses, file);
|
||||
} catch (IOException exp) {
|
||||
System.err.println("Error writing addressbook " + file.getAbsolutePath() + " : " + exp.toString());
|
||||
}
|
||||
@@ -332,4 +371,27 @@ class AddressBook {
|
||||
protected void finalize() {
|
||||
delete();
|
||||
}
|
||||
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
String[] tests = { "foo.i2p",
|
||||
"3bnipzzu67cdq2rcygyxz52xhvy6ylokn4zfrk36ywn6pixmaoza.b32.i2p",
|
||||
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAAA",
|
||||
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA==",
|
||||
"te9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
|
||||
"(*&(*&(*&(*",
|
||||
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAA",
|
||||
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA===",
|
||||
"!e9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
|
||||
"x"
|
||||
};
|
||||
for (String s : tests) {
|
||||
test(s);
|
||||
}
|
||||
}
|
||||
|
||||
public static void test(String s) {
|
||||
System.out.println(s + " valid host? " + isValidKey(s) + " valid dest? " + isValidDest(s));
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -29,12 +29,13 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
@@ -43,8 +44,7 @@ import net.i2p.util.SystemVersion;
|
||||
* Utility class providing methods to parse and write files in config file
|
||||
* format, and subscription file format.
|
||||
*
|
||||
* TODO: Change file encoding from default to UTF-8?
|
||||
* Or switch to the DataHelper loadProps/storeProps methods?
|
||||
* TODO: switch to the DataHelper loadProps/storeProps methods?
|
||||
*
|
||||
* @author Ragnarok
|
||||
*/
|
||||
@@ -87,20 +87,23 @@ class ConfigParser {
|
||||
* if the BufferedReader cannot be read.
|
||||
*
|
||||
*/
|
||||
public static Map<String, String> parse(BufferedReader input) throws IOException {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
String inputLine;
|
||||
inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
|
||||
private static Map<String, String> parse(BufferedReader input) throws IOException {
|
||||
try {
|
||||
Map<String, String> result = new HashMap<String, String>();
|
||||
String inputLine;
|
||||
while ((inputLine = input.readLine()) != null) {
|
||||
inputLine = stripComments(inputLine);
|
||||
if (inputLine.length() == 0)
|
||||
continue;
|
||||
String[] splitLine = DataHelper.split(inputLine, "=", 2);
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
|
||||
}
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
return result;
|
||||
} finally {
|
||||
try { input.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,15 +116,21 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map<String, String> parse(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
Map<String, String> rv = parse(input);
|
||||
public static Map<String, String> parse(File file) throws IOException {
|
||||
FileInputStream fileStream = null;
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException ioe) {}
|
||||
return rv;
|
||||
fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream, "UTF-8"));
|
||||
Map<String, String> rv = parse(input);
|
||||
return rv;
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,11 +143,13 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map<String, String> parse(String string) throws IOException {
|
||||
/****
|
||||
public static Map<String, String> parse(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return parse(input);
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. If file cannot be read,
|
||||
@@ -151,8 +162,8 @@ class ConfigParser {
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
public static Map<String, String> parse(File file, Map<String, String> map) {
|
||||
Map<String, String> result;
|
||||
public static Map<String, String> parse(File file, Map<String, String> map) {
|
||||
Map<String, String> result;
|
||||
try {
|
||||
result = parse(file);
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
@@ -178,19 +189,21 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if input cannot be read.
|
||||
*/
|
||||
public static List<String> parseSubscriptions(BufferedReader input)
|
||||
private static List<String> parseSubscriptions(BufferedReader input)
|
||||
throws IOException {
|
||||
List<String> result = new LinkedList<String>();
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = stripComments(inputLine).trim();
|
||||
if (inputLine.length() > 0) {
|
||||
result.add(inputLine);
|
||||
try {
|
||||
List<String> result = new ArrayList<String>(4);
|
||||
String inputLine;
|
||||
while ((inputLine = input.readLine()) != null) {
|
||||
inputLine = stripComments(inputLine).trim();
|
||||
if (inputLine.length() > 0) {
|
||||
result.add(inputLine);
|
||||
}
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
return result;
|
||||
} finally {
|
||||
try { input.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
input.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,15 +215,21 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static List<String> parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream));
|
||||
List<String> rv = parseSubscriptions(input);
|
||||
private static List<String> parseSubscriptions(File file) throws IOException {
|
||||
FileInputStream fileStream = null;
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException ioe) {}
|
||||
return rv;
|
||||
fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream, "UTF-8"));
|
||||
List<String> rv = parseSubscriptions(input);
|
||||
return rv;
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,11 +241,13 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if string cannot be read.
|
||||
*/
|
||||
/****
|
||||
public static List<String> parseSubscriptions(String string) throws IOException {
|
||||
StringReader stringReader = new StringReader(string);
|
||||
BufferedReader input = new BufferedReader(stringReader);
|
||||
return parseSubscriptions(input);
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Return a List using the contents of the File file. If file cannot be
|
||||
@@ -276,12 +297,15 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if the BufferedWriter cannot be written to.
|
||||
*/
|
||||
public static void write(Map<String, String> map, BufferedWriter output) throws IOException {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
output.write(entry.getKey() + '=' + entry.getValue());
|
||||
output.newLine();
|
||||
private static void write(Map<String, String> map, BufferedWriter output) throws IOException {
|
||||
try {
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
output.write(entry.getKey() + '=' + entry.getValue());
|
||||
output.newLine();
|
||||
}
|
||||
} finally {
|
||||
try { output.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +322,7 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map<String, String> map, File file) throws IOException {
|
||||
public static void write(Map<String, String> map, File file) throws IOException {
|
||||
boolean success = false;
|
||||
if (!isWindows) {
|
||||
File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile());
|
||||
@@ -326,13 +350,16 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List<String> list, BufferedWriter output)
|
||||
private static void writeSubscriptions(List<String> list, BufferedWriter output)
|
||||
throws IOException {
|
||||
for (String s : list) {
|
||||
output.write(s);
|
||||
output.newLine();
|
||||
try {
|
||||
for (String s : list) {
|
||||
output.write(s);
|
||||
output.newLine();
|
||||
}
|
||||
} finally {
|
||||
try { output.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
output.close();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,7 +373,7 @@ class ConfigParser {
|
||||
* @throws IOException
|
||||
* if output cannot be written to.
|
||||
*/
|
||||
public static void writeSubscriptions(List<String> list, File file)
|
||||
private static void writeSubscriptions(List<String> list, File file)
|
||||
throws IOException {
|
||||
writeSubscriptions(list, new BufferedWriter(
|
||||
new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));
|
||||
|
||||
@@ -23,19 +23,21 @@ package net.i2p.addressbook;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.client.naming.SingleFileNamingService;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
@@ -53,6 +55,12 @@ public class Daemon {
|
||||
private static final String DEFAULT_SUB = "http://i2p-projekt.i2p/hosts.txt";
|
||||
/** @since 0.9.12 */
|
||||
static final String OLD_DEFAULT_SUB = "http://www.i2p2.i2p/hosts.txt";
|
||||
/** Any properties we receive from the subscription, we store to the
|
||||
* addressbook with this prefix, so it knows it's part of the signature.
|
||||
* This is also chosen so that it can't be spoofed.
|
||||
*/
|
||||
private static final String RCVD_PROP_PREFIX = "=";
|
||||
private static final boolean MUST_VALIDATE = false;
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
@@ -80,10 +88,9 @@ public class Daemon {
|
||||
*/
|
||||
public static void update(AddressBook master, AddressBook router,
|
||||
File published, SubscriptionList subscriptions, Log log) {
|
||||
Iterator<AddressBook> iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
for (AddressBook book : subscriptions) {
|
||||
// yes, the EepGet fetch() is done in next()
|
||||
router.merge(iter.next(), false, log);
|
||||
router.merge(book, false, log);
|
||||
}
|
||||
router.write();
|
||||
if (published != null) {
|
||||
@@ -129,18 +136,22 @@ public class Daemon {
|
||||
while (iter.hasNext()) {
|
||||
// yes, the EepGet fetch() is done in next()
|
||||
long start = System.currentTimeMillis();
|
||||
AddressBook sub = iter.next();
|
||||
long end = System.currentTimeMillis();
|
||||
AddressBook addressbook = iter.next();
|
||||
// SubscriptionIterator puts in a dummy AddressBook with no location if no fetch is done
|
||||
if (DEBUG && log != null && sub.getLocation() != null)
|
||||
log.append("Fetch of " + sub.getLocation() + " took " + (end - start));
|
||||
start = end;
|
||||
if (DEBUG && log != null && addressbook.getLocation() != null) {
|
||||
long end = System.currentTimeMillis();
|
||||
log.append("Fetch of " + addressbook.getLocation() + " took " + (end - start));
|
||||
start = end;
|
||||
}
|
||||
int old = 0, nnew = 0, invalid = 0, conflict = 0, total = 0;
|
||||
for (Iterator<Map.Entry<String, String>> eIter = sub.iterator(); eIter.hasNext(); ) {
|
||||
Map.Entry<String, String> entry = eIter.next();
|
||||
int deleted = 0;
|
||||
for (Map.Entry<String, HostTxtEntry> entry : addressbook) {
|
||||
total++;
|
||||
// may be null for 'remove' entries
|
||||
String key = entry.getKey();
|
||||
boolean isKnown;
|
||||
Destination oldDest = null;
|
||||
// NOT set for text file NamingService
|
||||
Destination oldDest;
|
||||
if (isTextFile) {
|
||||
if (knownNames == null) {
|
||||
// load the hostname set
|
||||
@@ -148,22 +159,305 @@ public class Daemon {
|
||||
opts.setProperty("file", "hosts.txt");
|
||||
knownNames = router.getNames(opts);
|
||||
}
|
||||
isKnown = knownNames.contains(key);
|
||||
oldDest = null;
|
||||
isKnown = key != null ? knownNames.contains(key) : null;
|
||||
} else {
|
||||
oldDest = router.lookup(key);
|
||||
oldDest = key != null ? router.lookup(key) : null;
|
||||
isKnown = oldDest != null;
|
||||
}
|
||||
try {
|
||||
if (!isKnown) {
|
||||
if (AddressBook.isValidKey(key)) {
|
||||
Destination dest = new Destination(entry.getValue());
|
||||
Properties props = new Properties();
|
||||
props.setProperty("s", sub.getLocation());
|
||||
HostTxtEntry he = entry.getValue();
|
||||
Properties hprops = he.getProps();
|
||||
boolean mustValidate = MUST_VALIDATE || hprops != null;
|
||||
String action = hprops != null ? hprops.getProperty(HostTxtEntry.PROP_ACTION) : null;
|
||||
if (key == null && !he.hasValidRemoveSig()) {
|
||||
if (log != null) {
|
||||
log.append("Bad signature of action " + action + " for key " +
|
||||
hprops.getProperty(HostTxtEntry.PROP_NAME) +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
invalid++;
|
||||
} else if (key != null && mustValidate && !he.hasValidSig()) {
|
||||
if (log != null) {
|
||||
log.append("Bad signature of action " + action + " for key " + key +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
invalid++;
|
||||
} else if (action != null || !isKnown) {
|
||||
if (key != null && AddressBook.isValidKey(key)) {
|
||||
Destination dest = new Destination(he.getDest());
|
||||
Properties props = new OrderedProperties();
|
||||
props.setProperty("s", addressbook.getLocation());
|
||||
boolean allowExistingKeyInPublished = false;
|
||||
if (mustValidate) {
|
||||
// sig checked above
|
||||
props.setProperty("v", "true");
|
||||
}
|
||||
if (hprops != null) {
|
||||
// merge in all the received properties
|
||||
for (Map.Entry<Object, Object> e : hprops.entrySet()) {
|
||||
// Add prefix to indicate received property
|
||||
props.setProperty(RCVD_PROP_PREFIX + e.getKey(), (String) e.getValue());
|
||||
}
|
||||
}
|
||||
if (action != null) {
|
||||
// Process commands. hprops is non-null.
|
||||
// Must handle isKnown in each case.
|
||||
if (action.equals(HostTxtEntry.ACTION_ADDDEST)) {
|
||||
// Add an alternate destination (new crypto) for existing hostname
|
||||
// Requires new NamingService support if the key exists
|
||||
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
|
||||
if (polddest != null) {
|
||||
Destination pod = new Destination(polddest);
|
||||
List<Destination> pod2 = router.lookupAll(key);
|
||||
if (pod2 == null) {
|
||||
// we didn't know it before, so we'll add it
|
||||
// check inner sig anyway
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (pod2.contains(dest)) {
|
||||
// we knew it before, with the same dest
|
||||
old++;
|
||||
continue;
|
||||
} else if (pod2.contains(pod)) {
|
||||
// checks out, so verify the inner sig
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
// TODO Requires NamingService support
|
||||
// if (isTextFile), do we replace or not? check sigType.isAvailable()
|
||||
boolean success = router.addDestination(key, dest, props);
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("Additional address for " + key +
|
||||
" added to address book. From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Failed to add additional address for " + key +
|
||||
" From: " + addressbook.getLocation());
|
||||
}
|
||||
// now update the published addressbook
|
||||
// ditto
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
// FIXME this fails, no support in SFNS
|
||||
success = publishedNS.addDestination(key, dest, props);
|
||||
if (log != null && !success)
|
||||
log.append("Add to published address book " + published.getAbsolutePath() + " failed for " + key);
|
||||
}
|
||||
nnew++;
|
||||
continue;
|
||||
} else {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod2, he.getDest(), addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_ADDNAME)) {
|
||||
// Add an alias for an existing hostname, same dest
|
||||
if (isKnown) {
|
||||
// could be same or different dest
|
||||
old++;
|
||||
continue;
|
||||
}
|
||||
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
|
||||
if (poldname != null) {
|
||||
List<Destination> pod = router.lookupAll(poldname);
|
||||
if (pod == null) {
|
||||
// we didn't have the old one, so we'll add the new one
|
||||
} else if (pod.contains(dest)) {
|
||||
// checks out, so we'll add the new one
|
||||
} else {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod, he.getDest(), addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_ADDSUBDOMAIN)) {
|
||||
// add a subdomain with verification
|
||||
if (isKnown) {
|
||||
old++;
|
||||
continue;
|
||||
}
|
||||
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
|
||||
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
|
||||
if (polddest != null && poldname != null) {
|
||||
// check for valid subdomain
|
||||
if (!AddressBook.isValidKey(poldname) ||
|
||||
key.indexOf('.' + poldname) <= 0) {
|
||||
if (log != null)
|
||||
log.append("Action: " + action + " failed because" +
|
||||
" old name " + poldname +
|
||||
" is invalid" +
|
||||
". From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
Destination pod = new Destination(polddest);
|
||||
List<Destination> pod2 = router.lookupAll(poldname);
|
||||
if (pod2 == null) {
|
||||
// we didn't have the old name
|
||||
// check inner sig anyway
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (pod2.contains(pod)) {
|
||||
// checks out, so verify the inner sig
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod2, polddest, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_CHANGEDEST)) {
|
||||
// change destination on an existing entry
|
||||
// This removes all previous destinations under that hostname,
|
||||
// is this what we want?
|
||||
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
|
||||
if (polddest != null) {
|
||||
Destination pod = new Destination(polddest);
|
||||
List<Destination> pod2 = router.lookupAll(key);
|
||||
if (pod2 == null) {
|
||||
// we didn't have the old name
|
||||
// check inner sig anyway
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (pod2.contains(dest)) {
|
||||
// we already have the new dest
|
||||
old++;
|
||||
continue;
|
||||
} else if (pod2.contains(pod)) {
|
||||
// checks out, so verify the inner sig
|
||||
if (!he.hasValidInnerSig()) {
|
||||
logInner(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
if (log != null) {
|
||||
if (pod2.size() == 1)
|
||||
log.append("Changing destination for " + key +
|
||||
". From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Replacing " + pod2.size() + " destinations for " + key +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
allowExistingKeyInPublished = true;
|
||||
props.setProperty("m", Long.toString(I2PAppContext.getGlobalContext().clock().now()));
|
||||
} else {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod2, polddest, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_CHANGENAME)) {
|
||||
// Delete old name, replace with new
|
||||
// This removes all previous destinations under that hostname,
|
||||
// is this what we want?
|
||||
if (isKnown) {
|
||||
old++;
|
||||
continue;
|
||||
}
|
||||
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
|
||||
if (poldname != null) {
|
||||
List<Destination> pod = router.lookupAll(poldname);
|
||||
if (pod == null) {
|
||||
// we didn't have the old name
|
||||
} else if (pod.contains(dest)) {
|
||||
// checks out, so we'll delete it
|
||||
if (knownNames != null)
|
||||
knownNames.remove(poldname);
|
||||
boolean success = router.remove(poldname, dest);
|
||||
if (success)
|
||||
deleted++;
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("Removed: " + poldname +
|
||||
" to be replaced with " + key +
|
||||
". From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Remove failed for: " + poldname +
|
||||
" to be replaced with " + key +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
// now update the published addressbook
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.remove(poldname, dest);
|
||||
if (log != null && !success)
|
||||
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
|
||||
}
|
||||
} else {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod, he.getDest(), addressbook);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, key, addressbook);
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_REMOVE) ||
|
||||
action.equals(HostTxtEntry.ACTION_REMOVEALL)) {
|
||||
// w/o name=dest handled below
|
||||
if (log != null)
|
||||
log.append("Action: " + action + " with name=dest invalid" +
|
||||
". From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
continue;
|
||||
} else if (action.equals(HostTxtEntry.ACTION_UPDATE)) {
|
||||
if (isKnown) {
|
||||
allowExistingKeyInPublished = true;
|
||||
props.setProperty("m", Long.toString(I2PAppContext.getGlobalContext().clock().now()));
|
||||
}
|
||||
} else {
|
||||
if (log != null)
|
||||
log.append("Action: " + action + " unrecognized" +
|
||||
". From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} // action != null
|
||||
boolean success = router.put(key, dest, props);
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("New address " + key +
|
||||
" added to address book. From: " + sub.getLocation());
|
||||
" added to address book. From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Save to naming service " + router + " failed for new key " + key);
|
||||
}
|
||||
@@ -171,59 +465,236 @@ public class Daemon {
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.putIfAbsent(key, dest);
|
||||
if (allowExistingKeyInPublished)
|
||||
success = publishedNS.put(key, dest, props);
|
||||
else
|
||||
success = publishedNS.putIfAbsent(key, dest, props);
|
||||
if (log != null && !success) {
|
||||
try {
|
||||
log.append("Save to published address book " + published.getCanonicalPath() + " failed for new key " + key);
|
||||
} catch (IOException ioe) {}
|
||||
log.append("Save to published address book " + published.getAbsolutePath() + " failed for new key " + key);
|
||||
}
|
||||
}
|
||||
if (isTextFile)
|
||||
// keep track for later dup check
|
||||
knownNames.add(key);
|
||||
nnew++;
|
||||
} else if (key == null) {
|
||||
// 'remove' actions
|
||||
// isKnown is false
|
||||
if (action != null) {
|
||||
// Process commands. hprops is non-null.
|
||||
if (action.equals(HostTxtEntry.ACTION_REMOVE)) {
|
||||
// delete this entry
|
||||
String polddest = hprops.getProperty(HostTxtEntry.PROP_DEST);
|
||||
String poldname = hprops.getProperty(HostTxtEntry.PROP_NAME);
|
||||
if (polddest != null && poldname != null) {
|
||||
Destination pod = new Destination(polddest);
|
||||
List<Destination> pod2 = router.lookupAll(poldname);
|
||||
if (pod2 != null && pod2.contains(pod)) {
|
||||
if (knownNames != null && pod2.size() == 1)
|
||||
knownNames.remove(poldname);
|
||||
boolean success = router.remove(poldname, pod);
|
||||
if (success)
|
||||
deleted++;
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("Removed: " + poldname +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Remove failed for: " + poldname +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
// now update the published addressbook
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.remove(poldname, pod);
|
||||
if (log != null && !success)
|
||||
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
|
||||
}
|
||||
} else if (pod2 != null) {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod2, polddest, addressbook);
|
||||
invalid++;
|
||||
} else {
|
||||
old++;
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, "delete", addressbook);
|
||||
invalid++;
|
||||
}
|
||||
} else if (action.equals(HostTxtEntry.ACTION_REMOVEALL)) {
|
||||
// delete all entries with this destination
|
||||
String polddest = hprops.getProperty(HostTxtEntry.PROP_DEST);
|
||||
// oldname is optional, but nice because not all books support reverse lookup
|
||||
if (polddest != null) {
|
||||
Destination pod = new Destination(polddest);
|
||||
String poldname = hprops.getProperty(HostTxtEntry.PROP_NAME);
|
||||
if (poldname != null) {
|
||||
List<Destination> pod2 = router.lookupAll(poldname);
|
||||
if (pod2 != null && pod2.contains(pod)) {
|
||||
if (knownNames != null)
|
||||
knownNames.remove(poldname);
|
||||
boolean success = router.remove(poldname, pod);
|
||||
if (success)
|
||||
deleted++;
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("Removed: " + poldname +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Remove failed for: " + poldname +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
// now update the published addressbook
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.remove(poldname, pod);
|
||||
if (log != null && !success)
|
||||
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
|
||||
}
|
||||
} else if (pod2 != null) {
|
||||
// mismatch, disallow
|
||||
logMismatch(log, action, key, pod2, polddest, addressbook);
|
||||
invalid++;
|
||||
} else {
|
||||
old++;
|
||||
}
|
||||
}
|
||||
// reverse lookup, delete all
|
||||
List<String> revs = router.reverseLookupAll(pod);
|
||||
if (revs != null) {
|
||||
for (String rev : revs) {
|
||||
if (knownNames != null)
|
||||
knownNames.remove(rev);
|
||||
boolean success = router.remove(rev, pod);
|
||||
if (success)
|
||||
deleted++;
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("Removed: " + rev +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
else
|
||||
log.append("Remove failed for: " + rev +
|
||||
" as requested" +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
// now update the published addressbook
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.remove(rev, pod);
|
||||
if (log != null && !success)
|
||||
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + rev);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logMissing(log, action, "delete", addressbook);
|
||||
invalid++;
|
||||
}
|
||||
} else {
|
||||
if (log != null)
|
||||
log.append("Action: " + action + " w/o name=dest unrecognized" +
|
||||
". From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
if (log != null)
|
||||
log.append("No action in command line" +
|
||||
". From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
continue;
|
||||
}
|
||||
} else if (log != null) {
|
||||
log.append("Bad hostname " + key + " from "
|
||||
+ sub.getLocation());
|
||||
log.append("Bad hostname " + key + ". From: "
|
||||
+ addressbook.getLocation());
|
||||
invalid++;
|
||||
}
|
||||
/****
|
||||
} else if (false && DEBUG && log != null) {
|
||||
// lookup the conflict if we haven't yet (O(n**2) for text file)
|
||||
if (isTextFile)
|
||||
oldDest = router.lookup(key);
|
||||
if (oldDest != null && !oldDest.toBase64().equals(entry.getValue())) {
|
||||
log.append("Conflict for " + key + " from "
|
||||
+ sub.getLocation()
|
||||
log.append("Conflict for " + key + ". From: "
|
||||
+ addressbook.getLocation()
|
||||
+ ". Destination in remote address book is "
|
||||
+ entry.getValue());
|
||||
conflict++;
|
||||
} else {
|
||||
old++;
|
||||
}
|
||||
****/
|
||||
} else {
|
||||
old++;
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (log != null)
|
||||
log.append("Invalid b64 for " + key + " From: " + sub.getLocation());
|
||||
log.append("Invalid b64 for " + key + " From: " + addressbook.getLocation());
|
||||
invalid++;
|
||||
}
|
||||
total++;
|
||||
}
|
||||
if (DEBUG && log != null && total > 0) {
|
||||
log.append("Merge of " + sub.getLocation() + " into " + router +
|
||||
log.append("Merge of " + addressbook.getLocation() + " into " + router +
|
||||
" took " + (System.currentTimeMillis() - start) + " ms with " +
|
||||
total + " total, " +
|
||||
nnew + " new, " +
|
||||
old + " old, " +
|
||||
deleted + " deleted, " +
|
||||
invalid + " invalid, " +
|
||||
conflict + " conflicts");
|
||||
}
|
||||
sub.delete();
|
||||
}
|
||||
} // entries
|
||||
addressbook.delete();
|
||||
} // subscriptions
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
/** @since 0.9.26 */
|
||||
private static void logInner(Log log, String action, String name, AddressBook addressbook) {
|
||||
if (log != null) {
|
||||
log.append("Action: " + action + " failed because" +
|
||||
" inner signature for key " + name +
|
||||
" failed" +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.9.26 */
|
||||
private static void logMissing(Log log, String action, String name, AddressBook addressbook) {
|
||||
if (log != null) {
|
||||
log.append("Action: " + action + " for " + name +
|
||||
" failed, missing required parameters" +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.9.26 */
|
||||
private static void logMismatch(Log log, String action, String name, List<Destination> dests,
|
||||
String olddest, AddressBook addressbook) {
|
||||
if (log != null) {
|
||||
StringBuilder buf = new StringBuilder(16);
|
||||
final int sz = dests.size();
|
||||
for (int i = 0; i < sz; i++) {
|
||||
buf.append(dests.get(i).toBase64().substring(0, 6));
|
||||
if (i != sz - 1)
|
||||
buf.append(", ");
|
||||
}
|
||||
log.append("Action: " + action + " failed because" +
|
||||
" destinations for " + name +
|
||||
" (" + buf + ')' +
|
||||
" do not include" +
|
||||
" (" + olddest.substring(0, 6) + ')' +
|
||||
". From: " + addressbook.getLocation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update, using the Map settings to provide the parameters.
|
||||
*
|
||||
@@ -236,16 +707,12 @@ public class Daemon {
|
||||
File published = null;
|
||||
boolean should_publish = Boolean.parseBoolean(settings.get("should_publish"));
|
||||
if (should_publish)
|
||||
published = new File(home, settings
|
||||
.get("published_addressbook"));
|
||||
File subscriptionFile = new File(home, settings
|
||||
.get("subscriptions"));
|
||||
published = new File(home, settings.get("published_addressbook"));
|
||||
File subscriptionFile = new File(home, settings.get("subscriptions"));
|
||||
File logFile = new File(home, settings.get("log"));
|
||||
File etagsFile = new File(home, settings.get("etags"));
|
||||
File lastModifiedFile = new File(home, settings
|
||||
.get("last_modified"));
|
||||
File lastFetchedFile = new File(home, settings
|
||||
.get("last_fetched"));
|
||||
File lastModifiedFile = new File(home, settings.get("last_modified"));
|
||||
File lastFetchedFile = new File(home, settings.get("last_fetched"));
|
||||
long delay;
|
||||
try {
|
||||
delay = Long.parseLong(settings.get("update_delay"));
|
||||
@@ -254,13 +721,14 @@ public class Daemon {
|
||||
}
|
||||
delay *= 60 * 60 * 1000;
|
||||
|
||||
List<String> defaultSubs = new LinkedList<String>();
|
||||
List<String> defaultSubs = new ArrayList<String>(4);
|
||||
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
defaultSubs.add(DEFAULT_SUB);
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, lastFetchedFile, delay, defaultSubs, settings
|
||||
.get("proxy_host"), Integer.parseInt(settings.get("proxy_port")));
|
||||
etagsFile, lastModifiedFile, lastFetchedFile,
|
||||
delay, defaultSubs, settings.get("proxy_host"),
|
||||
Integer.parseInt(settings.get("proxy_port")));
|
||||
Log log = SystemVersion.isAndroid() ? null : new Log(logFile);
|
||||
|
||||
// If false, add hosts via naming service; if true, write hosts.txt file directly
|
||||
@@ -319,7 +787,24 @@ public class Daemon {
|
||||
* others are ignored.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
_instance.run(args);
|
||||
if (args != null && args.length > 0 && args[0].equals("test"))
|
||||
_instance.test(args);
|
||||
else
|
||||
_instance.run(args);
|
||||
}
|
||||
|
||||
/** @since 0.9.26 */
|
||||
private static void test(String[] args) {
|
||||
Properties ctxProps = new Properties();
|
||||
String PROP_FORCE = "i2p.naming.blockfile.writeInAppContext";
|
||||
ctxProps.setProperty(PROP_FORCE, "true");
|
||||
I2PAppContext ctx = new I2PAppContext(ctxProps);
|
||||
NamingService ns = getNamingService("hosts.txt");
|
||||
File published = new File("test-published.txt");
|
||||
Log log = new Log(new File("test-log.txt"));
|
||||
SubscriptionList subscriptions = new SubscriptionList("test-sub.txt");
|
||||
update(ns, published, subscriptions, log);
|
||||
ctx.logManager().flush();
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.NamingServiceUpdater;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
* A thread that waits five minutes, then runs the addressbook daemon.
|
||||
@@ -32,9 +33,9 @@ import net.i2p.client.naming.NamingServiceUpdater;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
public class DaemonThread extends Thread implements NamingServiceUpdater {
|
||||
public class DaemonThread extends I2PAppThread implements NamingServiceUpdater {
|
||||
|
||||
private String[] args;
|
||||
private final String[] args;
|
||||
|
||||
/**
|
||||
* Construct a DaemonThread with the command line arguments args.
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -31,6 +32,9 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* A class to iterate through a hosts.txt or config file without
|
||||
* reading the whole thing into memory.
|
||||
@@ -39,26 +43,27 @@ import java.util.NoSuchElementException;
|
||||
* Callers should iterate all the way through or call close()
|
||||
* to ensure the underlying stream is closed.
|
||||
*
|
||||
* @since 0.8.7
|
||||
* This is not used for config files.
|
||||
* It is only used for subscriptions.
|
||||
*
|
||||
* @since 0.8.7, renamed from ConfigIterator in 0.9.26
|
||||
*/
|
||||
class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
class HostTxtIterator implements Iterator<Map.Entry<String, HostTxtEntry>>, Closeable {
|
||||
|
||||
private BufferedReader input;
|
||||
private ConfigEntry next;
|
||||
private MapEntry next;
|
||||
|
||||
/**
|
||||
* A dummy iterator in which hasNext() is always false.
|
||||
*/
|
||||
public ConfigIterator() {}
|
||||
public HostTxtIterator() {}
|
||||
|
||||
/**
|
||||
* An iterator over the key/value pairs in the file.
|
||||
*/
|
||||
public ConfigIterator(File file) {
|
||||
try {
|
||||
public HostTxtIterator(File file) throws IOException {
|
||||
FileInputStream fileStream = new FileInputStream(file);
|
||||
input = new BufferedReader(new InputStreamReader(fileStream));
|
||||
} catch (IOException ioe) {}
|
||||
input = new BufferedReader(new InputStreamReader(fileStream, "UTF-8"));
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
@@ -67,15 +72,13 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
if (next != null)
|
||||
return true;
|
||||
try {
|
||||
String inputLine = input.readLine();
|
||||
while (inputLine != null) {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
next = new ConfigEntry(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
|
||||
return true;
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
String inputLine;
|
||||
while ((inputLine = input.readLine()) != null) {
|
||||
HostTxtEntry he = HostTxtParser.parse(inputLine, true);
|
||||
if (he == null)
|
||||
continue;
|
||||
next = new MapEntry(he.getName(), he);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ioe) {}
|
||||
try { input.close(); } catch (IOException ioe) {}
|
||||
@@ -84,10 +87,15 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Map.Entry<String, String> next() {
|
||||
/**
|
||||
* 'remove' entries will be returned with a null key,
|
||||
* and the value will contain a null name, null dest,
|
||||
* and non-null props.
|
||||
*/
|
||||
public Map.Entry<String, HostTxtEntry> next() {
|
||||
if (!hasNext())
|
||||
throw new NoSuchElementException();
|
||||
Map.Entry<String, String> rv = next;
|
||||
Map.Entry<String, HostTxtEntry> rv = next;
|
||||
next = null;
|
||||
return rv;
|
||||
}
|
||||
@@ -110,11 +118,11 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
/**
|
||||
* The object returned by the iterator.
|
||||
*/
|
||||
private static class ConfigEntry implements Map.Entry<String, String> {
|
||||
private static class MapEntry implements Map.Entry<String, HostTxtEntry> {
|
||||
private final String key;
|
||||
private final String value;
|
||||
private final HostTxtEntry value;
|
||||
|
||||
public ConfigEntry(String k, String v) {
|
||||
public MapEntry(String k, HostTxtEntry v) {
|
||||
key = k;
|
||||
value = v;
|
||||
}
|
||||
@@ -123,11 +131,11 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
public HostTxtEntry getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String setValue(String v) {
|
||||
public HostTxtEntry setValue(HostTxtEntry v) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
250
apps/addressbook/java/src/net/i2p/addressbook/HostTxtParser.java
Normal file
250
apps/addressbook/java/src/net/i2p/addressbook/HostTxtParser.java
Normal file
@@ -0,0 +1,250 @@
|
||||
package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.SecureFile;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* Utility class providing methods to parse and write files in a hosts.txt file
|
||||
* format, and subscription file format.
|
||||
*
|
||||
* @since 0.9.26 modified from ConfigParser
|
||||
*/
|
||||
class HostTxtParser {
|
||||
|
||||
private static final boolean isWindows = SystemVersion.isWindows();
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of BufferedReader input. input must have
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
* The key is converted to lower case.
|
||||
*
|
||||
* Returned map will not contain null ("remove") entries.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
* a Map.
|
||||
* @return A Map containing the key, value pairs from input.
|
||||
* @throws IOException
|
||||
* if the BufferedReader cannot be read.
|
||||
*
|
||||
*/
|
||||
private static Map<String, HostTxtEntry> parse(BufferedReader input) throws IOException {
|
||||
try {
|
||||
Map<String, HostTxtEntry> result = new HashMap<String, HostTxtEntry>();
|
||||
String inputLine;
|
||||
while ((inputLine = input.readLine()) != null) {
|
||||
HostTxtEntry he = parse(inputLine, false);
|
||||
if (he == null)
|
||||
continue;
|
||||
result.put(he.getName(), he);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
try { input.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a HostTxtEntry from the contents of the inputLine.
|
||||
*
|
||||
* @param inputLine key=value[#!k1=v1#k2=v2...]
|
||||
* @param allowCommandOnly if true, a line starting with #! will return
|
||||
* a HostTxtEntry with a null name and dest and non-null props.
|
||||
* If false, these lines will return null.
|
||||
* @return null if no entry found or on error
|
||||
*/
|
||||
public static HostTxtEntry parse(String inputLine, boolean allowCommandOnly) {
|
||||
if (inputLine.startsWith(";"))
|
||||
return null;
|
||||
int comment = inputLine.indexOf("#");
|
||||
String kv;
|
||||
String sprops;
|
||||
if (comment >= 0) {
|
||||
int shebang = inputLine.indexOf(HostTxtEntry.PROPS_SEPARATOR);
|
||||
if (shebang == comment && shebang + 2 < inputLine.length()) {
|
||||
if (comment == 0 && !allowCommandOnly)
|
||||
return null;
|
||||
sprops = inputLine.substring(shebang + 2);
|
||||
} else {
|
||||
if (comment == 0)
|
||||
return null;
|
||||
sprops = null;
|
||||
}
|
||||
kv = inputLine.substring(0, comment);
|
||||
} else {
|
||||
sprops = null;
|
||||
kv = inputLine;
|
||||
}
|
||||
String name, dest;
|
||||
if (comment != 0) {
|
||||
// we have a name=dest
|
||||
String[] splitLine = DataHelper.split(kv, "=", 2);
|
||||
if (splitLine.length < 2)
|
||||
return null;
|
||||
name = splitLine[0].trim().toLowerCase(Locale.US);
|
||||
dest = splitLine[1].trim();
|
||||
if (name.length() == 0 || dest.length() == 0)
|
||||
return null;
|
||||
} else {
|
||||
// line starts with #!, rv will contain props only
|
||||
name = null;
|
||||
dest = null;
|
||||
}
|
||||
HostTxtEntry he;
|
||||
if (sprops != null) {
|
||||
try {
|
||||
he = new HostTxtEntry(name, dest, sprops);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
he = new HostTxtEntry(name, dest);
|
||||
}
|
||||
return he;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. See parse(BufferedReader)
|
||||
* for details of the input format.
|
||||
*
|
||||
* Returned map will not contain null ("remove") entries.
|
||||
*
|
||||
* @param file
|
||||
* A File to parse.
|
||||
* @return A Map containing the key, value pairs from file.
|
||||
* @throws IOException
|
||||
* if file cannot be read.
|
||||
*/
|
||||
public static Map<String, HostTxtEntry> parse(File file) throws IOException {
|
||||
FileInputStream fileStream = null;
|
||||
try {
|
||||
fileStream = new FileInputStream(file);
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(
|
||||
fileStream, "UTF-8"));
|
||||
Map<String, HostTxtEntry> rv = parse(input);
|
||||
return rv;
|
||||
} finally {
|
||||
if (fileStream != null) {
|
||||
try {
|
||||
fileStream.close();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map using the contents of the File file. If file cannot be read,
|
||||
* use map instead, and write the result to where file should have been.
|
||||
*
|
||||
* Returned map will not contain null ("remove") entries.
|
||||
*
|
||||
* @param file
|
||||
* A File to attempt to parse.
|
||||
* @param map
|
||||
* A Map containing values to use as defaults.
|
||||
* @return A Map containing the key, value pairs from file, or if file
|
||||
* cannot be read, map.
|
||||
*/
|
||||
public static Map<String, HostTxtEntry> parse(File file, Map<String, HostTxtEntry> map) {
|
||||
Map<String, HostTxtEntry> result;
|
||||
try {
|
||||
result = parse(file);
|
||||
for (Map.Entry<String, HostTxtEntry> entry : map.entrySet()) {
|
||||
if (!result.containsKey(entry.getKey()))
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
} catch (IOException exp) {
|
||||
result = map;
|
||||
try {
|
||||
write(result, file);
|
||||
} catch (IOException exp2) {
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to BufferedWriter output. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to output.
|
||||
* @param output
|
||||
* A BufferedWriter to write the Map to.
|
||||
* @throws IOException
|
||||
* if the BufferedWriter cannot be written to.
|
||||
*/
|
||||
private static void write(Map<String, HostTxtEntry> map, BufferedWriter output) throws IOException {
|
||||
try {
|
||||
for (Map.Entry<String, HostTxtEntry> entry : map.entrySet()) {
|
||||
entry.getValue().write(output);
|
||||
}
|
||||
} finally {
|
||||
try { output.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write contents of Map map to the File file. Output is written
|
||||
* with one key, value pair on each line, in the format: key=value.
|
||||
* Write to a temp file in the same directory and then rename, to not corrupt
|
||||
* simultaneous accesses by the router. Except on Windows where renameTo()
|
||||
* will fail if the target exists.
|
||||
*
|
||||
* @param map
|
||||
* A Map to write to file.
|
||||
* @param file
|
||||
* A File to write the Map to.
|
||||
* @throws IOException
|
||||
* if file cannot be written to.
|
||||
*/
|
||||
public static void write(Map<String, HostTxtEntry> map, File file) throws IOException {
|
||||
boolean success = false;
|
||||
if (!isWindows) {
|
||||
File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile());
|
||||
write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8")));
|
||||
success = tmp.renameTo(file);
|
||||
if (!success) {
|
||||
tmp.delete();
|
||||
//System.out.println("Warning: addressbook rename fail from " + tmp + " to " + file);
|
||||
}
|
||||
}
|
||||
if (!success) {
|
||||
// hmm, that didn't work, try it the old way
|
||||
write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
File f = new File("tmp-hosts.txt");
|
||||
Map<String, HostTxtEntry> map = parse(f);
|
||||
for (HostTxtEntry e : map.values()) {
|
||||
System.out.println("Host: " + e.getName() +
|
||||
"\nDest: " + e.getDest() +
|
||||
"\nAction: " + (e.getProps() != null ? e.getProps().getProperty("action") : "(none)") +
|
||||
"\nValid Inner? " + e.hasValidInnerSig() +
|
||||
"\nValid? " + e.hasValidSig() +
|
||||
'\n');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,8 +23,9 @@ package net.i2p.addressbook;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -35,7 +36,7 @@ import java.util.Date;
|
||||
*/
|
||||
class Log {
|
||||
|
||||
private File file;
|
||||
private final File file;
|
||||
|
||||
/**
|
||||
* Construct a Log instance that writes to the File file.
|
||||
@@ -56,8 +57,8 @@ class Log {
|
||||
public void append(String entry) {
|
||||
BufferedWriter bw = null;
|
||||
try {
|
||||
bw = new BufferedWriter(new FileWriter(this.file,
|
||||
true));
|
||||
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.file,
|
||||
true), "UTF-8"));
|
||||
String timestamp = new Date().toString();
|
||||
bw.write(timestamp + " -- " + entry);
|
||||
bw.newLine();
|
||||
@@ -73,7 +74,9 @@ class Log {
|
||||
*
|
||||
* @return The File that the log is writing to.
|
||||
*/
|
||||
/****
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
@@ -29,10 +29,8 @@ package net.i2p.addressbook;
|
||||
*/
|
||||
class Subscription {
|
||||
|
||||
private String location;
|
||||
|
||||
private final String location;
|
||||
private String etag;
|
||||
|
||||
private String lastModified;
|
||||
private long lastFetched;
|
||||
|
||||
@@ -41,14 +39,15 @@ class Subscription {
|
||||
* was last read at the time represented by etag and lastModified.
|
||||
*
|
||||
* @param location
|
||||
* A String representing a url to a remote address book.
|
||||
* A String representing a url to a remote address book. Non-null.
|
||||
* @param etag
|
||||
* The etag header that we recieved the last time we read this
|
||||
* subscription.
|
||||
* The etag header that we received the last time we read this
|
||||
* subscription. May be null.
|
||||
* @param lastModified
|
||||
* the last-modified header we recieved the last time we read
|
||||
* this subscription.
|
||||
* @param lastFetched when the subscription was last fetched (Java time, as a String)
|
||||
* the last-modified header we received the last time we read
|
||||
* this subscription. May be null.
|
||||
* @param lastFetched when the subscription was last fetched (Java time, as a String).
|
||||
* May be null.
|
||||
*/
|
||||
public Subscription(String location, String etag, String lastModified, String lastFetched) {
|
||||
this.location = location;
|
||||
@@ -71,7 +70,7 @@ class Subscription {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the etag header that we recieved the last time we read this
|
||||
* Return the etag header that we received the last time we read this
|
||||
* subscription.
|
||||
*
|
||||
* @return A String containing the etag header.
|
||||
@@ -91,7 +90,7 @@ class Subscription {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last-modified header that we recieved the last time we read
|
||||
* Return the last-modified header that we received the last time we read
|
||||
* this subscription.
|
||||
*
|
||||
* @return A String containing the last-modified header.
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.HostTxtEntry;
|
||||
import net.i2p.util.PortMapper;
|
||||
|
||||
/**
|
||||
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
|
||||
@@ -36,9 +38,9 @@ import net.i2p.I2PAppContext;
|
||||
*/
|
||||
class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
|
||||
private Iterator<Subscription> subIterator;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
private final Iterator<Subscription> subIterator;
|
||||
private final String proxyHost;
|
||||
private final int proxyPort;
|
||||
private final long delay;
|
||||
|
||||
/**
|
||||
@@ -69,11 +71,17 @@ class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
* Yes, the EepGet fetch() is done in here in next().
|
||||
*
|
||||
* see java.util.Iterator#next()
|
||||
* @return an AddressBook (empty if the minimum delay has not been met)
|
||||
* @return non-null AddressBook (empty if the minimum delay has not been met,
|
||||
* or there is no proxy tunnel, or the fetch otherwise fails)
|
||||
*/
|
||||
public AddressBook next() {
|
||||
Subscription sub = this.subIterator.next();
|
||||
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
|
||||
if (sub.getLocation().startsWith("file:")) {
|
||||
// test only
|
||||
return new AddressBook(sub.getLocation().substring(5));
|
||||
} else if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now() &&
|
||||
I2PAppContext.getGlobalContext().portMapper().getPort(PortMapper.SVC_HTTP_PROXY) >= 0 &&
|
||||
!I2PAppContext.getGlobalContext().getBooleanProperty("i2p.vmCommSystem")) {
|
||||
//System.err.println("Fetching addressbook from " + sub.getLocation());
|
||||
return new AddressBook(sub, this.proxyHost, this.proxyPort);
|
||||
} else {
|
||||
@@ -81,7 +89,7 @@ class SubscriptionIterator implements Iterator<AddressBook> {
|
||||
// DataHelper.formatDuration(I2PAppContext.getGlobalContext().clock().now() - sub.getLastFetched()) +
|
||||
// " ago but the minimum delay is " +
|
||||
// DataHelper.formatDuration(this.delay));
|
||||
return new AddressBook(Collections.<String, String> emptyMap());
|
||||
return new AddressBook(Collections.<String, HostTxtEntry>emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ package net.i2p.addressbook;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -34,19 +35,15 @@ import java.util.Map;
|
||||
* @author Ragnarok
|
||||
*
|
||||
*/
|
||||
class SubscriptionList {
|
||||
class SubscriptionList implements Iterable<AddressBook> {
|
||||
|
||||
private List<Subscription> subscriptions;
|
||||
|
||||
private File etagsFile;
|
||||
|
||||
private File lastModifiedFile;
|
||||
private File lastFetchedFile;
|
||||
private final List<Subscription> subscriptions;
|
||||
private final File etagsFile;
|
||||
private final File lastModifiedFile;
|
||||
private final File lastFetchedFile;
|
||||
private final long delay;
|
||||
|
||||
private String proxyHost;
|
||||
|
||||
private int proxyPort;
|
||||
private final String proxyHost;
|
||||
private final int proxyPort;
|
||||
|
||||
/**
|
||||
* Construct a SubscriptionList using the urls from locationsFile and, if
|
||||
@@ -69,7 +66,7 @@ class SubscriptionList {
|
||||
public SubscriptionList(File locationsFile, File etagsFile,
|
||||
File lastModifiedFile, File lastFetchedFile, long delay, List<String> defaultSubs, String proxyHost,
|
||||
int proxyPort) {
|
||||
this.subscriptions = new LinkedList<Subscription>();
|
||||
this.subscriptions = new ArrayList<Subscription>(4);
|
||||
this.etagsFile = etagsFile;
|
||||
this.lastModifiedFile = lastModifiedFile;
|
||||
this.lastFetchedFile = lastFetchedFile;
|
||||
@@ -84,17 +81,17 @@ class SubscriptionList {
|
||||
try {
|
||||
etags = ConfigParser.parse(etagsFile);
|
||||
} catch (IOException exp) {
|
||||
etags = new HashMap<String, String>();
|
||||
etags = Collections.<String, String>emptyMap();
|
||||
}
|
||||
try {
|
||||
lastModified = ConfigParser.parse(lastModifiedFile);
|
||||
} catch (IOException exp) {
|
||||
lastModified = new HashMap<String, String>();
|
||||
lastModified = Collections.<String, String>emptyMap();
|
||||
}
|
||||
try {
|
||||
lastFetched = ConfigParser.parse(lastFetchedFile);
|
||||
} catch (IOException exp) {
|
||||
lastFetched = new HashMap<String, String>();
|
||||
lastFetched = Collections.<String, String>emptyMap();
|
||||
}
|
||||
for (String location : locations) {
|
||||
this.subscriptions.add(new Subscription(location, etags.get(location),
|
||||
@@ -103,6 +100,24 @@ class SubscriptionList {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing only.
|
||||
*
|
||||
* @param hoststxt path to a local file used as the test 'subscription' input
|
||||
* @since 0.9.26
|
||||
*/
|
||||
public SubscriptionList(String hoststxt) {
|
||||
File dummy = new File("/dev/null");
|
||||
this.etagsFile = dummy;
|
||||
this.lastModifiedFile = dummy;
|
||||
this.lastFetchedFile = dummy;
|
||||
this.delay = 0;
|
||||
this.proxyHost = "127.0.0.1";
|
||||
this.proxyPort = 4444;
|
||||
Subscription sub = new Subscription("file:" + hoststxt, null, null, null);
|
||||
this.subscriptions = Collections.singletonList(sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator over the AddressBooks represented by the Subscriptions
|
||||
* in this SubscriptionList.
|
||||
@@ -121,9 +136,10 @@ class SubscriptionList {
|
||||
* won't be read back correctly; the '=' should be escaped.
|
||||
*/
|
||||
public void write() {
|
||||
Map<String, String> etags = new HashMap<String, String>();
|
||||
Map<String, String> lastModified = new HashMap<String, String>();
|
||||
Map<String, String> lastFetched = new HashMap<String, String>();
|
||||
int sz = subscriptions.size();
|
||||
Map<String, String> etags = new HashMap<String, String>(sz);
|
||||
Map<String, String> lastModified = new HashMap<String, String>(sz);
|
||||
Map<String, String> lastFetched = new HashMap<String, String>(sz);
|
||||
for (Subscription sub : this.subscriptions) {
|
||||
if (sub.getEtag() != null) {
|
||||
etags.put(sub.getLocation(), sub.getEtag());
|
||||
@@ -131,13 +147,16 @@ class SubscriptionList {
|
||||
if (sub.getLastModified() != null) {
|
||||
lastModified.put(sub.getLocation(), sub.getLastModified());
|
||||
}
|
||||
lastFetched.put(sub.getLocation(), "" + sub.getLastFetched());
|
||||
lastFetched.put(sub.getLocation(), Long.toString(sub.getLastFetched()));
|
||||
}
|
||||
try {
|
||||
ConfigParser.write(etags, this.etagsFile);
|
||||
} catch (IOException exp) {}
|
||||
try {
|
||||
ConfigParser.write(lastModified, this.lastModifiedFile);
|
||||
} catch (IOException exp) {}
|
||||
try {
|
||||
ConfigParser.write(lastFetched, this.lastFetchedFile);
|
||||
} catch (IOException exp) {
|
||||
}
|
||||
} catch (IOException exp) {}
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/addressbook/java/src/net/i2p/addressbook/package.html
Normal file
11
apps/addressbook/java/src/net/i2p/addressbook/package.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The addressbook application, which fetches hosts.txt files from subscription URLs via
|
||||
HTTP and adds new hosts to the local database.
|
||||
While implemented as a webapp, this application contains no user interface.
|
||||
May also be packaged as a jar, as is done for Android.
|
||||
The webapp named 'addressbook' in the console is actually SusiDNS.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -28,4 +28,11 @@
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!-- this webapp doesn't actually use sessions or cookies -->
|
||||
<session-config>
|
||||
<session-timeout>30</session-timeout>
|
||||
<cookie-config>
|
||||
<http-only>true</http-only>
|
||||
</cookie-config>
|
||||
</session-config>
|
||||
</web-app>
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.DataHelper
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.I2PThread;
|
||||
@@ -47,7 +48,7 @@ class AdminRunner implements Runnable {
|
||||
reply(out, "this is not a website");
|
||||
} else if ( (command.indexOf("routerStats.html") >= 0) || (command.indexOf("oldstats.jsp") >= 0) ) {
|
||||
try {
|
||||
out.write("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n"));
|
||||
_generator.generateStatsPage(new OutputStreamWriter(out));
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
@@ -61,7 +62,7 @@ class AdminRunner implements Runnable {
|
||||
reply(out, shutdown(command));
|
||||
} else if (true || command.indexOf("routerConsole.html") > 0) {
|
||||
try {
|
||||
out.write("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n".getBytes());
|
||||
out.write(DataHelper.getASCII("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n"));
|
||||
_context.router().renderStatusHTML(new OutputStreamWriter(out));
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
@@ -80,7 +81,7 @@ class AdminRunner implements Runnable {
|
||||
reply.append("Content-type: text/html\n\n");
|
||||
reply.append(content);
|
||||
try {
|
||||
out.write(reply.toString().getBytes());
|
||||
out.write(DataHelper.getASCII(reply.toString()));
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -97,7 +98,7 @@ class AdminRunner implements Runnable {
|
||||
reply.append("Content-type: text/plain\n\n");
|
||||
reply.append(content);
|
||||
try {
|
||||
out.write(reply.toString().getBytes());
|
||||
out.write(DataHelper.getASCII(reply.toString()));
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
||||
99
apps/apparmor/home.i2p.i2prouter
Normal file
99
apps/apparmor/home.i2p.i2prouter
Normal file
@@ -0,0 +1,99 @@
|
||||
#Last Modified: Sun Dec 06 12:30:32 2015
|
||||
# vim:syntax=apparmor et ts=8 sw=4
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
$INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/fonts>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/ssl_certs>
|
||||
|
||||
capability sys_ptrace,
|
||||
network inet stream,
|
||||
network inet6 stream,
|
||||
|
||||
$INSTALL_PATH/ r,
|
||||
$INSTALL_PATH/{i2psvc,wrapper} rmix,
|
||||
owner $INSTALL_PATH/** rwkm,
|
||||
|
||||
# Needed for Java
|
||||
owner @{PROC} r,
|
||||
owner @{PROC}/[0-9]*/ r,
|
||||
owner @{PROC}/[0-9]*/status r,
|
||||
owner @{PROC}/[0-9]*/stat r,
|
||||
owner @{PROC}/[0-9]*/cmdline r,
|
||||
@{PROC}/uptime r,
|
||||
@{PROC}/sys/kernel/pid_max r,
|
||||
/sys/devices/system/cpu/ r,
|
||||
/sys/devices/system/cpu/** r,
|
||||
|
||||
/dev/random r,
|
||||
/dev/urandom r,
|
||||
|
||||
@{PROC}/1/comm r,
|
||||
|
||||
/etc/ssl/certs/java/** r,
|
||||
/etc/timezone r,
|
||||
/usr/share/javazi/** r,
|
||||
|
||||
# Debian
|
||||
/etc/java-{6,7,8}-openjdk/** r,
|
||||
/usr/lib/jvm/default-java/jre/bin/java rix,
|
||||
|
||||
# Debian, Ubuntu, openSUSE
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/java rix,
|
||||
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/keytool rix,
|
||||
|
||||
# Raspbian
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/java rix,
|
||||
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/keytool rix,
|
||||
|
||||
|
||||
# Fonts are needed for I2P's graphs
|
||||
/usr/share/java/java-atk-wrapper.jar r,
|
||||
|
||||
# Used by some plugins
|
||||
/usr/share/java/eclipse-ecj-*.jar r,
|
||||
|
||||
/{,var/}tmp/ rwm,
|
||||
owner /{,var/}tmp/** rwkm,
|
||||
|
||||
/{,usr/}bin/{,b,d}ash rix,
|
||||
/{,usr/}bin/cat rix,
|
||||
/{,usr/}bin/cut rix,
|
||||
/{,usr/}bin/dirname rix,
|
||||
/{,usr/}bin/expr rix,
|
||||
/{,usr/}bin/{,g,m}awk rix,
|
||||
/{,usr/}bin/grep rix,
|
||||
/{,usr/}bin/id rix,
|
||||
/{,usr/}bin/ldd rix,
|
||||
/{,usr/}bin/ls rix,
|
||||
/{,usr/}bin/mkdir rix,
|
||||
/{,usr/}bin/nohup rix,
|
||||
/{,usr/}bin/ps rix,
|
||||
/{,usr/}bin/rm rix,
|
||||
/{,usr/}bin/sed rix,
|
||||
/{,usr/}bin/sleep rix,
|
||||
/{,usr/}bin/tail rix,
|
||||
/{,usr/}bin/tr rix,
|
||||
/{,usr/}bin/uname rix,
|
||||
/{,usr/}bin/which rix,
|
||||
|
||||
@{HOME}/.java/fonts/** r,
|
||||
owner @{HOME}/.i2p/ rw,
|
||||
owner @{HOME}/.i2p/** rwk,
|
||||
|
||||
# Prevent spamming the logs
|
||||
deny owner @{HOME}/.java/ wk,
|
||||
deny @{HOME}/.fontconfig/ wk,
|
||||
deny @{HOME}/.java/fonts/** w,
|
||||
deny /dev/tty rw,
|
||||
deny /dev/pts/[0-9]* rw,
|
||||
deny @{PROC}/[0-9]*/fd/ r,
|
||||
deny /usr/local/share/fonts/ r,
|
||||
deny /var/cache/fontconfig/ wk,
|
||||
# Used by some versions of the Tanuki wrapper but never used by I2P
|
||||
deny /usr/share/java/hamcrest*.jar r,
|
||||
deny /usr/share/java/junit*.jar r,
|
||||
}
|
||||
@@ -26,12 +26,12 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
# Fast mode - update ondemond
|
||||
# set LG2 to the language you need in envrionment varibales to enable this
|
||||
# set LG2 to the language you need in environment variables to enable this
|
||||
|
||||
# add ../java/ so the refs will work in the po file
|
||||
JPATHS="src"
|
||||
@@ -64,19 +64,19 @@ do
|
||||
echo "Updating the $i file from the tags..."
|
||||
# extract strings from java and jsp files, and update messages.po files
|
||||
# translate calls must be one of the forms:
|
||||
# _("foo")
|
||||
# _t("foo")
|
||||
# _x("foo")
|
||||
# intl._("foo")
|
||||
# intl._t("foo")
|
||||
# intl.title("foo")
|
||||
# handler._("foo")
|
||||
# formhandler._("foo")
|
||||
# handler._t("foo")
|
||||
# formhandler._t("foo")
|
||||
# net.i2p.router.web.Messages.getString("foo")
|
||||
# In a jsp, you must use a helper or handler that has the context set.
|
||||
# To start a new translation, copy the header from an old translation to the new .po file,
|
||||
# then ant distclean updater.
|
||||
find $JPATHS -name *.java > $TMPFILE
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
--keyword=_t --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
--keyword=handler._ --keyword=formhandler._ \
|
||||
--keyword=net.i2p.router.web.Messages.getString \
|
||||
-o ${i}t
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# <kia___@hushmail.com>, 2011.
|
||||
# Translators:
|
||||
# Aesthese, 2016
|
||||
# KIA <kia___@hushmail.com>, 2011
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2011-07-12 19:41+0000\n"
|
||||
"Last-Translator: KIA <kia___@hushmail.com>\n"
|
||||
"Language-Team: Danish (http://www.transifex.net/projects/p/I2P/team/da/)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2016-01-08 07:54+0000\n"
|
||||
"Last-Translator: Aesthese\n"
|
||||
"Language-Team: Danish (http://www.transifex.com/otf/I2P/language/da/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: da\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@@ -24,7 +26,7 @@ msgstr "Start I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "I2P is starting!"
|
||||
msgstr "I2P starter nu!"
|
||||
msgstr "I2P starter!"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
|
||||
msgid "Starting"
|
||||
@@ -46,12 +48,10 @@ msgstr "Genstart I2P"
|
||||
msgid "Stop I2P"
|
||||
msgstr "Stop I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Konfiguration af processbar ikonet"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Skal processbar ikonet være aktivt?"
|
||||
|
||||
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
# This file is distributed under the same license as the desktopgui package.
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# <b790979@klzlk.com>, 2011.
|
||||
# Translators:
|
||||
# PolishAnon <b790979@klzlk.com>, 2011
|
||||
# polacco <polacco@i2pmail.org>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2011-05-25 18:36+0000\n"
|
||||
"Last-Translator: PolishAnon <b790979@klzlk.com>\n"
|
||||
"Language-Team: Polish (http://www.transifex.net/projects/p/I2P/team/pl/)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2015-02-17 20:54+0000\n"
|
||||
"Last-Translator: polacco <polacco@i2pmail.org>\n"
|
||||
"Language-Team: Polish (http://www.transifex.com/projects/p/I2P/language/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pl\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@@ -32,7 +34,7 @@ msgstr "Uruchamianie"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
|
||||
msgid "Launch I2P Browser"
|
||||
msgstr "Uruchom Przeglądarke I2P"
|
||||
msgstr "Uruchom przeglądarkę I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
@@ -46,12 +48,10 @@ msgstr "Zrestartuj I2P"
|
||||
msgid "Stop I2P"
|
||||
msgstr "Zatrzymaj I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Konfiguracja ikony zasobnika"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Czy ikona zasobnika powinna być aktywna?"
|
||||
|
||||
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
# Translators:
|
||||
# testsubject67 <deborinha97@hotmail.com>, 2014
|
||||
# blueboy, 2013
|
||||
# blueboy, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2014-07-05 17:40+0000\n"
|
||||
"Last-Translator: testsubject67 <deborinha97@hotmail.com>\n"
|
||||
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/I2P/language/pt_BR/)\n"
|
||||
"PO-Revision-Date: 2015-12-23 00:12+0000\n"
|
||||
"Last-Translator: blueboy\n"
|
||||
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/otf/I2P/language/pt_BR/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -50,7 +51,7 @@ msgstr "Interromper o roteador I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Configuração de ícone de bandeja"
|
||||
msgstr "Configuração do ícone de bandeja"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
|
||||
@@ -6,20 +6,21 @@
|
||||
# Translators:
|
||||
# ducki2p <ducki2p@gmail.com>, 2011
|
||||
# foo <foo@bar>, 2009
|
||||
# Роман Азаренко <transifex@basicxp.ru>, 2013
|
||||
# Foster Snowhill, 2013
|
||||
# brianhopes <voganc-12@live.ru>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
|
||||
"PO-Revision-Date: 2013-12-04 11:46+0000\n"
|
||||
"Last-Translator: Bergitte <alvina_alexandrova@mail.ru>\n"
|
||||
"Language-Team: Russian (Russia) (http://www.transifex.com/projects/p/I2P/language/ru_RU/)\n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2015-11-01 08:19+0000\n"
|
||||
"Last-Translator: brianhopes <voganc-12@live.ru>\n"
|
||||
"Language-Team: Russian (Russia) (http://www.transifex.com/otf/I2P/language/ru_RU/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru_RU\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
|
||||
msgid "Start I2P"
|
||||
@@ -39,7 +40,7 @@ msgstr "Запустить браузер I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
|
||||
msgid "Configure desktopgui"
|
||||
msgstr "Настроить desktopgui"
|
||||
msgstr "Настроить рабочий стол"
|
||||
|
||||
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
|
||||
msgid "Restart I2P"
|
||||
@@ -49,10 +50,10 @@ msgstr "Перезапустить I2P"
|
||||
msgid "Stop I2P"
|
||||
msgstr "Остановить I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Конфигурация значка в области уведомлений"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
msgstr "Отображать ли значок в области уведомлений?"
|
||||
|
||||
@@ -4,16 +4,17 @@
|
||||
# To contribute translations, see http://www.i2p2.de/newdevelopers
|
||||
#
|
||||
# Translators:
|
||||
# Denis Blank <gribua@gmail.com>, 2011
|
||||
# Denis Lysenko <gribua@gmail.com>, 2011
|
||||
# LinuxChata, 2014
|
||||
# madjong <madjong@i2pmail.org>, 2014
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
|
||||
"PO-Revision-Date: 2014-06-22 10:20+0000\n"
|
||||
"Last-Translator: LinuxChata\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/projects/p/I2P/language/uk_UA/)\n"
|
||||
"PO-Revision-Date: 2015-08-07 16:31+0000\n"
|
||||
"Last-Translator: Denis Lysenko <gribua@gmail.com>\n"
|
||||
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/otf/I2P/language/uk_UA/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
@@ -50,7 +51,7 @@ msgstr "Зупинити I2P"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
|
||||
msgid "Tray icon configuration"
|
||||
msgstr "Настройка трей-іконки"
|
||||
msgstr "Налаштування трей-іконки"
|
||||
|
||||
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
|
||||
msgid "Should tray icon be enabled?"
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ExternalTrayManager extends TrayManager {
|
||||
@Override
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
MenuItem startItem = new MenuItem(_("Start I2P"));
|
||||
MenuItem startItem = new MenuItem(_t("Start I2P"));
|
||||
startItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@@ -35,7 +35,7 @@ public class ExternalTrayManager extends TrayManager {
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
trayIcon.displayMessage(_("Starting"), _("I2P is starting!"), TrayIcon.MessageType.INFO);
|
||||
trayIcon.displayMessage(_t("Starting"), _t("I2P is starting!"), TrayIcon.MessageType.INFO);
|
||||
//Hide the tray icon.
|
||||
//We cannot stop the desktopgui program entirely,
|
||||
//since that risks killing the I2P process as well.
|
||||
|
||||
@@ -23,7 +23,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
public PopupMenu getMainMenu() {
|
||||
PopupMenu popup = new PopupMenu();
|
||||
|
||||
MenuItem browserLauncher = new MenuItem(_("Launch I2P Browser"));
|
||||
MenuItem browserLauncher = new MenuItem(_t("Launch I2P Browser"));
|
||||
browserLauncher.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@@ -47,7 +47,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
MenuItem desktopguiConfigurationLauncher = new MenuItem(_("Configure desktopgui"));
|
||||
MenuItem desktopguiConfigurationLauncher = new MenuItem(_t("Configure desktopgui"));
|
||||
desktopguiConfigurationLauncher.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@@ -64,7 +64,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
});
|
||||
MenuItem restartItem = new MenuItem(_("Restart I2P"));
|
||||
MenuItem restartItem = new MenuItem(_t("Restart I2P"));
|
||||
restartItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
@@ -82,7 +82,7 @@ public class InternalTrayManager extends TrayManager {
|
||||
}
|
||||
|
||||
});
|
||||
MenuItem stopItem = new MenuItem(_("Stop I2P"));
|
||||
MenuItem stopItem = new MenuItem(_t("Stop I2P"));
|
||||
stopItem.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -78,7 +78,7 @@ public abstract class TrayManager {
|
||||
return image;
|
||||
}
|
||||
|
||||
protected static String _(String s) {
|
||||
return DesktopguiTranslator._(s);
|
||||
protected static String _t(String s) {
|
||||
return DesktopguiTranslator._t(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
|
||||
cancelButton = new javax.swing.JButton();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setTitle(_("Tray icon configuration"));
|
||||
setTitle(_t("Tray icon configuration"));
|
||||
|
||||
desktopguiEnabled.setSelected(true);
|
||||
desktopguiEnabled.setText(_("Should tray icon be enabled?"));
|
||||
desktopguiEnabled.setText(_t("Should tray icon be enabled?"));
|
||||
desktopguiEnabled.setActionCommand("shouldDesktopguiBeEnabled");
|
||||
|
||||
okButton.setText("OK");
|
||||
@@ -98,8 +98,8 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
|
||||
configureDesktopgui();
|
||||
}//GEN-LAST:event_okButtonMouseReleased
|
||||
|
||||
protected static String _(String s) {
|
||||
return DesktopguiTranslator._(s);
|
||||
protected static String _t(String s) {
|
||||
return DesktopguiTranslator._t(s);
|
||||
}
|
||||
|
||||
private void configureDesktopgui() {
|
||||
|
||||
@@ -16,11 +16,11 @@ public class DesktopguiTranslator {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static String _(String s) {
|
||||
public static String _t(String s) {
|
||||
return Translate.getString(s, getRouterContext(), BUNDLE_NAME);
|
||||
}
|
||||
|
||||
public static String _(String s, Object o) {
|
||||
public static String _t(String s, Object o) {
|
||||
return Translate.getString(s, o, getRouterContext(), BUNDLE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,340 +1 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
See ../../licenses/LICENSE-GPLv2.txt
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
- I2PSnark:
|
||||
- add multitorrent support by checking the metainfo hash in the
|
||||
PeerAcceptor and feeding it off to the appropriate coordinator
|
||||
- add a web interface
|
||||
|
||||
- BEncode
|
||||
- Byte array length indicator can overflow.
|
||||
- Support really big BigNums (only 256 chars allowed now)
|
||||
- Better BEValue toString(). Uses stupid heuristic now for debugging.
|
||||
- Implemented bencoding.
|
||||
- Remove application level hack to calculate sha1 hash for metainfo
|
||||
(But can it be done as efficiently?)
|
||||
|
||||
- Storage
|
||||
- Check file name filter.
|
||||
|
||||
- TrackerClient
|
||||
- Support undocumented &numwant= request.
|
||||
|
||||
- PeerCoordinator
|
||||
- Disconnect from other seeds as soon as you are a seed yourself.
|
||||
|
||||
- Text UI
|
||||
- Make it completely silent.
|
||||
@@ -18,6 +18,8 @@
|
||||
<pathelement location="../../../core/java/build/obj" />
|
||||
<pathelement location="../../ministreaming/java/build/obj" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<!-- jsp-api.jar only present for debian builds -->
|
||||
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
|
||||
</classpath>
|
||||
</depend>
|
||||
</target>
|
||||
@@ -37,9 +39,15 @@
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
|
||||
destdir="./build/obj"
|
||||
includeAntRuntime="false"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" >
|
||||
includeAntRuntime="false" >
|
||||
<compilerarg line="${javac.compilerargs}" />
|
||||
<classpath>
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../../ministreaming/java/build/mstreaming.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<!-- jsp-api.jar only present for debian builds -->
|
||||
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
@@ -62,7 +70,7 @@
|
||||
<property name="workspace.changes.tr" value="" />
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/web/* **/messages_*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||
<attribute name="Main-Class" value="org.klomp.snark.CommandLine" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
|
||||
@@ -25,12 +25,12 @@ then
|
||||
fi
|
||||
|
||||
# on windows, one must specify the path of commnad find
|
||||
# since windows has its own retarded version of find.
|
||||
# since windows has its own version of find.
|
||||
if which find|grep -q -i windows ; then
|
||||
export PATH=.:/bin:/usr/local/bin:$PATH
|
||||
fi
|
||||
# Fast mode - update ondemond
|
||||
# set LG2 to the language you need in envrionment varibales to enable this
|
||||
# set LG2 to the language you need in environment variables to enable this
|
||||
|
||||
# add ../java/ so the refs will work in the po file
|
||||
JPATHS="../java/src"
|
||||
@@ -63,13 +63,13 @@ do
|
||||
echo "Updating the $i file from the tags..."
|
||||
# extract strings from java and jsp files, and update messages.po files
|
||||
# translate calls must be one of the forms:
|
||||
# _("foo")
|
||||
# _t("foo")
|
||||
# _x("foo")
|
||||
# To start a new translation, copy the header from an old translation to the new .po file,
|
||||
# then ant distclean poupdate.
|
||||
find $JPATHS -name *.java > $TMPFILE
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x \
|
||||
--keyword=_t --keyword=_x \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Container of a byte array representing set and unset bits.
|
||||
@@ -66,7 +68,7 @@ public class BitField
|
||||
|
||||
/**
|
||||
* This returns the actual byte array used. Changes to this array
|
||||
* effect this BitField. Note that some bits at the end of the byte
|
||||
* affect this BitField. Note that some bits at the end of the byte
|
||||
* array are supposed to be always unset if they represent bits
|
||||
* bigger then the size of the bitfield.
|
||||
*/
|
||||
@@ -105,6 +107,37 @@ public class BitField
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given bit to false.
|
||||
*
|
||||
* @exception IndexOutOfBoundsException if bit is smaller then zero
|
||||
* bigger then size (inclusive).
|
||||
* @since 0.9.22
|
||||
*/
|
||||
public void clear(int bit)
|
||||
{
|
||||
if (bit < 0 || bit >= size)
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
synchronized(this) {
|
||||
if ((bitfield[index] & mask) != 0) {
|
||||
count--;
|
||||
bitfield[index] &= ~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all bits to true.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
public void setAll() {
|
||||
Arrays.fill(bitfield, (byte) 0xff);
|
||||
count = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bit is set or false if it is not.
|
||||
*
|
||||
|
||||
45
apps/i2psnark/java/src/org/klomp/snark/CommandLine.java
Normal file
45
apps/i2psnark/java/src/org/klomp/snark/CommandLine.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
|
||||
/**
|
||||
* Simple command line access to various utilities.
|
||||
* Not a public API. Subject to change.
|
||||
* Apps and plugins should use specific classes.
|
||||
*
|
||||
* @since 0.9.26
|
||||
*/
|
||||
public class CommandLine extends net.i2p.util.CommandLine {
|
||||
|
||||
protected static final List<String> SCLASSES = Arrays.asList(new String[] {
|
||||
"org.klomp.snark.MetaInfo",
|
||||
//"org.klomp.snark.Snark",
|
||||
//"org.klomp.snark.StaticSnark",
|
||||
"org.klomp.snark.Storage",
|
||||
"org.klomp.snark.bencode.BDecoder",
|
||||
//"org.klomp.snark.web.RunStandalone",
|
||||
});
|
||||
|
||||
protected CommandLine() {}
|
||||
|
||||
public static void main(String args[]) {
|
||||
List<String> classes = new ArrayList<String>(SCLASSES.size() + CLASSES.size());
|
||||
classes.addAll(SCLASSES);
|
||||
classes.addAll(CLASSES);
|
||||
if (args.length > 0) {
|
||||
exec(args, classes);
|
||||
}
|
||||
usage(classes);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static void usage(List<String> classes) {
|
||||
System.err.println("I2PSnark version " + CoreVersion.VERSION + '\n' +
|
||||
"USAGE: java -jar /path/to/i2psnark.jar command [args]");
|
||||
printCommands(classes);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -213,6 +214,20 @@ class ConnectionAcceptor implements Runnable
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ConnectException ioe)
|
||||
{
|
||||
// This is presumed to be due to socket closing by I2PSnarkUtil.disconnect(),
|
||||
// which does not currently call our halt(), although it should
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Error while accepting", ioe);
|
||||
synchronized(this) {
|
||||
if (!stop) {
|
||||
locked_halt();
|
||||
thread = null;
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
int level = stop ? Log.WARN : Log.ERROR;
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class ExtensionHandler {
|
||||
* @param dht advertise DHT capability
|
||||
* @return bencoded outgoing handshake message
|
||||
*/
|
||||
public static byte[] getHandshake(int metasize, boolean pexAndMetadata, boolean dht) {
|
||||
public static byte[] getHandshake(int metasize, boolean pexAndMetadata, boolean dht, boolean uploadOnly) {
|
||||
Map<String, Object> handshake = new HashMap<String, Object>();
|
||||
Map<String, Integer> m = new HashMap<String, Integer>();
|
||||
if (pexAndMetadata) {
|
||||
@@ -59,6 +59,9 @@ abstract class ExtensionHandler {
|
||||
handshake.put("p", Integer.valueOf(TrackerClient.PORT));
|
||||
handshake.put("v", "I2PSnark");
|
||||
handshake.put("reqq", Integer.valueOf(5));
|
||||
// BEP 21
|
||||
if (uploadOnly)
|
||||
handshake.put("upload_only", Integer.valueOf(1));
|
||||
return BEncoder.bencode(handshake);
|
||||
}
|
||||
|
||||
@@ -90,17 +93,20 @@ abstract class ExtensionHandler {
|
||||
peer.setHandshakeMap(map);
|
||||
Map<String, BEValue> msgmap = map.get("m").getMap();
|
||||
|
||||
if (msgmap.get(TYPE_PEX) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports PEX extension: " + peer);
|
||||
// peer state calls peer listener calls sendPEX()
|
||||
}
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer " + peer + " supports extensions: " + msgmap.keySet());
|
||||
|
||||
if (msgmap.get(TYPE_DHT) != null) {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Peer supports DHT extension: " + peer);
|
||||
// peer state calls peer listener calls sendDHT()
|
||||
}
|
||||
//if (msgmap.get(TYPE_PEX) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports PEX extension: " + peer);
|
||||
// // peer state calls peer listener calls sendPEX()
|
||||
//}
|
||||
|
||||
//if (msgmap.get(TYPE_DHT) != null) {
|
||||
// if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Peer supports DHT extension: " + peer);
|
||||
// // peer state calls peer listener calls sendDHT()
|
||||
//}
|
||||
|
||||
MagnetState state = peer.getMagnetState();
|
||||
|
||||
@@ -204,30 +210,31 @@ abstract class ExtensionHandler {
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got request for " + piece + " from: " + peer);
|
||||
byte[] pc;
|
||||
int totalSize;
|
||||
synchronized(state) {
|
||||
pc = state.getChunk(piece);
|
||||
totalSize = state.getSize();
|
||||
}
|
||||
sendPiece(peer, piece, pc);
|
||||
sendPiece(peer, piece, pc, totalSize);
|
||||
// Do this here because PeerConnectionOut only reports for PIECE messages
|
||||
peer.uploaded(pc.length);
|
||||
listener.uploaded(peer, pc.length);
|
||||
} else if (type == TYPE_DATA) {
|
||||
int size = map.get("total_size").getInt();
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
// On close reading of BEP 9, this is the total metadata size.
|
||||
// Prior to 0.9.21, we sent the piece size, so we can't count on it.
|
||||
// just ignore it. The actual length will be verified in saveChunk()
|
||||
//int size = map.get("total_size").getInt();
|
||||
//if (log.shouldLog(Log.DEBUG))
|
||||
// log.debug("Got data for " + piece + " length " + size + " from: " + peer);
|
||||
boolean done;
|
||||
int chk = -1;
|
||||
synchronized(state) {
|
||||
if (state.isComplete())
|
||||
return;
|
||||
int len = is.available();
|
||||
if (len != size) {
|
||||
// probably fatal
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("total_size " + size + " but avail data " + len);
|
||||
}
|
||||
peer.downloaded(len);
|
||||
listener.downloaded(peer, len);
|
||||
// this checks the size
|
||||
done = state.saveChunk(piece, bs, bs.length - len, len);
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Got chunk " + piece + " from " + peer);
|
||||
@@ -290,11 +297,15 @@ abstract class ExtensionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data) {
|
||||
private static void sendPiece(Peer peer, int piece, byte[] data, int totalSize) {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("msg_type", Integer.valueOf(TYPE_DATA));
|
||||
map.put("piece", Integer.valueOf(piece));
|
||||
map.put("total_size", Integer.valueOf(data.length));
|
||||
// BEP 9
|
||||
// "This key has the same semantics as the 'metadata_size' in the extension header"
|
||||
// which apparently means the same value. Fixed in 0.9.21.
|
||||
//map.put("total_size", Integer.valueOf(data.length));
|
||||
map.put("total_size", Integer.valueOf(totalSize));
|
||||
byte[] dict = BEncoder.bencode(map);
|
||||
byte[] payload = new byte[dict.length + data.length];
|
||||
System.arraycopy(dict, 0, payload, 0, dict.length);
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.klomp.snark;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -14,6 +14,7 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
@@ -74,8 +75,7 @@ public class I2PSnarkUtil {
|
||||
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
|
||||
public static final int DEFAULT_STARTUP_DELAY = 3;
|
||||
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
|
||||
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
|
||||
public static final int MAX_CONNECTIONS = 16; // per torrent
|
||||
public static final int MAX_CONNECTIONS = 24; // per torrent
|
||||
public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
|
||||
public static final boolean DEFAULT_USE_DHT = true;
|
||||
public static final String EEPGET_USER_AGENT = "I2PSnark";
|
||||
@@ -97,7 +97,7 @@ public class I2PSnarkUtil {
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_banlist = new ConcurrentHashSet<Hash>();
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
_maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW;
|
||||
_maxConnections = MAX_CONNECTIONS;
|
||||
_startupDelay = DEFAULT_STARTUP_DELAY;
|
||||
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
|
||||
@@ -106,8 +106,8 @@ public class I2PSnarkUtil {
|
||||
// This is used for both announce replies and .torrent file downloads,
|
||||
// so it must be available even if not connected to I2CP.
|
||||
// so much for multiple instances
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
|
||||
FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName + '-' + ctx.random().nextInt());
|
||||
//FileUtil.rmdir(_tmpDir, false);
|
||||
_tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ public class I2PSnarkUtil {
|
||||
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
|
||||
if (i2cpHost != null)
|
||||
_i2cpHost = i2cpHost;
|
||||
@@ -256,6 +257,8 @@ public class I2PSnarkUtil {
|
||||
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
|
||||
if (opts.getProperty("i2p.streaming.answerPings") == null)
|
||||
opts.setProperty("i2p.streaming.answerPings", "false");
|
||||
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
|
||||
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
_connecting = false;
|
||||
}
|
||||
@@ -329,7 +332,7 @@ public class I2PSnarkUtil {
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
_banlist.add(dest);
|
||||
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
_context.simpleTimer2().addEvent(new Unbanlist(dest), 10*60*1000);
|
||||
IOException ioe = new IOException("Unable to reach the peer " + peer);
|
||||
ioe.initCause(ie);
|
||||
throw ioe;
|
||||
@@ -457,7 +460,7 @@ public class I2PSnarkUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
public String getOurIPString() {
|
||||
Destination dest = getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
@@ -587,10 +590,10 @@ public class I2PSnarkUtil {
|
||||
*/
|
||||
public boolean isKnownOpenTracker(String url) {
|
||||
try {
|
||||
URL u = new URL(url);
|
||||
URI u = new URI(url);
|
||||
String host = u.getHost();
|
||||
return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
|
||||
} catch (MalformedURLException mue) {
|
||||
} catch (URISyntaxException use) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -658,7 +661,7 @@ public class I2PSnarkUtil {
|
||||
* The {0} will be replaced by the parameter.
|
||||
* Single quotes must be doubled, i.e. ' -> '' in the string.
|
||||
* @param o parameter, not translated.
|
||||
* To tranlslate parameter also, use _("foo {0} bar", _("baz"))
|
||||
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
|
||||
* Do not double the single quotes in the parameter.
|
||||
* Use autoboxing to call with ints, longs, floats, etc.
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,9 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
private int _consec;
|
||||
private int _consecNotRunning;
|
||||
private boolean _isIdle;
|
||||
private String _lastIn = "3";
|
||||
private String _lastOut = "3";
|
||||
private final Object _lock = new Object();
|
||||
|
||||
private static final long CHECK_TIME = 63*1000;
|
||||
private static final int MAX_CONSEC_IDLE = 4;
|
||||
@@ -46,16 +49,19 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
synchronized (_lock) {
|
||||
locked_timeReached();
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_timeReached() {
|
||||
if (_util.connected()) {
|
||||
boolean torrentRunning = false;
|
||||
boolean hasPeers = false;
|
||||
int peerCount = 0;
|
||||
for (PeerCoordinator pc : _pcs) {
|
||||
if (!pc.halted()) {
|
||||
torrentRunning = true;
|
||||
if (pc.getPeers() > 0) {
|
||||
hasPeers = true;
|
||||
break;
|
||||
}
|
||||
peerCount += pc.getPeers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,19 +79,22 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPeers) {
|
||||
if (_isIdle)
|
||||
restoreTunnels();
|
||||
if (peerCount > 0) {
|
||||
restoreTunnels(peerCount);
|
||||
} else {
|
||||
if (!_isIdle) {
|
||||
if (_consec++ >= MAX_CONSEC_IDLE)
|
||||
reduceTunnels();
|
||||
else
|
||||
restoreTunnels(1); // pretend we have one peer for now
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_isIdle = false;
|
||||
_consec = 0;
|
||||
_consecNotRunning = 0;
|
||||
_lastIn = "3";
|
||||
_lastOut = "3";
|
||||
}
|
||||
schedule(CHECK_TIME);
|
||||
}
|
||||
@@ -101,26 +110,50 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore tunnel count
|
||||
* Restore or adjust tunnel count based on current peer count
|
||||
* @param peerCount greater than zero
|
||||
*/
|
||||
private void restoreTunnels() {
|
||||
_isIdle = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
private void restoreTunnels(int peerCount) {
|
||||
if (_isIdle && _log.shouldLog(Log.INFO))
|
||||
_log.info("Restoring tunnels on activity");
|
||||
_isIdle = false;
|
||||
Map<String, String> opts = _util.getI2CPOptions();
|
||||
String i = opts.get("inbound.quantity");
|
||||
if (i == null)
|
||||
i = "3";
|
||||
i = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY);
|
||||
String o = opts.get("outbound.quantity");
|
||||
if (o == null)
|
||||
o = "3";
|
||||
o = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY);
|
||||
String ib = opts.get("inbound.backupQuantity");
|
||||
if (ib == null)
|
||||
ib = "0";
|
||||
String ob= opts.get("outbound.backupQuantity");
|
||||
if (ob == null)
|
||||
ob = "0";
|
||||
setTunnels(i, o, ib, ob);
|
||||
// we don't need more tunnels than we have peers, reduce if so
|
||||
// reduce to max(peerCount / 2, 2)
|
||||
int in, out;
|
||||
try {
|
||||
in = Integer.parseInt(i);
|
||||
} catch (NumberFormatException nfe) {
|
||||
in = 3;
|
||||
}
|
||||
try {
|
||||
out = Integer.parseInt(o);
|
||||
} catch (NumberFormatException nfe) {
|
||||
out = 3;
|
||||
}
|
||||
int target = Math.max(peerCount / 2, 2);
|
||||
if (target < in && in > 2) {
|
||||
in = target;
|
||||
i = Integer.toString(in);
|
||||
}
|
||||
if (target < out && out > 2) {
|
||||
out = target;
|
||||
o = Integer.toString(out);
|
||||
}
|
||||
if (!(_lastIn.equals(i) && _lastOut.equals(o)))
|
||||
setTunnels(i, o, ib, ob);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,12 +165,16 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
|
||||
if (mgr != null) {
|
||||
I2PSession sess = mgr.getSession();
|
||||
if (sess != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob);
|
||||
Properties newProps = new Properties();
|
||||
newProps.setProperty("inbound.quantity", i);
|
||||
newProps.setProperty("outbound.quantity", o);
|
||||
newProps.setProperty("inbound.backupQuantity", ib);
|
||||
newProps.setProperty("outbound.backupQuantity", ob);
|
||||
sess.updateOptions(newProps);
|
||||
_lastIn = i;
|
||||
_lastOut = o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,11 +55,13 @@ class Message
|
||||
byte type;
|
||||
|
||||
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
|
||||
// Also SUGGEST, REJECT, ALLOWED_FAST
|
||||
// low byte used for EXTENSION message
|
||||
// low two bytes used for PORT message
|
||||
int piece;
|
||||
|
||||
// Used for REQUEST, PIECE and CANCEL messages.
|
||||
// Also REJECT
|
||||
int begin;
|
||||
int length;
|
||||
|
||||
@@ -104,15 +106,18 @@ class Message
|
||||
int datalen = 1;
|
||||
|
||||
// piece is 4 bytes.
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
datalen += 4;
|
||||
|
||||
// begin/offset is 4 bytes
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// length is 4 bytes
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
datalen += 4;
|
||||
|
||||
// msg type is 1 byte
|
||||
@@ -131,15 +136,18 @@ class Message
|
||||
dos.writeByte(type & 0xFF);
|
||||
|
||||
// Send additional info (piece number)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
|
||||
dos.writeInt(piece);
|
||||
|
||||
// Send additional info (begin/offset)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL)
|
||||
if (type == REQUEST || type == PIECE || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(begin);
|
||||
|
||||
// Send additional info (length); for PIECE this is implicit.
|
||||
if (type == REQUEST || type == CANCEL)
|
||||
if (type == REQUEST || type == CANCEL ||
|
||||
type == REJECT)
|
||||
dos.writeInt(length);
|
||||
|
||||
if (type == EXTENSION)
|
||||
@@ -173,21 +181,32 @@ class Message
|
||||
case UNINTERESTED:
|
||||
return "UNINTERESTED";
|
||||
case HAVE:
|
||||
return "HAVE(" + piece + ")";
|
||||
return "HAVE(" + piece + ')';
|
||||
case BITFIELD:
|
||||
return "BITFIELD";
|
||||
case REQUEST:
|
||||
return "REQUEST(" + piece + "," + begin + "," + length + ")";
|
||||
return "REQUEST(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PIECE:
|
||||
return "PIECE(" + piece + "," + begin + "," + length + ")";
|
||||
return "PIECE(" + piece + ',' + begin + ',' + length + ')';
|
||||
case CANCEL:
|
||||
return "CANCEL(" + piece + "," + begin + "," + length + ")";
|
||||
return "CANCEL(" + piece + ',' + begin + ',' + length + ')';
|
||||
case PORT:
|
||||
return "PORT(" + piece + ")";
|
||||
return "PORT(" + piece + ')';
|
||||
case EXTENSION:
|
||||
return "EXTENSION(" + piece + ',' + data.length + ')';
|
||||
// fast extensions below here
|
||||
case SUGGEST:
|
||||
return "SUGGEST(" + piece + ')';
|
||||
case HAVE_ALL:
|
||||
return "HAVE_ALL";
|
||||
case HAVE_NONE:
|
||||
return "HAVE_NONE";
|
||||
case REJECT:
|
||||
return "REJECT(" + piece + ',' + begin + ',' + length + ')';
|
||||
case ALLOWED_FAST:
|
||||
return "ALLOWED_FAST(" + piece + ')';
|
||||
default:
|
||||
return "<UNKNOWN>";
|
||||
return "UNKNOWN (" + type + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +74,11 @@ public class MetaInfo
|
||||
* @param files null for single-file torrent
|
||||
* @param lengths null for single-file torrent
|
||||
* @param announce_list may be null
|
||||
* @param created_by may be null
|
||||
*/
|
||||
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
|
||||
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
|
||||
List<List<String>> announce_list)
|
||||
List<List<String>> announce_list, String created_by)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
@@ -91,8 +92,8 @@ public class MetaInfo
|
||||
this.privateTorrent = privateTorrent;
|
||||
this.announce_list = announce_list;
|
||||
this.comment = null;
|
||||
this.created_by = null;
|
||||
this.creation_date = 0;
|
||||
this.created_by = created_by;
|
||||
this.creation_date = I2PAppContext.getGlobalContext().clock().now();
|
||||
|
||||
// TODO if we add a parameter for other keys
|
||||
//if (other != null) {
|
||||
@@ -444,7 +445,7 @@ public class MetaInfo
|
||||
|
||||
/**
|
||||
* The creation date (ms) or zero.
|
||||
* Not available for locally-created torrents.
|
||||
* As of 0.9.19, available for locally-created torrents.
|
||||
* @since 0.9.7
|
||||
*/
|
||||
public long getCreationDate() {
|
||||
@@ -595,6 +596,14 @@ public class MetaInfo
|
||||
m.put("announce", announce);
|
||||
if (announce_list != null)
|
||||
m.put("announce-list", announce_list);
|
||||
// misc. optional top-level stuff
|
||||
if (comment != null)
|
||||
m.put("comment", comment);
|
||||
if (created_by != null)
|
||||
m.put("created by", created_by);
|
||||
if (creation_date != 0)
|
||||
m.put("creation date", creation_date / 1000);
|
||||
|
||||
Map<String, BEValue> info = createInfoMap();
|
||||
m.put("info", info);
|
||||
// don't save this locally, we should only do this once
|
||||
|
||||
@@ -108,7 +108,8 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Convert this PartialPiece to a request for the next chunk.
|
||||
* Used by PeerState only.
|
||||
* Used by PeerState only. This depends on the downloaded value
|
||||
* as set by setDownloaded() or read().
|
||||
*/
|
||||
|
||||
public Request getRequest() {
|
||||
@@ -128,14 +129,16 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes are good - only valid by setDownloaded()
|
||||
* How many bytes are good - as set by setDownloaded() or read()
|
||||
*/
|
||||
public int getDownloaded() {
|
||||
return this.off;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this before returning a PartialPiece to the PeerCoordinator
|
||||
* Call this if necessary before returning a PartialPiece to the PeerCoordinator.
|
||||
* We do not use a bitmap to track individual chunks received.
|
||||
* Any chunks after a 'hole' will be lost.
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void setDownloaded(int offset) {
|
||||
@@ -191,11 +194,20 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
|
||||
/**
|
||||
* Blocking.
|
||||
* If offset matches the previous downloaded amount
|
||||
* (as set by a previous call to read() or setDownlaoded()),
|
||||
* the downloaded amount will be incremented by len.
|
||||
*
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public void read(DataInputStream din, int off, int len) throws IOException {
|
||||
public void read(DataInputStream din, int offset, int len) throws IOException {
|
||||
if (bs != null) {
|
||||
din.readFully(bs, off, len);
|
||||
din.readFully(bs, offset, len);
|
||||
synchronized (this) {
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
} else {
|
||||
// read in fully before synching on raf
|
||||
ByteArray ba;
|
||||
@@ -211,8 +223,11 @@ class PartialPiece implements Comparable<PartialPiece> {
|
||||
synchronized (this) {
|
||||
if (raf == null)
|
||||
createTemp();
|
||||
raf.seek(off);
|
||||
raf.seek(offset);
|
||||
raf.write(tmp);
|
||||
// only works for in-order chunks
|
||||
if (this.off == offset)
|
||||
this.off += len;
|
||||
}
|
||||
if (ba != null)
|
||||
_cache.release(ba, false);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class Peer implements Comparable<Peer>
|
||||
|
||||
// Keeps state for in/out connections. Non-null when the handshake
|
||||
// was successful, the connection setup and runs
|
||||
PeerState state;
|
||||
volatile PeerState state;
|
||||
|
||||
/** shared across all peers on this torrent */
|
||||
MagnetState magnetState;
|
||||
@@ -79,15 +79,15 @@ public class Peer implements Comparable<Peer>
|
||||
private long uploaded_old[] = {-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1};
|
||||
|
||||
// bytes per bt spec: 0011223344556677
|
||||
static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
static final long OPTION_FAST = 0x0000000000000004l;
|
||||
static final long OPTION_DHT = 0x0000000000000001l;
|
||||
// bytes per bt spec: 0011223344556677
|
||||
private static final long OPTION_EXTENSION = 0x0000000000100000l;
|
||||
private static final long OPTION_FAST = 0x0000000000000004l;
|
||||
//private static final long OPTION_DHT = 0x0000000000000001l;
|
||||
/** we use a different bit since the compact format is different */
|
||||
/* no, let's use an extension message
|
||||
static final long OPTION_I2P_DHT = 0x0000000040000000l;
|
||||
*/
|
||||
static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
//private static final long OPTION_AZMP = 0x1000000000000000l;
|
||||
private long options;
|
||||
|
||||
/**
|
||||
@@ -194,6 +194,7 @@ public class Peer implements Comparable<Peer>
|
||||
* Compares the PeerIDs.
|
||||
* @deprecated unused?
|
||||
*/
|
||||
@Deprecated
|
||||
public int compareTo(Peer p)
|
||||
{
|
||||
int rv = peerID.compareTo(p.peerID);
|
||||
@@ -217,9 +218,11 @@ public class Peer implements Comparable<Peer>
|
||||
*
|
||||
* If the given BitField is non-null it is send to the peer as first
|
||||
* message.
|
||||
*
|
||||
* @param uploadOnly if we are complete with skipped files, i.e. a partial seed
|
||||
*/
|
||||
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield, MagnetState mState)
|
||||
{
|
||||
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield,
|
||||
MagnetState mState, boolean uploadOnly) {
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
|
||||
@@ -275,17 +278,9 @@ public class Peer implements Comparable<Peer>
|
||||
int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
|
||||
boolean pexAndMetadata = metainfo == null || !metainfo.isPrivate();
|
||||
boolean dht = util.getDHT() != null;
|
||||
out.sendExtension(0, ExtensionHandler.getHandshake(metasize, pexAndMetadata, dht));
|
||||
out.sendExtension(0, ExtensionHandler.getHandshake(metasize, pexAndMetadata, dht, uploadOnly));
|
||||
}
|
||||
|
||||
// Old DHT PORT message
|
||||
//if ((options & OPTION_I2P_DHT) != 0 && util.getDHT() != null) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Peer supports DHT, sending PORT message");
|
||||
// int port = util.getDHT().getPort();
|
||||
// out.sendPort(port);
|
||||
//}
|
||||
|
||||
// Send our bitmap
|
||||
if (bitfield != null)
|
||||
s.out.sendBitfield(bitfield);
|
||||
@@ -297,7 +292,7 @@ public class Peer implements Comparable<Peer>
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// Use this thread for running the incoming connection.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
Thread.currentThread().setName("Snark reader from " + peerID);
|
||||
@@ -338,6 +333,9 @@ public class Peer implements Comparable<Peer>
|
||||
dout.write("BitTorrent protocol".getBytes("UTF-8"));
|
||||
// Handshake write - options
|
||||
long myOptions = OPTION_EXTENSION;
|
||||
// we can't handle HAVE_ALL or HAVE_NONE if we don't know the number of pieces
|
||||
if (metainfo != null)
|
||||
myOptions |= OPTION_FAST;
|
||||
// FIXME get util here somehow
|
||||
//if (util.getDHT() != null)
|
||||
// myOptions |= OPTION_I2P_DHT;
|
||||
@@ -385,15 +383,15 @@ public class Peer implements Comparable<Peer>
|
||||
if (options != 0) {
|
||||
// send them something in runConnection() above
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
|
||||
_log.debug("Peer supports options 0x" + Long.toHexString(options) + ": " + toString());
|
||||
}
|
||||
|
||||
return bs;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
public long getOptions() {
|
||||
return options;
|
||||
/** @since 0.9.21 */
|
||||
public boolean supportsFast() {
|
||||
return (options & OPTION_FAST) != 0;
|
||||
}
|
||||
|
||||
/** @since 0.8.4 */
|
||||
@@ -534,6 +532,7 @@ public class Peer implements Comparable<Peer>
|
||||
* @deprecated deadlocks
|
||||
* @since 0.8.1
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isRequesting(int p) {
|
||||
PeerState s = state;
|
||||
return s != null && s.isRequesting(p);
|
||||
@@ -566,6 +565,7 @@ public class Peer implements Comparable<Peer>
|
||||
* us then we start downloading from it. Has no effect when not connected.
|
||||
* @deprecated unused
|
||||
*/
|
||||
@Deprecated
|
||||
public void setInteresting(boolean interest)
|
||||
{
|
||||
PeerState s = state;
|
||||
|
||||
@@ -75,6 +75,8 @@ class PeerCheckerTask implements Runnable
|
||||
List<Peer> removed = new ArrayList<Peer>();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
boolean overBWLimit = coordinator.overUpBWLimit();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("peers: " + peerList.size() + " limit: " + uploadLimit + " overBW? " + overBWLimit);
|
||||
DHT dht = _util.getDHT();
|
||||
for (Peer peer : peerList) {
|
||||
|
||||
@@ -265,7 +267,23 @@ class PeerCheckerTask implements Runnable
|
||||
|
||||
// close out unused files, but we don't need to do it every time
|
||||
Storage storage = coordinator.getStorage();
|
||||
if (storage != null && (_runCount % 4) == 0) {
|
||||
if (storage != null) {
|
||||
// The more files a torrent has, the more often we call the cleaner,
|
||||
// to keep from running out of FDs
|
||||
int files = storage.getFileCount();
|
||||
int skip;
|
||||
if (files == 1)
|
||||
skip = 6;
|
||||
else if (files <= 4)
|
||||
skip = 4;
|
||||
else if (files <= 20)
|
||||
skip = 3;
|
||||
else if (files <= 50)
|
||||
skip = 2;
|
||||
else
|
||||
skip = 1;
|
||||
|
||||
if ((_runCount % skip) == 0)
|
||||
storage.cleanRAFs();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class PeerConnectionIn implements Runnable
|
||||
private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
|
||||
MagnetState.CHUNK_SIZE + 100); // 100 for the ext msg dictionary
|
||||
|
||||
private Thread thread;
|
||||
private volatile Thread thread;
|
||||
private volatile boolean quit;
|
||||
|
||||
long lastRcvd;
|
||||
@@ -75,9 +75,12 @@ class PeerConnectionIn implements Runnable
|
||||
thread = Thread.currentThread();
|
||||
try
|
||||
{
|
||||
PeerState ps = peer.state;
|
||||
while (!quit && ps != null)
|
||||
while (!quit)
|
||||
{
|
||||
final PeerState ps = peer.state;
|
||||
if (ps == null)
|
||||
break;
|
||||
|
||||
// Common variables used for some messages.
|
||||
int piece;
|
||||
int begin;
|
||||
@@ -91,59 +94,64 @@ class PeerConnectionIn implements Runnable
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
ps.keepAliveMessage();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received keepalive from " + peer);
|
||||
ps.keepAliveMessage();
|
||||
continue;
|
||||
}
|
||||
|
||||
byte b = din.readByte();
|
||||
Message m = new Message();
|
||||
m.type = b;
|
||||
switch (b)
|
||||
{
|
||||
case 0:
|
||||
ps.chokeMessage(true);
|
||||
case Message.CHOKE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received choke from " + peer);
|
||||
ps.chokeMessage(true);
|
||||
break;
|
||||
case 1:
|
||||
ps.chokeMessage(false);
|
||||
|
||||
case Message.UNCHOKE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unchoke from " + peer);
|
||||
ps.chokeMessage(false);
|
||||
break;
|
||||
case 2:
|
||||
ps.interestedMessage(true);
|
||||
|
||||
case Message.INTERESTED:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received interested from " + peer);
|
||||
ps.interestedMessage(true);
|
||||
break;
|
||||
case 3:
|
||||
ps.interestedMessage(false);
|
||||
|
||||
case Message.UNINTERESTED:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received not interested from " + peer);
|
||||
ps.interestedMessage(false);
|
||||
break;
|
||||
case 4:
|
||||
|
||||
case Message.HAVE:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received havePiece(" + piece + ") from " + peer);
|
||||
ps.haveMessage(piece);
|
||||
break;
|
||||
case 5:
|
||||
|
||||
case Message.BITFIELD:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
|
||||
ps.bitfieldMessage(bitmap);
|
||||
break;
|
||||
case 6:
|
||||
|
||||
case Message.REQUEST:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.requestMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received request(" + piece + "," + begin + ") from " + peer);
|
||||
ps.requestMessage(piece, begin, len);
|
||||
break;
|
||||
case 7:
|
||||
|
||||
case Message.PIECE:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = i-9;
|
||||
@@ -151,9 +159,9 @@ class PeerConnectionIn implements Runnable
|
||||
if (req != null)
|
||||
{
|
||||
req.read(din);
|
||||
ps.pieceMessage(req);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received data(" + piece + "," + begin + ") from " + peer);
|
||||
ps.pieceMessage(req);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -165,21 +173,24 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
|
||||
case Message.CANCEL:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.cancelMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
|
||||
ps.cancelMessage(piece, begin, len);
|
||||
break;
|
||||
case 9: // PORT message
|
||||
|
||||
case Message.PORT:
|
||||
int port = din.readUnsignedShort();
|
||||
ps.portMessage(port);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received port message from " + peer);
|
||||
ps.portMessage(port);
|
||||
break;
|
||||
case 20: // Extension message
|
||||
|
||||
case Message.EXTENSION:
|
||||
int id = din.readUnsignedByte();
|
||||
byte[] payload = new byte[i-2];
|
||||
din.readFully(payload);
|
||||
@@ -187,6 +198,43 @@ class PeerConnectionIn implements Runnable
|
||||
_log.debug("Received extension message from " + peer);
|
||||
ps.extensionMessage(id, payload);
|
||||
break;
|
||||
|
||||
// fast extensions below here
|
||||
case Message.SUGGEST:
|
||||
piece = din.readInt();
|
||||
ps.suggestMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received suggest(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_ALL:
|
||||
ps.haveMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_all from " + peer);
|
||||
break;
|
||||
|
||||
case Message.HAVE_NONE:
|
||||
ps.haveMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received have_none from " + peer);
|
||||
break;
|
||||
|
||||
case Message.REJECT:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.rejectMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received reject(" + piece + ',' + begin + ',' + len + ") from " + peer);
|
||||
break;
|
||||
|
||||
case Message.ALLOWED_FAST:
|
||||
piece = din.readInt();
|
||||
ps.allowedFastMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received allowed_fast(" + piece + ") from " + peer);
|
||||
break;
|
||||
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
@@ -202,11 +250,9 @@ class PeerConnectionIn implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("IOError talking with " + peer, ioe);
|
||||
}
|
||||
catch (Throwable t)
|
||||
catch (RuntimeException t)
|
||||
{
|
||||
_log.error("Error talking with " + peer, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -22,15 +22,15 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
//import net.i2p.util.SimpleScheduler;
|
||||
//import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
@@ -43,7 +43,7 @@ class PeerConnectionOut implements Runnable
|
||||
private boolean quit;
|
||||
|
||||
// Contains Messages.
|
||||
private final List<Message> sendQueue = new ArrayList<Message>();
|
||||
private final BlockingQueue<Message> sendQueue = new LinkedBlockingQueue<Message>();
|
||||
|
||||
private static final AtomicLong __id = new AtomicLong();
|
||||
private final long _id;
|
||||
@@ -125,6 +125,16 @@ class PeerConnectionOut implements Runnable
|
||||
if (state.choking) {
|
||||
it.remove();
|
||||
//SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
if (peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = nm.piece;
|
||||
r.begin = nm.begin;
|
||||
r.length = nm.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
r.sendMessage(dout);
|
||||
}
|
||||
}
|
||||
nm = null;
|
||||
}
|
||||
@@ -142,8 +152,8 @@ class PeerConnectionOut implements Runnable
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && !sendQueue.isEmpty()) {
|
||||
m = sendQueue.remove(0);
|
||||
if (m == null) {
|
||||
m = sendQueue.poll();
|
||||
//SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
}
|
||||
@@ -160,6 +170,8 @@ class PeerConnectionOut implements Runnable
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
// FiXME this causes REJECT messages to be sent before sending the CHOKE;
|
||||
// BEP 6 recommends sending them after.
|
||||
if (m.type == Message.CHOKE)
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
@@ -234,7 +246,7 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -278,11 +290,22 @@ class PeerConnectionOut implements Runnable
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = it.next();
|
||||
if (m.type == type)
|
||||
{
|
||||
if (m.type == type) {
|
||||
it.remove();
|
||||
removed = true;
|
||||
}
|
||||
if (type == Message.PIECE && peer.supportsFast()) {
|
||||
Message r = new Message();
|
||||
r.type = Message.REJECT;
|
||||
r.piece = m.piece;
|
||||
r.begin = m.begin;
|
||||
r.length = m.length;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + r);
|
||||
try {
|
||||
r.sendMessage(dout);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
@@ -297,7 +320,7 @@ class PeerConnectionOut implements Runnable
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.offer(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
@@ -350,12 +373,19 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendBitfield(BitField bitfield)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
boolean fast = peer.supportsFast();
|
||||
if (fast && bitfield.complete()) {
|
||||
sendHaveAll();
|
||||
} else if (fast && bitfield.count() <= 0) {
|
||||
sendHaveNone();
|
||||
} else {
|
||||
Message m = new Message();
|
||||
m.type = Message.BITFIELD;
|
||||
m.data = bitfield.getFieldBytes();
|
||||
m.off = 0;
|
||||
m.len = m.data.length;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
@@ -480,7 +510,6 @@ class PeerConnectionOut implements Runnable
|
||||
m.len = length;
|
||||
// since we have the data already loaded, queue a timeout to remove it
|
||||
// no longer prefetched
|
||||
//SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@@ -511,7 +540,8 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all Request messages from the queue
|
||||
* Remove all Request messages from the queue.
|
||||
* Does not send a cancel message.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
void cancelRequestMessages() {
|
||||
@@ -523,9 +553,12 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the PeerState when the other side doesn't want this
|
||||
// request to be handled anymore. Removes any pending Piece Message
|
||||
// from out send queue.
|
||||
/**
|
||||
* Called by the PeerState when the other side doesn't want this
|
||||
* request to be handled anymore. Removes any pending Piece Message
|
||||
* from out send queue.
|
||||
* Does not send a cancel message.
|
||||
*/
|
||||
void cancelRequest(int piece, int begin, int length)
|
||||
{
|
||||
synchronized (sendQueue)
|
||||
@@ -561,4 +594,50 @@ class PeerConnectionOut implements Runnable
|
||||
m.piece = port;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendSuggest(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.SUGGEST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveAll() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_ALL;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
private void sendHaveNone() {
|
||||
Message m = new Message();
|
||||
m.type = Message.HAVE_NONE;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** @since 0.9.21 */
|
||||
void sendReject(int piece, int begin, int length) {
|
||||
Message m = new Message();
|
||||
m.type = Message.REJECT;
|
||||
m.piece = piece;
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void sendAllowedFast(int piece) {
|
||||
Message m = new Message();
|
||||
m.type = Message.ALLOWED_FAST;
|
||||
m.piece = piece;
|
||||
addMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,15 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
@@ -52,7 +53,7 @@ import org.klomp.snark.dht.DHT;
|
||||
*/
|
||||
class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
|
||||
private final Log _log;
|
||||
|
||||
/**
|
||||
* External use by PeerMonitorTask only.
|
||||
@@ -69,7 +70,7 @@ class PeerCoordinator implements PeerListener
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 40*1000; // 40 seconds
|
||||
final static int MAX_UPLOADERS = 6;
|
||||
final static int MAX_UPLOADERS = 8;
|
||||
public static final long MAX_INACTIVE = 8*60*1000;
|
||||
|
||||
/**
|
||||
@@ -87,8 +88,8 @@ class PeerCoordinator implements PeerListener
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
private final AtomicLong uploaded = new AtomicLong();
|
||||
private final AtomicLong downloaded = new AtomicLong();
|
||||
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
|
||||
private final long uploaded_old[] = {-1,-1,-1};
|
||||
private final long downloaded_old[] = {-1,-1,-1};
|
||||
@@ -98,7 +99,7 @@ class PeerCoordinator implements PeerListener
|
||||
* This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking.
|
||||
* External use by PeerMonitorTask only.
|
||||
*/
|
||||
final Queue<Peer> peers;
|
||||
final Deque<Peer> peers;
|
||||
|
||||
/**
|
||||
* Peers we heard about via PEX
|
||||
@@ -144,6 +145,7 @@ class PeerCoordinator implements PeerListener
|
||||
{
|
||||
_util = util;
|
||||
_random = util.getContext().random();
|
||||
_log = util.getContext().logManager().getLog(PeerCoordinator.class);
|
||||
this.id = id;
|
||||
this.infohash = infohash;
|
||||
this.metainfo = metainfo;
|
||||
@@ -154,7 +156,7 @@ class PeerCoordinator implements PeerListener
|
||||
wantedPieces = new ArrayList<Piece>();
|
||||
setWantedPieces();
|
||||
partialPieces = new ArrayList<PartialPiece>(getMaxConnections() + 1);
|
||||
peers = new LinkedBlockingQueue<Peer>();
|
||||
peers = new LinkedBlockingDeque<Peer>();
|
||||
magnetState = new MagnetState(infohash, metainfo);
|
||||
pexPeers = new ConcurrentHashSet<PeerID>();
|
||||
|
||||
@@ -278,7 +280,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +288,7 @@ class PeerCoordinator implements PeerListener
|
||||
* @since 0.9.15
|
||||
*/
|
||||
public void setUploaded(long up) {
|
||||
uploaded = up;
|
||||
uploaded.set(up);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +296,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,16 +322,22 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
public long getUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getCurrentUploadRate()
|
||||
{
|
||||
if (halted)
|
||||
return 0;
|
||||
// no need to synchronize, only one value
|
||||
long r = uploaded_old[0];
|
||||
if (r <= 0)
|
||||
@@ -395,7 +403,7 @@ class PeerCoordinator implements PeerListener
|
||||
* Formerly used to
|
||||
* reduce max if huge pieces to keep from ooming when leeching
|
||||
* but now we don't
|
||||
* @return usually 16
|
||||
* @return usually I2PSnarkUtil.MAX_CONNECTIONS
|
||||
*/
|
||||
private int getMaxConnections() {
|
||||
if (metainfo == null)
|
||||
@@ -522,7 +530,10 @@ class PeerCoordinator implements PeerListener
|
||||
// Can't add to beginning since we converted from a List to a Queue
|
||||
// We can do this in Java 6 with a Deque
|
||||
//peers.add(0, peer);
|
||||
peers.add(peer);
|
||||
if (_util.getContext().random().nextInt(4) == 0)
|
||||
peers.push(peer);
|
||||
else
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
@@ -593,11 +604,13 @@ class PeerCoordinator implements PeerListener
|
||||
bitfield = storage.getBitField();
|
||||
else
|
||||
bitfield = null;
|
||||
// if we aren't a seed but we don't want any more
|
||||
final boolean partialComplete = wantedBytes == 0 && bitfield != null && !bitfield.complete();
|
||||
Runnable r = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
peer.runConnection(_util, listener, bitfield, magnetState);
|
||||
peer.runConnection(_util, listener, bitfield, magnetState, partialComplete);
|
||||
}
|
||||
};
|
||||
String threadName = "Snark peer " + peer.toString();
|
||||
@@ -909,6 +922,7 @@ class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
*/
|
||||
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
|
||||
@@ -940,7 +954,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void uploaded(Peer peer, int size)
|
||||
{
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -951,7 +965,7 @@ class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public void downloaded(Peer peer, int size)
|
||||
{
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
|
||||
//if (listener != null)
|
||||
// listener.peerChange(this, peer);
|
||||
@@ -972,8 +986,9 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
int piece = pp.getPiece();
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
// try/catch outside the synch to avoid deadlock in the catch
|
||||
try {
|
||||
synchronized(wantedPieces) {
|
||||
Piece p = new Piece(piece);
|
||||
if (!wantedPieces.contains(p))
|
||||
{
|
||||
@@ -989,8 +1004,7 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// try/catch moved outside of synch
|
||||
// this takes forever if complete, as it rechecks
|
||||
if (storage.putPiece(pp))
|
||||
{
|
||||
@@ -999,26 +1013,38 @@ class PeerCoordinator implements PeerListener
|
||||
}
|
||||
else
|
||||
{
|
||||
// so we will try again
|
||||
markUnrequested(peer, piece);
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
|
||||
// Mark this peer as not having the piece. PeerState will update its bitfield.
|
||||
for (Piece pc : wantedPieces) {
|
||||
if (pc.getId() == piece) {
|
||||
pc.removePeer(peer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
|
||||
wantedPieces.remove(p);
|
||||
wantedBytes -= metainfo.getPieceLength(p.getId());
|
||||
} // synch
|
||||
} catch (IOException ioe) {
|
||||
String msg = "Error writing storage (piece " + piece + ") for " + metainfo.getName() + ": " + ioe;
|
||||
_log.error(msg, ioe);
|
||||
if (listener != null) {
|
||||
listener.addMessage(msg);
|
||||
listener.addMessage("Fatal storage error: Stopping torrent " + metainfo.getName());
|
||||
}
|
||||
// deadlock was here
|
||||
snark.stopTorrent();
|
||||
throw new RuntimeException(msg, ioe);
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
wantedBytes -= metainfo.getPieceLength(p.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// just in case
|
||||
removePartialPiece(piece);
|
||||
@@ -1130,8 +1156,9 @@ class PeerCoordinator implements PeerListener
|
||||
*
|
||||
* Also mark the piece unrequested if this peer was the only one.
|
||||
*
|
||||
* @param peer partials, must include the zero-offset (empty) ones too
|
||||
* No dup pieces, piece.setDownloaded() must be set
|
||||
* @param peer partials, must include the zero-offset (empty) ones too.
|
||||
* No dup pieces, piece.setDownloaded() must be set.
|
||||
* len field in Requests is ignored.
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public void savePartialPieces(Peer peer, List<Request> partials)
|
||||
@@ -1461,8 +1488,8 @@ class PeerCoordinator implements PeerListener
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (listener != null && listener.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
} else if (uploaders < MAX_UPLOADERS)
|
||||
return uploaders + 1;
|
||||
|
||||
@@ -196,6 +196,7 @@ public class PeerID implements Comparable<PeerID>
|
||||
* Compares port, address and id.
|
||||
* @deprecated unused? and will NPE now that address can be null?
|
||||
*/
|
||||
@Deprecated
|
||||
public int compareTo(PeerID pid)
|
||||
{
|
||||
int result = port - pid.port;
|
||||
|
||||
@@ -30,6 +30,7 @@ import net.i2p.data.DataHelper;
|
||||
*
|
||||
* @deprecated unused, for command line client only, commented out in Snark.java
|
||||
*/
|
||||
@Deprecated
|
||||
class PeerMonitorTask implements Runnable
|
||||
{
|
||||
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -36,7 +37,10 @@ class PeerState implements DataLoader
|
||||
private final Peer peer;
|
||||
/** Fixme, used by Peer.disconnect() to get to the coordinator */
|
||||
final PeerListener listener;
|
||||
/** Null before we have it. locking: this */
|
||||
private MetaInfo metainfo;
|
||||
/** Null unless needed. Contains -1 for all. locking: this */
|
||||
private List<Integer> havesBeforeMetaInfo;
|
||||
|
||||
// Interesting and choking describes whether we are interested in or
|
||||
// are choking the other side.
|
||||
@@ -48,7 +52,7 @@ class PeerState implements DataLoader
|
||||
volatile boolean interested;
|
||||
volatile boolean choked = true;
|
||||
|
||||
/** the pieces the peer has */
|
||||
/** the pieces the peer has. locking: this */
|
||||
BitField bitfield;
|
||||
|
||||
// Package local for use by Peer.
|
||||
@@ -65,6 +69,7 @@ class PeerState implements DataLoader
|
||||
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
|
||||
public final static int PARTSIZE = 16*1024; // outbound request
|
||||
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
||||
private static final Integer PIECE_ALL = Integer.valueOf(-1);
|
||||
|
||||
/**
|
||||
* @param metainfo null if in magnet mode
|
||||
@@ -130,37 +135,73 @@ class PeerState implements DataLoader
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv have(" + piece + ")");
|
||||
// FIXME we will lose these until we get the metainfo
|
||||
if (metainfo == null)
|
||||
return;
|
||||
// Sanity check
|
||||
if (piece < 0 || piece >= metainfo.getPieces())
|
||||
{
|
||||
// XXX disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
if (piece < 0) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
if (metainfo == null) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got HAVE " + piece + " before metainfo from " + peer);
|
||||
if (bitfield != null) {
|
||||
if (piece < bitfield.size())
|
||||
bitfield.set(piece);
|
||||
} else {
|
||||
// note reception for later
|
||||
if (havesBeforeMetaInfo == null) {
|
||||
havesBeforeMetaInfo = new ArrayList<Integer>(8);
|
||||
} else if (havesBeforeMetaInfo.size() > 1000) {
|
||||
// don't blow up
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Got too many haves before metainfo from " + peer);
|
||||
return;
|
||||
}
|
||||
havesBeforeMetaInfo.add(Integer.valueOf(piece));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (piece >= metainfo.getPieces()) {
|
||||
// XXX disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
// Can happen if the other side never send a bitfield message.
|
||||
if (bitfield == null)
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
bitfield.set(piece);
|
||||
}
|
||||
}
|
||||
|
||||
if (listener.gotHave(peer, piece))
|
||||
setInteresting(true);
|
||||
}
|
||||
|
||||
void bitfieldMessage(byte[] bitmap)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
void bitfieldMessage(byte[] bitmap) {
|
||||
bitfieldMessage(bitmap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmap null to use the isAll param
|
||||
* @param isAll only if bitmap == null: true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (bitmap != null)
|
||||
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
|
||||
else if (isAll)
|
||||
_log.debug(peer + " rcv bitfield HAVE_ALL");
|
||||
else
|
||||
_log.debug(peer + " rcv bitfield HAVE_NONE");
|
||||
}
|
||||
|
||||
synchronized(this) {
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you accept?
|
||||
@@ -170,15 +211,36 @@ class PeerState implements DataLoader
|
||||
}
|
||||
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
// FIXME will have to regenerate the bitfield after we know exactly
|
||||
// Will have to regenerate the bitfield after we know exactly
|
||||
// how many pieces there are, as we don't know how many spare bits there are.
|
||||
if (metainfo == null)
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
else
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
}
|
||||
if (metainfo == null)
|
||||
return;
|
||||
// This happens in setMetaInfo() below.
|
||||
if (metainfo == null) {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, bitmap.length * 8);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("have_x w/o metainfo: " + isAll);
|
||||
if (isAll) {
|
||||
// note reception for later
|
||||
if (havesBeforeMetaInfo == null)
|
||||
havesBeforeMetaInfo = new ArrayList<Integer>(1);
|
||||
else
|
||||
havesBeforeMetaInfo.clear();
|
||||
havesBeforeMetaInfo.add(PIECE_ALL);
|
||||
} // else HAVE_NONE, ignore
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (bitmap != null) {
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
} else {
|
||||
bitfield = new BitField(metainfo.getPieces());
|
||||
if (isAll)
|
||||
bitfield.setAll();
|
||||
}
|
||||
}
|
||||
} // synch
|
||||
|
||||
boolean interest = listener.gotBitField(peer, bitfield);
|
||||
setInteresting(interest);
|
||||
if (bitfield.complete() && !interest) {
|
||||
@@ -198,14 +260,21 @@ class PeerState implements DataLoader
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (metainfo == null)
|
||||
return;
|
||||
if (choking)
|
||||
{
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Request received, but choking " + peer);
|
||||
if (choking) {
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, sending reject to choked " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Request received, but choking " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
// There is no check here that we actually have the piece;
|
||||
// this will be caught in loadData() below
|
||||
if (piece < 0
|
||||
|| piece >= metainfo.getPieces()
|
||||
|| begin < 0
|
||||
@@ -219,6 +288,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,8 +298,14 @@ class PeerState implements DataLoader
|
||||
// Todo: limit number of requests also? (robert 64 x 4KB)
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
if (peer.supportsFast()) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Rejecting request over pipeline limit from " + peer);
|
||||
out.sendReject(piece, begin, length);
|
||||
} else {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Discarding request over pipeline limit from " + peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,7 +320,8 @@ class PeerState implements DataLoader
|
||||
/**
|
||||
* This is the callback that PeerConnectionOut calls
|
||||
*
|
||||
* @return bytes or null for errors
|
||||
* @return bytes or null for errors such as not having the piece yet
|
||||
* @throws RuntimeException on IOE getting the data
|
||||
* @since 0.8.2
|
||||
*/
|
||||
public ByteArray loadData(int piece, int begin, int length) {
|
||||
@@ -253,6 +331,8 @@ class PeerState implements DataLoader
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -265,6 +345,8 @@ class PeerState implements DataLoader
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer);
|
||||
if (peer.supportsFast())
|
||||
out.sendReject(piece, begin, length);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -322,6 +404,11 @@ class PeerState implements DataLoader
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
|
||||
synchronized(this) {
|
||||
// so we don't ask again
|
||||
if (bitfield != null)
|
||||
bitfield.clear(req.getPiece());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +542,12 @@ class PeerState implements DataLoader
|
||||
for (Integer p : pcs) {
|
||||
Request req = getLowestOutstandingRequest(p.intValue());
|
||||
if (req != null) {
|
||||
req.getPartialPiece().setDownloaded(req.off);
|
||||
PartialPiece pp = req.getPartialPiece();
|
||||
synchronized(pp) {
|
||||
int dl = pp.getDownloaded();
|
||||
if (req.off != dl)
|
||||
req = new Request(pp, dl, 1);
|
||||
}
|
||||
rv.add(req);
|
||||
}
|
||||
}
|
||||
@@ -508,22 +600,43 @@ class PeerState implements DataLoader
|
||||
* @param meta non-null
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void setMetaInfo(MetaInfo meta) {
|
||||
public synchronized void setMetaInfo(MetaInfo meta) {
|
||||
if (metainfo != null)
|
||||
return;
|
||||
BitField oldBF = bitfield;
|
||||
if (oldBF != null) {
|
||||
if (oldBF.size() != meta.getPieces())
|
||||
if (bitfield != null) {
|
||||
if (bitfield.size() != meta.getPieces())
|
||||
// fix bitfield, it was too big by 1-7 bits
|
||||
bitfield = new BitField(oldBF.getFieldBytes(), meta.getPieces());
|
||||
bitfield = new BitField(bitfield.getFieldBytes(), meta.getPieces());
|
||||
// else no extra
|
||||
} else if (havesBeforeMetaInfo != null) {
|
||||
// initialize it now
|
||||
bitfield = new BitField(meta.getPieces());
|
||||
} else {
|
||||
// it will be initialized later
|
||||
//bitfield = new BitField(meta.getPieces());
|
||||
}
|
||||
metainfo = meta;
|
||||
if (bitfield != null && bitfield.count() > 0)
|
||||
setInteresting(true);
|
||||
if (bitfield != null) {
|
||||
if (havesBeforeMetaInfo != null) {
|
||||
// set all 'haves' we got before the metainfo in the bitfield
|
||||
for (Integer i : havesBeforeMetaInfo) {
|
||||
if (i.equals(PIECE_ALL)) {
|
||||
bitfield.setAll();
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("set have_all after rcv metainfo");
|
||||
break;
|
||||
}
|
||||
int piece = i.intValue();
|
||||
if (piece >= 0 && piece < meta.getPieces())
|
||||
bitfield.set(piece);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("set have " + piece + " after rcv metainfo");
|
||||
}
|
||||
havesBeforeMetaInfo = null;
|
||||
}
|
||||
if (bitfield.count() > 0)
|
||||
setInteresting(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,6 +649,89 @@ class PeerState implements DataLoader
|
||||
listener.gotPort(peer, port, port + 1);
|
||||
}
|
||||
|
||||
/////////// fast message handlers /////////
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Treated as "have" for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void suggestMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Handling suggest as have(" + piece + ") from " + peer);
|
||||
haveMessage(piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* @param isAll true for have_all, false for have_none
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void haveMessage(boolean isAll) {
|
||||
bitfieldMessage(null, isAll);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* If the peer rejects lower chunks but not higher ones, thus creating holes,
|
||||
* we won't figure it out and the piece will fail, since we don't currently
|
||||
* keep a chunk bitmap in PartialPiece.
|
||||
* As long as the peer rejects all the chunks, or rejects only the last chunks,
|
||||
* no holes are created and we will be fine. The reject messages may be in any order,
|
||||
* just don't make a hole when it's over.
|
||||
*
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void rejectMessage(int piece, int begin, int length) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
out.cancelRequest(piece, begin, length);
|
||||
synchronized(this) {
|
||||
Request deletedRequest = null;
|
||||
// for this piece only
|
||||
boolean haveMoreRequests = false;
|
||||
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
|
||||
Request req = iter.next();
|
||||
if (req.getPiece() == piece) {
|
||||
if (req.off == begin && req.len == length) {
|
||||
iter.remove();
|
||||
deletedRequest = req;
|
||||
} else {
|
||||
haveMoreRequests = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deletedRequest != null && !haveMoreRequests) {
|
||||
// We must return the piece to the coordinator
|
||||
// Create a new fake request so we can set the offset correctly
|
||||
PartialPiece pp = deletedRequest.getPartialPiece();
|
||||
int downloaded = pp.getDownloaded();
|
||||
Request req;
|
||||
if (deletedRequest.off == downloaded)
|
||||
req = deletedRequest;
|
||||
else
|
||||
req = new Request(pp, downloaded, 1);
|
||||
List<Request> pcs = Collections.singletonList(req);
|
||||
listener.savePartialPieces(this.peer, pcs);
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
|
||||
}
|
||||
if (lastRequest != null && lastRequest.getPiece() == piece &&
|
||||
lastRequest.off == begin && lastRequest.len == length)
|
||||
lastRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BEP 6
|
||||
* Ignored for now
|
||||
* @since 0.9.21
|
||||
*/
|
||||
void allowedFastMessage(int piece) {
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -543,6 +739,8 @@ class PeerState implements DataLoader
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
/////////// end message handlers /////////
|
||||
|
||||
/**
|
||||
* We now have this piece.
|
||||
* Tell the peer and cancel any requests for the piece.
|
||||
@@ -601,6 +799,7 @@ class PeerState implements DataLoader
|
||||
* @deprecated deadlocks
|
||||
* @since 0.8.1
|
||||
*/
|
||||
@Deprecated
|
||||
synchronized boolean isRequesting(int piece) {
|
||||
if (pendingRequest != null && pendingRequest.getPiece() == piece)
|
||||
return true;
|
||||
|
||||
@@ -43,13 +43,13 @@ class Request
|
||||
*/
|
||||
Request(PartialPiece piece, int off, int len)
|
||||
{
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
|
||||
this.piece = piece;
|
||||
this.off = off;
|
||||
this.len = len;
|
||||
|
||||
// Sanity check
|
||||
if (off < 0 || len <= 0 || off + len > piece.getLength())
|
||||
throw new IndexOutOfBoundsException("Illegal Request " + toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -290,6 +290,7 @@ public class Snark
|
||||
|
||||
/**
|
||||
* multitorrent
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener,
|
||||
@@ -304,6 +305,7 @@ public class Snark
|
||||
* multitorrent
|
||||
*
|
||||
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.9.11
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
@@ -478,6 +480,7 @@ public class Snark
|
||||
* @param torrent a fake name for now (not a file name)
|
||||
* @param ih 20-byte info hash
|
||||
* @param trackerURL may be null
|
||||
* @throws RuntimeException via fatal()
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL,
|
||||
@@ -531,6 +534,8 @@ public class Snark
|
||||
/**
|
||||
* Start up contacting peers and querying the tracker.
|
||||
* Blocks if tunnel is not yet open.
|
||||
*
|
||||
* @throws RuntimeException via fatal()
|
||||
*/
|
||||
public synchronized void startTorrent() {
|
||||
starting = true;
|
||||
@@ -612,7 +617,6 @@ public class Snark
|
||||
* @since 0.9.1
|
||||
*/
|
||||
public synchronized void stopTorrent(boolean fast) {
|
||||
stopped = true;
|
||||
TrackerClient tc = trackerclient;
|
||||
if (tc != null)
|
||||
tc.halt(fast);
|
||||
@@ -620,17 +624,28 @@ public class Snark
|
||||
if (pc != null)
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (!fast)
|
||||
// HACK: Needed a way to distinguish between user-stop and
|
||||
// shutdown-stop. stopTorrent(true) is in stopAllTorrents().
|
||||
// (#766)
|
||||
stopped = true;
|
||||
if (st != null) {
|
||||
boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
|
||||
// TODO: Cache the config-in-mem to compare vs config-on-disk
|
||||
// (needed for auto-save to not double-save in some cases)
|
||||
//boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
|
||||
boolean changed = true;
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
if (changed && completeListener != null)
|
||||
completeListener.updateStatus(this);
|
||||
}
|
||||
if (fast)
|
||||
// HACK: See above if(!fast)
|
||||
stopped = true;
|
||||
if (pc != null && _peerCoordinatorSet != null)
|
||||
_peerCoordinatorSet.remove(pc);
|
||||
if (_peerCoordinatorSet == null)
|
||||
@@ -730,6 +745,18 @@ public class Snark
|
||||
return storage != null && storage.isChecking();
|
||||
}
|
||||
|
||||
/**
|
||||
* If checking is in progress, return completion 0.0 ... 1.0,
|
||||
* else return 1.0.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public double getCheckingProgress() {
|
||||
if (storage != null && storage.isChecking())
|
||||
return storage.getCheckingProgress();
|
||||
else
|
||||
return 1.0d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disk allocation (ballooning) in progress.
|
||||
* @since 0.9.3
|
||||
@@ -884,6 +911,30 @@ public class Snark
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bytes not received and set to skipped.
|
||||
* This is not the same as the total of all skipped files,
|
||||
* since pieces may span multiple files.
|
||||
*
|
||||
* @return exact value. or 0 if no storage yet.
|
||||
* @since 0.9.24
|
||||
*/
|
||||
public long getSkippedLength() {
|
||||
PeerCoordinator coord = coordinator;
|
||||
if (coord != null) {
|
||||
// fast way
|
||||
long r = getRemainingLength();
|
||||
if (r <= 0)
|
||||
return 0;
|
||||
long n = coord.getNeededLength();
|
||||
return r - n;
|
||||
} else if (storage != null) {
|
||||
// slow way
|
||||
return storage.getSkippedLength();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not account (i.e. includes) for skipped files.
|
||||
* @return number of pieces still needed (magnet mode or not), or -1 if unknown
|
||||
@@ -1249,7 +1300,8 @@ public class Snark
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
if (coordinator != null)
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
///////////// End StorageListener methods
|
||||
@@ -1258,7 +1310,7 @@ public class Snark
|
||||
/** SnarkSnutdown callback unused */
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
// Should not be necessary since all non-daemon threads should
|
||||
// have died. But in reality this does not always happen.
|
||||
//System.exit(0);
|
||||
}
|
||||
@@ -1277,7 +1329,7 @@ public class Snark
|
||||
* coordinatorListener
|
||||
*/
|
||||
final static int MIN_TOTAL_UPLOADERS = 4;
|
||||
final static int MAX_TOTAL_UPLOADERS = 10;
|
||||
final static int MAX_TOTAL_UPLOADERS = 20;
|
||||
|
||||
public boolean overUploadLimit(int uploaders) {
|
||||
if (_peerCoordinatorSet == null || uploaders <= 0)
|
||||
@@ -1288,7 +1340,8 @@ public class Snark
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = _util.getMaxUploaders();
|
||||
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Total uploaders: " + totalUploaders + " Limit: " + limit);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ import net.i2p.util.I2PAppThread;
|
||||
* Makes sure everything ends correctly when shutting down.
|
||||
* @deprecated unused
|
||||
*/
|
||||
@Deprecated
|
||||
public class SnarkShutdown extends I2PAppThread
|
||||
{
|
||||
private final Storage storage;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -38,6 +39,8 @@ import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import gnu.getopt.Getopt;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.data.ByteArray;
|
||||
@@ -50,7 +53,7 @@ import net.i2p.util.SystemVersion;
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
public class Storage
|
||||
public class Storage implements Closeable
|
||||
{
|
||||
private final MetaInfo metainfo;
|
||||
private final List<TorrentFile> _torrentFiles;
|
||||
@@ -70,18 +73,20 @@ public class Storage
|
||||
private boolean changed;
|
||||
private volatile boolean _isChecking;
|
||||
private final AtomicInteger _allocateCount = new AtomicInteger();
|
||||
private final AtomicInteger _checkProgress = new AtomicInteger();
|
||||
|
||||
/** The default piece size. */
|
||||
private static final int DEFAULT_PIECE_SIZE = 256*1024;
|
||||
/** bigger than this will be rejected */
|
||||
public static final int MAX_PIECE_SIZE = 8*1024*1024;
|
||||
public static final int MAX_PIECE_SIZE = 16*1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
public static final int MAX_PIECES = 10*1024;
|
||||
public static final int MAX_PIECES = 32*1024;
|
||||
public static final long MAX_TOTAL_SIZE = MAX_PIECE_SIZE * (long) MAX_PIECES;
|
||||
|
||||
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap<String, String>();
|
||||
|
||||
private static final boolean _isWindows = SystemVersion.isWindows();
|
||||
private static final boolean _isARM = SystemVersion.isARM();
|
||||
|
||||
private static final int BUFSIZE = PeerState.PARTSIZE;
|
||||
private static final ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
|
||||
@@ -122,10 +127,12 @@ public class Storage
|
||||
*
|
||||
* @param announce may be null
|
||||
* @param listener may be null
|
||||
* @param created_by may be null
|
||||
* @throws IOException when creating and/or checking files fails.
|
||||
*/
|
||||
public Storage(I2PSnarkUtil util, File baseFile, String announce,
|
||||
List<List<String>> announce_list,
|
||||
String created_by,
|
||||
boolean privateTorrent, StorageListener listener)
|
||||
throws IOException
|
||||
{
|
||||
@@ -159,7 +166,7 @@ public class Storage
|
||||
else
|
||||
pc_size = DEFAULT_PIECE_SIZE;
|
||||
int pcs = (int) ((total - 1)/pc_size) + 1;
|
||||
while (pcs > (MAX_PIECES * 2 / 3) && pc_size < MAX_PIECE_SIZE)
|
||||
while (pcs > (MAX_PIECES / 3) && pc_size < MAX_PIECE_SIZE)
|
||||
{
|
||||
pc_size *= 2;
|
||||
pcs = (int) ((total - 1)/pc_size) +1;
|
||||
@@ -194,7 +201,7 @@ public class Storage
|
||||
byte[] piece_hashes = fast_digestCreate();
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
|
||||
lengthsList, piece_size, piece_hashes, total, privateTorrent,
|
||||
announce_list);
|
||||
announce_list, created_by);
|
||||
|
||||
}
|
||||
|
||||
@@ -306,6 +313,18 @@ public class Storage
|
||||
return _isChecking;
|
||||
}
|
||||
|
||||
/**
|
||||
* If checking is in progress, return completion 0.0 ... 1.0,
|
||||
* else return 1.0.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public double getCheckingProgress() {
|
||||
if (_isChecking)
|
||||
return _checkProgress.get() / (double) pieces;
|
||||
else
|
||||
return 1.0d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disk allocation (ballooning) in progress.
|
||||
* Always false on Windows.
|
||||
@@ -336,29 +355,28 @@ public class Storage
|
||||
* @return number of bytes remaining; -1 if unknown file
|
||||
* @since 0.7.14
|
||||
*/
|
||||
/****
|
||||
public long remaining(int fileIndex) {
|
||||
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
|
||||
return -1;
|
||||
if (complete())
|
||||
return 0;
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
if (i == fileIndex) {
|
||||
File f = tf.RAFfile;
|
||||
if (complete())
|
||||
return 0;
|
||||
int psz = piece_size;
|
||||
long start = bytes;
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / psz);
|
||||
int pc = (int) (bytes / piece_size);
|
||||
long rv = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rv = Math.min(psz - (start % psz), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
|
||||
rv = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*psz < end)
|
||||
rv += psz;
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
rv += piece_size;
|
||||
else
|
||||
rv += end - (((long)j) * psz);
|
||||
rv += end - (((long)j) * piece_size);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
@@ -367,6 +385,40 @@ public class Storage
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* For efficiency, calculate remaining bytes for all files at once
|
||||
*
|
||||
* @return number of bytes remaining for each file, use indexOf() to get index for a file
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public long[] remaining() {
|
||||
long[] rv = new long[_torrentFiles.size()];
|
||||
if (complete())
|
||||
return rv;
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < _torrentFiles.size(); i++) {
|
||||
TorrentFile tf = _torrentFiles.get(i);
|
||||
long start = bytes;
|
||||
long end = start + tf.length;
|
||||
int pc = (int) (bytes / piece_size);
|
||||
long rvi = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rvi = Math.min(piece_size - (start % piece_size), tf.length);
|
||||
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*piece_size < end)
|
||||
rvi += piece_size;
|
||||
else
|
||||
rvi += end - (((long)j) * piece_size);
|
||||
}
|
||||
}
|
||||
rv[i] = rvi;
|
||||
bytes += tf.length;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileIndex as obtained from indexOf
|
||||
@@ -450,9 +502,8 @@ public class Storage
|
||||
int file = 0;
|
||||
long pcEnd = -1;
|
||||
long fileEnd = _torrentFiles.get(0).length - 1;
|
||||
int psz = piece_size;
|
||||
for (int i = 0; i < rv.length; i++) {
|
||||
pcEnd += psz;
|
||||
pcEnd += piece_size;
|
||||
int pri = _torrentFiles.get(file).priority;
|
||||
while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) {
|
||||
file++;
|
||||
@@ -467,6 +518,31 @@ public class Storage
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call setPriority() for all changed files first,
|
||||
* then call this.
|
||||
* The length of all the pieces that are not yet downloaded,
|
||||
* and are set to skipped.
|
||||
* This is not the same as the total of all skipped files,
|
||||
* since pieces may span multiple files.
|
||||
*
|
||||
* @return 0 on error, if complete, or if only one file
|
||||
* @since 0.9.24
|
||||
*/
|
||||
public long getSkippedLength() {
|
||||
int[] pri = getPiecePriorities();
|
||||
if (pri == null)
|
||||
return 0;
|
||||
long rv = 0;
|
||||
final int end = pri.length - 1;
|
||||
for (int i = 0; i <= end; i++) {
|
||||
if (pri[i] <= -9 && !bitfield.get(i)) {
|
||||
rv += (i != end) ? piece_size : metainfo.getPieceLength(i);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
@@ -496,6 +572,9 @@ public class Storage
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
* Use recheck() to check again later.
|
||||
*
|
||||
* @throws IllegalStateException if called more than once
|
||||
*/
|
||||
public void check() throws IOException
|
||||
{
|
||||
@@ -506,6 +585,9 @@ public class Storage
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
* Use a saved bitfield and timestamp from a config file.
|
||||
* Only call this once, and only after the constructor with the metainfo.
|
||||
* Use recheck() to check again later.
|
||||
*
|
||||
* @throws IllegalStateException if called more than once
|
||||
*/
|
||||
public void check(long savedTime, BitField savedBitField) throws IOException
|
||||
{
|
||||
@@ -691,7 +773,7 @@ public class Storage
|
||||
}
|
||||
rv = repl;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
} catch (RuntimeException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
@@ -763,6 +845,14 @@ public class Storage
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not include directories.
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public int getFileCount() {
|
||||
return _torrentFiles.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Includes the base for a multi-file torrent.
|
||||
* Sorted bottom-up for easy deletion.
|
||||
@@ -784,6 +874,24 @@ public class Storage
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocking. Holds lock.
|
||||
* Recommend running only when stopped.
|
||||
* Caller should thread.
|
||||
* Calls listener.setWantedPieces() on completion if anything changed.
|
||||
*
|
||||
* @return true if anything changed, false otherwise
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public boolean recheck() throws IOException {
|
||||
int previousNeeded = needed;
|
||||
checkCreateFiles(true);
|
||||
boolean changed = previousNeeded != needed;
|
||||
if (listener != null && changed)
|
||||
listener.setWantedPieces(this);
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called at the beginning, and at presumed completion,
|
||||
* so we have to be careful about locking.
|
||||
@@ -808,6 +916,7 @@ public class Storage
|
||||
|
||||
private void locked_checkCreateFiles(boolean recheck) throws IOException
|
||||
{
|
||||
_checkProgress.set(0);
|
||||
// Whether we are resuming or not,
|
||||
// if any of the files already exists we assume we are resuming.
|
||||
boolean resume = false;
|
||||
@@ -824,13 +933,16 @@ public class Storage
|
||||
|
||||
// Make sure all files are available and of correct length
|
||||
// The files should all exist as they have been created with zero length by createFilesFromNames()
|
||||
long lengthProgress = 0;
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
long length = tf.RAFfile.length();
|
||||
lengthProgress += tf.length;
|
||||
if(tf.RAFfile.exists() && length == tf.length)
|
||||
{
|
||||
if (listener != null)
|
||||
listener.storageAllocated(this, length);
|
||||
_checkProgress.set(0);
|
||||
resume = true; // XXX Could dynamicly check
|
||||
}
|
||||
else if (length == 0) {
|
||||
@@ -842,6 +954,8 @@ public class Storage
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
if (!resume)
|
||||
_checkProgress.set((int) (pieces * lengthProgress / total_length));
|
||||
} else {
|
||||
String msg = "File '" + tf.name + "' exists, but has wrong length (expected " +
|
||||
tf.length + " but found " + length + ") - repairing corruption";
|
||||
@@ -850,6 +964,7 @@ public class Storage
|
||||
_log.error(msg);
|
||||
changed = true;
|
||||
resume = true;
|
||||
_checkProgress.set(0);
|
||||
_probablyComplete = false; // to force RW
|
||||
synchronized(tf) {
|
||||
RandomAccessFile raf = tf.checkRAF();
|
||||
@@ -870,17 +985,16 @@ public class Storage
|
||||
long pieceEnd = 0;
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
_checkProgress.set(i);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
// close as we go so we don't run out of file descriptors
|
||||
pieceEnd += length;
|
||||
while (fileEnd <= pieceEnd) {
|
||||
TorrentFile tf = _torrentFiles.get(file);
|
||||
synchronized(tf) {
|
||||
try {
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
try {
|
||||
tf.closeRAF();
|
||||
} catch (IOException ioe) {}
|
||||
if (++file >= _torrentFiles.size())
|
||||
break;
|
||||
fileEnd += _torrentFiles.get(file).length;
|
||||
@@ -896,6 +1010,7 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
_checkProgress.set(pieces);
|
||||
_probablyComplete = complete();
|
||||
// close all the files so we don't end up with a zillion open ones;
|
||||
// we will reopen as needed
|
||||
@@ -952,9 +1067,7 @@ public class Storage
|
||||
for (TorrentFile tf : _torrentFiles)
|
||||
{
|
||||
try {
|
||||
synchronized(tf) {
|
||||
tf.closeRAF();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error closing " + tf, ioe);
|
||||
// gobble gobble
|
||||
@@ -1179,17 +1292,15 @@ public class Storage
|
||||
return length;
|
||||
}
|
||||
|
||||
private static final long RAFCloseDelay = 4*60*1000;
|
||||
private static final long RAF_CLOSE_DELAY = 4*60*1000;
|
||||
|
||||
/**
|
||||
* Close unused RAFs - call periodically
|
||||
*/
|
||||
public void cleanRAFs() {
|
||||
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
|
||||
long cutoff = System.currentTimeMillis() - RAF_CLOSE_DELAY;
|
||||
for (TorrentFile tf : _torrentFiles) {
|
||||
synchronized(tf) {
|
||||
tf.closeRAF(cutoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,7 +1426,9 @@ public class Storage
|
||||
// Windows will zero-fill up to the point of the write, which
|
||||
// will make the file fairly unfragmented, on average, at least until
|
||||
// near the end where it will get exponentially more fragmented.
|
||||
if (!_isWindows)
|
||||
// Also don't ballon on ARM, as a proxy for solid state disk, where fragmentation doesn't matter too much.
|
||||
// Actual detection of SSD is almost impossible.
|
||||
if (!_isWindows && !_isARM)
|
||||
isSparse = true;
|
||||
}
|
||||
|
||||
@@ -1372,18 +1485,44 @@ public class Storage
|
||||
* @since 0.9.4
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1 || args.length > 2) {
|
||||
System.err.println("Usage: Storage file-or-dir [announceURL]");
|
||||
boolean error = false;
|
||||
String created_by = null;
|
||||
String announce = null;
|
||||
Getopt g = new Getopt("Storage", args, "a:c:");
|
||||
try {
|
||||
int c;
|
||||
while ((c = g.getopt()) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
announce = g.getOptarg();
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
created_by = g.getOptarg();
|
||||
break;
|
||||
|
||||
case '?':
|
||||
case ':':
|
||||
default:
|
||||
error = true;
|
||||
break;
|
||||
} // switch
|
||||
} // while
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
error = true;
|
||||
}
|
||||
if (error || args.length - g.getOptind() != 1) {
|
||||
System.err.println("Usage: Storage [-a announceURL] [-c created-by] file-or-dir");
|
||||
System.exit(1);
|
||||
}
|
||||
File base = new File(args[0]);
|
||||
String announce = args.length == 2 ? args[1] : null;
|
||||
File base = new File(args[g.getOptind()]);
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
I2PSnarkUtil util = new I2PSnarkUtil(ctx);
|
||||
File file = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
Storage storage = new Storage(util, base, announce, null, false, null);
|
||||
Storage storage = new Storage(util, base, announce, null, created_by, false, null);
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
file = new File(storage.getBaseName() + ".torrent");
|
||||
out = new FileOutputStream(file);
|
||||
|
||||
@@ -23,8 +23,8 @@ package org.klomp.snark;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -36,7 +36,9 @@ import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConvertToHash;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
@@ -88,6 +90,8 @@ public class TrackerClient implements Runnable {
|
||||
private static final int DHT_ANNOUNCE_PEERS = 4;
|
||||
public static final int PORT = 6881;
|
||||
private static final int MAX_TRACKERS = 12;
|
||||
// tracker.welterde.i2p
|
||||
private static final Hash DSA_ONLY_TRACKER = ConvertToHash.getHash("cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p");
|
||||
|
||||
private final I2PSnarkUtil _util;
|
||||
private final MetaInfo meta;
|
||||
@@ -156,6 +160,7 @@ public class TrackerClient implements Runnable {
|
||||
consecutiveFails = 0;
|
||||
runStarted = false;
|
||||
_fastUnannounce = false;
|
||||
snark.setTrackerProblems(null);
|
||||
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
|
||||
_thread.start();
|
||||
started = true;
|
||||
@@ -362,12 +367,21 @@ public class TrackerClient implements Runnable {
|
||||
if (h == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Bad announce URL: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
// comment this out if tracker.welterde.i2p upgrades
|
||||
if (h.equals(DSA_ONLY_TRACKER)) {
|
||||
Destination dest = _util.getMyDestination();
|
||||
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Skipping incompatible tracker: " + ann);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (existing.size() >= MAX_TRACKERS) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Not using announce URL, we have enough: [" + ann + ']');
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
boolean rv = existing.add(h);
|
||||
if (!rv) {
|
||||
@@ -528,9 +542,9 @@ public class TrackerClient implements Runnable {
|
||||
!snark.isChecking() &&
|
||||
info.getSeedCount() > 100 &&
|
||||
coordinator.getPeerCount() <= 0 &&
|
||||
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
|
||||
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
|
||||
snark.getTotalLength() > 0 &&
|
||||
uploaded >= snark.getTotalLength() * 5 / 4) {
|
||||
uploaded >= snark.getTotalLength() / 2) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Auto stopping " + snark.getBaseName());
|
||||
snark.setAutoStoppable(false);
|
||||
@@ -861,18 +875,20 @@ public class TrackerClient implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ann an announce URL
|
||||
* @param ann an announce URL, may be null, returns false if null
|
||||
* @return true for i2p hosts only
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public static boolean isValidAnnounce(String ann) {
|
||||
URL url;
|
||||
if (ann == null)
|
||||
return false;
|
||||
URI url;
|
||||
try {
|
||||
url = new URL(ann);
|
||||
} catch (MalformedURLException mue) {
|
||||
return false;
|
||||
url = new URI(ann);
|
||||
} catch (URISyntaxException use) {
|
||||
return false;
|
||||
}
|
||||
return url.getProtocol().equals("http") &&
|
||||
return "http".equals(url.getScheme()) && url.getHost() != null &&
|
||||
(url.getHost().endsWith(".i2p") || url.getHost().equals("i2p"));
|
||||
}
|
||||
|
||||
@@ -882,15 +898,17 @@ public class TrackerClient implements Runnable {
|
||||
* @since 0.9.5
|
||||
*/
|
||||
private static Hash getHostHash(String ann) {
|
||||
URL url;
|
||||
URI url;
|
||||
try {
|
||||
url = new URL(ann);
|
||||
} catch (MalformedURLException mue) {
|
||||
url = new URI(ann);
|
||||
} catch (URISyntaxException use) {
|
||||
return null;
|
||||
}
|
||||
if (!url.getProtocol().equals("http"))
|
||||
if (!"http".equals(url.getScheme()))
|
||||
return null;
|
||||
String host = url.getHost();
|
||||
if (host == null)
|
||||
return null;
|
||||
if (host.endsWith(".i2p"))
|
||||
return ConvertToHash.getHash(host);
|
||||
if (host.equals("i2p")) {
|
||||
@@ -898,7 +916,7 @@ public class TrackerClient implements Runnable {
|
||||
if (path == null || path.length() < 517 ||
|
||||
!path.startsWith("/"))
|
||||
return null;
|
||||
String[] parts = path.substring(1).split("/?&;", 2);
|
||||
String[] parts = DataHelper.split(path.substring(1), "[/\\?&;]", 2);
|
||||
return ConvertToHash.getHash(parts[0]);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
|
||||
/**
|
||||
* Holds different types that a bencoded byte array can represent.
|
||||
@@ -208,7 +209,7 @@ public class BEValue
|
||||
} else if (bin) {
|
||||
buf.append(bs.length).append(" bytes: ").append(Base64.encode(bs));
|
||||
} else {
|
||||
buf.append('"').append(new String(bs)).append('"');
|
||||
buf.append('"').append(DataHelper.getUTF8(bs)).append('"');
|
||||
}
|
||||
valueString = buf.toString();
|
||||
} else
|
||||
|
||||
@@ -106,6 +106,7 @@ class DHTTracker {
|
||||
* @param noSeeds true if we do not want seeds in the result
|
||||
* @return list or empty list (never null)
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
List<Hash> getPeers(InfoHash ih, int max, boolean noSeeds) {
|
||||
Peers peers = _torrents.get(ih);
|
||||
if (peers == null || max <= 0)
|
||||
|
||||
@@ -40,6 +40,7 @@ import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer2;
|
||||
|
||||
import org.klomp.snark.I2PSnarkUtil;
|
||||
import org.klomp.snark.SnarkManager;
|
||||
import org.klomp.snark.TrackerClient;
|
||||
import org.klomp.snark.bencode.BDecoder;
|
||||
@@ -128,8 +129,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
|
||||
/** Max number of nodes to return. BEP 5 says 8 */
|
||||
private static final int K = 8;
|
||||
/** Max number of peers to return. BEP 5 doesn't say. We'll use the same as I2PSnarkUtil.MAX_CONNECTIONS */
|
||||
private static final int MAX_WANT = 16;
|
||||
/** Max number of peers to return. BEP 5 doesn't say.
|
||||
* We'll use more than I2PSnarkUtil.MAX_CONNECTIONS since lots could be old.
|
||||
*/
|
||||
private static final int MAX_WANT = I2PSnarkUtil.MAX_CONNECTIONS * 3 / 2;
|
||||
|
||||
/** overloads error codes which start with 201 */
|
||||
private static final int REPLY_NONE = 0;
|
||||
@@ -243,6 +246,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param maxWait how long to wait for each to reply (not total) must be > 0
|
||||
* @param parallel how many outstanding at once (unimplemented, always 1)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void explore(NID target, int maxNodes, long maxWait, int parallel) {
|
||||
List<NodeInfo> nodes = _knownNodes.findClosest(target, maxNodes);
|
||||
if (nodes.isEmpty()) {
|
||||
@@ -327,6 +331,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param noSeeds true if we do not want seeds in the result
|
||||
* @return possibly empty (never null)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Collection<Hash> getPeersAndAnnounce(byte[] ih, int max, long maxWait,
|
||||
int annMax, long annMaxWait,
|
||||
boolean isSeed, boolean noSeeds) {
|
||||
@@ -842,7 +847,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending error " + msg + " to: " + nInfo);
|
||||
Map<String, Object> map = new HashMap<String, Object>(4);
|
||||
List<Object> error = new ArrayList(2);
|
||||
List<Object> error = new ArrayList<Object>(2);
|
||||
error.add(Integer.valueOf(err));
|
||||
error.add(msg);
|
||||
map.put("e", error);
|
||||
@@ -858,6 +863,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param repliable true for all but announce
|
||||
* @return null on error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private ReplyWaiter sendQuery(NodeInfo nInfo, Map<String, Object> map, boolean repliable) {
|
||||
if (nInfo.equals(_myNodeInfo))
|
||||
throw new IllegalArgumentException("wtf don't send to ourselves");
|
||||
@@ -907,6 +913,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
* @param toPort the query port, we will increment here
|
||||
* @return success
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean sendResponse(NodeInfo nInfo, MsgID msgID, Map<String, Object> map) {
|
||||
if (nInfo.equals(_myNodeInfo))
|
||||
throw new IllegalArgumentException("wtf don't send to ourselves");
|
||||
@@ -1294,7 +1301,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
} else {
|
||||
List<byte[]> hashes;
|
||||
if (peers.isEmpty()) {
|
||||
hashes = Collections.EMPTY_LIST;
|
||||
hashes = Collections.emptyList();
|
||||
} else {
|
||||
hashes = new ArrayList<byte[]>(peers.size());
|
||||
for (Hash peer : peers) {
|
||||
@@ -1408,7 +1415,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
|
||||
private List<Hash> receivePeers(NodeInfo nInfo, List<BEValue> peers) throws InvalidBEncodingException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Rcvd peers from: " + nInfo);
|
||||
int max = Math.min(MAX_WANT, peers.size());
|
||||
int max = Math.min(MAX_WANT * 2, peers.size());
|
||||
List<Hash> rv = new ArrayList<Hash>(max);
|
||||
for (BEValue bev : peers) {
|
||||
byte[] b = bev.getBytes();
|
||||
|
||||
@@ -102,7 +102,7 @@ class NodeInfo extends SimpleDataStructure {
|
||||
*/
|
||||
public NodeInfo(String s) throws DataFormatException {
|
||||
super();
|
||||
String[] parts = s.split(":", 4);
|
||||
String[] parts = DataHelper.split(s, ":", 4);
|
||||
if (parts.length != 4)
|
||||
throw new DataFormatException("Bad format");
|
||||
byte[] nid = Base64.decode(parts[0]);
|
||||
@@ -225,7 +225,7 @@ class NodeInfo extends SimpleDataStructure {
|
||||
NodeInfo ni = (NodeInfo) o;
|
||||
// assume dest matches, ignore it
|
||||
return this.hash.equals(ni.hash) && nID.equals(ni.nID) && port == ni.port;
|
||||
} catch (Exception e) {
|
||||
} catch (RuntimeException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ abstract class PersistDHT {
|
||||
out.println(ni.toPersistentString());
|
||||
count++;
|
||||
}
|
||||
if (out.checkError())
|
||||
throw new IOException("Failed write to " + file);
|
||||
} catch (IOException ioe) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Error writing the DHT File", ioe);
|
||||
|
||||
8
apps/i2psnark/java/src/org/klomp/snark/package.html
Normal file
8
apps/i2psnark/java/src/org/klomp/snark/package.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
I2P version of the snark bittorrent client, imported in 2005 and heavily enhanced
|
||||
to add a web UI, DHT support, and other features.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -358,6 +358,7 @@ class BasicServlet extends HttpServlet
|
||||
writeHeaders(response, content, content_length);
|
||||
response.setStatus(416);
|
||||
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
|
||||
in.close();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,7 +378,7 @@ class BasicServlet extends HttpServlet
|
||||
{
|
||||
if (content.getContentType()!=null && response.getContentType()==null)
|
||||
response.setContentType(content.getContentType());
|
||||
|
||||
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||
long lml = content.getLastModified();
|
||||
if (lml > 0)
|
||||
response.setDateHeader("Last-Modified",lml);
|
||||
@@ -393,7 +394,6 @@ class BasicServlet extends HttpServlet
|
||||
long ct = content.getCacheTime();
|
||||
if (ct>=0)
|
||||
response.setHeader("Cache-Control", "public, max-age=" + ct);
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
@@ -77,7 +77,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
_log = ctx.logManager().getLog(FetchAndAdd.class);
|
||||
_mgr = mgr;
|
||||
_url = url;
|
||||
_name = _("Download torrent file from {0}", url);
|
||||
_name = _t("Download torrent file from {0}", url);
|
||||
_dataDir = dataDir;
|
||||
byte[] fake = null;
|
||||
try {
|
||||
@@ -90,7 +90,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
* Set off by startTorrent()
|
||||
*/
|
||||
public void run() {
|
||||
_mgr.addMessageNoEscape(_("Fetching {0}", urlify(_url)));
|
||||
_mgr.addMessageNoEscape(_t("Fetching {0}", urlify(_url)));
|
||||
File file = get();
|
||||
if (!_isRunning) // stopped?
|
||||
return;
|
||||
@@ -100,7 +100,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
_mgr.deleteMagnet(this);
|
||||
add(file);
|
||||
} else {
|
||||
_mgr.addMessageNoEscape(_("Torrent was not retrieved from {0}", urlify(_url)) +
|
||||
_mgr.addMessageNoEscape(_t("Torrent was not retrieved from {0}", urlify(_url)) +
|
||||
((_failCause != null) ? (": " + DataHelper.stripHTML(_failCause)) : ""));
|
||||
}
|
||||
if (file != null)
|
||||
@@ -127,7 +127,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
out.deleteOnExit();
|
||||
|
||||
if (!_mgr.util().connected()) {
|
||||
_mgr.addMessage(_("Opening the I2P tunnel"));
|
||||
_mgr.addMessage(_t("Opening the I2P tunnel"));
|
||||
if (!_mgr.util().connect())
|
||||
return null;
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
* This Snark may then be deleted.
|
||||
*/
|
||||
private void add(File file) {
|
||||
_mgr.addMessageNoEscape(_("Torrent fetched from {0}", urlify(_url)));
|
||||
_mgr.addMessageNoEscape(_t("Torrent fetched from {0}", urlify(_url)));
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
@@ -163,7 +163,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
|
||||
if (snark != null) {
|
||||
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||
_mgr.addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,9 +175,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_mgr.getTorrent(canonical) != null)
|
||||
_mgr.addMessage(_("Torrent already running: {0}", name));
|
||||
_mgr.addMessage(_t("Torrent already running: {0}", name));
|
||||
else
|
||||
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
|
||||
_mgr.addMessage(_t("Torrent already in the queue: {0}", name));
|
||||
} else {
|
||||
// This may take a LONG time to create the storage.
|
||||
_mgr.copyAndAddTorrent(file, canonical, _dataDir);
|
||||
@@ -188,9 +188,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
throw new IOException("Unknown error - check logs");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_mgr.addMessageNoEscape(_("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
|
||||
_mgr.addMessageNoEscape(_t("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_mgr.addMessageNoEscape(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
|
||||
_mgr.addMessageNoEscape(_t("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
|
||||
} finally {
|
||||
try { if (in != null) in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
@@ -345,11 +345,11 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
|
||||
|
||||
// End of EepGet status listeners
|
||||
|
||||
private String _(String s) {
|
||||
private String _t(String s) {
|
||||
return _mgr.util().getString(s);
|
||||
}
|
||||
|
||||
private String _(String s, String o) {
|
||||
private String _t(String s, String o) {
|
||||
return _mgr.util().getString(s, o);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ import net.i2p.util.FileUtil;
|
||||
/**
|
||||
* @deprecated does not work
|
||||
*/
|
||||
@Deprecated
|
||||
public class RunStandalone {
|
||||
/****
|
||||
static {
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.text.Collator;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.klomp.snark.MetaInfo;
|
||||
import org.klomp.snark.Snark;
|
||||
@@ -18,6 +20,13 @@ import org.klomp.snark.Storage;
|
||||
*/
|
||||
class Sorters {
|
||||
|
||||
/**
|
||||
* See below
|
||||
*/
|
||||
private static final Pattern PATTERN_DE, PATTERN_EN, PATTERN_ES, PATTERN_FR,
|
||||
PATTERN_IT, PATTERN_NL, PATTERN_PT;
|
||||
private static Pattern _pattern;
|
||||
|
||||
/**
|
||||
* Negative is reverse
|
||||
*
|
||||
@@ -113,8 +122,8 @@ class Sorters {
|
||||
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading "the "
|
||||
* (I guess this is worth it, a lot of torrents start with "The "
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading
|
||||
* articles such as "the" if the pattern is set by setPattern()
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
|
||||
@@ -130,13 +139,16 @@ class Sorters {
|
||||
if (l.getStorage() != null && r.getStorage() == null)
|
||||
return 1;
|
||||
String ls = l.getBaseName();
|
||||
String llc = ls.toLowerCase(Locale.US);
|
||||
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
|
||||
ls = ls.substring(4);
|
||||
String rs = r.getBaseName();
|
||||
String rlc = rs.toLowerCase(Locale.US);
|
||||
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
|
||||
rs = rs.substring(4);
|
||||
Pattern p = _pattern;
|
||||
if (p != null) {
|
||||
Matcher m = p.matcher(ls);
|
||||
if (m.matches())
|
||||
ls = ls.substring(m.group(1).length());
|
||||
m = p.matcher(rs);
|
||||
if (m.matches())
|
||||
rs = rs.substring(m.group(1).length());
|
||||
}
|
||||
return Collator.getInstance().compare(ls, rs);
|
||||
}
|
||||
}
|
||||
@@ -356,13 +368,14 @@ class Sorters {
|
||||
|
||||
/**
|
||||
* @param storage may be null
|
||||
* @param remainingArray precomputed, non-null iff storage is non-null
|
||||
*/
|
||||
public FileAndIndex(File file, Storage storage) {
|
||||
public FileAndIndex(File file, Storage storage, long[] remainingArray) {
|
||||
this.file = file;
|
||||
index = storage != null ? storage.indexOf(file) : -1;
|
||||
if (index >= 0) {
|
||||
isDirectory = false;
|
||||
remaining = storage.remaining(index);
|
||||
remaining = remainingArray[index];
|
||||
priority = storage.getPriority(index);
|
||||
} else {
|
||||
isDirectory = file.isDirectory();
|
||||
@@ -527,4 +540,104 @@ class Sorters {
|
||||
return r.priority - l.priority;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Match an indefinite or definite article in the language,
|
||||
* followed by one or more whitespace, '.', or '_'.
|
||||
* Does not match "partitive" articles.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Article_%28grammar%29
|
||||
* http://www.loc.gov/marc/bibliographic/bdapndxf.html
|
||||
*/
|
||||
static {
|
||||
PATTERN_DE = Pattern.compile(
|
||||
// can't make the non-capturing innner group work
|
||||
//"^((?:" +
|
||||
"^((" +
|
||||
"der|die|das|des|dem|den|ein|eine|einer|eines|einem|einen" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_EN = Pattern.compile(
|
||||
"^((" +
|
||||
"a|an|the" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_ES = Pattern.compile(
|
||||
"^((" +
|
||||
"el|la|lo|los|las|un|una|unos|unas" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_FR = Pattern.compile(
|
||||
// note l' doesn't require whitespace after
|
||||
"^(l'|((" +
|
||||
"le|la|les|un|une|des" +
|
||||
")[\\s\\._]+)).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_IT = Pattern.compile(
|
||||
// note l' and un' don't require whitespace after
|
||||
"^(l'|un'|((" +
|
||||
"il|lo|la|i|gli|le|uno|una|un" +
|
||||
")[\\s\\._]+)).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_NL = Pattern.compile(
|
||||
"^((" +
|
||||
"de|het|het'n|een|een'n" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
PATTERN_PT = Pattern.compile(
|
||||
"^((" +
|
||||
"o|a|os|as|um|uma|uns|umas" +
|
||||
")[\\s\\._]+).*",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets static field, oh well
|
||||
* @param lang null for none
|
||||
* @since 0.9.23
|
||||
*/
|
||||
public static void setPattern(String lang) {
|
||||
Pattern p;
|
||||
if (lang == null)
|
||||
p = null;
|
||||
else if (lang.equals("de"))
|
||||
p = PATTERN_DE;
|
||||
else if (lang.equals("en"))
|
||||
p = PATTERN_EN;
|
||||
else if (lang.equals("es"))
|
||||
p = PATTERN_ES;
|
||||
else if (lang.equals("fr"))
|
||||
p = PATTERN_FR;
|
||||
else if (lang.equals("it"))
|
||||
p = PATTERN_IT;
|
||||
else if (lang.equals("nl"))
|
||||
p = PATTERN_NL;
|
||||
else if (lang.equals("pt"))
|
||||
p = PATTERN_PT;
|
||||
else
|
||||
p = null;
|
||||
_pattern = p;
|
||||
}
|
||||
|
||||
/****
|
||||
public static final void main(String[] args) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: Sorters lang 'string'");
|
||||
System.exit(1);
|
||||
}
|
||||
String lang = args[0];
|
||||
setPattern(lang);
|
||||
if (_pattern == null) {
|
||||
System.out.println("Unsupported " + lang);
|
||||
System.exit(1);
|
||||
}
|
||||
String s = args[1];
|
||||
Matcher m = _pattern.matcher(s);
|
||||
if (m.matches()) {
|
||||
System.out.println("Match is \"" + m.group(1) + '"');
|
||||
} else {
|
||||
System.out.println("No match for \"" + s + '"');
|
||||
}
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
||||
7
apps/i2psnark/java/src/org/klomp/snark/web/package.html
Normal file
7
apps/i2psnark/java/src/org/klomp/snark/web/package.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
The i2psnark user interface, implemented as a webapp in i2psnark.war.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user