propagate from branch 'i2p.i2p' (head 833ef88c125ba48423bc704701303ba55858336f)

to branch 'i2p.i2p.str4d.test2' (head 6f21d8d6c6d4cca5b6442f8effb56841e81a07b6)
This commit is contained in:
str4d
2015-12-29 21:08:36 +00:00
1503 changed files with 222755 additions and 125149 deletions

View File

@@ -12,9 +12,11 @@ trans.it = apps/i2ptunnel/locale/messages_it.po
trans.ja = apps/i2ptunnel/locale/messages_ja.po
trans.nb = apps/i2ptunnel/locale/messages_nb.po
trans.nl = apps/i2ptunnel/locale/messages_nl.po
trans.nn = apps/i2ptunnel/locale/messages_nn.po
trans.pl = apps/i2ptunnel/locale/messages_pl.po
trans.pt = apps/i2ptunnel/locale/messages_pt.po
trans.pt_BR = apps/i2ptunnel/locale/messages_pt_BR.po
trans.ro = apps/i2ptunnel/locale/messages_ro.po
trans.ru_RU = apps/i2ptunnel/locale/messages_ru.po
trans.sk = apps/i2ptunnel/locale/messages_sk.po
trans.sv_SE = apps/i2ptunnel/locale/messages_sv.po
@@ -31,6 +33,8 @@ trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
;; Java converts id to in
trans.id = apps/i2ptunnel/locale-proxy/messages_in.po
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
@@ -39,6 +43,7 @@ trans.pt = apps/i2ptunnel/locale-proxy/messages_pt.po
trans.pt_BR = apps/i2ptunnel/locale-proxy/messages_pt_BR.po
trans.ro = apps/i2ptunnel/locale-proxy/messages_ro.po
trans.ru_RU = apps/i2ptunnel/locale-proxy/messages_ru.po
trans.sk = apps/i2ptunnel/locale-proxy/messages_sk.po
trans.sv_SE = apps/i2ptunnel/locale-proxy/messages_sv.po
trans.uk_UA = apps/i2ptunnel/locale-proxy/messages_uk.po
trans.vi = apps/i2ptunnel/locale-proxy/messages_vi.po
@@ -78,10 +83,16 @@ source_lang = en
trans.ar = apps/routerconsole/locale-news/messages_ar.po
trans.de = apps/routerconsole/locale-news/messages_de.po
trans.es = apps/routerconsole/locale-news/messages_es.po
trans.fi = apps/routerconsole/locale-news/messages_fi.po
trans.fr = apps/routerconsole/locale-news/messages_fr.po
trans.he = apps/routerconsole/locale-news/messages_he.po
trans.ja = apps/routerconsole/locale-news/messages_ja.po
;; Java converts id to in
trans.id = apps/routerconsole/locale-news/messages_in.po
trans.it = apps/routerconsole/locale-news/messages_it.po
trans.ja = apps/routerconsole/locale-news/messages_ja.po
trans.ko = apps/routerconsole/locale-news/messages_ko.po
trans.mg = apps/routerconsole/locale-news/messages_mg.po
trans.nb = apps/routerconsole/locale-news/messages_nb.po
trans.nl = apps/routerconsole/locale-news/messages_nl.po
trans.pl = apps/routerconsole/locale-news/messages_pl.po
trans.pt = apps/routerconsole/locale-news/messages_pt.po
@@ -89,14 +100,17 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
trans.ro = apps/routerconsole/locale-news/messages_ro.po
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
trans.sk = apps/routerconsole/locale-news/messages_sk.po
trans.sq = apps/routerconsole/locale-news/messages_sq.po
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
trans.uk_UA = apps/routerconsole/locale-news/messages_uk.po
trans.zh_CN = apps/routerconsole/locale-news/messages_zh.po
[I2P.countries]
type = PO
source_file = apps/routerconsole/locale-countries/messages_en.po
source_lang = en
trans.ca = apps/routerconsole/locale-countries/messages_ca.po
trans.da = apps/routerconsole/locale-countries/messages_da.po
trans.de = apps/routerconsole/locale-countries/messages_de.po
trans.el = apps/routerconsole/locale-countries/messages_el.po
@@ -107,6 +121,7 @@ trans.fr = apps/routerconsole/locale-countries/messages_fr.po
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
trans.it = apps/routerconsole/locale-countries/messages_it.po
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
trans.mg = apps/routerconsole/locale-countries/messages_mg.po
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
@@ -115,7 +130,9 @@ trans.pt_BR = apps/routerconsole/locale-countries/messages_pt_BR.po
trans.ro = apps/routerconsole/locale-countries/messages_ro.po
trans.ru_RU = apps/routerconsole/locale-countries/messages_ru.po
trans.sk = apps/routerconsole/locale-countries/messages_sk.po
trans.sq = apps/routerconsole/locale-countries/messages_sq.po
trans.sv_SE = apps/routerconsole/locale-countries/messages_sv.po
trans.uk_UA = apps/routerconsole/locale-countries/messages_uk.po
trans.tr_TR = apps/routerconsole/locale-countries/messages_tr.po
trans.vi = apps/routerconsole/locale-countries/messages_vi.po
trans.zh_CN = apps/routerconsole/locale-countries/messages_zh.po
@@ -134,6 +151,7 @@ trans.nb = apps/i2psnark/locale/messages_nb.po
trans.nl = apps/i2psnark/locale/messages_nl.po
trans.pl = apps/i2psnark/locale/messages_pl.po
trans.pt = apps/i2psnark/locale/messages_pt.po
trans.pt_BR = apps/i2psnark/locale/messages_pt_bR.po
trans.ro = apps/i2psnark/locale/messages_ro.po
trans.ru_RU = apps/i2psnark/locale/messages_ru.po
trans.sk = apps/i2psnark/locale/messages_sk.po
@@ -161,6 +179,7 @@ trans.pt_BR = apps/susidns/locale/messages_pt_BR.po
trans.ro = apps/susidns/locale/messages_ro.po
trans.ru_RU = apps/susidns/locale/messages_ru.po
trans.sv_SE = apps/susidns/locale/messages_sv.po
trans.tr_TR = apps/susidns/locale/messages_tr.po
trans.uk_UA = apps/susidns/locale/messages_uk.po
trans.vi = apps/susidns/locale/messages_vi.po
trans.zh_CN = apps/susidns/locale/messages_zh.po
@@ -194,19 +213,25 @@ trans.zh_CN = apps/desktopgui/locale/messages_zh.po
source_file = apps/susimail/locale/messages_en.po
source_lang = en
trans.cs = apps/susimail/locale/messages_cs.po
trans.da = apps/susimail/locale/messages_da.po
trans.de = apps/susimail/locale/messages_de.po
trans.es = apps/susimail/locale/messages_es.po
trans.fi = apps/susimail/locale/messages_fi.po
trans.fr = apps/susimail/locale/messages_fr.po
trans.hu = apps/susimail/locale/messages_hu.po
;; Java converts id to in
trans.id = apps/susimail/locale/messages_in.po
trans.it = apps/susimail/locale/messages_it.po
trans.ja = apps/susimail/locale/messages_ja.po
trans.mg = apps/susimail/locale/messages_mg.po
trans.nl = apps/susimail/locale/messages_nl.po
trans.ru_RU = apps/susimail/locale/messages_ru.po
trans.sv_SE = apps/susimail/locale/messages_sv.po
trans.pl = apps/susimail/locale/messages_pl.po
trans.pt = apps/susimail/locale/messages_pt.po
trans.pt_BR = apps/susimail/locale/messages_pt_BR.po
trans.ro = apps/susimail/locale/messages_ro.po
trans.ru_RU = apps/susimail/locale/messages_ru.po
trans.sq = apps/susimail/locale/messages_sq.po
trans.sv_SE = apps/susimail/locale/messages_sv.po
trans.uk_UA = apps/susimail/locale/messages_uk.po
trans.vi = apps/susimail/locale/messages_vi.po
trans.zh_CN = apps/susimail/locale/messages_zh.po
@@ -218,16 +243,21 @@ trans.cs = debian/po/cs.po
trans.de = debian/po/de.po
trans.el = debian/po/el.po
trans.es = debian/po/es.po
trans.fi = debian/po/fi.po
trans.fr = debian/po/fr.po
trans.id = debian/po/id.po
trans.it = debian/po/it.po
trans.hu = debian/po/hu.po
trans.ja = debian/po/ja.po
trans.ko = debian/po/ko.po
trans.nl = debian/po/nl.po
trans.pl = debian/po/pl.po
trans.pt = debian/po/pt.po
trans.pt_BR = debian/po/pt_BR.po
trans.ro = debian/po/ro.po
trans.ru_RU = debian/po/ru.po
trans.sk = debian/po/sk.po
trans.sq = debian/po/sq.po
trans.sv_SE = debian/po/sv.po
trans.uk_UA = debian/po/uk.po
trans.tr_TR = debian/po/tr.po
@@ -236,12 +266,21 @@ trans.zh_CN = debian/po/zh.po
[I2P.i2prouter-script]
source_file = installer/resources/locale/po/messages_en.po
source_lang = en
;; currently fails check
;;trans.ca = installer/resources/locale/po/messages_ca.po
trans.de = installer/resources/locale/po/messages_de.po
trans.es = installer/resources/locale/po/messages_es.po
;; currently fails check
;;trans.fi = installer/resources/locale/po/messages_fi.po
trans.fr = installer/resources/locale/po/messages_fr.po
trans.id = installer/resources/locale/po/messages_id.po
trans.it = installer/resources/locale/po/messages_it.po
trans.pl = installer/resources/locale/po/messages_pl.po
trans.ja = installer/resources/locale/po/messages_ja.po
;; currently fails check
;;trans.ko = installer/resources/locale/po/messages_ko.po
trans.nl = installer/resources/locale/po/messages_nl.po
trans.pl = installer/resources/locale/po/messages_pl.po
trans.pt = installer/resources/locale/po/messages_pt.po
trans.pt_BR = installer/resources/locale/po/messages_pt_BR.po
trans.ro = installer/resources/locale/po/messages_ro.po
@@ -249,6 +288,8 @@ trans.ru_RU = installer/resources/locale/po/messages_ru.po
trans.sk = installer/resources/locale/po/messages_sk.po
trans.sv_SE = installer/resources/locale/po/messages_sv.po
trans.tr_TR = installer/resources/locale/po/messages_tr.po
;; currently fails check
;;trans.uk_UA = installer/resources/locale/po/messages_uk.po
trans.zh_CN = installer/resources/locale/po/messages_zh.po
[I2P.getopt]
@@ -258,22 +299,46 @@ type = PROPERTIES
trans.cs = core/java/src/gnu/getopt/MessagesBundle_cs.properties
trans.de = core/java/src/gnu/getopt/MessagesBundle_de.properties
trans.es = core/java/src/gnu/getopt/MessagesBundle_es.properties
trans.fi = core/java/src/gnu/getopt/MessagesBundle_fi.properties
trans.fr = core/java/src/gnu/getopt/MessagesBundle_fr.properties
trans.hu = core/java/src/gnu/getopt/MessagesBundle_hu.properties
;; Java converts id to in
trans.id = core/java/src/gnu/getopt/MessagesBundle_in.properties
trans.it = core/java/src/gnu/getopt/MessagesBundle_it.properties
trans.ja = core/java/src/gnu/getopt/MessagesBundle_ja.properties
trans.ko = core/java/src/gnu/getopt/MessagesBundle_ko.properties
trans.nl = core/java/src/gnu/getopt/MessagesBundle_nl.properties
trans.nb = core/java/src/gnu/getopt/MessagesBundle_nb.properties
trans.pl = core/java/src/gnu/getopt/MessagesBundle_pl.properties
trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
;; currently corrupt, non-UTF-8
;;trans.pt = core/java/src/gnu/getopt/MessagesBundle_pt.properties
;; currently corrupt, non-UTF-8
;;trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
;; currently corrupt, non-UTF-8
;;trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
trans.uk_UA = core/java/src/gnu/getopt/MessagesBundle_uk.properties
trans.zh_CN = core/java/src/gnu/getopt/MessagesBundle_zh.properties
[I2P.streaming]
source_file = apps/ministreaming/locale/messages_en.po
source_lang = en
trans.ca = apps/ministreaming/locale/messages_ca.po
trans.de = apps/ministreaming/locale/messages_de.po
trans.es = apps/ministreaming/locale/messages_es.po
trans.fr = apps/ministreaming/locale/messages_fr.po
;; Java converts id to in
trans.id = apps/ministreaming/locale/messages_in.po
trans.it = apps/ministreaming/locale/messages_it.po
trans.nb = apps/ministreaming/locale/messages_nb.po
trans.pl = apps/ministreaming/locale/messages_pl.po
trans.ro = apps/ministreaming/locale/messages_ro.po
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
trans.uk_UA = apps/ministreaming/locale/messages_uk.po
trans.zh_CN = apps/ministreaming/locale/messages_zh.po
[main]
host = https://www.transifex.com

View File

@@ -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

View File

@@ -1,8 +1,9 @@
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/
@@ -40,29 +41,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

View File

@@ -80,6 +80,10 @@ Public domain except as listed below:
Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
See licenses/LICENSE-LGPLv2.1.txt
HostnameVerifier:
From Apache HttpClient 4.4.1 and HttpCore 4.4.1
See licenses/LICENSE-Apache2.0.txt
Router (router.jar):
Public domain except as listed below:
@@ -87,7 +91,7 @@ Public domain except as listed below:
From freenet
See licenses/LICENSE-GPLv2.txt
UPnP subsystem (CyberLink) 2.1:
UPnP subsystem (CyberLink) 3.0:
Copyright (C) 2003-2010 Satoshi Konno
See licenses/LICENSE-UPnP.txt
@@ -137,7 +141,7 @@ Installer:
Java Service Wrapper Community Edition 32-bit 3.5.19:
Java Service Wrapper Community Edition 32-bit 3.5.25:
Copyright (C) 1999-2011 Tanuki Software, Ltd. All Rights Reserved.
See licenses/LICENSE-Wrapper.txt
@@ -182,7 +186,7 @@ Applications:
By welterde.
See licenses/LICENSE-GPLv2.txt
Jetty 8.1.15.v20140411:
Jetty 8.1.17.v20150415:
See licenses/ABOUT-Jetty.html
See licenses/NOTICE-Jetty.html
See licenses/LICENSE-Apache2.0.txt
@@ -248,8 +252,8 @@ Applications:
Bundles systray4j-2.4.1:
See licenses/LICENSE-LGPLv2.1.txt
Tomcat 6.0.41:
Copyright 1999-2014 The Apache Software Foundation
Tomcat 6.0.44:
Copyright 1999-2015 The Apache Software Foundation
See licenses/LICENSE-Apache2.0.txt
See licenses/NOTICE-Tomcat.txt

View File

@@ -1,6 +1,7 @@
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/
@@ -30,7 +31,8 @@ Old build system:
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
@@ -45,6 +47,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

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -38,7 +38,6 @@ import net.i2p.I2PAppContext;
import net.i2p.app.*;
import net.i2p.client.I2PClient;
import net.i2p.util.I2PAppThread;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer2;
/**
@@ -119,15 +118,16 @@ public class BOB implements Runnable, ClientApp {
public final static String PROP_BOB_HOST = "BOB.host";
public final static String PROP_CFG_VER = "BOB.CFG.VER";
/** unused when started via the ClientApp interface */
private static BOB _bob;
private NamedDB database;
private Properties props = new Properties();
private AtomicBoolean spin = new AtomicBoolean(true);
private final NamedDB database;
private final Properties props = new Properties();
private final AtomicBoolean spin = new AtomicBoolean(true);
private static final String P_RUNNING = "RUNNING";
private static final String P_STARTING = "STARTING";
private static final String P_STOPPING = "STOPPING";
private AtomicBoolean lock = new AtomicBoolean(false);
private final AtomicBoolean lock = new AtomicBoolean(false);
// no longer used.
// private static int maxConnections = 0;
@@ -143,8 +143,9 @@ public class BOB implements Runnable, ClientApp {
* Stop BOB gracefully
* @deprecated unused
*/
public static void stop() {
_bob.shutdown(null);
public synchronized static void stop() {
if (_bob != null)
_bob.shutdown(null);
}
/**
@@ -189,7 +190,7 @@ public class BOB implements Runnable, ClientApp {
*
* @param args
*/
public static void main(String[] args) {
public synchronized static void main(String[] args) {
try {
_bob = new BOB(I2PAppContext.getGlobalContext(), null, args);
_bob.startup();
@@ -212,9 +213,7 @@ public class BOB implements Runnable, ClientApp {
// Re-reading the config file in each thread is pretty damn stupid.
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
// This is here just to ensure there is no interference with our threadgroups.
SimpleScheduler Y1 = SimpleScheduler.getInstance();
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
i = Y1.hashCode();
i = Y2.hashCode();
{
File cfg = new File(configLocation);
@@ -248,11 +247,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")) {
@@ -339,7 +338,7 @@ public class BOB implements Runnable, ClientApp {
if (g) {
DoCMDS conn_c = new DoCMDS(spin, lock, server, props, database, _log);
Thread t = new Thread(conn_c);
Thread t = new I2PAppThread(conn_c);
t.setName("BOB.DoCMDS " + i);
t.start();
i++;

View File

@@ -25,12 +25,13 @@ import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClientFactory;
//import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
//import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.I2PAppThread;
// needed only for debugging.
// import java.util.logging.Level;
// import java.util.logging.Logger;
@@ -1307,7 +1308,7 @@ public class DoCMDS implements Runnable {
// wait
}
tunnel = new MUXlisten(lock, database, nickinfo, _log);
Thread t = new Thread(tunnel);
Thread t = new I2PAppThread(tunnel);
t.start();
// try {
// Thread.sleep(1000 * 10); // Slow down the startup.

View File

@@ -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();
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -34,15 +34,17 @@ import net.i2p.util.Log;
* The skeletal frame is here, just needs to be finished.
*
* @author sponge
* @deprecated incomplete, unused
*/
public class UDPIOthread implements I2PSessionListener, Runnable {
private NamedDB info;
private Log _log;
private Socket socket;
private final NamedDB info;
private final Log _log;
private final Socket socket;
private DataInputStream in;
private DataOutputStream out;
private I2PSession _session;
private final I2PSession _session;
// FIXME never set
private Destination _peerDestination;
private boolean up;
@@ -58,7 +60,6 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
this._log = _log;
this.socket = socket;
this._session = _session;
}
/**

View File

@@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import net.i2p.I2PAppContext;
import net.i2p.util.EepGet;
@@ -49,6 +50,26 @@ class AddressBook {
private boolean modified;
private static final boolean DEBUG = false;
private static final int MIN_DEST_LENGTH = 516;
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
/**
* 5-67 chars lower/upper case
*/
private static final Pattern HOST_PATTERN =
Pattern.compile("^[0-9a-zA-Z\\.-]{5,67}$");
/**
* 52 chars lower/upper case
* Always ends in 'a' or 'q'
*/
private static final Pattern B32_PATTERN =
Pattern.compile("^[2-7a-zA-Z]{51}[aAqQ]$");
/** not a complete qualification, just a quick check */
private static final Pattern B64_PATTERN =
Pattern.compile("^[0-9a-zA-Z~-]{" + MIN_DEST_LENGTH + ',' + MAX_DEST_LENGTH + "}={0,2}$");
/**
* Construct an AddressBook from the contents of the Map addresses.
*
@@ -159,8 +180,13 @@ class AddressBook {
* @since 0.8.7
*/
public Iterator<Map.Entry<String, String>> iterator() {
if (this.subFile != null)
return new ConfigIterator(this.subFile);
if (this.subFile != null) {
try {
return new ConfigIterator(this.subFile);
} catch (IOException ioe) {
return new ConfigIterator();
}
}
return this.addresses.entrySet().iterator();
}
@@ -201,9 +227,6 @@ class AddressBook {
return "Map containing " + this.addresses.size() + " entries";
}
private static final int MIN_DEST_LENGTH = 516;
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
/**
* Do basic validation of the hostname
* hostname was already converted to lower case by ConfigParser.parse()
@@ -220,9 +243,10 @@ class AddressBook {
host.indexOf("..") < 0 &&
// IDN - basic check, not complete validation
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
HOST_PATTERN.matcher(host).matches() &&
// Base32 spoofing (52chars.i2p)
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
// We didn't do it this way, we use a .b32.i2p suffix, but let's prohibit it anyway
(! (host.length() == 56 && B32_PATTERN.matcher(host.substring(0,52)).matches())) &&
// ... or maybe we do Base32 this way ...
(! host.equals("b32.i2p")) &&
(! host.endsWith(".b32.i2p")) &&
@@ -246,7 +270,7 @@ class AddressBook {
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
// B64 comes in groups of 2, 3, or 4 chars, but never 1
((dest.length() % 4) != 1) &&
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
B64_PATTERN.matcher(dest).matches()
;
}
@@ -332,4 +356,27 @@ class AddressBook {
protected void finalize() {
delete();
}
/****
public static void main(String[] args) {
String[] tests = { "foo.i2p",
"3bnipzzu67cdq2rcygyxz52xhvy6ylokn4zfrk36ywn6pixmaoza.b32.i2p",
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAAA",
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA==",
"te9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
"(*&(*&(*&(*",
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAA",
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA===",
"!e9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
"x"
};
for (String s : tests) {
test(s);
}
}
public static void test(String s) {
System.out.println(s + " valid host? " + isValidKey(s) + " valid dest? " + isValidDest(s));
}
****/
}

View File

@@ -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,8 @@ import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import net.i2p.data.DataHelper;
/**
* A class to iterate through a hosts.txt or config file without
* reading the whole thing into memory.
@@ -41,7 +44,7 @@ import java.util.NoSuchElementException;
*
* @since 0.8.7
*/
class ConfigIterator implements Iterator<Map.Entry<String, String>> {
class ConfigIterator implements Iterator<Map.Entry<String, String>>, Closeable {
private BufferedReader input;
private ConfigEntry next;
@@ -54,11 +57,9 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
/**
* An iterator over the key/value pairs in the file.
*/
public ConfigIterator(File file) {
try {
public ConfigIterator(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
input = new BufferedReader(new InputStreamReader(fileStream));
} catch (IOException ioe) {}
input = new BufferedReader(new InputStreamReader(fileStream, "UTF-8"));
}
public boolean hasNext() {
@@ -70,7 +71,7 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
String inputLine = input.readLine();
while (inputLine != null) {
inputLine = ConfigParser.stripComments(inputLine);
String[] splitLine = inputLine.split("=");
String[] splitLine = DataHelper.split(inputLine, "=");
if (splitLine.length == 2) {
next = new ConfigEntry(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
return true;

View File

@@ -35,6 +35,7 @@ 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;
@@ -64,10 +65,11 @@ class ConfigParser {
if (inputLine.startsWith(";")) {
return "";
}
if (inputLine.split("#").length > 0) {
return inputLine.split("#")[0];
int hash = inputLine.indexOf('#');
if (hash >= 0) {
return inputLine.substring(0, hash);
} else {
return "";
return inputLine;
}
}
@@ -92,7 +94,7 @@ class ConfigParser {
inputLine = input.readLine();
while (inputLine != null) {
inputLine = stripComments(inputLine);
String[] splitLine = inputLine.split("=");
String[] splitLine = DataHelper.split(inputLine, "=");
if (splitLine.length == 2) {
result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
}
@@ -115,7 +117,7 @@ class ConfigParser {
public static Map<String, String> parse(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
fileStream, "UTF-8"));
Map<String, String> rv = parse(input);
try {
fileStream.close();
@@ -204,7 +206,7 @@ class ConfigParser {
public static List<String> parseSubscriptions(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
fileStream, "UTF-8"));
List<String> rv = parseSubscriptions(input);
try {
fileStream.close();

View File

@@ -25,6 +25,7 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.NamingServiceUpdater;
import net.i2p.util.I2PAppThread;
/**
* A thread that waits five minutes, then runs the addressbook daemon.
@@ -32,7 +33,7 @@ import net.i2p.client.naming.NamingServiceUpdater;
* @author Ragnarok
*
*/
public class DaemonThread extends Thread implements NamingServiceUpdater {
public class DaemonThread extends I2PAppThread implements NamingServiceUpdater {
private String[] args;

View File

@@ -23,8 +23,9 @@ package net.i2p.addressbook;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
/**
@@ -56,8 +57,8 @@ class Log {
public void append(String entry) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(this.file,
true));
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.file,
true), "UTF-8"));
String timestamp = new Date().toString();
bw.write(timestamp + " -- " + entry);
bw.newLine();

View File

@@ -26,6 +26,7 @@ import java.util.Iterator;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.PortMapper;
/**
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
@@ -69,11 +70,14 @@ class SubscriptionIterator implements Iterator<AddressBook> {
* Yes, the EepGet fetch() is done in here in next().
*
* see java.util.Iterator#next()
* @return an AddressBook (empty if the minimum delay has not been met)
* @return non-null AddressBook (empty if the minimum delay has not been met,
* or there is no proxy tunnel, or the fetch otherwise fails)
*/
public AddressBook next() {
Subscription sub = this.subIterator.next();
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now() &&
I2PAppContext.getGlobalContext().portMapper().getPort(PortMapper.SVC_HTTP_PROXY) >= 0 &&
!I2PAppContext.getGlobalContext().getBooleanProperty("i2p.vmCommSystem")) {
//System.err.println("Fetching addressbook from " + sub.getLocation());
return new AddressBook(sub, this.proxyHost, this.proxyPort);
} else {

View 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>

View File

@@ -4,6 +4,15 @@
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<filter>
<filter-name>XSSFilter</filter-name>
<filter-class>net.i2p.servlet.filters.XSSFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XSSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>addressbook</servlet-name>
<servlet-class>net.i2p.addressbook.Servlet</servlet-class>
@@ -19,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>

View File

@@ -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))

View 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,
}

View File

@@ -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

View File

@@ -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?"

View File

@@ -4,14 +4,15 @@
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# Translators:
# testsubject67 <deborinha97@hotmail.com>, 2014
# blueboy, 2013
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-11-23 16:31+0000\n"
"Last-Translator: blueboy\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"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -33,11 +34,11 @@ msgstr "Conectando"
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
msgid "Launch I2P Browser"
msgstr ""
msgstr "Lançar o navegador I2P "
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
msgid "Configure desktopgui"
msgstr ""
msgstr "Configurar desktopgui"
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
msgid "Restart I2P"
@@ -47,10 +48,10 @@ msgstr "Reinicializar o roteador I2P"
msgid "Stop I2P"
msgstr "Interromper o roteador I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr ""
msgstr "Configuração de ícone de bandeja"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"
msgstr ""
msgstr "Ativar ícone de bandeja?"

View File

@@ -3,20 +3,23 @@
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# <gribua@gmail.com>, 2011.
# Translators:
# Denis Lysenko <gribua@gmail.com>, 2011
# LinuxChata, 2014
# madjong <madjong@i2pmail.org>, 2014
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-06-19 14:01+0000\n"
"Last-Translator: Pharmasolin <gribua@gmail.com>\n"
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.net/projects/p/I2P/team/uk_UA/)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-09 19:27+0000\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"
"Language: uk_UA\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=3; plural=(n%10==1 && n%100!=11 ? 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"
@@ -46,12 +49,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 "Настройка трей-іконки"
msgstr "Налаштування трей-іконки"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"
msgstr "Чм повинна трей-іконка бути включена?"
msgstr "Чи повинна трей-іконка бути включена?"

View File

@@ -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.

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -100,15 +100,15 @@
<target name="war" depends="jar, bundle, warUpToDate, listChangedFiles" unless="war.uptodate" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<copy todir="build/icons/.icons" >
<fileset dir="../icons/" />
<copy todir="build/resources/.resources" >
<fileset dir="../resources/" />
</copy>
<!-- mime.properties must be in with the classes -->
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
<war destfile="../i2psnark.war" webxml="../web.xml" >
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
<classes dir="./build/obj" includes="**/web/*" />
<fileset dir="build/icons/" />
<fileset dir="build/resources/" />
<manifest>
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
@@ -121,7 +121,7 @@
<target name="warUpToDate">
<uptodate property="war.uptodate" targetfile="../i2psnark.war" >
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../icons/* ../web.xml" />
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../resources/**/* ../web.xml" />
</uptodate>
</target>

View File

@@ -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

View File

@@ -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.
*

View File

@@ -54,7 +54,15 @@ public interface CompleteListener {
*/
public void gotPiece(Snark snark);
// not really listeners but the easiest way to get back to an optional SnarkManager
/** not really listeners but the easiest way to get back to an optional SnarkManager */
public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark);
/**
* @since 0.9.15
*/
public boolean getSavedPreserveNamesSetting(Snark snark);
/**
* @since 0.9.15
*/
public long getSavedUploaded(Snark snark);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -3,14 +3,18 @@ package org.klomp.snark;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PServerSocket;
@@ -71,9 +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 String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
public static final int MAX_CONNECTIONS = 16; // per torrent
public static final 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";
@@ -95,18 +97,17 @@ public class I2PSnarkUtil {
setI2CPConfig("127.0.0.1", 7654, null);
_banlist = new ConcurrentHashSet<Hash>();
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
_maxUpBW = DEFAULT_MAX_UP_BW;
_maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW;
_maxConnections = MAX_CONNECTIONS;
_startupDelay = DEFAULT_STARTUP_DELAY;
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
// FIXME split if default has more than one
_openTrackers = Collections.singletonList(DEFAULT_OPENTRACKERS);
_openTrackers = Collections.emptyList();
_shouldUseDHT = DEFAULT_USE_DHT;
// This is used for both announce replies and .torrent file downloads,
// so it must be available even if not connected to I2CP.
// so much for multiple instances
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
FileUtil.rmdir(_tmpDir, false);
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName + '-' + ctx.random().nextInt());
//FileUtil.rmdir(_tmpDir, false);
_tmpDir.mkdirs();
}
@@ -135,6 +136,7 @@ public class I2PSnarkUtil {
public boolean configured() { return _configured; }
@SuppressWarnings({"unchecked", "rawtypes"})
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
if (i2cpHost != null)
_i2cpHost = i2cpHost;
@@ -255,6 +257,8 @@ public class I2PSnarkUtil {
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
if (opts.getProperty("i2p.streaming.answerPings") == null)
opts.setProperty("i2p.streaming.answerPings", "false");
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
_connecting = false;
}
@@ -328,7 +332,7 @@ public class I2PSnarkUtil {
return rv;
} catch (I2PException ie) {
_banlist.add(dest);
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
_context.simpleTimer2().addEvent(new Unbanlist(dest), 10*60*1000);
IOException ioe = new IOException("Unable to reach the peer " + peer);
ioe.initCause(ie);
throw ioe;
@@ -456,7 +460,7 @@ public class I2PSnarkUtil {
return null;
}
String getOurIPString() {
public String getOurIPString() {
Destination dest = getMyDestination();
if (dest != null)
return dest.toBase64();
@@ -565,12 +569,12 @@ public class I2PSnarkUtil {
return rv;
}
/** @param ot non-null */
/** @param ot non-null list of announce URLs */
public void setOpenTrackers(List<String> ot) {
_openTrackers = ot;
}
/** List of open trackers to use as backups
/** List of open tracker announce URLs to use as backups
* @return non-null, possibly unmodifiable, empty if disabled
*/
public List<String> getOpenTrackers() {
@@ -580,7 +584,22 @@ public class I2PSnarkUtil {
}
/**
* List of open trackers to use as backups even if disabled
* Is this announce URL probably for an open tracker?
*
* @since 0.9.17
*/
public boolean isKnownOpenTracker(String url) {
try {
URI u = new URI(url);
String host = u.getHost();
return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
} catch (URISyntaxException use) {
return false;
}
}
/**
* List of open tracker announce URLs to use as backups even if disabled
* @return non-null
* @since 0.9.4
*/
@@ -642,7 +661,7 @@ public class I2PSnarkUtil {
* The {0} will be replaced by the parameter.
* Single quotes must be doubled, i.e. ' -> '' in the string.
* @param o parameter, not translated.
* To tranlslate parameter also, use _("foo {0} bar", _("baz"))
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
* Do not double the single quotes in the parameter.
* Use autoboxing to call with ints, longs, floats, etc.
*/

View File

@@ -29,6 +29,9 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
private int _consec;
private int _consecNotRunning;
private boolean _isIdle;
private String _lastIn = "3";
private String _lastOut = "3";
private final Object _lock = new Object();
private static final long CHECK_TIME = 63*1000;
private static final int MAX_CONSEC_IDLE = 4;
@@ -46,16 +49,19 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
}
public void timeReached() {
synchronized (_lock) {
locked_timeReached();
}
}
private void locked_timeReached() {
if (_util.connected()) {
boolean torrentRunning = false;
boolean hasPeers = false;
int peerCount = 0;
for (PeerCoordinator pc : _pcs) {
if (!pc.halted()) {
torrentRunning = true;
if (pc.getPeers() > 0) {
hasPeers = true;
break;
}
peerCount += pc.getPeers();
}
}
@@ -66,25 +72,29 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
if (_log.shouldLog(Log.WARN))
_log.warn("Closing tunnels on idle");
_util.disconnect();
_mgr.addMessage(_util.getString("I2P tunnel closed."));
_mgr.addMessage(_util.getString("No more torrents running.") + ' ' +
_util.getString("I2P tunnel closed."));
schedule(3 * CHECK_TIME);
return;
}
}
if (hasPeers) {
if (_isIdle)
restoreTunnels();
if (peerCount > 0) {
restoreTunnels(peerCount);
} else {
if (!_isIdle) {
if (_consec++ >= MAX_CONSEC_IDLE)
reduceTunnels();
else
restoreTunnels(1); // pretend we have one peer for now
}
}
} else {
_isIdle = false;
_consec = 0;
_consecNotRunning = 0;
_lastIn = "3";
_lastOut = "3";
}
schedule(CHECK_TIME);
}
@@ -100,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);
}
/**
@@ -131,12 +165,16 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
if (mgr != null) {
I2PSession sess = mgr.getSession();
if (sess != null) {
if (_log.shouldLog(Log.INFO))
_log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob);
Properties newProps = new Properties();
newProps.setProperty("inbound.quantity", i);
newProps.setProperty("outbound.quantity", o);
newProps.setProperty("inbound.backupQuantity", ib);
newProps.setProperty("outbound.backupQuantity", ob);
sess.updateOptions(newProps);
_lastIn = i;
_lastOut = o;
}
}
}

View File

@@ -42,7 +42,7 @@ public class MagnetURI {
name = util.getString("Magnet") + ' ' + ihash;
String dn = getParam("dn", url);
if (dn != null)
name += " (" + Storage.filterName(dn) + ')';
name += " (" + dn + ')';
} else if (url.startsWith(MAGGOT)) {
// maggot://0691e40aae02e552cfcb57af1dca56214680c0c5:0b557bbdf8718e95d352fbe994dec3a383e2ede7
ihash = url.substring(MAGGOT.length()).trim();
@@ -82,7 +82,7 @@ public class MagnetURI {
}
/**
* @return pretty name or null
* @return pretty name or null, NOT HTML escaped
*/
public String getName() {
return _name;
@@ -175,18 +175,25 @@ public class MagnetURI {
}
/**
* Decode %xx encoding, convert to UTF-8 if necessary
* Copied from i2ptunnel LocalHTTPServer
* Decode %xx encoding, convert to UTF-8 if necessary.
* Copied from i2ptunnel LocalHTTPServer.
* Also converts '+' to ' ' so the dn parameter comes out right
* These are coming in via a application/x-www-form-urlencoded form so
* the pluses are in there...
* hopefully any real + is encoded as %2B.
*
* @since 0.9.1
*/
private static String decode(String s) {
if (!s.contains("%"))
if (!(s.contains("%") || s.contains("+")))
return s;
StringBuilder buf = new StringBuilder(s.length());
boolean utf8 = false;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '%') {
if (c == '+') {
buf.append(' ');
} else if (c != '%') {
buf.append(c);
} else {
try {

View File

@@ -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 + ')';
}
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -57,12 +57,12 @@ public class Peer implements Comparable<Peer>
private DataOutputStream dout;
/** running counters */
private long downloaded;
private long uploaded;
private final AtomicLong downloaded = new AtomicLong();
private final AtomicLong uploaded = new AtomicLong();
// Keeps state for in/out connections. Non-null when the handshake
// was successful, the connection setup and runs
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;
/**
@@ -217,9 +217,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 +277,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 +291,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 +332,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 +382,15 @@ public class Peer implements Comparable<Peer>
if (options != 0) {
// send them something in runConnection() above
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
_log.debug("Peer supports options 0x" + Long.toHexString(options) + ": " + toString());
}
return bs;
}
/** @since 0.8.4 */
public long getOptions() {
return options;
/** @since 0.9.21 */
public boolean supportsFast() {
return (options & OPTION_FAST) != 0;
}
/** @since 0.8.4 */
@@ -618,7 +615,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void downloaded(int size) {
downloaded += size;
downloaded.addAndGet(size);
}
/**
@@ -626,7 +623,7 @@ public class Peer implements Comparable<Peer>
* @since 0.8.4
*/
public void uploaded(int size) {
uploaded += size;
uploaded.addAndGet(size);
}
/**
@@ -635,7 +632,7 @@ public class Peer implements Comparable<Peer>
*/
public long getDownloaded()
{
return downloaded;
return downloaded.get();
}
/**
@@ -644,7 +641,7 @@ public class Peer implements Comparable<Peer>
*/
public long getUploaded()
{
return uploaded;
return uploaded.get();
}
/**
@@ -652,8 +649,8 @@ public class Peer implements Comparable<Peer>
*/
public void resetCounters()
{
downloaded = 0;
uploaded = 0;
downloaded.set(0);
uploaded.set(0);
}
public long getInactiveTime() {

View File

@@ -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();
}

View File

@@ -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
{

View File

@@ -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);
}
}

View File

@@ -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,15 @@ class PeerCoordinator implements PeerListener
*/
public long getUploaded()
{
return uploaded;
return uploaded.get();
}
/**
* Sets the initial total of uploaded bytes of all peers (from a saved status)
* @since 0.9.15
*/
public void setUploaded(long up) {
uploaded.set(up);
}
/**
@@ -286,7 +296,7 @@ class PeerCoordinator implements PeerListener
*/
public long getDownloaded()
{
return downloaded;
return downloaded.get();
}
/**
@@ -312,16 +322,22 @@ class PeerCoordinator implements PeerListener
*/
public long getDownloadRate()
{
if (halted)
return 0;
return getRate(downloaded_old);
}
public long getUploadRate()
{
if (halted)
return 0;
return getRate(uploaded_old);
}
public long getCurrentUploadRate()
{
if (halted)
return 0;
// no need to synchronize, only one value
long r = uploaded_old[0];
if (r <= 0)
@@ -387,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)
@@ -514,7 +530,10 @@ class PeerCoordinator implements PeerListener
// Can't add to beginning since we converted from a List to a Queue
// We can do this in Java 6 with a Deque
//peers.add(0, peer);
peers.add(peer);
if (_util.getContext().random().nextInt(4) == 0)
peers.push(peer);
else
peers.add(peer);
peerCount = peers.size();
unchokePeer();
@@ -585,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();
@@ -901,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)
@@ -932,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);
@@ -943,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);
@@ -964,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))
{
@@ -981,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))
{
@@ -991,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);
@@ -1122,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)
@@ -1202,22 +1237,23 @@ class PeerCoordinator implements PeerListener
boolean skipped = false;
for(Piece piece : wantedPieces) {
if (piece.getId() == savedPiece) {
if (peer.isCompleted() && piece.getPeerCount() > 1) {
if (peer.isCompleted() && piece.getPeerCount() > 1 &&
wantedPieces.size() > 2*END_GAME_THRESHOLD) {
// Try to preserve rarest-first
// by not requesting a partial piece that non-seeders also have
// by not requesting a partial piece that at least two non-seeders also have
// from a seeder
boolean nonSeeds = false;
int nonSeeds = 0;
for (Peer pr : peers) {
PeerState state = pr.state;
if (state == null) continue;
BitField bf = state.bitfield;
if (bf == null) continue;
if (bf.get(savedPiece) && !pr.isCompleted()) {
nonSeeds = true;
break;
if (++nonSeeds > 1)
break;
}
}
if (nonSeeds) {
if (nonSeeds > 1) {
skipped = true;
break;
}
@@ -1452,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;

View File

@@ -21,6 +21,7 @@
package org.klomp.snark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -155,15 +156,28 @@ class PeerState implements DataLoader
setInteresting(true);
}
void bitfieldMessage(byte[] bitmap)
{
synchronized(this)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv bitfield");
void bitfieldMessage(byte[] bitmap) {
bitfieldMessage(bitmap, false);
}
/**
* @param bitmap null to use the isAll param
* @param isAll only if bitmap == null: true for have_all, false for have_none
* @since 0.9.21
*/
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
if (_log.shouldLog(Log.DEBUG)) {
if (bitmap != null)
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
else if (isAll)
_log.debug(peer + " rcv bitfield HAVE_ALL");
else
_log.debug(peer + " rcv bitfield HAVE_NONE");
}
synchronized(this) {
if (bitfield != null)
{
// XXX - Be liberal in what you except?
// XXX - Be liberal in what you accept?
if (_log.shouldLog(Log.WARN))
_log.warn("Got unexpected bitfield message from " + peer);
return;
@@ -172,10 +186,24 @@ class PeerState implements DataLoader
// XXX - Check for weird bitfield and disconnect?
// FIXME will have to regenerate the bitfield after we know exactly
// how many pieces there are, as we don't know how many spare bits there are.
if (metainfo == null)
bitfield = new BitField(bitmap, bitmap.length * 8);
else
bitfield = new BitField(bitmap, metainfo.getPieces());
if (metainfo == null) {
if (bitmap != null) {
bitfield = new BitField(bitmap, bitmap.length * 8);
} else {
// we can't handle this situation
if (_log.shouldLog(Log.WARN))
_log.warn("have_x w/o metainfo: " + isAll);
return;
}
} else {
if (bitmap != null) {
bitfield = new BitField(bitmap, metainfo.getPieces());
} else {
bitfield = new BitField(metainfo.getPieces());
if (isAll)
bitfield.setAll();
}
}
}
if (metainfo == null)
return;
@@ -198,14 +226,21 @@ class PeerState implements DataLoader
+ piece + ", " + begin + ", " + length + ") ");
if (metainfo == null)
return;
if (choking)
{
if (_log.shouldLog(Log.INFO))
_log.info("Request received, but choking " + peer);
if (choking) {
if (peer.supportsFast()) {
if (_log.shouldInfo())
_log.info("Request received, sending reject to choked " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldInfo())
_log.info("Request received, but choking " + peer);
}
return;
}
}
// Sanity check
// There is no check here that we actually have the piece;
// this will be caught in loadData() below
if (piece < 0
|| piece >= metainfo.getPieces()
|| begin < 0
@@ -219,6 +254,8 @@ class PeerState implements DataLoader
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return;
}
@@ -227,8 +264,14 @@ class PeerState implements DataLoader
// Todo: limit number of requests also? (robert 64 x 4KB)
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Discarding request over pipeline limit from " + peer);
if (peer.supportsFast()) {
if (_log.shouldWarn())
_log.warn("Rejecting request over pipeline limit from " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldWarn())
_log.warn("Discarding request over pipeline limit from " + peer);
}
return;
}
@@ -243,7 +286,8 @@ class PeerState implements DataLoader
/**
* This is the callback that PeerConnectionOut calls
*
* @return bytes or null for errors
* @return bytes or null for errors such as not having the piece yet
* @throws RuntimeException on IOE getting the data
* @since 0.8.2
*/
public ByteArray loadData(int piece, int begin, int length) {
@@ -253,6 +297,8 @@ class PeerState implements DataLoader
// XXX - Protocol error-> diconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got request for unknown piece: " + piece);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
@@ -265,6 +311,8 @@ class PeerState implements DataLoader
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
@@ -322,6 +370,11 @@ class PeerState implements DataLoader
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
synchronized(this) {
// so we don't ask again
if (bitfield != null)
bitfield.clear(req.getPiece());
}
}
}
@@ -455,7 +508,12 @@ class PeerState implements DataLoader
for (Integer p : pcs) {
Request req = getLowestOutstandingRequest(p.intValue());
if (req != null) {
req.getPartialPiece().setDownloaded(req.off);
PartialPiece pp = req.getPartialPiece();
synchronized(pp) {
int dl = pp.getDownloaded();
if (req.off != dl)
req = new Request(pp, dl, 1);
}
rv.add(req);
}
}
@@ -536,6 +594,89 @@ class PeerState implements DataLoader
listener.gotPort(peer, port, port + 1);
}
/////////// fast message handlers /////////
/**
* BEP 6
* Treated as "have" for now
* @since 0.9.21
*/
void suggestMessage(int piece) {
if (_log.shouldInfo())
_log.info("Handling suggest as have(" + piece + ") from " + peer);
haveMessage(piece);
}
/**
* BEP 6
* @param isAll true for have_all, false for have_none
* @since 0.9.21
*/
void haveMessage(boolean isAll) {
bitfieldMessage(null, isAll);
}
/**
* BEP 6
* If the peer rejects lower chunks but not higher ones, thus creating holes,
* we won't figure it out and the piece will fail, since we don't currently
* keep a chunk bitmap in PartialPiece.
* As long as the peer rejects all the chunks, or rejects only the last chunks,
* no holes are created and we will be fine. The reject messages may be in any order,
* just don't make a hole when it's over.
*
* @since 0.9.21
*/
void rejectMessage(int piece, int begin, int length) {
if (_log.shouldInfo())
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
out.cancelRequest(piece, begin, length);
synchronized(this) {
Request deletedRequest = null;
// for this piece only
boolean haveMoreRequests = false;
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
Request req = iter.next();
if (req.getPiece() == piece) {
if (req.off == begin && req.len == length) {
iter.remove();
deletedRequest = req;
} else {
haveMoreRequests = true;
}
}
}
if (deletedRequest != null && !haveMoreRequests) {
// We must return the piece to the coordinator
// Create a new fake request so we can set the offset correctly
PartialPiece pp = deletedRequest.getPartialPiece();
int downloaded = pp.getDownloaded();
Request req;
if (deletedRequest.off == downloaded)
req = deletedRequest;
else
req = new Request(pp, downloaded, 1);
List<Request> pcs = Collections.singletonList(req);
listener.savePartialPieces(this.peer, pcs);
if (_log.shouldWarn())
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
}
if (lastRequest != null && lastRequest.getPiece() == piece &&
lastRequest.off == begin && lastRequest.len == length)
lastRequest = null;
}
}
/**
* BEP 6
* Ignored for now
* @since 0.9.21
*/
void allowedFastMessage(int piece) {
if (_log.shouldInfo())
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
}
void unknownMessage(int type, byte[] bs)
{
if (_log.shouldLog(Log.WARN))
@@ -543,6 +684,8 @@ class PeerState implements DataLoader
+ " length: " + bs.length);
}
/////////// end message handlers /////////
/**
* We now have this piece.
* Tell the peer and cancel any requests for the piece.

View File

@@ -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());
}
/**

View File

@@ -27,13 +27,13 @@ import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
/**
* Main Snark program startup class.
@@ -221,7 +221,7 @@ public class Snark
private PeerCoordinator coordinator;
private ConnectionAcceptor acceptor;
private TrackerClient trackerclient;
private String rootDataDir = ".";
private final File rootDataDir;
private final CompleteListener completeListener;
private volatile boolean stopped;
private volatile boolean starting;
@@ -236,15 +236,27 @@ public class Snark
private volatile boolean _autoStoppable;
// String indicating main activity
private volatile String activity = "Not started";
private final long savedUploaded;
/** from main() via parseArguments() single torrent */
/**
* from main() via parseArguments() single torrent
*
* @deprecated unused
*/
/****
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
}
****/
/** single torrent - via router */
/**
* single torrent - via router
*
* @deprecated unused
*/
/****
public Snark(I2PAppContext ctx, Properties opts, String torrent,
StorageListener slistener, boolean start, String rootDir) {
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
@@ -274,12 +286,32 @@ public class Snark
if (start)
this.startTorrent();
}
****/
/** multitorrent */
/**
* multitorrent
* @throws RuntimeException via fatal()
*/
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener,
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
{
this(util, torrent, ip, user_port, slistener, clistener, complistener,
peerCoordinatorSet, connectionAcceptor, start, rootDir, null);
}
/**
* multitorrent
*
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
* @throws RuntimeException via fatal()
* @since 0.9.11
*/
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener,
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir, File baseFile)
{
if (slistener == null)
slistener = this;
@@ -291,7 +323,7 @@ public class Snark
acceptor = connectionAcceptor;
this.torrent = torrent;
this.rootDataDir = rootDir;
this.rootDataDir = new File(rootDir);
stopped = true;
activity = "Network setup";
@@ -394,13 +426,22 @@ public class Snark
try
{
activity = "Checking storage";
storage = new Storage(_util, meta, slistener);
boolean shouldPreserve = completeListener != null && completeListener.getSavedPreserveNamesSetting(this);
if (baseFile == null) {
String base = meta.getName();
if (!shouldPreserve)
base = Storage.filterName(base);
if (_util.getFilesPublic())
baseFile = new File(rootDataDir, base);
else
baseFile = new SecureFile(rootDataDir, base);
}
storage = new Storage(_util, baseFile, meta, slistener, shouldPreserve);
if (completeListener != null) {
storage.check(rootDataDir,
completeListener.getSavedTorrentTime(this),
storage.check(completeListener.getSavedTorrentTime(this),
completeListener.getSavedTorrentBitField(this));
} else {
storage.check(rootDataDir);
storage.check();
}
// have to figure out when to reopen
// if (!start)
@@ -428,6 +469,7 @@ public class Snark
trackerclient = new TrackerClient(meta, coordinator);
*/
savedUploaded = (completeListener != null) ? completeListener.getSavedUploaded(this) : 0;
if (start)
startTorrent();
}
@@ -438,6 +480,7 @@ public class Snark
* @param torrent a fake name for now (not a file name)
* @param ih 20-byte info hash
* @param trackerURL may be null
* @throws RuntimeException via fatal()
* @since 0.8.4
*/
public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL,
@@ -452,7 +495,8 @@ public class Snark
this.torrent = torrent;
this.infoHash = ih;
this.additionalTrackerURL = trackerURL;
this.rootDataDir = rootDir;
this.rootDataDir = rootDir != null ? new File(rootDir) : null; // null only for FetchAndAdd extension
savedUploaded = 0;
stopped = true;
id = generateID();
@@ -477,24 +521,21 @@ public class Snark
// Create a new ID and fill it with something random. First nine
// zeros bytes, then three bytes filled with snark and then
// sixteen random bytes.
// eight random bytes.
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
byte[] rv = new byte[20];
Random random = I2PAppContext.getGlobalContext().random();
int i;
for (i = 0; i < 9; i++)
rv[i] = 0;
rv[i++] = snark;
rv[i++] = snark;
rv[i++] = snark;
while (i < 20)
rv[i++] = (byte)random.nextInt(256);
rv[9] = snark;
rv[10] = snark;
rv[11] = snark;
I2PAppContext.getGlobalContext().random().nextBytes(rv, 12, 8);
return rv;
}
/**
* Start up contacting peers and querying the tracker.
* Blocks if tunnel is not yet open.
*
* @throws RuntimeException via fatal()
*/
public synchronized void startTorrent() {
starting = true;
@@ -521,6 +562,7 @@ public class Snark
_log.info("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient");
activity = "Collecting pieces";
coordinator = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
coordinator.setUploaded(savedUploaded);
if (_peerCoordinatorSet != null) {
// multitorrent
_peerCoordinatorSet.add(coordinator);
@@ -547,7 +589,7 @@ public class Snark
} else if (trackerclient.halted()) {
if (storage != null) {
try {
storage.reopen(rootDataDir);
storage.reopen();
} catch (IOException ioe) {
try { storage.close(); } catch (IOException ioee) {
ioee.printStackTrace();
@@ -575,7 +617,6 @@ public class Snark
* @since 0.9.1
*/
public synchronized void stopTorrent(boolean fast) {
stopped = true;
TrackerClient tc = trackerclient;
if (tc != null)
tc.halt(fast);
@@ -583,17 +624,28 @@ public class Snark
if (pc != null)
pc.halt();
Storage st = storage;
if (!fast)
// HACK: Needed a way to distinguish between user-stop and
// shutdown-stop. stopTorrent(true) is in stopAllTorrents().
// (#766)
stopped = true;
if (st != null) {
boolean changed = storage.isChanged();
// TODO: Cache the config-in-mem to compare vs config-on-disk
// (needed for auto-save to not double-save in some cases)
//boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
boolean changed = true;
if (changed && completeListener != null)
completeListener.updateStatus(this);
try {
storage.close();
} catch (IOException ioe) {
System.out.println("Error closing " + torrent);
ioe.printStackTrace();
}
if (changed && completeListener != null)
completeListener.updateStatus(this);
}
if (fast)
// HACK: See above if(!fast)
stopped = true;
if (pc != null && _peerCoordinatorSet != null)
_peerCoordinatorSet.remove(pc);
if (_peerCoordinatorSet == null)
@@ -693,6 +745,18 @@ public class Snark
return storage != null && storage.isChecking();
}
/**
* If checking is in progress, return completion 0.0 ... 1.0,
* else return 1.0.
* @since 0.9.23
*/
public double getCheckingProgress() {
if (storage != null && storage.isChecking())
return storage.getCheckingProgress();
else
return 1.0d;
}
/**
* Disk allocation (ballooning) in progress.
* @since 0.9.3
@@ -738,7 +802,7 @@ public class Snark
PeerCoordinator coord = coordinator;
if (coord != null)
return coord.getUploaded();
return 0;
return savedUploaded;
}
/**
@@ -762,6 +826,7 @@ public class Snark
}
/**
* Not HTML escaped.
* @return String returned from tracker, or null if no error
* @since 0.8.4
*/
@@ -834,7 +899,7 @@ public class Snark
}
/**
* Bytes still wanted. DOES account for skipped files.
* Bytes still wanted. DOES account for (i.e. does not include) skipped files.
* FIXME -1 when not running.
* @return exact value. or -1 if no storage yet or when not running.
* @since 0.9.1
@@ -847,7 +912,31 @@ public class Snark
}
/**
* Does not account for skipped files.
* 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
* @since 0.8.4
*/
@@ -918,6 +1007,7 @@ public class Snark
* non-valid argument list. The given listeners will be
* passed to all components that take one.
*/
/****
private static Snark parseArguments(String[] args,
StorageListener slistener,
CoordinatorListener clistener)
@@ -932,6 +1022,7 @@ public class Snark
int i = 0;
while (i < args.length)
{
****/
/*
if (args[i].equals("--debug"))
{
@@ -953,7 +1044,9 @@ public class Snark
catch (NumberFormatException nfe) { }
}
}
else */ if (args[i].equals("--port"))
else */
/****
if (args[i].equals("--port"))
{
if (args.length - 1 < i + 1)
usage("--port needs port number to listen on");
@@ -1059,6 +1152,7 @@ public class Snark
System.out.println
(" \tor (with --share) a file to share.");
}
****/
/**
* Aborts program abnormally.
@@ -1102,9 +1196,15 @@ public class Snark
*/
public void gotMetaInfo(PeerCoordinator coordinator, MetaInfo metainfo) {
try {
String base = Storage.filterName(metainfo.getName());
File baseFile;
if (_util.getFilesPublic())
baseFile = new File(rootDataDir, base);
else
baseFile = new SecureFile(rootDataDir, base);
// The following two may throw IOE...
storage = new Storage(_util, metainfo, this);
storage.check(rootDataDir);
storage = new Storage(_util, baseFile, metainfo, this, false);
storage.check();
// ... so don't set meta until here
meta = metainfo;
if (completeListener != null) {
@@ -1200,7 +1300,8 @@ public class Snark
public void setWantedPieces(Storage storage)
{
coordinator.setWantedPieces();
if (coordinator != null)
coordinator.setWantedPieces();
}
///////////// End StorageListener methods
@@ -1209,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);
}
@@ -1228,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)
@@ -1239,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

View File

@@ -20,6 +20,7 @@
package org.klomp.snark;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,10 +33,14 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA1;
import net.i2p.data.ByteArray;
@@ -48,10 +53,11 @@ import net.i2p.util.SystemVersion;
/**
* Maintains pieces on disk. Can be used to store and retrieve pieces.
*/
public class Storage
public class Storage implements Closeable
{
private final MetaInfo metainfo;
private final List<TorrentFile> _torrentFiles;
private final File _base;
private final StorageListener listener;
private final I2PSnarkUtil _util;
private final Log _log;
@@ -63,35 +69,42 @@ public class Storage
private final int piece_size;
private final int pieces;
private final long total_length;
private final boolean _preserveFileNames;
private boolean changed;
private volatile boolean _isChecking;
private final AtomicInteger _allocateCount = new AtomicInteger();
private final AtomicInteger _checkProgress = new AtomicInteger();
/** The default piece size. */
private static final int DEFAULT_PIECE_SIZE = 256*1024;
/** bigger than this will be rejected */
public static final int MAX_PIECE_SIZE = 4*1024*1024;
public static final int MAX_PIECE_SIZE = 16*1024*1024;
/** The maximum number of pieces in a torrent. */
public static final int MAX_PIECES = 10*1024;
public static final int MAX_PIECES = 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);
/**
* Creates a new storage based on the supplied MetaInfo. This will
* Creates a new storage based on the supplied MetaInfo.
*
* Does not check storage. Caller MUST call check(), which will
* try to create and/or check all needed files in the MetaInfo.
*
* Does not check storage. Caller MUST call check()
* @param baseFile the torrent data file or dir
* @param preserveFileNames if true, do not remap names to a 'safe' charset
*/
public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
public Storage(I2PSnarkUtil util, File baseFile, MetaInfo metainfo, StorageListener listener, boolean preserveFileNames)
{
_util = util;
_log = util.getContext().logManager().getLog(Storage.class);
_base = baseFile;
this.metainfo = metainfo;
this.listener = listener;
needed = metainfo.getPieces();
@@ -102,6 +115,7 @@ public class Storage
List<List<String>> files = metainfo.getFiles();
int sz = files != null ? files.size() : 1;
_torrentFiles = new ArrayList<TorrentFile>(sz);
_preserveFileNames = preserveFileNames;
}
/**
@@ -113,16 +127,20 @@ public class Storage
*
* @param announce may be null
* @param listener may be null
* @param created_by may be null
* @throws IOException when creating and/or checking files fails.
*/
public Storage(I2PSnarkUtil util, File baseFile, String announce,
List<List<String>> announce_list,
String created_by,
boolean privateTorrent, StorageListener listener)
throws IOException
{
_util = util;
_base = baseFile;
_log = util.getContext().logManager().getLog(Storage.class);
this.listener = listener;
_preserveFileNames = true;
// Create names, rafs and lengths arrays.
_torrentFiles = getFiles(baseFile);
@@ -148,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;
@@ -183,7 +201,7 @@ public class Storage
byte[] piece_hashes = fast_digestCreate();
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
lengthsList, piece_size, piece_hashes, total, privateTorrent,
announce_list);
announce_list, created_by);
}
@@ -295,6 +313,18 @@ public class Storage
return _isChecking;
}
/**
* If checking is in progress, return completion 0.0 ... 1.0,
* else return 1.0.
* @since 0.9.23
*/
public double getCheckingProgress() {
if (_isChecking)
return _checkProgress.get() / (double) pieces;
else
return 1.0d;
}
/**
* Disk allocation (ballooning) in progress.
* Always false on Windows.
@@ -305,39 +335,48 @@ public class Storage
}
/**
* @param file canonical path (non-directory)
* Get index to pass to remaining(), getPriority(), setPriority()
*
* @param file non-canonical path (non-directory)
* @return internal index of file; -1 if unknown file
* @since 0.9.15
*/
public int indexOf(File file) {
for (int i = 0; i < _torrentFiles.size(); i++) {
File f = _torrentFiles.get(i).RAFfile;
if (f.equals(file))
return i;
}
return -1;
}
/**
* @param fileIndex as obtained from indexOf
* @return number of bytes remaining; -1 if unknown file
* @since 0.7.14
*/
public long remaining(String file) {
/****
public long remaining(int fileIndex) {
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return -1;
if (complete())
return 0;
long bytes = 0;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
// use canonical in case snark dir or sub dirs are symlinked
String canonical = null;
if (f != null) {
try {
canonical = f.getCanonicalPath();
} catch (IOException ioe) {
f = null;
}
}
if (f != null && canonical.equals(file)) {
if (complete())
return 0;
int psz = piece_size;
for (int i = 0; i < _torrentFiles.size(); i++) {
TorrentFile tf = _torrentFiles.get(i);
if (i == fileIndex) {
long start = bytes;
long end = start + tf.length;
int pc = (int) (bytes / psz);
int pc = (int) (bytes / piece_size);
long rv = 0;
if (!bitfield.get(pc))
rv = Math.min(psz - (start % psz), tf.length);
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
rv = Math.min(piece_size - (start % piece_size), tf.length);
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
if (!bitfield.get(j)) {
if (((long)(j+1))*psz < end)
rv += psz;
if (((long)(j+1))*piece_size < end)
rv += piece_size;
else
rv += end - (((long)j) * psz);
rv += end - (((long)j) * piece_size);
}
}
return rv;
@@ -346,51 +385,66 @@ public class Storage
}
return -1;
}
****/
/**
* @param file canonical path (non-directory)
* For efficiency, calculate remaining bytes for all files at once
*
* @return number of bytes remaining for each file, use indexOf() to get index for a file
* @since 0.9.23
*/
public long[] remaining() {
long[] rv = new long[_torrentFiles.size()];
if (complete())
return rv;
long bytes = 0;
for (int i = 0; i < _torrentFiles.size(); i++) {
TorrentFile tf = _torrentFiles.get(i);
long start = bytes;
long end = start + tf.length;
int pc = (int) (bytes / piece_size);
long rvi = 0;
if (!bitfield.get(pc))
rvi = Math.min(piece_size - (start % piece_size), tf.length);
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
if (!bitfield.get(j)) {
if (((long)(j+1))*piece_size < end)
rvi += piece_size;
else
rvi += end - (((long)j) * piece_size);
}
}
rv[i] = rvi;
bytes += tf.length;
}
return rv;
}
/**
* @param fileIndex as obtained from indexOf
* @since 0.8.1
*/
public int getPriority(String file) {
public int getPriority(int fileIndex) {
if (complete() || metainfo.getFiles() == null)
return 0;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
// use canonical in case snark dir or sub dirs are symlinked
if (f != null) {
try {
String canonical = f.getCanonicalPath();
if (canonical.equals(file))
return tf.priority;
} catch (IOException ioe) {}
}
}
return 0;
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return 0;
return _torrentFiles.get(fileIndex).priority;
}
/**
* Must call Snark.updatePiecePriorities()
* (which calls getPiecePriorities()) after calling this.
* @param file canonical path (non-directory)
* @param fileIndex as obtained from indexOf
* @param pri default 0; <0 to disable
* @since 0.8.1
*/
public void setPriority(String file, int pri) {
public void setPriority(int fileIndex, int pri) {
if (complete() || metainfo.getFiles() == null)
return;
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
// use canonical in case snark dir or sub dirs are symlinked
if (f != null) {
try {
String canonical = f.getCanonicalPath();
if (canonical.equals(file)) {
tf.priority = pri;
return;
}
} catch (IOException ioe) {}
}
}
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return;
_torrentFiles.get(fileIndex).priority = pri;
}
/**
@@ -448,9 +502,8 @@ public class Storage
int file = 0;
long pcEnd = -1;
long fileEnd = _torrentFiles.get(0).length - 1;
int psz = piece_size;
for (int i = 0; i < rv.length; i++) {
pcEnd += psz;
pcEnd += piece_size;
int pri = _torrentFiles.get(file).priority;
while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) {
file++;
@@ -465,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.
@@ -483,31 +561,37 @@ public class Storage
* @since 0.7.14
*/
public String getBaseName() {
return filterName(metainfo.getName());
return optFilterName(metainfo.getName());
}
/** @since 0.9.15 */
public boolean getPreserveFileNames() {
return _preserveFileNames;
}
/**
* Creates (and/or checks) all files from the metainfo file list.
* Only call this once, and only after the constructor with the metainfo.
* Use recheck() to check again later.
*
* @throws IllegalStateException if called more than once
*/
public void check(String rootDir) throws IOException
public void check() throws IOException
{
check(rootDir, 0, null);
check(0, null);
}
/**
* Creates (and/or checks) all files from the metainfo file list.
* Use a saved bitfield and timestamp from a config file.
* Only call this once, and only after the constructor with the metainfo.
* Use recheck() to check again later.
*
* @throws IllegalStateException if called more than once
*/
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
public void check(long savedTime, BitField savedBitField) throws IOException
{
File base;
boolean areFilesPublic = _util.getFilesPublic();
if (areFilesPublic)
base = new File(rootDir, filterName(metainfo.getName()));
else
base = new SecureFile(rootDir, filterName(metainfo.getName()));
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
if (!_torrentFiles.isEmpty())
@@ -517,18 +601,18 @@ public class Storage
{
// Create base as file.
if (_log.shouldLog(Log.INFO))
_log.info("Creating/Checking file: " + base);
_log.info("Creating/Checking file: " + _base);
// createNewFile() can throw a "Permission denied" IOE even if the file exists???
// so do it second
if (!base.exists() && !base.createNewFile())
throw new IOException("Could not create file " + base);
if (!_base.exists() && !_base.createNewFile())
throw new IOException("Could not create file " + _base);
_torrentFiles.add(new TorrentFile(base, base, metainfo.getTotalLength()));
_torrentFiles.add(new TorrentFile(_base, _base, metainfo.getTotalLength()));
if (useSavedBitField) {
long lm = base.lastModified();
long lm = _base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
else if (base.length() != metainfo.getTotalLength())
else if (_base.length() != metainfo.getTotalLength())
useSavedBitField = false;
}
}
@@ -536,9 +620,9 @@ public class Storage
{
// Create base as dir.
if (_log.shouldLog(Log.INFO))
_log.info("Creating/Checking directory: " + base);
if (!base.mkdir() && !base.isDirectory())
throw new IOException("Could not create directory " + base);
_log.info("Creating/Checking directory: " + _base);
if (!_base.mkdir() && !_base.isDirectory())
throw new IOException("Could not create directory " + _base);
List<Long> ls = metainfo.getLengths();
int size = files.size();
@@ -546,7 +630,7 @@ public class Storage
for (int i = 0; i < size; i++)
{
List<String> path = files.get(i);
File f = createFileFromNames(base, path, areFilesPublic);
File f = createFileFromNames(_base, path, areFilesPublic);
// dup file name check after filtering
for (int j = 0; j < i; j++) {
if (f.equals(_torrentFiles.get(j).RAFfile)) {
@@ -562,12 +646,12 @@ public class Storage
else
lastPath = '_' + lastPath;
path.set(last, lastPath);
f = createFileFromNames(base, path, areFilesPublic);
f = createFileFromNames(_base, path, areFilesPublic);
j = 0;
}
}
long len = ls.get(i).longValue();
_torrentFiles.add(new TorrentFile(base, f, len));
_torrentFiles.add(new TorrentFile(_base, f, len));
total += len;
if (useSavedBitField) {
long lm = f.lastModified();
@@ -611,10 +695,9 @@ public class Storage
* Doesn't really reopen the file descriptors for a restart.
* Just does an existence check but no length check or data reverification
*
* @param rootDir ignored
* @throws IOE on fail
*/
public void reopen(String rootDir) throws IOException
public void reopen() throws IOException
{
if (_torrentFiles.isEmpty())
throw new IOException("Storage not checked yet");
@@ -637,6 +720,19 @@ public class Storage
0x2028, 0x2029
};
/**
* Filter the name, but only if configured to do so.
* We will do so on torrents received from others, but not
* on those we created ourselves, so we do not lose track of files.
*
* @since 0.9.15
*/
private String optFilterName(String name) {
if (_preserveFileNames)
return name;
return filterName(name);
}
/**
* Removes 'suspicious' characters from the given file name.
* http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
@@ -677,7 +773,7 @@ public class Storage
}
rv = repl;
}
} catch (Exception ex) {
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
@@ -690,14 +786,16 @@ public class Storage
* Note that filtering each path element individually may lead to
* things going in the wrong place if there are duplicates
* in intermediate path elements after filtering.
*
* @param names path elements
*/
private static File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
private File createFileFromNames(File base, List<String> names, boolean areFilesPublic) throws IOException
{
File f = null;
Iterator<String> it = names.iterator();
while (it.hasNext())
{
String name = filterName(it.next());
String name = optFilterName(it.next());
if (it.hasNext())
{
// Another dir in the hierarchy.
@@ -725,15 +823,73 @@ public class Storage
return f;
}
public static File getFileFromNames(File base, List<String> names)
{
Iterator<String> it = names.iterator();
while (it.hasNext())
{
String name = filterName(it.next());
base = new File(base, name);
/**
* The base file or directory.
* @return the File
* @since 0.9.15
*/
public File getBase() {
return _base;
}
/**
* Does not include directories. Unsorted.
* @return a new List
* @since 0.9.15
*/
public List<File> getFiles() {
List<File> rv = new ArrayList<File>(_torrentFiles.size());
for (TorrentFile tf : _torrentFiles) {
rv.add(tf.RAFfile);
}
return base;
return rv;
}
/**
* Does not include directories.
* @since 0.9.23
*/
public int getFileCount() {
return _torrentFiles.size();
}
/**
* Includes the base for a multi-file torrent.
* Sorted bottom-up for easy deletion.
* Slow. Use for deletion only.
* @return a new Set or null for a single-file torrent
* @since 0.9.15
*/
public SortedSet<File> getDirectories() {
if (!_base.isDirectory())
return null;
SortedSet<File> rv = new TreeSet<File>(Collections.reverseOrder());
rv.add(_base);
for (TorrentFile tf : _torrentFiles) {
File f = tf.RAFfile;
do {
f = f.getParentFile();
} while (f != null && rv.add(f));
}
return rv;
}
/**
* Blocking. Holds lock.
* Recommend running only when stopped.
* Caller should thread.
* Calls listener.setWantedPieces() on completion if anything changed.
*
* @return true if anything changed, false otherwise
* @since 0.9.23
*/
public boolean recheck() throws IOException {
int previousNeeded = needed;
checkCreateFiles(true);
boolean changed = previousNeeded != needed;
if (listener != null && changed)
listener.setWantedPieces(this);
return changed;
}
/**
@@ -760,6 +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;
@@ -776,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) {
@@ -794,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";
@@ -802,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();
@@ -822,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;
@@ -848,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
@@ -904,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
@@ -1131,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);
}
}
}
@@ -1267,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;
}
@@ -1324,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);

View 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;
@@ -72,8 +74,8 @@ public class TrackerClient implements Runnable {
private static final String STOPPED_EVENT = "stopped";
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
private static final String NOT_REGISTERED_2 = "torrent not found"; // diftracker
/** this is our equivalent to router.utorrent.com for bootstrap */
private static final String DEFAULT_BACKUP_TRACKER = "http://tracker.welterde.i2p/a";
private static final String NOT_REGISTERED_3 = "torrent unauthorised"; // vuze
private static final String ERROR_GOT_HTML = "received html"; // fake return
private final static int SLEEP = 5; // 5 minutes.
private final static int DELAY_MIN = 2000; // 2 secs.
@@ -88,6 +90,8 @@ public class TrackerClient implements Runnable {
private static final int DHT_ANNOUNCE_PEERS = 4;
public static final int PORT = 6881;
private static final int MAX_TRACKERS = 12;
// tracker.welterde.i2p
private static final Hash DSA_ONLY_TRACKER = ConvertToHash.getHash("cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p");
private final I2PSnarkUtil _util;
private final MetaInfo meta;
@@ -156,6 +160,7 @@ public class TrackerClient implements Runnable {
consecutiveFails = 0;
runStarted = false;
_fastUnannounce = false;
snark.setTrackerProblems(null);
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
_thread.start();
started = true;
@@ -342,7 +347,9 @@ public class TrackerClient implements Runnable {
_log.debug("Backup announce: [" + url + "] for infoHash: " + infoHash);
}
if (backupTrackers.isEmpty()) {
backupTrackers.add(new TCTracker(DEFAULT_BACKUP_TRACKER, false));
backupTrackers.add(new TCTracker(SnarkManager.DEFAULT_BACKUP_TRACKER, false));
} else if (trackers.size() > 1) {
Collections.shuffle(backupTrackers, _util.getContext().random());
}
}
this.completed = coordinator.getLeft() == 0;
@@ -360,12 +367,21 @@ public class TrackerClient implements Runnable {
if (h == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad announce URL: [" + ann + ']');
return false;
return false;
}
// comment this out if tracker.welterde.i2p upgrades
if (h.equals(DSA_ONLY_TRACKER)) {
Destination dest = _util.getMyDestination();
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping incompatible tracker: " + ann);
return false;
}
}
if (existing.size() >= MAX_TRACKERS) {
if (_log.shouldLog(Log.INFO))
_log.info("Not using announce URL, we have enough: [" + ann + ']');
return false;
return false;
}
boolean rv = existing.add(h);
if (!rv) {
@@ -526,9 +542,9 @@ public class TrackerClient implements Runnable {
!snark.isChecking() &&
info.getSeedCount() > 100 &&
coordinator.getPeerCount() <= 0 &&
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
snark.getTotalLength() > 0 &&
uploaded >= snark.getTotalLength() * 3 / 2) {
uploaded >= snark.getTotalLength() / 2) {
if (_log.shouldLog(Log.WARN))
_log.warn("Auto stopping " + snark.getBaseName());
snark.setAutoStoppable(false);
@@ -577,14 +593,20 @@ public class TrackerClient implements Runnable {
if (tr.isPrimary)
snark.setTrackerProblems(tr.trackerProblems);
String tplc = tr.trackerProblems.toLowerCase(Locale.US);
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2)) {
if (tplc.startsWith(NOT_REGISTERED) || tplc.startsWith(NOT_REGISTERED_2) ||
tplc.startsWith(NOT_REGISTERED_3) || tplc.startsWith(ERROR_GOT_HTML)) {
// Give a guy some time to register it if using opentrackers too
//if (trckrs.size() == 1) {
// stop = true;
// snark.stopTorrent();
//} else { // hopefully each on the opentrackers list is really open
if (tr.registerFails++ > MAX_REGISTER_FAILS ||
!completed || // no use retrying if we aren't seeding
tplc.startsWith(ERROR_GOT_HTML) || // fake msg from doRequest()
(!tr.isPrimary && tr.registerFails > MAX_REGISTER_FAILS / 2))
if (_log.shouldLog(Log.WARN))
_log.warn("Not longer announcing to " + tr.announce + " : " +
tr.trackerProblems + " after " + tr.registerFails + " failures");
tr.stop = true;
//
}
@@ -797,10 +819,15 @@ public class TrackerClient implements Runnable {
tr.lastRequestTime = System.currentTimeMillis();
// Don't wait for a response to stopped when shutting down
boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 8*1024);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
byte[] fetched = _util.get(s, true, fast ? -1 : 0, small ? 128 : 1024, small ? 1024 : 32*1024);
if (fetched == null)
throw new IOException("Error fetching");
if (fetched.length == 0)
throw new IOException("No data");
// The HTML check only works if we didn't exceed the maxium fetch size specified in get(),
// otherwise we already threw an IOE.
if (fetched[0] == '<')
throw new IOException(ERROR_GOT_HTML);
InputStream in = new ByteArrayInputStream(fetched);
@@ -848,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"));
}
@@ -869,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")) {
@@ -885,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;

View File

@@ -196,6 +196,9 @@ class TrackerInfo
return complete;
}
/**
* Not HTML escaped.
*/
public String getFailureReason()
{
return failure_reason;

View File

@@ -6,6 +6,7 @@ import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.update.*;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
@@ -109,7 +110,7 @@ class UpdateRunner implements UpdateTask, CompleteListener {
_umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
continue;
}
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, null, this);
if (_snark != null) {
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>");
new Timeout();
@@ -290,12 +291,21 @@ class UpdateRunner implements UpdateTask, CompleteListener {
return _smgr.getSavedTorrentBitField(snark);
}
public boolean getSavedPreserveNamesSetting(Snark snark) {
return _smgr.getSavedPreserveNamesSetting(snark);
}
public long getSavedUploaded(Snark snark) {
return _smgr.getSavedUploaded(snark);
}
//////// end CompleteListener methods
private static String linkify(String url) {
String durl = url.length() <= 28 ? url :
url.substring(0, 25) + "&hellip;";
return "<a target=\"_blank\" href=\"" + url + "\"/>" + durl + "</a>";
String durl = url.length() <= 28 ? DataHelper.escapeHTML(url) :
DataHelper.escapeHTML(url.substring(0, 25)) + "&hellip;";
// TODO urlEncode instead
return "<a target=\"_blank\" href=\"" + DataHelper.escapeHTML(url) + "\"/>" + durl + "</a>";
}
private void updateStatus(String s) {

View File

@@ -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

View File

@@ -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)

View File

@@ -40,6 +40,8 @@ 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;
import org.klomp.snark.bencode.BEncoder;
@@ -127,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;
@@ -152,7 +156,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
private static final long CLEAN_TIME = 63*1000;
private static final long EXPLORE_TIME = 877*1000;
private static final long BLACKLIST_CLEAN_TIME = 17*60*1000;
private static final String DHT_FILE_SUFFIX = ".dht.dat";
public static final String DHT_FILE_SUFFIX = ".dht.dat";
private static final int SEND_CRYPTO_TAGS = 8;
private static final int LOW_CRYPTO_TAGS = 4;
@@ -185,8 +189,14 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
_myNID = new NID(_myID);
}
_myNodeInfo = new NodeInfo(_myNID, session.getMyDestination(), _qPort);
_dhtFile = new File(ctx.getConfigDir(), baseName + DHT_FILE_SUFFIX);
_backupDhtFile = baseName.equals("i2psnark") ? null : new File(ctx.getConfigDir(), "i2psnark" + DHT_FILE_SUFFIX);
File conf = new File(ctx.getConfigDir(), baseName + ".config" + SnarkManager.CONFIG_DIR_SUFFIX);
_dhtFile = new File(conf, "i2psnark" + DHT_FILE_SUFFIX);
if (baseName.equals("i2psnark")) {
_backupDhtFile = null;
} else {
File bconf = new File(ctx.getConfigDir(), "i2psnark.config" + SnarkManager.CONFIG_DIR_SUFFIX);
_backupDhtFile = new File(bconf, "i2psnark" + DHT_FILE_SUFFIX);
}
_knownNodes = new DHTNodes(ctx, _myNID);
start();
@@ -236,6 +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()) {
@@ -320,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) {
@@ -835,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);
@@ -851,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");
@@ -900,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");
@@ -1287,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) {
@@ -1401,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();

View File

@@ -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;
}
}

View File

@@ -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);

View 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>

View File

@@ -181,11 +181,10 @@ class BasicServlet extends HttpServlet
HttpContent r = null;
if (_warBase != null && pathInContext.startsWith(_warBase)) {
r = new JarContent(pathInContext);
} else if (!pathInContext.contains("..") &&
!pathInContext.endsWith("/")) {
File f = new File(_resourceBase, pathInContext);
} else {
File f = getResource(pathInContext);
// exists && !directory
if (f.isFile())
if (f != null && f.isFile())
r = new FileContent(f);
}
return r;
@@ -283,7 +282,12 @@ class BasicServlet extends HttpServlet
{
if (content.getLastModified()/1000 <= ifmsl/1000)
{
response.reset();
try {
response.reset();
} catch (IllegalStateException ise) {
// committed
return true;
}
response.setStatus(304);
response.flushBuffer();
return false;
@@ -354,6 +358,7 @@ class BasicServlet extends HttpServlet
writeHeaders(response, content, content_length);
response.setStatus(416);
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
in.close();
return;
}
@@ -555,14 +560,17 @@ class BasicServlet extends HttpServlet
/**
* Simple version of URIUtil.encodePath()
*/
protected static String encodePath(String path) throws MalformedURLException {
try {
URI uri = new URI(null, null, path, null);
return uri.toString();
} catch (URISyntaxException use) {
// for ease of use, since a USE is not an IOE but a MUE is...
throw new MalformedURLException(use.getMessage());
}
protected static String encodePath(String path) /* throws MalformedURLException */ {
// Does NOT handle a ':' correctly, throws MUE.
// Can't convert to %3a before hand or the % gets escaped
//try {
// URI uri = new URI(null, null, path, null);
// return uri.toString();
//} catch (URISyntaxException use) {
// // for ease of use, since a USE is not an IOE but a MUE is...
// throw new MalformedURLException(use.getMessage());
//}
return URIUtil.encodePath(path);
}
/**

View File

@@ -50,6 +50,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
private final String _url;
private final byte[] _fakeHash;
private final String _name;
private final File _dataDir;
private volatile long _remaining = -1;
private volatile long _total = -1;
private volatile long _transferred;
@@ -65,8 +66,10 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
/**
* Caller should call _mgr.addDownloader(this), which
* will start things off.
*
* @param dataDir null to default to snark data directory
*/
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url) {
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url, File dataDir) {
// magnet constructor
super(mgr.util(), "Torrent download",
null, null, null, null, null, false, null);
@@ -74,7 +77,8 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
_log = ctx.logManager().getLog(FetchAndAdd.class);
_mgr = mgr;
_url = url;
_name = _("Download torrent file from {0}", url);
_name = _t("Download torrent file from {0}", url);
_dataDir = dataDir;
byte[] fake = null;
try {
fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1"));
@@ -86,7 +90,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
* Set off by startTorrent()
*/
public void run() {
_mgr.addMessage(_("Fetching {0}", urlify(_url)));
_mgr.addMessageNoEscape(_t("Fetching {0}", urlify(_url)));
File file = get();
if (!_isRunning) // stopped?
return;
@@ -96,7 +100,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
_mgr.deleteMagnet(this);
add(file);
} else {
_mgr.addMessage(_("Torrent was not retrieved from {0}", urlify(_url)) +
_mgr.addMessageNoEscape(_t("Torrent was not retrieved from {0}", urlify(_url)) +
((_failCause != null) ? (": " + DataHelper.stripHTML(_failCause)) : ""));
}
if (file != null)
@@ -123,7 +127,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
out.deleteOnExit();
if (!_mgr.util().connected()) {
_mgr.addMessage(_("Opening the I2P tunnel"));
_mgr.addMessage(_t("Opening the I2P tunnel"));
if (!_mgr.util().connect())
return null;
}
@@ -150,7 +154,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
* This Snark may then be deleted.
*/
private void add(File file) {
_mgr.addMessage(_("Torrent fetched from {0}", urlify(_url)));
_mgr.addMessageNoEscape(_t("Torrent fetched from {0}", urlify(_url)));
FileInputStream in = null;
try {
in = new FileInputStream(file);
@@ -159,7 +163,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
try { in.close(); } catch (IOException ioe) {}
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
if (snark != null) {
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
_mgr.addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
}
@@ -171,12 +175,12 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
if (torrentFile.exists()) {
if (_mgr.getTorrent(canonical) != null)
_mgr.addMessage(_("Torrent already running: {0}", name));
_mgr.addMessage(_t("Torrent already running: {0}", name));
else
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
_mgr.addMessage(_t("Torrent already in the queue: {0}", name));
} else {
// This may take a LONG time to create the storage.
_mgr.copyAndAddTorrent(file, canonical);
_mgr.copyAndAddTorrent(file, canonical, _dataDir);
snark = _mgr.getTorrentByBaseName(originalName);
if (snark != null)
snark.startTorrent();
@@ -184,9 +188,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
throw new IOException("Unknown error - check logs");
}
} catch (IOException ioe) {
_mgr.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
_mgr.addMessageNoEscape(_t("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
} catch (OutOfMemoryError oom) {
_mgr.addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + oom.getMessage());
_mgr.addMessageNoEscape(_t("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
@@ -341,11 +345,11 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
// End of EepGet status listeners
private String _(String s) {
private String _t(String s) {
return _mgr.util().getString(s);
}
private String _(String s, String o) {
private String _t(String s, String o) {
return _mgr.util().getString(s, o);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,643 @@
package org.klomp.snark.web;
import java.io.File;
import java.io.Serializable;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
import org.klomp.snark.Storage;
/**
* Comparators for various columns
*
* @since 0.9.16 from TorrentNameComparator, moved from I2PSnarkservlet
*/
class Sorters {
/**
* See below
*/
private static final Pattern PATTERN_DE, PATTERN_EN, PATTERN_ES, PATTERN_FR,
PATTERN_IT, PATTERN_NL, PATTERN_PT;
private static Pattern _pattern;
/**
* Negative is reverse
*
*<ul>
*<li>0, 1: Name
*<li>2: Status
*<li>3: Peers
*<li>4: ETA
*<li>5: Size
*<li>6: Downloaded
*<li>7: Uploaded
*<li>8: Down rate
*<li>9: Up rate
*<li>10: Remaining (needed)
*<li>11: Upload ratio
*<li>12: File type
*</ul>
*
* @param servlet for file type callback only
*/
public static Comparator<Snark> getComparator(int type, I2PSnarkServlet servlet) {
boolean rev = type < 0;
Comparator<Snark> rv;
switch (type) {
case -1:
case 0:
case 1:
default:
rv = new TorrentNameComparator();
if (rev)
rv = Collections.reverseOrder(rv);
break;
case -2:
case 2:
rv = new StatusComparator(rev);
break;
case -3:
case 3:
rv = new PeersComparator(rev);
break;
case -4:
case 4:
rv = new ETAComparator(rev);
break;
case -5:
case 5:
rv = new SizeComparator(rev);
break;
case -6:
case 6:
rv = new DownloadedComparator(rev);
break;
case -7:
case 7:
rv = new UploadedComparator(rev);
break;
case -8:
case 8:
rv = new DownRateComparator(rev);
break;
case -9:
case 9:
rv = new UpRateComparator(rev);
break;
case -10:
case 10:
rv = new RemainingComparator(rev);
break;
case -11:
case 11:
rv = new RatioComparator(rev);
break;
case -12:
case 12:
rv = new FileTypeComparator(rev, servlet);
break;
}
return rv;
}
/**
* Sort alphabetically in current locale, ignore case, ignore leading
* articles such as "the" if the pattern is set by setPattern()
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
public int compare(Snark l, Snark r) {
return comp(l, r);
}
public static int comp(Snark l, Snark r) {
// put downloads and magnets first
if (l.getStorage() == null && r.getStorage() != null)
return -1;
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String rs = r.getBaseName();
Pattern p = _pattern;
if (p != null) {
Matcher m = p.matcher(ls);
if (m.matches())
ls = ls.substring(m.group(1).length());
m = p.matcher(rs);
if (m.matches())
rs = rs.substring(m.group(1).length());
}
return Collator.getInstance().compare(ls, rs);
}
}
/**
* Forward or reverse sort, but the fallback is always forward
*/
private static abstract class Sort implements Comparator<Snark>, Serializable {
private final boolean _rev;
public Sort(boolean rev) {
_rev = rev;
}
public int compare(Snark l, Snark r) {
int rv = compareIt(l, r);
if (rv != 0)
return _rev ? 0 - rv : rv;
return TorrentNameComparator.comp(l, r);
}
protected abstract int compareIt(Snark l, Snark r);
protected static int compLong(long l, long r) {
if (l < r)
return -1;
if (l > r)
return 1;
return 0;
}
}
private static class StatusComparator extends Sort {
private StatusComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
int rv = getStatus(l) - getStatus(r);
if (rv != 0)
return rv;
// use reverse remaining as first tie break
return compLong(r.getNeededLength(), l.getNeededLength());
}
private static int getStatus(Snark snark) {
long remaining = snark.getRemainingLength();
if (snark.isStopped()) {
if (remaining < 0)
return 0;
if (remaining > 0)
return 5;
return 10;
}
if (snark.isStarting())
return 15;
if (snark.isAllocating())
return 20;
if (remaining < 0)
return 15; // magnet
if (remaining == 0)
return 100;
if (snark.isChecking())
return 95;
if (snark.getNeededLength() <= 0)
return 90;
if (snark.getPeerCount() <= 0)
return 40;
if (snark.getDownloadRate() <= 0)
return 50;
return 60;
}
}
private static class PeersComparator extends Sort {
public PeersComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return l.getPeerCount() - r.getPeerCount();
}
}
private static class RemainingComparator extends Sort {
public RemainingComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getNeededLength(), r.getNeededLength());
}
}
private static class ETAComparator extends Sort {
public ETAComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(eta(l), eta(r));
}
private static long eta(Snark snark) {
long needed = snark.getNeededLength();
if (needed <= 0)
return 0;
long total = snark.getTotalLength();
if (needed > total)
needed = total;
long downBps = snark.getDownloadRate();
if (downBps > 0)
return needed / downBps;
return Long.MAX_VALUE;
}
}
private static class SizeComparator extends Sort {
public SizeComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getTotalLength(), r.getTotalLength());
}
}
private static class DownloadedComparator extends Sort {
public DownloadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long ld = l.getTotalLength() - l.getRemainingLength();
long rd = r.getTotalLength() - r.getRemainingLength();
return compLong(ld, rd);
}
}
private static class UploadedComparator extends Sort {
public UploadedComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploaded(), r.getUploaded());
}
}
private static class DownRateComparator extends Sort {
public DownRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getDownloadRate(), r.getDownloadRate());
}
}
private static class UpRateComparator extends Sort {
public UpRateComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
return compLong(l.getUploadRate(), r.getUploadRate());
}
}
private static class RatioComparator extends Sort {
private static final long M = 128 * 1024 * 1024;
public RatioComparator(boolean rev) { super(rev); }
public int compareIt(Snark l, Snark r) {
long lt = l.getTotalLength();
long ld = lt > 0 ? ((M * l.getUploaded()) / lt) : 0;
long rt = r.getTotalLength();
long rd = rt > 0 ? ((M * r.getUploaded()) / rt) : 0;
return compLong(ld, rd);
}
}
private static class FileTypeComparator extends Sort {
private final I2PSnarkServlet servlet;
public FileTypeComparator(boolean rev, I2PSnarkServlet servlet) {
super(rev);
this.servlet = servlet;
}
public int compareIt(Snark l, Snark r) {
String ls = toName(l);
String rs = toName(r);
return ls.compareTo(rs);
}
private String toName(Snark snark) {
MetaInfo meta = snark.getMetaInfo();
if (meta == null)
return "0";
if (meta.getFiles() != null)
return "1";
// arbitrary sort based on icon name
return servlet.toIcon(meta.getName());
}
}
////////////// Comparators for details page below
/**
* Class to precompute and efficiently sort data
* on a torrent file entry.
*/
public static class FileAndIndex {
public final File file;
public final boolean isDirectory;
public final long length;
public final long remaining;
public final int priority;
public final int index;
/**
* @param storage may be null
* @param remainingArray precomputed, non-null iff storage is non-null
*/
public FileAndIndex(File file, Storage storage, long[] remainingArray) {
this.file = file;
index = storage != null ? storage.indexOf(file) : -1;
if (index >= 0) {
isDirectory = false;
remaining = remainingArray[index];
priority = storage.getPriority(index);
} else {
isDirectory = file.isDirectory();
remaining = -1;
priority = -999;
}
length = isDirectory ? 0 : file.length();
}
}
/**
* Negative is reverse
*
*<ul>
*<li>0, 1: Name
*<li>5: Size
*<li>10: Remaining (needed)
*<li>12: File type
*<li>13: Priority
*</ul>
*
* @param servlet for file type callback only
*/
public static Comparator<FileAndIndex> getFileComparator(int type, I2PSnarkServlet servlet) {
boolean rev = type < 0;
Comparator<FileAndIndex> rv;
switch (type) {
case -1:
case 0:
case 1:
default:
rv = new FileNameComparator();
if (rev)
rv = Collections.reverseOrder(rv);
break;
case -5:
case 5:
rv = new FAISizeComparator(rev);
break;
case -10:
case 10:
rv = new FAIRemainingComparator(rev);
break;
case -12:
case 12:
rv = new FAITypeComparator(rev, servlet);
break;
case -13:
case 13:
rv = new FAIPriorityComparator(rev);
break;
}
return rv;
}
/**
* Sort alphabetically in current locale, ignore case,
* directories first
* @since 0.9.6 moved from I2PSnarkServlet in 0.9.16
*/
private static class FileNameComparator implements Comparator<FileAndIndex>, Serializable {
public int compare(FileAndIndex l, FileAndIndex r) {
return comp(l, r);
}
public static int comp(FileAndIndex l, FileAndIndex r) {
boolean ld = l.isDirectory;
boolean rd = r.isDirectory;
if (ld && !rd)
return -1;
if (rd && !ld)
return 1;
return Collator.getInstance().compare(l.file.getName(), r.file.getName());
}
}
/**
* Forward or reverse sort, but the fallback is always forward
*/
private static abstract class FAISort implements Comparator<FileAndIndex>, Serializable {
private final boolean _rev;
public FAISort(boolean rev) {
_rev = rev;
}
public int compare(FileAndIndex l, FileAndIndex r) {
int rv = compareIt(l, r);
if (rv != 0)
return _rev ? 0 - rv : rv;
return FileNameComparator.comp(l, r);
}
protected abstract int compareIt(FileAndIndex l, FileAndIndex r);
protected static int compLong(long l, long r) {
if (l < r)
return -1;
if (l > r)
return 1;
return 0;
}
}
private static class FAIRemainingComparator extends FAISort {
public FAIRemainingComparator(boolean rev) { super(rev); }
public int compareIt(FileAndIndex l, FileAndIndex r) {
return compLong(l.remaining, r.remaining);
}
}
private static class FAISizeComparator extends FAISort {
public FAISizeComparator(boolean rev) { super(rev); }
public int compareIt(FileAndIndex l, FileAndIndex r) {
return compLong(l.length, r.length);
}
}
private static class FAITypeComparator extends FAISort {
private final I2PSnarkServlet servlet;
public FAITypeComparator(boolean rev, I2PSnarkServlet servlet) {
super(rev);
this.servlet = servlet;
}
public int compareIt(FileAndIndex l, FileAndIndex r) {
String ls = toName(l);
String rs = toName(r);
return ls.compareTo(rs);
}
private String toName(FileAndIndex fai) {
if (fai.isDirectory)
return "0";
// arbitrary sort based on icon name
return servlet.toIcon(fai.file.getName());
}
}
private static class FAIPriorityComparator extends FAISort {
public FAIPriorityComparator(boolean rev) { super(rev); }
/** highest first */
public int compareIt(FileAndIndex l, FileAndIndex r) {
return r.priority - l.priority;
}
}
/*
* Match an indefinite or definite article in the language,
* followed by one or more whitespace, '.', or '_'.
* Does not match "partitive" articles.
*
* https://en.wikipedia.org/wiki/Article_%28grammar%29
* http://www.loc.gov/marc/bibliographic/bdapndxf.html
*/
static {
PATTERN_DE = Pattern.compile(
// can't make the non-capturing innner group work
//"^((?:" +
"^((" +
"der|die|das|des|dem|den|ein|eine|einer|eines|einem|einen" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_EN = Pattern.compile(
"^((" +
"a|an|the" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_ES = Pattern.compile(
"^((" +
"el|la|lo|los|las|un|una|unos|unas" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_FR = Pattern.compile(
// note l' doesn't require whitespace after
"^(l'|((" +
"le|la|les|un|une|des" +
")[\\s\\._]+)).*",
Pattern.CASE_INSENSITIVE);
PATTERN_IT = Pattern.compile(
// note l' and un' don't require whitespace after
"^(l'|un'|((" +
"il|lo|la|i|gli|le|uno|una|un" +
")[\\s\\._]+)).*",
Pattern.CASE_INSENSITIVE);
PATTERN_NL = Pattern.compile(
"^((" +
"de|het|het'n|een|een'n" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_PT = Pattern.compile(
"^((" +
"o|a|os|as|um|uma|uns|umas" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
}
/**
* Sets static field, oh well
* @param lang null for none
* @since 0.9.23
*/
public static void setPattern(String lang) {
Pattern p;
if (lang == null)
p = null;
else if (lang.equals("de"))
p = PATTERN_DE;
else if (lang.equals("en"))
p = PATTERN_EN;
else if (lang.equals("es"))
p = PATTERN_ES;
else if (lang.equals("fr"))
p = PATTERN_FR;
else if (lang.equals("it"))
p = PATTERN_IT;
else if (lang.equals("nl"))
p = PATTERN_NL;
else if (lang.equals("pt"))
p = PATTERN_PT;
else
p = null;
_pattern = p;
}
/****
public static final void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: Sorters lang 'string'");
System.exit(1);
}
String lang = args[0];
setPattern(lang);
if (_pattern == null) {
System.out.println("Unsupported " + lang);
System.exit(1);
}
String s = args[1];
Matcher m = _pattern.matcher(s);
if (m.matches()) {
System.out.println("Match is \"" + m.group(1) + '"');
} else {
System.out.println("No match for \"" + s + '"');
}
}
****/
}

View File

@@ -0,0 +1,250 @@
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.klomp.snark.web;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import net.i2p.data.DataHelper;
/** URI Holder.
* This class assists with the decoding and encoding or HTTP URI's.
* It differs from the java.net.URL class as it does not provide
* communications ability, but it does assist with query string
* formatting.
* <P>UTF-8 encoding is used by default for % encoded characters. This
* may be overridden with the org.eclipse.jetty.util.URI.charset system property.
* see UrlEncoded
*
* I2P modded from Jetty 8.1.15
* @since 0.9.15
*/
class URIUtil
{
/** Encode a URI path.
* This is the same encoding offered by URLEncoder, except that
* the '/' character is not encoded.
* @param path The path the encode
* @return The encoded path
*/
public static String encodePath(String path)
{
if (path==null || path.length()==0)
return path;
StringBuilder buf = encodePath(null,path);
return buf==null?path:buf.toString();
}
/** Encode a URI path.
*
* Somewhat oddly, this encodes all chars >= 0x80 if buf is null, (strict RFC 2396)
* but only the control, space, and special chars if buf is non-null.
*
* @param path The path the encode
* @param buf StringBuilder to encode path into (or null)
* @return The StringBuilder or null if no substitutions required.
*/
public static StringBuilder encodePath(StringBuilder buf, String path)
{
byte[] bytes=null;
if (buf==null)
{
loop:
for (int i=0;i<path.length();i++)
{
char c=path.charAt(i);
switch(c)
{
case '%':
case '?':
case ';':
case '#':
case '\'':
case '"':
case '<':
case '>':
case ' ':
case ':':
case '[':
case ']':
buf=new StringBuilder(path.length()*2);
break loop;
default:
if (c >= 0x7f || c <= 0x1f)
{
bytes = DataHelper.getUTF8(path);
buf=new StringBuilder(path.length()*2);
break loop;
}
}
}
if (buf==null)
return null;
}
//synchronized(buf)
//{
if (bytes!=null)
{
for (int i=0;i<bytes.length;i++)
{
byte c=bytes[i];
switch(c)
{
case '%':
buf.append("%25");
continue;
case '?':
buf.append("%3F");
continue;
case ';':
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
buf.append("%3E");
continue;
case ' ':
buf.append("%20");
continue;
case 0x7f:
buf.append("%7F");
continue;
case ':':
buf.append("%3A");
continue;
case '[':
buf.append("%5B");
continue;
case ']':
buf.append("%5D");
continue;
default:
if (c <= 0x1f) // includes negative
toHex(c,buf);
else
buf.append((char)c);
continue;
}
}
}
else
{
for (int i=0;i<path.length();i++)
{
char c=path.charAt(i);
switch(c)
{
case '%':
buf.append("%25");
continue;
case '?':
buf.append("%3F");
continue;
case ';':
buf.append("%3B");
continue;
case '#':
buf.append("%23");
continue;
case '"':
buf.append("%22");
continue;
case '\'':
buf.append("%27");
continue;
case '<':
buf.append("%3C");
continue;
case '>':
buf.append("%3E");
continue;
case ' ':
buf.append("%20");
continue;
case ':':
buf.append("%3A");
continue;
case '[':
buf.append("%5B");
continue;
case ']':
buf.append("%5D");
continue;
default:
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f) || Character.isSpaceChar(c))
toHex(c,buf);
else
buf.append(c);
continue;
}
}
}
//}
return buf;
}
/**
* Modded from Jetty TypeUtil
*/
private static void toHex(byte b, StringBuilder buf)
{
buf.append('%');
int d=0xf&((0xF0&b)>>4);
buf.append((char)((d>9?('A'-10):'0')+d));
d=0xf&b;
buf.append((char)((d>9?('A'-10):'0')+d));
}
/**
* UTF-8
*/
private static void toHex(char c, StringBuilder buf)
{
if (c > 0x7f) {
byte[] b = DataHelper.getUTF8(Character.toString(c));
for (int i = 0; i < b.length; i++) {
toHex(b[i], buf);
}
} else {
toHex((byte) c, buf);
}
}
}

View 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

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

View File

@@ -3,13 +3,16 @@
7z = application/x-7z-compressed
ape = audio/x-monkeys-audio
bz2 = application/x-bzip2
cue = application/x-cue
dmg = application/apple-diskimage
epub = application/epub+zip
flac = audio/flac
flv = video/x-flv
iso = application/x-iso9660-image
m4a = audio/mp4a-latm
m4b = audio/mp4a-latm
m4v = video/x-m4v
mka = audio/x-matroska
mkv = video/x-matroska
mobi = application/x-mobipocket-ebook
mp4 = video/mp4
@@ -24,8 +27,10 @@ su2 = application/zip
su3 = application/zip
sud = application/zip
tbz = application/x-bzip2
torrent = application/x-bittorrent
txt = text/plain
war = application/java-archive
webm = video/webm
wma = audio/x-ms-wma
wmv = video/x-ms-wmv
xz = application/x-xz

View File

@@ -1,6 +1,4 @@
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
to run it with the web UI, run "launch-i2psnark". I2PSnark is
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
(http://www.i2p2.de/) within a webserver (such as the bundled Jetty from
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
with the folks at http://forum.i2p2.de/
i2psnark is packaged as a webapp running in the router console.
Command line and standalone operation of i2psnark are not currently supported.
See http://trac.i2p2.i2p/ticket/1191 or http://trac.i2p2.de/ticket/1191
for the status of restoring standalone support.

View File

@@ -1,11 +1,9 @@
This is an I2P port of snark [http://klomp.org/snark], a GPL'ed bittorrent client
This is i2psnark, an I2P port of snark http://klomp.org/snark/ , a GPLv2 bittorrent client.
It contains significant enhancements including a web UI and support for
multitorrent, magnet, PEX and DHT.
The build in tracker has been removed for simplicity.
i2psnark is packaged as a webapp running in the router console.
Example usage:
java -jar lib/i2psnark.jar myFile.torrent
or, a more verbose setting:
java -jar lib/i2psnark.jar --eepproxy 127.0.0.1 4444 \
--i2cp 127.0.0.1 7654 "inbound.length=2 outbound.length=2" \
--debug 6 myFile.torrent
See http://i2p-projekt.i2p/en/docs/applications/bittorrent
or https://geti2p.net/en/docs/applications/bittorrent
for the specification of the protocols for bittorrent over I2P.

Some files were not shown because too many files have changed in this diff Show More