Compare commits
306 Commits
i2p-0.7.11
...
i2p-0.7.14
Author | SHA1 | Date | |
---|---|---|---|
746bad3c30 | |||
5bbd61b75c | |||
27eb7e46d0 | |||
c20bef3731 | |||
fc60768a66 | |||
2024fb1b65 | |||
617ca79b8f | |||
7bfb5b1bf4 | |||
8d73529fa4 | |||
a19d04d3ba | |||
a9c7748a52 | |||
41e4e952b7 | |||
c0b0b5e4c5 | |||
e424479e7e | |||
a8804f3093 | |||
6479a24bb7 | |||
8b372ad306 | |||
86791a2f1b | |||
7cf0aad388 | |||
c5ea51beec | |||
7cc8e51d73 | |||
75ba58d68c | |||
cd35b219db | |||
4a863f8ce7 | |||
24264548a6 | |||
f9e4b1a56b | |||
13b54b864e | |||
05d45fe945 | |||
2781f6035a | |||
dc3378d084 | |||
9132e94143 | |||
b61e2aa73c | |||
7fdbae3b0f | |||
4dc6fc3b5d | |||
618275b1f9 | |||
7a1111d845 | |||
3af356840e | |||
911a278926 | |||
014063700f | |||
f7c0db0454 | |||
a534d25d82 | |||
bcf3e4a2d3 | |||
0cdfbd9803 | |||
a3e5654d86 | |||
2f9364db2b | |||
5d7c9ebf82 | |||
48da98d0e4 | |||
55e994ac3c | |||
6d46a21f9f | |||
fdc83484fd | |||
6786817fff | |||
b77cd0db15 | |||
20bef76878 | |||
7a30490482 | |||
3bc2e469cc | |||
d770d3c6da | |||
339a001592 | |||
ace57a96a9 | |||
2c26b8d422 | |||
e1eafa2394 | |||
39cb51c9eb | |||
fa5016ab04 | |||
b134ef1a74 | |||
234dff888d | |||
a08c15a3ee | |||
cfa894e7b6 | |||
d6c8e64575 | |||
dc91580e30 | |||
7ec1dd7a98 | |||
82f3f7506c | |||
e26df1c26b | |||
aea77cf225 | |||
a1e3ef9c5c | |||
7aece71342 | |||
bdbde54f04 | |||
157e035710 | |||
97d9a3a4e5 | |||
cb7f111ade | |||
35f670706a | |||
3fac888fe5 | |||
d843646b4f | |||
c2b73d9fb5 | |||
9da95b8165 | |||
5bcd8efe14 | |||
027a1d748d | |||
6d6e012c19 | |||
a8db6b007f | |||
f3576e54c6 | |||
0325f6c4d2 | |||
8225ce063a | |||
c2c379c994 | |||
7344c2af47 | |||
f484ea8c64 | |||
ac790492eb | |||
9ac5fb4890 | |||
2baee7413c | |||
16bec08f09 | |||
afb3c76922 | |||
2f526b35e8 | |||
2dc32aa310 | |||
10e669165a | |||
b6cb90d731 | |||
949a8901fb | |||
d608f450af | |||
e0a1341901 | |||
2cfb03f17d | |||
4dd0f51da4 | |||
d65a3e54a2 | |||
c212eacf19 | |||
46f341d782 | |||
ab4ff5548d | |||
d4713e1e6c | |||
8a3a1466c9 | |||
a5af9dc973 | |||
049a083e42 | |||
9683a110d6 | |||
c44698f61a | |||
106bccda0e | |||
b1aafa5aaf | |||
e2e43cd534 | |||
43b4fe8300 | |||
7c3e4fd947 | |||
9916ef4d3d | |||
ad4da54bc4 | |||
2415c5a38b | |||
ecbc0a2a2d | |||
10d37a9be5 | |||
590d2e4639 | |||
806a07acc5 | |||
8258cdd6cf | |||
2fcee6e87a | |||
27587e83c8 | |||
a0d6741ff5 | |||
63562ddd48 | |||
aac96b15b0 | |||
0f502b4229 | |||
a916f970b1 | |||
7f2d0acc3b | |||
8b6751f419 | |||
70e9cf5838 | |||
24020302fd | |||
d7e2f39d25 | |||
89d0d7b266 | |||
e3c222b5c1 | |||
a199015bc9 | |||
23617f7b30 | |||
f5f02236df | |||
ad76bc378c | |||
570d8d15af | |||
e254c5f31a | |||
2a92be5946 | |||
caab860351 | |||
32861b7ce9 | |||
a08802c4b6 | |||
6b51be6fae | |||
5b5c975884 | |||
605dfec5e7 | |||
71aa0cfba7 | |||
55e45c4274 | |||
8c880b2518 | |||
c43b16cfbb | |||
394903a8f0 | |||
e31c0636ab | |||
e9fe80f8e5 | |||
7671550a9f | |||
83d24fa90d | |||
3e2956da3f | |||
cf3fd01012 | |||
319071c73b | |||
ab8d9bb79b | |||
25eaf8cad7 | |||
c8f97d9c73 | |||
d3f1fe1c30 | |||
5fb01a01af | |||
617d1cd648 | |||
f672193fcf | |||
d3c490e9d7 | |||
2e8fd23f2b | |||
3eef403b04 | |||
f3b78fc82f | |||
80654b2732 | |||
05597ae914 | |||
0f1eb464e8 | |||
8745ffd42f | |||
db99e98658 | |||
9f1a663f63 | |||
db0b3da446 | |||
5d22d41201 | |||
b397de1d54 | |||
4bda79b263 | |||
697a9dbd06 | |||
accaabcfde | |||
5026cbdc8d | |||
16a14d4ebd | |||
c151352910 | |||
52e2aaa20d | |||
9df87ba167 | |||
b80f70fc54 | |||
939cdb019b | |||
fde36fe238 | |||
116be93160 | |||
40e820cabb | |||
d79387bd92 | |||
05f2a62cbb | |||
78a965dc90 | |||
5b603d6627 | |||
e93d2046d3 | |||
995871db8a | |||
501535f196 | |||
91e854e99c | |||
9b05d8e774 | |||
e70793c3bc | |||
abb2603bea | |||
c91218be27 | |||
9eab44128a | |||
f98101afa6 | |||
4fae7a8cb6 | |||
26cf1922db | |||
c087b0695f | |||
16930d2004 | |||
33939e7cfb | |||
e759ef5865 | |||
2be1b1ece4 | |||
1820a29aed | |||
ee9f85d53c | |||
afbb1dbe86 | |||
9244bd6b0f | |||
6bb4403207 | |||
24ebd503d4 | |||
285a5eed35 | |||
26aebe6a0f | |||
ca9f174171 | |||
ffbced22b3 | |||
45ca459ceb | |||
5a539f0619 | |||
c6cef72cb7 | |||
8081d053cc | |||
efdc8e5df0 | |||
b4911a2b2f | |||
7b70210c9a | |||
e3353df8bb | |||
25285fc059 | |||
7720f71e44 | |||
1657ac5357 | |||
299214aa1d | |||
5fd4488e08 | |||
87fcaf2651 | |||
f6b9cf6f21 | |||
eae18e61b7 | |||
96735f2543 | |||
54459d3b5c | |||
82444f9e7b | |||
3d8365a473 | |||
e2dc9715d2 | |||
d4f1230b37 | |||
7701693d37 | |||
b6704fce4e | |||
39a68d4a2b | |||
94633899d7 | |||
c45bc1554f | |||
789c8edc45 | |||
e0b44f43e3 | |||
b45069e377 | |||
c3a156ce4b | |||
ee5cc099ed | |||
f189587153 | |||
a1fb5ef6ed | |||
8c2550c39a | |||
1d3f0fe96c | |||
49a6cdbda6 | |||
2700028da7 | |||
4f70a7d0fe | |||
f265db4037 | |||
62308f26bc | |||
cada9fae44 | |||
949aea951e | |||
cfc49ab261 | |||
880f1866dc | |||
54171e4be2 | |||
a820c01ba5 | |||
9d1ae891bb | |||
2df7247e83 | |||
a109ebef28 | |||
c0135b592d | |||
b7a0aeea34 | |||
66375e25c6 | |||
85482a67f5 | |||
9012baf51e | |||
3c8355790f | |||
e9f1da85e4 | |||
58adccfd4a | |||
040f3e016e | |||
505d5f5cae | |||
f7780b6745 | |||
7a59d15e9c | |||
9b141bd9dd | |||
375118fe02 | |||
3aebe45a7d | |||
2f8b55ceda | |||
b77be20cc1 | |||
b0502b1873 | |||
d7015cf2e6 | |||
5689fa8512 | |||
25e51a945c | |||
839986db22 | |||
56b3e6a993 |
@ -64,6 +64,9 @@ Public domain except as listed below:
|
||||
Copyright 2006 Gregory Rubin grrubin@gmail.com
|
||||
See licenses/LICENSE-HashCash.txt
|
||||
|
||||
GettextResource from gettext v0.18:
|
||||
Copyright (C) 2001, 2007 Free Software Foundation, Inc.
|
||||
See licenses/LICENSE-LGPLv2.1.txt
|
||||
|
||||
|
||||
Router:
|
||||
@ -139,6 +142,7 @@ Applications:
|
||||
I2PSnark:
|
||||
Copyright (C) 2003 Mark J. Wielaard
|
||||
See licenses/LICENSE-GPLv2.txt
|
||||
Silk icons: See licenses/LICENSE-SilkIcons.txt
|
||||
|
||||
I2PTunnel:
|
||||
(c) 2003 - 2004 mihi
|
||||
@ -176,6 +180,7 @@ Applications:
|
||||
Router console:
|
||||
Public domain.
|
||||
Flag icons: public domain, courtesy mjames@gmail.com http://www.famfamfam.com/
|
||||
Silk icons: See licenses/LICENSE-SilkIcons.txt
|
||||
|
||||
GeoIP Data:
|
||||
Copyright (c) 2003 Direct Information Pvt. Ltd. All Rights Reserved.
|
||||
|
@ -1,29 +1,38 @@
|
||||
These instructions are for the 1.5 Android SDK.
|
||||
These instructions are for a recent Android SDK (1.6 or later)..
|
||||
Should also still work with a 1.5 SDK.
|
||||
The build file is not compatible with the 1.1 SDK any more.
|
||||
1.6 and 2.0 SDKs are untested.
|
||||
|
||||
#Download the SDK from http://developer.android.com/sdk/index.html
|
||||
#Unzip the android SDK in ../../
|
||||
#So then the android tools will be in ../../android-sdk-linux_x86-1.5_r2/tools/
|
||||
#So then the android tools will be in ../../android-sdk-linux_86/tools/
|
||||
#
|
||||
# now go to the available packages tab, check the box and click refresh,
|
||||
# and download an SDK Platform
|
||||
# Since I2P is configured to run on 1.1 or higher
|
||||
# (API 2) download that one. Otherwise you must change the
|
||||
# target in default.properties from android-2 to andriod-x
|
||||
# where x is the API version.
|
||||
|
||||
# create a file local.properties with the following line:
|
||||
# sdk-location=/path/to/your/android-sdk-linux_x86-1.5_r2
|
||||
# sdk-location=/path/to/your/android-sdk-linux_86
|
||||
|
||||
#then build the android apk file:
|
||||
ant debug
|
||||
|
||||
# Create the android 1.5 virtual device
|
||||
# Create the android 1.1 (API 2) virtual device
|
||||
# (don't make a custom hardware profile)
|
||||
../../android-sdk-linux_x86-1.5_r2/tools/android create avd --name i2p --target 2
|
||||
# A AVD created with the 1.5 SDK will not work with the newer tools
|
||||
../../android-sdk-linux_86/tools/android create avd --name i2p --target 2
|
||||
|
||||
#then run the emulator:
|
||||
../../android-sdk-linux_x86-1.5_r2/tools/emulator -avd i2p &
|
||||
../../android-sdk-linux_86/tools/emulator -avd i2p &
|
||||
|
||||
#then wait a couple minutes until the emulator is up
|
||||
#then install the I2P app
|
||||
ant install
|
||||
|
||||
#then run the debugger
|
||||
../../android-sdk-linux_x86-1.5_r2/tools/ddms &
|
||||
../../android-sdk-linux_86/tools/ddms &
|
||||
|
||||
#to rebuild and reinstall to emulator:
|
||||
ant reinstall
|
||||
|
@ -113,6 +113,10 @@
|
||||
<delete file="${external-libs-folder}/crypto.jar" />
|
||||
</target>
|
||||
|
||||
<!-- fix for property name change sometime after SDK 1.5 -->
|
||||
<property name="android-jar" value="${android.jar}" />
|
||||
<property name="android-aidl" value="${android.aidl}" />
|
||||
|
||||
<!--
|
||||
================================================================================
|
||||
From here down copied from SDK platforms/android-1.1/templates/android_rules.xml
|
||||
|
@ -10,7 +10,13 @@ i2np.udp.maxConnections=30
|
||||
# no I2CP
|
||||
i2p.dummyClientFacade=true
|
||||
# for now
|
||||
i2np.ntcp.enable=false
|
||||
#i2np.ntcp.enable=false
|
||||
#
|
||||
# UDP crashes the JVM, don't know why
|
||||
#
|
||||
i2np.udp.enable=false
|
||||
# no COMM at all!!!
|
||||
#i2p.vmCommSystem=true
|
||||
# not on android
|
||||
i2np.upnp.enable=false
|
||||
routerconsole.geoip.enable=false
|
||||
|
@ -69,7 +69,7 @@ public class I2PAndroid extends Activity
|
||||
|
||||
// from routerconsole ContextHelper
|
||||
List contexts = RouterContext.listContexts();
|
||||
if ( (contexts == null) || (contexts.size() <= 0) )
|
||||
if ( (contexts == null) || (contexts.isEmpty()) )
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
RouterContext ctx = (RouterContext)contexts.get(0);
|
||||
|
||||
|
@ -1,4 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
|
||||
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
|
||||
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
|
||||
<file>file:/usblv/NetBeansProjects/i2p.i2p/apps/BOB/src/net/i2p/BOB/MUXlisten.java</file>
|
||||
<file>file:/usblv/NetBeansProjects/i2p.i2p/apps/BOB/src/net/i2p/BOB/Main.java</file>
|
||||
</open-files>
|
||||
</project-private>
|
||||
|
@ -50,7 +50,7 @@ public class DoCMDS implements Runnable {
|
||||
|
||||
// FIX ME
|
||||
// I need a better way to do versioning, but this will do for now.
|
||||
public static final String BMAJ = "00", BMIN = "00", BREV = "0B", BEXT = "";
|
||||
public static final String BMAJ = "00", BMIN = "00", BREV = "0C", BEXT = "";
|
||||
public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
|
||||
private Socket server;
|
||||
private Properties props;
|
||||
|
@ -311,6 +311,19 @@ public class MUXlisten implements Runnable {
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
|
||||
// Hopefully nuke stuff here...
|
||||
{
|
||||
String boner = tg.getName();
|
||||
try {
|
||||
_log.warn("destroySocketManager " + boner);
|
||||
socketManager.destroySocketManager();
|
||||
_log.warn("destroySocketManager Successful" + boner);
|
||||
} catch (Exception e) {
|
||||
// nop
|
||||
_log.warn("destroySocketManager Failed" + boner);
|
||||
_log.warn(e.toString());
|
||||
}
|
||||
}
|
||||
// zero out everything.
|
||||
try {
|
||||
wlock();
|
||||
|
@ -39,6 +39,7 @@ import net.i2p.I2PAppContext;
|
||||
public class Daemon {
|
||||
public static final String VERSION = "2.0.3";
|
||||
private static final Daemon _instance = new Daemon();
|
||||
private boolean _running;
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
@ -126,6 +127,7 @@ public class Daemon {
|
||||
}
|
||||
|
||||
public void run(String[] args) {
|
||||
_running = true;
|
||||
String settingsLocation = "config.txt";
|
||||
File homeFile;
|
||||
if (args.length > 0) {
|
||||
@ -166,7 +168,7 @@ public class Daemon {
|
||||
// Static method, and redundent Thread.currentThread().sleep(5*60*1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
while (true) {
|
||||
while (_running) {
|
||||
long delay = Long.parseLong((String) settings.get("update_delay"));
|
||||
if (delay < 1) {
|
||||
delay = 1;
|
||||
@ -179,6 +181,8 @@ public class Daemon {
|
||||
}
|
||||
} catch (InterruptedException exp) {
|
||||
}
|
||||
if (!_running)
|
||||
break;
|
||||
settings = ConfigParser.parse(settingsFile, defaultSettings);
|
||||
}
|
||||
}
|
||||
@ -192,4 +196,9 @@ public class Daemon {
|
||||
_instance.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static void stop() {
|
||||
_instance._running = false;
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
|
@ -51,4 +51,9 @@ public class DaemonThread extends Thread {
|
||||
//}
|
||||
Daemon.main(this.args);
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() {
|
||||
Daemon.stop();
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
*
|
||||
*/
|
||||
public class Servlet extends HttpServlet {
|
||||
private Thread thread;
|
||||
private DaemonThread thread;
|
||||
private String nonce;
|
||||
private static final String PROP_NONCE = "addressbook.nonce";
|
||||
|
||||
@ -88,4 +88,9 @@ public class Servlet extends HttpServlet {
|
||||
//System.out.println("INFO: config root under " + args[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.thread.halt();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
BIN
apps/i2psnark/_icons/application.png
Normal file
After Width: | Height: | Size: 464 B |
BIN
apps/i2psnark/_icons/book.png
Normal file
After Width: | Height: | Size: 593 B |
BIN
apps/i2psnark/_icons/bug.png
Normal file
After Width: | Height: | Size: 774 B |
BIN
apps/i2psnark/_icons/cancel.png
Normal file
After Width: | Height: | Size: 587 B |
BIN
apps/i2psnark/_icons/clock.png
Normal file
After Width: | Height: | Size: 882 B |
BIN
apps/i2psnark/_icons/compress.png
Normal file
After Width: | Height: | Size: 766 B |
BIN
apps/i2psnark/_icons/film.png
Normal file
After Width: | Height: | Size: 653 B |
BIN
apps/i2psnark/_icons/folder.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
apps/i2psnark/_icons/html.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
apps/i2psnark/_icons/music.png
Normal file
After Width: | Height: | Size: 385 B |
BIN
apps/i2psnark/_icons/package.png
Normal file
After Width: | Height: | Size: 853 B |
BIN
apps/i2psnark/_icons/page.png
Normal file
After Width: | Height: | Size: 635 B |
BIN
apps/i2psnark/_icons/page_white_acrobat.png
Normal file
After Width: | Height: | Size: 591 B |
BIN
apps/i2psnark/_icons/photo.png
Normal file
After Width: | Height: | Size: 589 B |
BIN
apps/i2psnark/_icons/plugin.png
Normal file
After Width: | Height: | Size: 591 B |
BIN
apps/i2psnark/_icons/tick.png
Normal file
After Width: | Height: | Size: 537 B |
@ -52,8 +52,9 @@
|
||||
<classes dir="./build/obj" includes="**/I2PSnarkServlet*.class" />
|
||||
-->
|
||||
<target name="war" depends="jar, bundle">
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml">
|
||||
<classes dir="./build/obj" includes="**/*.class" excludes="**/RunStandalone.class" />
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml" basedir="../" includes="_icons/*" >
|
||||
<!-- 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/*.class" />
|
||||
</war>
|
||||
</target>
|
||||
|
||||
|
@ -49,7 +49,7 @@ do
|
||||
# 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 \
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
|
@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@ -36,7 +37,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private Log _log = new Log(ConnectionAcceptor.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ConnectionAcceptor.class);
|
||||
private I2PServerSocket serverSocket;
|
||||
private PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
|
@ -4,7 +4,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -23,6 +22,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
@ -45,10 +45,10 @@ public class I2PSnarkUtil {
|
||||
private int _proxyPort;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private Map _opts;
|
||||
private Map<String, String> _opts;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
private final Set _shitlist;
|
||||
private final Set<Hash> _shitlist;
|
||||
private int _maxUploaders;
|
||||
private int _maxUpBW;
|
||||
private int _maxConnections;
|
||||
@ -65,9 +65,9 @@ public class I2PSnarkUtil {
|
||||
_context = ctx;
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
//setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_shitlist = new HashSet(64);
|
||||
_shitlist = new ConcurrentHashSet();
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
_maxUpBW = DEFAULT_MAX_UP_BW;
|
||||
@ -85,6 +85,7 @@ public class I2PSnarkUtil {
|
||||
* host for no proxying)
|
||||
*
|
||||
*/
|
||||
/*****
|
||||
public void setProxy(String host, int port) {
|
||||
if ( (host != null) && (port > 0) ) {
|
||||
_shouldProxy = true;
|
||||
@ -97,6 +98,7 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
_configured = true;
|
||||
}
|
||||
******/
|
||||
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
@ -128,7 +130,7 @@ public class I2PSnarkUtil {
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public Map<String, String> getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
@ -187,18 +189,15 @@ public class I2PSnarkUtil {
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
Hash dest = peer.getAddress().calculateHash();
|
||||
synchronized (_shitlist) {
|
||||
if (_shitlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
|
||||
}
|
||||
if (_shitlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
|
||||
try {
|
||||
I2PSocket rv = _manager.connect(peer.getAddress());
|
||||
if (rv != null) synchronized (_shitlist) { _shitlist.remove(dest); }
|
||||
if (rv != null)
|
||||
_shitlist.remove(dest);
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.add(dest);
|
||||
}
|
||||
_shitlist.add(dest);
|
||||
SimpleScheduler.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
@ -207,7 +206,7 @@ public class I2PSnarkUtil {
|
||||
private class Unshitlist implements SimpleTimer.TimedEvent {
|
||||
private Hash _dest;
|
||||
public Unshitlist(Hash dest) { _dest = dest; }
|
||||
public void timeReached() { synchronized (_shitlist) { _shitlist.remove(_dest); } }
|
||||
public void timeReached() { _shitlist.remove(_dest); }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,7 +357,7 @@ public class I2PSnarkUtil {
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
|
||||
if (rv.size() <= 0)
|
||||
if (rv.isEmpty())
|
||||
return null;
|
||||
return rv;
|
||||
}
|
||||
@ -431,4 +430,9 @@ public class I2PSnarkUtil {
|
||||
public String getString(String s, Object o, Object o2) {
|
||||
return Translate.getString(s, o, o2, _context, BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** ngettext @since 0.7.14 */
|
||||
public String getString(int n, String s, String p) {
|
||||
return Translate.getString(n, s, p, _context, BUNDLE_NAME);
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.SHA1;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.Log;
|
||||
@ -47,7 +48,7 @@ import org.klomp.snark.bencode.InvalidBEncodingException;
|
||||
*/
|
||||
public class MetaInfo
|
||||
{
|
||||
private static final Log _log = new Log(MetaInfo.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(MetaInfo.class);
|
||||
private final String announce;
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
|
@ -388,6 +388,7 @@ public class Peer implements Comparable
|
||||
* Sets whether or not we are interested in pieces from this peer.
|
||||
* Defaults to false. When interest is true and this peer unchokes
|
||||
* us then we start downloading from it. Has no effect when not connected.
|
||||
* @deprecated unused
|
||||
*/
|
||||
public void setInteresting(boolean interest)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ import java.io.OutputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
@ -41,7 +42,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
{
|
||||
private static final Log _log = new Log(PeerAcceptor.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerAcceptor.class);
|
||||
private final PeerCoordinator coordinator;
|
||||
final PeerCoordinatorSet coordinators;
|
||||
|
||||
|
@ -26,6 +26,8 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* TimerTask that checks for good/bad up/downloader. Works together
|
||||
* with the PeerCoordinator to select which Peers get (un)choked.
|
||||
@ -43,7 +45,7 @@ class PeerCheckerTask extends TimerTask
|
||||
this.coordinator = coordinator;
|
||||
}
|
||||
|
||||
private Random random = new Random();
|
||||
private static final Random random = I2PAppContext.getGlobalContext().random();
|
||||
|
||||
public void run()
|
||||
{
|
||||
@ -113,7 +115,7 @@ class PeerCheckerTask extends TimerTask
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
|
||||
// Choke half of them rather than all so it isn't so drastic...
|
||||
// Choke a percentage of them rather than all so it isn't so drastic...
|
||||
// unless this torrent is over the limit all by itself.
|
||||
boolean overBWLimitChoke = upload > 0 &&
|
||||
((overBWLimit && random.nextBoolean()) ||
|
||||
|
@ -23,11 +23,12 @@ package org.klomp.snark;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerConnectionIn implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionIn.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerConnectionIn.class);
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
@ -129,7 +130,7 @@ class PeerConnectionIn implements Runnable
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) + ": " + ps.bitfield);
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
@ -33,7 +34,7 @@ import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionOut.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerConnectionOut.class);
|
||||
private final Peer peer;
|
||||
private final DataOutputStream dout;
|
||||
|
||||
@ -141,7 +142,7 @@ class PeerConnectionOut implements Runnable
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && sendQueue.size() > 0) {
|
||||
if (m == null && !sendQueue.isEmpty()) {
|
||||
m = (Message)sendQueue.remove(0);
|
||||
SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
@ -151,7 +152,11 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
|
||||
m.sendMessage(dout);
|
||||
|
||||
// This can block for quite a while.
|
||||
// To help get slow peers going, and track the bandwidth better,
|
||||
// move this _after_ state.uploaded() and see how it works.
|
||||
//m.sendMessage(dout);
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
@ -159,9 +164,22 @@ class PeerConnectionOut implements Runnable
|
||||
removeMessage(Message.PIECE);
|
||||
|
||||
// XXX - Should also register overhead...
|
||||
if (m.type == Message.PIECE)
|
||||
state.uploaded(m.len);
|
||||
// Don't let other clients requesting big chunks get an advantage
|
||||
// when we are seeding;
|
||||
// only count the rest of the upload after sendMessage().
|
||||
int remainder = 0;
|
||||
if (m.type == Message.PIECE) {
|
||||
if (m.len <= PeerState.PARTSIZE) {
|
||||
state.uploaded(m.len);
|
||||
} else {
|
||||
state.uploaded(PeerState.PARTSIZE);
|
||||
remainder = m.len - PeerState.PARTSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
m.sendMessage(dout);
|
||||
if (remainder > 0)
|
||||
state.uploaded(remainder);
|
||||
m = null;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -37,7 +38,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = new Log(PeerCoordinator.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
|
||||
final MetaInfo metainfo;
|
||||
final Storage storage;
|
||||
final Snark snark;
|
||||
@ -96,7 +97,7 @@ public class PeerCoordinator implements PeerListener
|
||||
// Install a timer to check the uploaders.
|
||||
// Randomize the first start time so multiple tasks are spread out,
|
||||
// this will help the behavior with global limits
|
||||
Random r = new Random();
|
||||
Random r = I2PAppContext.getGlobalContext().random();
|
||||
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
|
||||
}
|
||||
|
||||
@ -240,15 +241,18 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
/** reduce max if huge pieces to keep from ooming */
|
||||
/**
|
||||
* Reduce max if huge pieces to keep from ooming when leeching
|
||||
* @return 512K: 16; 1M: 11; 2M: 6
|
||||
*/
|
||||
private int getMaxConnections() {
|
||||
int size = metainfo.getPieceLength(0);
|
||||
int max = _util.getMaxConnections();
|
||||
if (size <= 1024*1024)
|
||||
if (size <= 512*1024 || completed())
|
||||
return max;
|
||||
if (size <= 2*1024*1024)
|
||||
return (max + 1) / 2;
|
||||
return (max + 3) / 4;
|
||||
if (size <= 1024*1024)
|
||||
return (max + max + 2) / 3;
|
||||
return (max + 2) / 3;
|
||||
}
|
||||
|
||||
public boolean halted() { return halted; }
|
||||
@ -268,7 +272,7 @@ public class PeerCoordinator implements PeerListener
|
||||
peerCount = 0;
|
||||
}
|
||||
|
||||
while (removed.size() > 0) {
|
||||
while (!removed.isEmpty()) {
|
||||
Peer peer = (Peer)removed.remove(0);
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
@ -424,7 +428,7 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < maxUploaders && interested.size() > 0)
|
||||
while (uploaders < maxUploaders && !interested.isEmpty())
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -485,6 +489,13 @@ public class PeerCoordinator implements PeerListener
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be somewhat less than the max conns per torrent,
|
||||
* but not too much less, so a torrent doesn't get stuck near the end.
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static final int END_GAME_THRESHOLD = 8;
|
||||
|
||||
/**
|
||||
* Returns one of pieces in the given BitField that is still wanted or
|
||||
* -1 if none of the given pieces are wanted.
|
||||
@ -518,6 +529,11 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
// AND if there are almost no wanted pieces left (real end game).
|
||||
// If we do end game all the time, we generate lots of extra traffic
|
||||
// when the seeder is super-slow and all the peers are "caught up"
|
||||
if (wantedPieces.size() > END_GAME_THRESHOLD)
|
||||
return -1; // nothing to request and not in end game
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
Iterator it2 = requested.iterator();
|
||||
|
@ -24,11 +24,12 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerState
|
||||
{
|
||||
private Log _log = new Log(PeerState.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerState.class);
|
||||
final Peer peer;
|
||||
final PeerListener listener;
|
||||
final MetaInfo metainfo;
|
||||
@ -152,7 +153,16 @@ class PeerState
|
||||
// XXX - Check for weird bitfield and disconnect?
|
||||
bitfield = new BitField(bitmap, metainfo.getPieces());
|
||||
}
|
||||
setInteresting(listener.gotBitField(peer, bitfield));
|
||||
boolean interest = listener.gotBitField(peer, bitfield);
|
||||
setInteresting(interest);
|
||||
if (bitfield.complete() && !interest) {
|
||||
// They are seeding and we are seeding,
|
||||
// why did they contact us? (robert)
|
||||
// Dump them quick before we send our whole bitmap
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Disconnecting seed that connects to seeds: " + peer);
|
||||
peer.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
void requestMessage(int piece, int begin, int length)
|
||||
@ -186,6 +196,7 @@ class PeerState
|
||||
|
||||
// Limit total pipelined requests to MAX_PIPELINE bytes
|
||||
// to conserve memory and prevent DOS
|
||||
// Todo: limit number of requests also? (robert 64 x 4KB)
|
||||
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
|
@ -321,7 +321,7 @@ public class Snark
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
id = new byte[20];
|
||||
Random random = new Random();
|
||||
Random random = I2PAppContext.getGlobalContext().random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
id[i] = 0;
|
||||
@ -618,14 +618,14 @@ public class Snark
|
||||
command_interpreter = false;
|
||||
i++;
|
||||
}
|
||||
else if (args[i].equals("--eepproxy"))
|
||||
{
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
if (!configured)
|
||||
util.setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
//else if (args[i].equals("--eepproxy"))
|
||||
// {
|
||||
// String proxyHost = args[i+1];
|
||||
// String proxyPort = args[i+2];
|
||||
// if (!configured)
|
||||
// util.setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
// i += 3;
|
||||
// }
|
||||
else if (args[i].equals("--i2cp"))
|
||||
{
|
||||
String i2cpHost = args[i+1];
|
||||
@ -734,7 +734,7 @@ public class Snark
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
stopTorrent();
|
||||
throw new RuntimeException("die bart die");
|
||||
throw new RuntimeException(s + (t == null ? "" : ": " + t));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,8 +29,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
private static SnarkManager _instance = new SnarkManager();
|
||||
public static SnarkManager instance() { return _instance; }
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private final Map _snarks;
|
||||
/** map of (canonical) filename of the .torrent file to Snark instance (unsynchronized) */
|
||||
private final Map<String, Snark> _snarks;
|
||||
private final Object _addSnarkLock;
|
||||
private /* FIXME final FIXME */ File _configFile;
|
||||
private Properties _config;
|
||||
@ -40,12 +40,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
private I2PSnarkUtil _util;
|
||||
private PeerCoordinatorSet _peerCoordinatorSet;
|
||||
private ConnectionAcceptor _connectionAcceptor;
|
||||
private Thread _monitor;
|
||||
private boolean _running;
|
||||
|
||||
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
|
||||
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
|
||||
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
|
||||
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
//public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
//public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
|
||||
public static final String PROP_UPBW_MAX = "i2psnark.upbw.max";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
@ -78,15 +80,22 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* for i2cp host/port or i2psnark.dir
|
||||
*/
|
||||
public void start() {
|
||||
_running = true;
|
||||
_peerCoordinatorSet = new PeerCoordinatorSet();
|
||||
_connectionAcceptor = new ConnectionAcceptor(_util);
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add(_("Adding torrents in {0} minutes", minutes));
|
||||
I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
_monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
|
||||
_monitor.start();
|
||||
_context.addShutdownTask(new SnarkManagerShutdown());
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
_running = false;
|
||||
_monitor.interrupt();
|
||||
_connectionAcceptor.halt();
|
||||
(new SnarkManagerShutdown()).run();
|
||||
}
|
||||
|
||||
/** hook to I2PSnarkUtil for the servlet */
|
||||
public I2PSnarkUtil util() { return _util; }
|
||||
@ -148,10 +157,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_I2CP_OPTS))
|
||||
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=2 inbound.lengthVariance=0 outbound.length=2 outbound.lengthVariance=0 inbound.quantity=3 outbound.quantity=3");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "127.0.0.1");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
_config.setProperty(PROP_EEP_PORT, "4444");
|
||||
//if (!_config.containsKey(PROP_EEP_HOST))
|
||||
// _config.setProperty(PROP_EEP_HOST, "127.0.0.1");
|
||||
//if (!_config.containsKey(PROP_EEP_PORT))
|
||||
// _config.setProperty(PROP_EEP_PORT, "4444");
|
||||
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
@ -189,15 +198,16 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_log.debug("Configuring with I2CP options " + i2cpOpts);
|
||||
}
|
||||
//I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
|
||||
String eepHost = _config.getProperty(PROP_EEP_HOST);
|
||||
int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
if (eepHost != null)
|
||||
_util.setProxy(eepHost, eepPort);
|
||||
//String eepHost = _config.getProperty(PROP_EEP_HOST);
|
||||
//int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
//if (eepHost != null)
|
||||
// _util.setProxy(eepHost, eepPort);
|
||||
_util.setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
|
||||
_util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
|
||||
String ot = _config.getProperty(I2PSnarkUtil.PROP_OPENTRACKERS);
|
||||
if (ot != null)
|
||||
_util.setOpenTrackerString(ot);
|
||||
// FIXME set util use open trackers property somehow
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
@ -216,20 +226,20 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
// unused, we use socket eepget
|
||||
int port = _util.getEepProxyPort();
|
||||
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
|
||||
String host = _util.getEepProxyHost();
|
||||
if ( (eepHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) {
|
||||
_util.setProxy(eepHost, port);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_EEP_HOST, eepHost);
|
||||
_config.setProperty(PROP_EEP_PORT, eepPort+"");
|
||||
addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
}
|
||||
}
|
||||
//if (eepHost != null) {
|
||||
// // unused, we use socket eepget
|
||||
// int port = _util.getEepProxyPort();
|
||||
// try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
|
||||
// String host = _util.getEepProxyHost();
|
||||
// if ( (eepHost.trim().length() > 0) && (port > 0) &&
|
||||
// ((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) {
|
||||
// _util.setProxy(eepHost, port);
|
||||
// changed = true;
|
||||
// _config.setProperty(PROP_EEP_HOST, eepHost);
|
||||
// _config.setProperty(PROP_EEP_PORT, eepPort+"");
|
||||
// addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
// }
|
||||
//}
|
||||
if (upLimit != null) {
|
||||
int limit = _util.getMaxUploaders();
|
||||
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
|
||||
@ -298,7 +308,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
if (snarksActive) {
|
||||
addMessage(_("Cannot change the I2CP settings while torrents are active"));
|
||||
Properties p = new Properties();
|
||||
p.putAll(opts);
|
||||
_util.setI2CPConfig(i2cpHost, port, p);
|
||||
addMessage(_("I2CP and tunnel changes will take effect after stopping all torrents"));
|
||||
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
|
||||
+ "] oldOpts [" + oldOpts + "]");
|
||||
} else {
|
||||
@ -383,12 +396,30 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
|
||||
private static final int MAX_FILES_PER_TORRENT = 512;
|
||||
|
||||
/** set of filenames that we are dealing with */
|
||||
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
|
||||
/** set of canonical .torrent filenames that we are dealing with */
|
||||
public Set<String> listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
|
||||
|
||||
/**
|
||||
* Grab the torrent given the (canonical) filename
|
||||
* Grab the torrent given the (canonical) filename of the .torrent file
|
||||
* @return Snark or null
|
||||
*/
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
|
||||
/**
|
||||
* Grab the torrent given the base name of the storage
|
||||
* @return Snark or null
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public Snark getTorrentByBaseName(String filename) {
|
||||
synchronized (_snarks) {
|
||||
for (Snark s : _snarks.values()) {
|
||||
if (s.storage.getBaseName().equals(filename))
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if ((!dontAutoStart) && !_util.connected()) {
|
||||
@ -424,10 +455,27 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
} catch (IOException ioe) {
|
||||
// catch this here so we don't try do delete it below
|
||||
addMessage(_("Cannot open \"{0}\"", sfile.getName()) + ": " + ioe.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
MetaInfo info = new MetaInfo(fis);
|
||||
fis.close();
|
||||
fis = null;
|
||||
try {
|
||||
fis.close();
|
||||
fis = null;
|
||||
} catch (IOException e) {}
|
||||
|
||||
if (!TrackerClient.isValidAnnounce(info.getAnnounce())) {
|
||||
if (_util.shouldUseOpenTrackers() && _util.getOpenTrackers() != null) {
|
||||
addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only", info.getName()));
|
||||
} else {
|
||||
addMessage(_("Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!", info.getName()));
|
||||
dontAutoStart = true;
|
||||
}
|
||||
}
|
||||
String rejectMessage = locked_validateTorrent(info);
|
||||
if (rejectMessage != null) {
|
||||
sfile.delete();
|
||||
@ -551,12 +599,10 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning - does not validate announce URL - use TrackerClient.isValidAnnounce()
|
||||
*/
|
||||
private String locked_validateTorrent(MetaInfo info) throws IOException {
|
||||
String announce = info.getAnnounce();
|
||||
// basic validation of url
|
||||
if ((!announce.startsWith("http://")) ||
|
||||
(announce.indexOf(".i2p/") < 0)) // need to do better than this
|
||||
return _("Non-i2p tracker in \"{0}\", deleting it from our list of trackers!", info.getName());
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return _("Too many files in \"{0}\" ({1}), deleting it!", info.getName(), files.size());
|
||||
@ -567,8 +613,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} else if (info.getPieces() > Storage.MAX_PIECES) {
|
||||
return _("Too many pieces in \"{0}\", limit is {1}, deleting it!", info.getName(), Storage.MAX_PIECES);
|
||||
} else if (info.getPieceLength(0) > Storage.MAX_PIECE_SIZE) {
|
||||
return _("Pieces are too large in \"{0}\" ({1}B), deleting it.", info.getName(), DataHelper.formatSize(info.getPieceLength(0))) + ' ' +
|
||||
_("Limit is {0}B", DataHelper.formatSize(Storage.MAX_PIECE_SIZE));
|
||||
return _("Pieces are too large in \"{0}\" ({1}B), deleting it.", info.getName(), DataHelper.formatSize2(info.getPieceLength(0))) + ' ' +
|
||||
_("Limit is {0}B", DataHelper.formatSize2(Storage.MAX_PIECE_SIZE));
|
||||
} else if (info.getTotalLength() > Storage.MAX_TOTAL_SIZE) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
List lengths = info.getLengths();
|
||||
@ -661,7 +707,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public void torrentComplete(Snark snark) {
|
||||
File f = new File(snark.torrent);
|
||||
long len = snark.meta.getTotalLength();
|
||||
addMessage(_("Download finished: \"{0}\"", f.getName()) + " (" + _("size: {0}B", DataHelper.formatSize(len)) + ')');
|
||||
addMessage(_("Download finished: \"{0}\"", f.getName()) + " (" + _("size: {0}B", DataHelper.formatSize2(len)) + ')');
|
||||
updateStatus(snark);
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,50 @@ public class Storage
|
||||
return needed == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file canonical path (non-directory)
|
||||
* @return number of bytes remaining; -1 if unknown file
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public long remaining(String file) {
|
||||
long bytes = 0;
|
||||
for (int i = 0; i < rafs.length; i++) {
|
||||
File f = RAFfile[i];
|
||||
// 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 = metainfo.getPieceLength(0);
|
||||
long start = bytes;
|
||||
long end = start + lengths[i];
|
||||
int pc = (int) (bytes / psz);
|
||||
long rv = 0;
|
||||
if (!bitfield.get(pc))
|
||||
rv = Math.min(psz - (start % psz), lengths[i]);
|
||||
int pieces = metainfo.getPieces();
|
||||
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
|
||||
if (!bitfield.get(j)) {
|
||||
if (((long)(j+1))*psz < end)
|
||||
rv += psz;
|
||||
else
|
||||
rv += end - (((long)j) * psz);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
bytes += lengths[i];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BitField that tells which pieces this storage contains.
|
||||
* Do not change this since this is the current state of the storage.
|
||||
@ -295,6 +339,18 @@ public class Storage
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base file or directory name of the data,
|
||||
* as specified in the .torrent file, but filtered to remove
|
||||
* illegal characters. This is where the data actually is,
|
||||
* relative to the snark base dir.
|
||||
*
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public String getBaseName() {
|
||||
return filterName(metainfo.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
@ -420,13 +476,29 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
private static final char[] ILLEGAL = new char[] {
|
||||
'<', '>', ':', '"', '/', '\\', '|', '?', '*',
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
* Removes 'suspicious' characters from the given file name.
|
||||
* http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
|
||||
*/
|
||||
private static String filterName(String name)
|
||||
{
|
||||
// XXX - Is this enough?
|
||||
return name.replace(File.separatorChar, '_');
|
||||
if (name.equals(".") || name.equals(" "))
|
||||
return "_";
|
||||
String rv = name;
|
||||
if (rv.startsWith("."))
|
||||
rv = '_' + rv.substring(1);
|
||||
if (rv.endsWith(".") || rv.endsWith(" "))
|
||||
rv = rv.substring(0, rv.length() - 1) + '_';
|
||||
for (int i = 0; i < ILLEGAL.length; i++) {
|
||||
if (rv.indexOf(ILLEGAL[i]) >= 0)
|
||||
rv = rv.replace(ILLEGAL[i], '_');
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private File createFileFromNames(File base, List names) throws IOException
|
||||
@ -577,6 +649,9 @@ public class Storage
|
||||
if (rafs == null) return;
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
// if we had an IOE in check(), the RAFlock may be null
|
||||
if (RAFlock[i] == null)
|
||||
continue;
|
||||
try {
|
||||
synchronized(RAFlock[i]) {
|
||||
closeRAF(i);
|
||||
|
@ -24,6 +24,8 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@ -31,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@ -42,7 +45,7 @@ import net.i2p.util.Log;
|
||||
*/
|
||||
public class TrackerClient extends I2PAppThread
|
||||
{
|
||||
private static final Log _log = new Log(TrackerClient.class);
|
||||
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(TrackerClient.class);
|
||||
private static final String NO_EVENT = "";
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
@ -123,13 +126,19 @@ public class TrackerClient extends I2PAppThread
|
||||
// followed by the secondary open trackers
|
||||
// It's painful, but try to make sure if an open tracker is also
|
||||
// the primary tracker, that we don't add it twice.
|
||||
// todo: check for b32 matches as well
|
||||
trackers = new ArrayList(2);
|
||||
trackers.add(new Tracker(meta.getAnnounce(), true));
|
||||
String primary = meta.getAnnounce();
|
||||
if (isValidAnnounce(primary)) {
|
||||
trackers.add(new Tracker(meta.getAnnounce(), true));
|
||||
} else {
|
||||
_log.warn("Skipping invalid or non-i2p announce: " + primary);
|
||||
}
|
||||
List tlist = _util.getOpenTrackers();
|
||||
if (tlist != null) {
|
||||
for (int i = 0; i < tlist.size(); i++) {
|
||||
String url = (String)tlist.get(i);
|
||||
if (!url.startsWith("http://")) {
|
||||
if (!isValidAnnounce(url)) {
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
@ -138,22 +147,29 @@ public class TrackerClient extends I2PAppThread
|
||||
_log.error("Bad announce URL: [" + url + "]");
|
||||
continue;
|
||||
}
|
||||
if (meta.getAnnounce().startsWith(url.substring(0, slash)))
|
||||
if (primary.startsWith(url.substring(0, slash)))
|
||||
continue;
|
||||
String dest = _util.lookup(url.substring(7, slash));
|
||||
if (dest == null) {
|
||||
_log.error("Announce host unknown: [" + url + "]");
|
||||
_log.error("Announce host unknown: [" + url.substring(7, slash) + "]");
|
||||
continue;
|
||||
}
|
||||
if (meta.getAnnounce().startsWith("http://" + dest))
|
||||
if (primary.startsWith("http://" + dest))
|
||||
continue;
|
||||
if (meta.getAnnounce().startsWith("http://i2p/" + dest))
|
||||
if (primary.startsWith("http://i2p/" + dest))
|
||||
continue;
|
||||
trackers.add(new Tracker(url, false));
|
||||
_log.debug("Additional announce: [" + url + "] for infoHash: " + infoHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (tlist.isEmpty()) {
|
||||
// FIXME really need to get this message to the gui
|
||||
stop = true;
|
||||
_log.error("No valid trackers for infoHash: " + infoHash);
|
||||
return;
|
||||
}
|
||||
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long left = coordinator.getLeft();
|
||||
@ -167,7 +183,7 @@ public class TrackerClient extends I2PAppThread
|
||||
boolean runStarted = false;
|
||||
boolean firstTime = true;
|
||||
int consecutiveFails = 0;
|
||||
Random r = new Random();
|
||||
Random r = I2PAppContext.getGlobalContext().random();
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
@ -243,7 +259,7 @@ public class TrackerClient extends I2PAppThread
|
||||
tr.started = true;
|
||||
|
||||
Set peers = info.getPeers();
|
||||
tr.seenPeers = peers.size();
|
||||
tr.seenPeers = info.getPeerCount();
|
||||
if (coordinator.trackerSeenPeers < tr.seenPeers) // update rising number quickly
|
||||
coordinator.trackerSeenPeers = tr.seenPeers;
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
@ -254,6 +270,7 @@ public class TrackerClient extends I2PAppThread
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
// FIXME if id == us || dest == us continue;
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
@ -341,6 +358,10 @@ public class TrackerClient extends I2PAppThread
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((! event.equals(NO_EVENT)) ? ("&event=" + event) : "");
|
||||
if (left <= 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
|
||||
s += "&numwant=0";
|
||||
else
|
||||
s += "&numwant=" + _util.getMaxConnections();
|
||||
_util.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
tr.lastRequestTime = System.currentTimeMillis();
|
||||
@ -399,7 +420,23 @@ public class TrackerClient extends I2PAppThread
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private class Tracker
|
||||
/**
|
||||
* @return true for i2p hosts only
|
||||
* @since 0.7.12
|
||||
*/
|
||||
static boolean isValidAnnounce(String ann) {
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(ann);
|
||||
} catch (MalformedURLException mue) {
|
||||
return false;
|
||||
}
|
||||
return url.getProtocol().equals("http") &&
|
||||
(url.getHost().endsWith(".i2p") || url.getHost().equals("i2p")) &&
|
||||
url.getPort() < 0;
|
||||
}
|
||||
|
||||
private static class Tracker
|
||||
{
|
||||
String announce;
|
||||
boolean isPrimary;
|
||||
|
@ -23,6 +23,7 @@ package org.klomp.snark;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -37,6 +38,8 @@ public class TrackerInfo
|
||||
private final String failure_reason;
|
||||
private final int interval;
|
||||
private final Set peers;
|
||||
private int complete;
|
||||
private int incomplete;
|
||||
|
||||
public TrackerInfo(InputStream in, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
@ -68,11 +71,26 @@ public class TrackerInfo
|
||||
throw new InvalidBEncodingException("No interval given");
|
||||
else
|
||||
interval = beInterval.getInt();
|
||||
|
||||
BEValue bePeers = (BEValue)m.get("peers");
|
||||
if (bePeers == null)
|
||||
throw new InvalidBEncodingException("No peer list");
|
||||
peers = Collections.EMPTY_SET;
|
||||
else
|
||||
peers = getPeers(bePeers.getList(), my_id, metainfo);
|
||||
|
||||
BEValue bev = (BEValue)m.get("complete");
|
||||
if (bev != null) try {
|
||||
complete = bev.getInt();
|
||||
if (complete < 0)
|
||||
complete = 0;
|
||||
} catch (InvalidBEncodingException ibe) {}
|
||||
|
||||
bev = (BEValue)m.get("incomplete");
|
||||
if (bev != null) try {
|
||||
incomplete = bev.getInt();
|
||||
if (incomplete < 0)
|
||||
incomplete = 0;
|
||||
} catch (InvalidBEncodingException ibe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +133,12 @@ public class TrackerInfo
|
||||
return peers;
|
||||
}
|
||||
|
||||
public int getPeerCount()
|
||||
{
|
||||
int pc = peers == null ? 0 : peers.size();
|
||||
return Math.max(pc, complete + incomplete - 1);
|
||||
}
|
||||
|
||||
public String getFailureReason()
|
||||
{
|
||||
return failure_reason;
|
||||
@ -132,6 +156,8 @@ public class TrackerInfo
|
||||
return "TrackerInfo[FAILED: " + failure_reason + "]";
|
||||
else
|
||||
return "TrackerInfo[interval=" + interval
|
||||
+ (complete > 0 ? (", complete=" + complete) : "" )
|
||||
+ (incomplete > 0 ? (", incomplete=" + incomplete) : "" )
|
||||
+ ", peers=" + peers + "]";
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +279,9 @@ public class BDecoder
|
||||
public BEValue bdecodeMap() throws IOException
|
||||
{
|
||||
int c = getNextIndicator();
|
||||
if (c != 'd')
|
||||
if (c == '<')
|
||||
throw new InvalidBEncodingException("Expected a .torrent metainfo file but found HTML? Check URL or file!");
|
||||
else if (c != 'd')
|
||||
throw new InvalidBEncodingException("Expected 'd', not '"
|
||||
+ (char)c + "'");
|
||||
indicator = 0;
|
||||
|
@ -179,7 +179,7 @@ public class BEValue
|
||||
if (value instanceof byte[])
|
||||
{
|
||||
byte[] bs = (byte[])value;
|
||||
// XXX - Stupid heuristic...
|
||||
// XXX - Stupid heuristic... and not UTF-8
|
||||
if (bs.length <= 12)
|
||||
valueString = new String(bs);
|
||||
else
|
||||
|
@ -7,6 +7,9 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -15,6 +18,7 @@ import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@ -34,20 +38,27 @@ import org.klomp.snark.SnarkManager;
|
||||
import org.klomp.snark.Storage;
|
||||
import org.klomp.snark.TrackerClient;
|
||||
|
||||
import org.mortbay.http.HttpResponse;
|
||||
import org.mortbay.jetty.servlet.Default;
|
||||
import org.mortbay.util.Resource;
|
||||
import org.mortbay.util.URI;
|
||||
|
||||
/**
|
||||
*
|
||||
* We extend Default instead of HTTPServlet so we can handle
|
||||
* i2psnark/ file requests with http:// instead of the flaky and
|
||||
* often-blocked-by-the-browser file://
|
||||
*/
|
||||
public class I2PSnarkServlet extends HttpServlet {
|
||||
public class I2PSnarkServlet extends Default {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private SnarkManager _manager;
|
||||
private static long _nonce;
|
||||
private Resource _resourceBase;
|
||||
|
||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig cfg) throws ServletException {
|
||||
super.init(cfg);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSnarkServlet.class);
|
||||
_nonce = _context.random().nextLong();
|
||||
@ -57,10 +68,82 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
configFile = "i2psnark.config";
|
||||
_manager.loadConfig(configFile);
|
||||
_manager.start();
|
||||
try {
|
||||
_resourceBase = Resource.newResource(_manager.getDataDir().getAbsolutePath());
|
||||
} catch (IOException ioe) {}
|
||||
super.init(cfg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
_manager.stop();
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* We override this instead of passing a resource base to super(), because
|
||||
* if a resource base is set, super.getResource() always uses that base,
|
||||
* and we can't get any resources (like icons) out of the .war
|
||||
*/
|
||||
@Override
|
||||
protected Resource getResource(String pathInContext) throws IOException
|
||||
{
|
||||
if (pathInContext == null || pathInContext.equals("/") || pathInContext.equals("/index.jsp") ||
|
||||
pathInContext.equals("/index.html") || pathInContext.startsWith("/_icons/"))
|
||||
return super.getResource(pathInContext);
|
||||
// files in the i2psnark/ directory
|
||||
return _resourceBase.addPath(pathInContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some parts modified from:
|
||||
* <pre>
|
||||
// ========================================================================
|
||||
// $Id: Default.java,v 1.51 2006/10/08 14:13:18 gregwilkins Exp $
|
||||
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
// this is the part after /i2psnark
|
||||
String path = req.getServletPath();
|
||||
// index.jsp doesn't work, it is grabbed by the war handler before here
|
||||
if (!(path == null || path.equals("/") || path.equals("/index.jsp") || path.equals("/index.html"))) {
|
||||
if (path.endsWith("/")) {
|
||||
// bypass the horrid Resource.getListHTML()
|
||||
String pathInfo = req.getPathInfo();
|
||||
String pathInContext = URI.addPaths(path, pathInfo);
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
Resource resource = getResource(pathInContext);
|
||||
if (resource == null || (!resource.exists()) || !resource.isDirectory()) {
|
||||
resp.sendError(HttpResponse.__404_Not_Found);
|
||||
} else {
|
||||
String base = URI.addPaths(req.getRequestURI(), "/");
|
||||
String listing = getListHTML(resource, base, true);
|
||||
if (listing != null)
|
||||
resp.getWriter().write(listing);
|
||||
else // shouldn't happen
|
||||
resp.sendError(HttpResponse.__404_Not_Found);
|
||||
}
|
||||
} else {
|
||||
super.service(req, resp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
@ -123,7 +206,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String uri = req.getRequestURI();
|
||||
out.write(TABLE_HEADER);
|
||||
out.write(_("Status"));
|
||||
if (_manager.util().connected() && snarks.size() > 0) {
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
out.write(" (<a href=\"");
|
||||
out.write(req.getRequestURI());
|
||||
if (peerParam != null) {
|
||||
@ -157,7 +240,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("\">");
|
||||
out.write(_("Stop All"));
|
||||
out.write("</a>");
|
||||
} else if (snarks.size() > 0) {
|
||||
} else if (!snarks.isEmpty()) {
|
||||
out.write("<a href=\"" + uri + "?action=StartAll&nonce=" + _nonce +
|
||||
"\" title=\"");
|
||||
out.write(_("Start all torrents and the I2P tunnel"));
|
||||
@ -175,7 +258,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
displaySnark(out, snark, uri, i, stats, showPeers, showDebug);
|
||||
}
|
||||
|
||||
if (snarks.size() <= 0) {
|
||||
if (snarks.isEmpty()) {
|
||||
out.write("<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"center\"" +
|
||||
" colspan=\"8\"><i>");
|
||||
@ -186,10 +269,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
" <th align=\"left\" colspan=\"2\">");
|
||||
out.write(_("Totals"));
|
||||
out.write(" (");
|
||||
out.write(_("{0} torrents", snarks.size()));
|
||||
out.write(ngettext("1 torrent", "{0} torrents", snarks.size()));
|
||||
out.write(", ");
|
||||
out.write(DataHelper.formatSize(stats[5]) + "B, ");
|
||||
out.write(_("{0} connected peers", stats[4]));
|
||||
out.write(DataHelper.formatSize2(stats[5]) + "B, ");
|
||||
out.write(ngettext("1 connected peer", "{0} connected peers", (int) stats[4]));
|
||||
out.write(")</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th align=\"right\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
@ -251,7 +334,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
} else if (newURL != null) {
|
||||
if (newURL.startsWith("http://")) {
|
||||
_manager.addMessage(_("Fetching {0}", newURL));
|
||||
_manager.addMessage(_("Fetching {0}", urlify(newURL)));
|
||||
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
|
||||
fetch.start();
|
||||
} else {
|
||||
@ -365,7 +448,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String eepPort = req.getParameter("eepPort");
|
||||
String i2cpHost = req.getParameter("i2cpHost");
|
||||
String i2cpPort = req.getParameter("i2cpPort");
|
||||
String i2cpOpts = req.getParameter("i2cpOpts");
|
||||
String i2cpOpts = buildI2CPOpts(req);
|
||||
String upLimit = req.getParameter("upLimit");
|
||||
String upBW = req.getParameter("upBW");
|
||||
boolean useOpenTrackers = req.getParameter("useOpenTrackers") != null;
|
||||
@ -432,11 +515,53 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private List getSortedSnarks(HttpServletRequest req) {
|
||||
Set files = _manager.listTorrentFiles();
|
||||
TreeSet fileNames = new TreeSet(Collator.getInstance()); // sorts it alphabetically
|
||||
private static final String iopts[] = {"inbound.length", "inbound.quantity",
|
||||
"outbound.length", "outbound.quantity" };
|
||||
|
||||
/** put the individual i2cp selections into the option string */
|
||||
private static String buildI2CPOpts(HttpServletRequest req) {
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
String p = req.getParameter("i2cpOpts");
|
||||
if (p != null)
|
||||
buf.append(p);
|
||||
for (int i = 0; i < iopts.length; i++) {
|
||||
p = req.getParameter(iopts[i]);
|
||||
if (p != null)
|
||||
buf.append(' ').append(iopts[i]).append('=').append(p);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading "the "
|
||||
* (I guess this is worth it, a lot of torrents start with "The "
|
||||
* These are full path names which makes it harder
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private class TorrentNameComparator implements Comparator<String> {
|
||||
private final Comparator collator = Collator.getInstance();
|
||||
private final String skip = _manager.getDataDir().getAbsolutePath() + File.separator;
|
||||
|
||||
public int compare(String l, String r) {
|
||||
if (l.startsWith(skip))
|
||||
l = l.substring(skip.length());
|
||||
if (r.startsWith(skip))
|
||||
r = r.substring(skip.length());
|
||||
String llc = l.toLowerCase();
|
||||
if (llc.startsWith("the ") || llc.startsWith("the."))
|
||||
l = l.substring(4);
|
||||
String rlc = r.toLowerCase();
|
||||
if (rlc.startsWith("the ") || rlc.startsWith("the."))
|
||||
r = r.substring(4);
|
||||
return collator.compare(l, r);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Snark> getSortedSnarks(HttpServletRequest req) {
|
||||
Set<String> files = _manager.listTorrentFiles();
|
||||
TreeSet<String> fileNames = new TreeSet(new TorrentNameComparator());
|
||||
fileNames.addAll(files);
|
||||
ArrayList rv = new ArrayList(fileNames.size());
|
||||
ArrayList<Snark> rv = new ArrayList(fileNames.size());
|
||||
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
@ -455,8 +580,11 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
int i = filename.lastIndexOf(".torrent");
|
||||
if (i > 0)
|
||||
filename = filename.substring(0, i);
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
String fullFilename = filename;
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH) {
|
||||
fullFilename = new String(filename);
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "…";
|
||||
}
|
||||
long total = snark.meta.getTotalLength();
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
|
||||
@ -496,17 +624,19 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
err = snark.coordinator.trackerProblems;
|
||||
curPeers = snark.coordinator.getPeerCount();
|
||||
stats[4] += curPeers;
|
||||
knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
knownPeers = Math.max(curPeers, snark.coordinator.trackerSeenPeers);
|
||||
}
|
||||
|
||||
String statusString = _("Unknown");
|
||||
if (err != null) {
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + "</a> (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
|
||||
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
|
||||
curPeers + '/' +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
|
||||
else if (isRunning)
|
||||
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + " (" + curPeers + '/' + knownPeers + ' ' + _("peers") + ')';
|
||||
statusString = "<a title=\"" + err + "\">" + _("TrackerErr") + " (" + curPeers + '/' +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + ')';
|
||||
else {
|
||||
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
|
||||
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "…";
|
||||
@ -515,25 +645,31 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = _("Seeding") + " (" +
|
||||
curPeers + '/' + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
|
||||
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
|
||||
curPeers + '/' +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
|
||||
else if (isRunning)
|
||||
statusString = _("Seeding") + " (" + curPeers + "/" + knownPeers + ' ' + _("peers") + ')';
|
||||
statusString = _("Seeding") + " (" + curPeers + "/" +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + ')';
|
||||
else
|
||||
statusString = _("Complete");
|
||||
} else {
|
||||
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
|
||||
statusString = _("OK") + " (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
|
||||
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
|
||||
curPeers + "/" +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
|
||||
else if (isRunning && curPeers > 0 && downBps > 0)
|
||||
statusString = _("OK") + " (" + curPeers + "/" + knownPeers + ' ' + _("peers") + ')';
|
||||
statusString = _("OK") + " (" + curPeers + "/" +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + ')';
|
||||
else if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = _("Stalled") + " (" +
|
||||
curPeers + '/' + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + _("peers") + "</a>)";
|
||||
"<a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" +
|
||||
curPeers + '/' +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>)";
|
||||
else if (isRunning && curPeers > 0)
|
||||
statusString = _("Stalled") + " (" + curPeers + '/' + knownPeers + ' ' + _("peers") + ')';
|
||||
statusString = _("Stalled") + " (" + curPeers + '/' +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + ')';
|
||||
else if (isRunning)
|
||||
statusString = _("No Peers") + " (0/" + knownPeers + ')';
|
||||
else
|
||||
@ -546,17 +682,25 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write(statusString + "</td>\n\t");
|
||||
out.write("<td align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
|
||||
|
||||
if (remaining == 0) {
|
||||
out.write("<a href=\"" + _manager.linkPrefix() + snark.meta.getName()
|
||||
+ "\" title=\"");
|
||||
if (remaining == 0 || snark.meta.getFiles() != null) {
|
||||
out.write("<a href=\"" + snark.storage.getBaseName());
|
||||
if (snark.meta.getFiles() != null)
|
||||
out.write("/");
|
||||
out.write("\" title=\"");
|
||||
if (snark.meta.getFiles() != null)
|
||||
out.write(_("View files"));
|
||||
else
|
||||
out.write(_("Open file"));
|
||||
out.write("\">");
|
||||
}
|
||||
String icon;
|
||||
if (snark.meta.getFiles() != null)
|
||||
icon = "folder";
|
||||
else
|
||||
icon = toIcon(snark.meta.getName());
|
||||
out.write(toImg(icon));
|
||||
out.write(filename);
|
||||
if (remaining == 0)
|
||||
if (remaining == 0 || snark.meta.getFiles() != null)
|
||||
out.write("</a>");
|
||||
// temporarily hardcoded for postman* and anonymity, requires bytemonsoon patch for lookup by info_hash
|
||||
String announce = snark.meta.getAnnounce();
|
||||
@ -590,7 +734,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0)
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
out.write(formatSize(total-remaining) + " / " + formatSize(total)); // 18MB/3GB
|
||||
else
|
||||
out.write(formatSize(total)); // 3GB
|
||||
out.write("</td>\n\t");
|
||||
@ -627,13 +771,23 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("<a href=\"" + uri + "?action=Remove" + parameters
|
||||
+ "\" title=\"");
|
||||
out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
|
||||
out.write("\">");
|
||||
out.write("\" onclick=\"if (!confirm('");
|
||||
// Can't figure out how to escape double quotes inside the onclick string.
|
||||
// Single quotes in translate strings with parameters must be doubled.
|
||||
// Then the remaining single quite must be escaped
|
||||
out.write(_("Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?", fullFilename));
|
||||
out.write("')) { return false; }\">");
|
||||
out.write(_("Remove"));
|
||||
out.write("</a><br>");
|
||||
out.write("<a href=\"" + uri + "?action=Delete" + parameters
|
||||
+ "\" title=\"");
|
||||
out.write(_("Delete the .torrent file and the associated data file(s)"));
|
||||
out.write("\">");
|
||||
out.write("\" onclick=\"if (!confirm('");
|
||||
// Can't figure out how to escape double quotes inside the onclick string.
|
||||
// Single quotes in translate strings with parameters must be doubled.
|
||||
// Then the remaining single quite must be escaped
|
||||
out.write(_("Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?", fullFilename));
|
||||
out.write("')) { return false; }\">");
|
||||
out.write(_("Delete"));
|
||||
out.write("</a>");
|
||||
}
|
||||
@ -668,7 +822,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
client = "Transmission";
|
||||
else
|
||||
client = _("Unknown") + " (" + ch + ')';
|
||||
out.write(client + " " + peer.toString().substring(5, 9));
|
||||
out.write(client + " <tt>" + peer.toString().substring(5, 9)+ "</tt>");
|
||||
if (showDebug)
|
||||
out.write(" inactive " + (peer.getInactiveTime() / 1000) + "s");
|
||||
out.write("</td>\n\t");
|
||||
@ -774,7 +928,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br>\n");
|
||||
out.write(_("Data to seed"));
|
||||
out.write(":<td>" + _manager.getDataDir().getAbsolutePath() + File.separatorChar
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"40\" value=\"" + baseFile
|
||||
+ "\" title=\"");
|
||||
out.write(_("File or directory to seed (must be within the specified path)"));
|
||||
out.write("\" ><tr><td>\n");
|
||||
@ -857,14 +1011,14 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
*/
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Total uploader limit"));
|
||||
out.write(": <td><input type=\"text\" name=\"upLimit\" value=\""
|
||||
out.write(": <td><input type=\"text\" name=\"upLimit\" class=\"r\" value=\""
|
||||
+ _manager.util().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" > ");
|
||||
out.write(_("peers"));
|
||||
out.write("<br>\n");
|
||||
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Up bandwidth limit"));
|
||||
out.write(": <td><input type=\"text\" name=\"upBW\" value=\""
|
||||
out.write(": <td><input type=\"text\" name=\"upBW\" class=\"r\" value=\""
|
||||
+ _manager.util().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" > KBps <i>(");
|
||||
out.write(_("Half available bandwidth recommended."));
|
||||
out.write(" <a href=\"/config.jsp\" target=\"blank\">");
|
||||
@ -890,6 +1044,20 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
//out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
|
||||
// + _manager.util().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br>\n");
|
||||
|
||||
Map<String, String> options = new TreeMap(_manager.util().getI2CPOptions());
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Inbound Settings"));
|
||||
out.write(":<td>");
|
||||
out.write(renderOptions(1, 6, options.remove("inbound.quantity"), "inbound.quantity", TUNNEL));
|
||||
out.write(" ");
|
||||
out.write(renderOptions(0, 4, options.remove("inbound.length"), "inbound.length", HOP));
|
||||
out.write("<tr><td>");
|
||||
out.write(_("Outbound Settings"));
|
||||
out.write(":<td>");
|
||||
out.write(renderOptions(1, 6, options.remove("outbound.quantity"), "outbound.quantity", TUNNEL));
|
||||
out.write(" ");
|
||||
out.write(renderOptions(0, 4, options.remove("outbound.length"), "outbound.length", HOP));
|
||||
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP host"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
@ -897,11 +1065,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
|
||||
out.write("<tr><td>");
|
||||
out.write(_("I2CP port"));
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpPort\" value=\"" +
|
||||
out.write(": <td><input type=\"text\" name=\"i2cpPort\" class=\"r\" value=\"" +
|
||||
+ _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" > <br>\n");
|
||||
|
||||
StringBuilder opts = new StringBuilder(64);
|
||||
Map options = new TreeMap(_manager.util().getI2CPOptions());
|
||||
for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
String key = (String)entry.getKey();
|
||||
@ -920,6 +1087,36 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</form></div>");
|
||||
}
|
||||
|
||||
/** copied from ConfigTunnelsHelper */
|
||||
private static final String HOP = "hop";
|
||||
private static final String TUNNEL = "tunnel";
|
||||
/** dummies for translation */
|
||||
private static final String HOPS = ngettext("1 hop", "{0} hops");
|
||||
private static final String TUNNELS = ngettext("1 tunnel", "{0} tunnels");
|
||||
/** prevents the ngettext line below from getting tagged */
|
||||
private static final String DUMMY0 = "{0} ";
|
||||
private static final String DUMMY1 = "1 ";
|
||||
|
||||
/** modded from ConfigTunnelsHelper @since 0.7.14 */
|
||||
private String renderOptions(int min, int max, String strNow, String selName, String name) {
|
||||
int now = 2;
|
||||
try {
|
||||
now = Integer.parseInt(strNow);
|
||||
} catch (Throwable t) {}
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
buf.append("<select name=\"").append(selName).append("\">\n");
|
||||
for (int i = min; i <= max; i++) {
|
||||
buf.append("<option value=\"").append(i).append("\" ");
|
||||
if (i == now)
|
||||
buf.append("selected=\"true\" ");
|
||||
// constants to prevent tagging
|
||||
buf.append(">").append(ngettext(DUMMY1 + name, DUMMY0 + name + 's', i));
|
||||
buf.append("</option>\n");
|
||||
}
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** translate */
|
||||
private String _(String s) {
|
||||
return _manager.util().getString(s);
|
||||
@ -930,19 +1127,36 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
return _manager.util().getString(s, o);
|
||||
}
|
||||
|
||||
/** translate (ngettext) @since 0.7.14 */
|
||||
private String ngettext(String s, String p, int n) {
|
||||
return _manager.util().getString(n, s, p);
|
||||
}
|
||||
|
||||
/** dummy for tagging */
|
||||
private static String ngettext(String s, String p) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// rounding makes us look faster :)
|
||||
private String formatSize(long bytes) {
|
||||
private static String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + "B";
|
||||
return bytes + " B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return ((bytes + 512)/1024) + "KB";
|
||||
return ((bytes + 512)/1024) + " KB";
|
||||
else if (bytes < 10*1024*1024*1024l)
|
||||
return ((bytes + 512*1024)/(1024*1024)) + "MB";
|
||||
return ((bytes + 512*1024)/(1024*1024)) + " MB";
|
||||
else
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + " GB";
|
||||
}
|
||||
|
||||
private static final String HEADER = "<link href=\"../themes/console/snark.css\" rel=\"stylesheet\" type=\"text/css\" >";
|
||||
/** @since 0.7.14 */
|
||||
private static String urlify(String s) {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
buf.append("<a href=\"").append(s).append("\">").append(s).append("</a>");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final String HEADER = "<link href=\"/themes/console/snark.css\" rel=\"stylesheet\" type=\"text/css\" >";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
|
||||
@ -953,6 +1167,231 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
|
||||
private static final String FOOTER = "</div></div></div></center></body></html>";
|
||||
|
||||
/**
|
||||
* Modded heavily from the Jetty version in Resource.java,
|
||||
* pass Resource as 1st param
|
||||
* All the xxxResource constructors are package local so we can't extend them.
|
||||
*
|
||||
* <pre>
|
||||
// ========================================================================
|
||||
// $Id: Resource.java,v 1.32 2009/05/16 01:53:36 gregwilkins Exp $
|
||||
// Copyright 1996-2004 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
* </pre>
|
||||
*
|
||||
* Get the resource list as a HTML directory listing.
|
||||
* @param r The Resource
|
||||
* @param base The base URL
|
||||
* @param parent True if the parent directory should be included
|
||||
* @return String of HTML
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String getListHTML(Resource r, String base, boolean parent)
|
||||
throws IOException
|
||||
{
|
||||
if (!r.isDirectory())
|
||||
return null;
|
||||
|
||||
String[] ls = r.list();
|
||||
if (ls==null)
|
||||
return null;
|
||||
Arrays.sort(ls, Collator.getInstance());
|
||||
|
||||
StringBuilder buf=new StringBuilder(4096);
|
||||
buf.append("<HTML><HEAD><TITLE>");
|
||||
String title = URI.decodePath(base);
|
||||
if (title.startsWith("/i2psnark/"))
|
||||
title = title.substring("/i2psnark/".length());
|
||||
|
||||
// Get the snark associated with this directory
|
||||
String torrentName;
|
||||
int slash = title.indexOf('/');
|
||||
if (slash > 0)
|
||||
torrentName = title.substring(0, slash);
|
||||
else
|
||||
torrentName = title;
|
||||
Snark snark = _manager.getTorrentByBaseName(torrentName);
|
||||
if (title.endsWith("/"))
|
||||
title = title.substring(0, title.length() - 1);
|
||||
title = _("Torrent") + ": " + title;
|
||||
buf.append(title);
|
||||
buf.append("</TITLE>").append(HEADER).append("</HEAD><BODY>\n<div class=\"snarknavbar\">");
|
||||
buf.append(title);
|
||||
|
||||
if (parent)
|
||||
{
|
||||
buf.append("\n<br><A HREF=\"");
|
||||
// corrupts utf-8
|
||||
//buf.append(URI.encodePath(URI.addPaths(base,"../")));
|
||||
buf.append(URI.addPaths(base,"../"));
|
||||
buf.append("\"><img border=\"0\" src=\"/themes/console/images/outbound.png\"> ")
|
||||
.append(_("Up to higher level directory")).append("</A>\n");
|
||||
}
|
||||
|
||||
buf.append("</div><div class=\"page\"><div class=\"mainsection\">" +
|
||||
"<TABLE BORDER=0 class=\"snarkTorrents\" cellpadding=\"5px 10px\">" +
|
||||
"<thead><tr><th>").append(_("File")).append("</th><th>").append(_("Size"))
|
||||
.append("</th><th>").append(_("Status")).append("</th></tr></thead>");
|
||||
//DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
|
||||
// DateFormat.MEDIUM);
|
||||
for (int i=0 ; i< ls.length ; i++)
|
||||
{
|
||||
String encoded=URI.encodePath(ls[i]);
|
||||
// bugfix for I2P - Backport from Jetty 6 (zero file lengths and last-modified times)
|
||||
// http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs
|
||||
// See resource.diff attachment
|
||||
//Resource item = addPath(encoded);
|
||||
Resource item = r.addPath(ls[i]);
|
||||
|
||||
String rowClass = (i % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
|
||||
buf.append("<TR class=\"").append(rowClass).append("\"><TD class=\"snarkFileName ")
|
||||
.append(rowClass).append("\">");
|
||||
|
||||
// Get completeness and status string
|
||||
boolean complete = false;
|
||||
String status = "";
|
||||
long length = item.length();
|
||||
if (item.isDirectory()) {
|
||||
complete = true;
|
||||
status = toImg("tick") + _("Directory");
|
||||
} else {
|
||||
if (snark == null) {
|
||||
// Assume complete, perhaps he removed a completed torrent but kept a bookmark
|
||||
complete = true;
|
||||
status = toImg("cancel") + _("Torrent not found?");
|
||||
} else {
|
||||
try {
|
||||
File f = item.getFile();
|
||||
if (f != null) {
|
||||
long remaining = snark.storage.remaining(f.getCanonicalPath());
|
||||
if (remaining < 0) {
|
||||
complete = true;
|
||||
status = toImg("cancel") + _("File not found in torrent?");
|
||||
} else if (remaining == 0 || length <= 0) {
|
||||
complete = true;
|
||||
status = toImg("tick") + _("Complete");
|
||||
} else {
|
||||
status = toImg("clock") +
|
||||
(100 * (length - remaining) / length) + "% " + _("complete") +
|
||||
" (" + DataHelper.formatSize2(remaining) + _("bytes remaining") + ")";
|
||||
}
|
||||
} else {
|
||||
status = "Not a file?";
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
status = "Not a file? " + ioe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String path=URI.addPaths(base,encoded);
|
||||
if (item.isDirectory() && !path.endsWith("/"))
|
||||
path=URI.addPaths(path,"/");
|
||||
String icon = toIcon(item);
|
||||
|
||||
if (complete) {
|
||||
buf.append("<a href=\"").append(path).append("\">");
|
||||
// thumbnail ?
|
||||
String plc = item.toString().toLowerCase();
|
||||
if (plc.endsWith(".jpg") || plc.endsWith(".jpeg") || plc.endsWith(".png") ||
|
||||
plc.endsWith(".gif") || plc.endsWith(".ico")) {
|
||||
buf.append("<img alt=\"\" border=\"0\" class=\"thumb\" src=\"")
|
||||
.append(path).append("\"></a> ");
|
||||
} else {
|
||||
buf.append(toImg(icon));
|
||||
}
|
||||
buf.append("<A HREF=\"");
|
||||
buf.append(path);
|
||||
buf.append("\">");
|
||||
} else {
|
||||
buf.append(toImg(icon));
|
||||
}
|
||||
buf.append(ls[i]);
|
||||
if (complete)
|
||||
buf.append("</a>");
|
||||
buf.append("</TD><TD ALIGN=right class=\"").append(rowClass).append(" snarkFileSize\">");
|
||||
if (!item.isDirectory())
|
||||
buf.append(DataHelper.formatSize2(length)).append('B');
|
||||
buf.append("</TD><TD class=\"").append(rowClass).append(" snarkFileStatus\">");
|
||||
//buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append(status);
|
||||
buf.append("</TD></TR>\n");
|
||||
}
|
||||
buf.append("</TABLE>\n");
|
||||
buf.append("</div></div></BODY></HTML>\n");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** @since 0.7.14 */
|
||||
private String toIcon(Resource item) {
|
||||
if (item.isDirectory())
|
||||
return "folder";
|
||||
return toIcon(item.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick an icon; try to catch the common types in an i2p environment
|
||||
* @return file name not including ".png"
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String toIcon(String path) {
|
||||
String icon;
|
||||
// Should really just add to the mime.properties file in org.mortbay.jetty.jar
|
||||
// instead of this mishmash. We can't get to HttpContext.setMimeMapping()
|
||||
// from here? We could do it from a web.xml perhaps.
|
||||
// Or could we put our own org/mortbay/http/mime.properties file in the war?
|
||||
String plc = path.toLowerCase();
|
||||
String mime = getServletContext().getMimeType(path);
|
||||
if (mime == null)
|
||||
mime = "";
|
||||
if (mime.equals("text/html"))
|
||||
icon = "html";
|
||||
else if (mime.equals("text/plain") || plc.endsWith(".nfo"))
|
||||
icon = "page";
|
||||
else if (mime.equals("application/java-archive") || plc.endsWith(".war"))
|
||||
icon = "package";
|
||||
else if (plc.endsWith(".xpi2p"))
|
||||
icon = "plugin";
|
||||
else if (mime.equals("application/pdf"))
|
||||
icon = "page_white_acrobat";
|
||||
else if (mime.startsWith("image/") || plc.endsWith(".ico"))
|
||||
icon = "photo";
|
||||
else if (mime.startsWith("audio/") || mime.equals("application/ogg") ||
|
||||
plc.endsWith(".flac") || plc.endsWith(".m4a") || plc.endsWith(".wma") ||
|
||||
plc.endsWith(".ape"))
|
||||
icon = "music";
|
||||
else if (mime.startsWith("video/") || plc.endsWith(".mkv") || plc.endsWith(".m4v") ||
|
||||
plc.endsWith(".mp4") || plc.endsWith(".wmv"))
|
||||
icon = "film";
|
||||
else if (mime.equals("application/zip") || mime.equals("application/x-gtar") ||
|
||||
mime.equals("application/compress") || mime.equals("application/gzip") ||
|
||||
mime.equals("application/x-tar") ||
|
||||
plc.endsWith(".rar") || plc.endsWith(".bz2") || plc.endsWith(".7z"))
|
||||
icon = "compress";
|
||||
else if (plc.endsWith(".exe"))
|
||||
icon = "application";
|
||||
else
|
||||
icon = "bug";
|
||||
return icon;
|
||||
}
|
||||
|
||||
/** @since 0.7.14 */
|
||||
private static String toImg(String icon) {
|
||||
return "<img alt=\"\" height=\"16\" width=\"16\" src=\"/i2psnark/_icons/" + icon + ".png\"> ";
|
||||
}
|
||||
|
||||
|
||||
/** inner class, don't bother reindenting */
|
||||
private static class FetchAndAdd implements Runnable {
|
||||
private SnarkManager _manager;
|
||||
@ -967,7 +1406,7 @@ private static class FetchAndAdd implements Runnable {
|
||||
File file = _manager.util().get(_url, false, 3);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage(_("Torrent fetched from {0}", _url));
|
||||
_manager.addMessage(_("Torrent fetched from {0}", urlify(_url)));
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
@ -995,12 +1434,12 @@ private static class FetchAndAdd implements Runnable {
|
||||
_manager.addTorrent(canonical);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage(_("Torrent at {0} was not valid", _url) + ": " + ioe.getMessage());
|
||||
_manager.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage(_("Torrent was not retrieved from {0}", _url));
|
||||
_manager.addMessage(_("Torrent was not retrieved from {0}", urlify(_url)));
|
||||
}
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
|
@ -8,641 +8,756 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-01-14 06:24+0000\n"
|
||||
"PO-Revision-Date: 2010-01-14 06:33+0000\n"
|
||||
"POT-Creation-Date: 2010-05-27 14:45+0000\n"
|
||||
"PO-Revision-Date: 2010-05-27 16:54+0000\n"
|
||||
"Last-Translator: 4get <forget@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Russian\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"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:84
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:87
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "Торренты будут подгружены через {0} минут(ы)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:241
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:251
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr "Новое значение лимита количества слотов отдачи: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:243
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:253
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr "Минимально допустимое значение для количества слотов: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:255
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:265
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "Новое значение лимита скорости отдачи: {0} KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:257
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:267
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "Минимально допустимое значение для лимита скорости отдачи: {0} KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:301
|
||||
msgid "Cannot change the I2CP settings while torrents are active"
|
||||
msgstr "Невозможно изменить настройки I2CP пока есть активные торренты"
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:314
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr "Изменения настроек I2CP и туннелей вступят в силу после остановки всех торрентов."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:307
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:320
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "Рассоединяемся по старому адресу I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:311
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:324
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "Новые параметры I2CP: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:315
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:328
|
||||
msgid "Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr "Не удалось соединиться с использованием новых настроек I2CP, возвращаемся к старым настройкам"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:319
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:332
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "Не удалось пересоединиться с использованием старых настроек I2CP!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:321
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:334
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "Пересоединились по новому адресу I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:332
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:345
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "I2CP-приёмник перезапущен для \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:343
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:356
|
||||
msgid "Enabled autostart"
|
||||
msgstr "Автостарт включен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:345
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:358
|
||||
msgid "Disabled autostart"
|
||||
msgstr "Автостарт выключен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:351
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:364
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Включено использование открытых трекеров. Требуется перезапуск торрента, чтобы изменения вступили в силу."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:353
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:366
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "Отключено использование открытых трекеров. Требуется перезапуск торрента, чтобы изменения вступили в силу."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:360
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:373
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "Изменен список открытых трекеров. Требуется перезапуск торрента, чтобы изменения вступили в силу."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:367
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:380
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "Настройки не изменились."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:377
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:390
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "Не удалось сохранить настройки в {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:395
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:408
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "Устанавливается соединение с I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:398
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:411
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "Ошибка соединения с I2P, проверьте настройки I2CP!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:407
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:420
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "Ошибка: Не удалось добавить торрент {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:446
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:442
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "Не удалось открыть \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only"
|
||||
msgstr "Внимание: указанные в \"{0}\" не-i2p трекеры будут проигнорированы, будут использоваться только открытые i2p трекеры"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:457
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr "Внимание: указанные в \"{0}\" не-i2p трекеры будут проигнорированы, однако использование открытых i2p трекеров отключено, Вы должны включить поддержку открытых i2p трекеров перед запуском этого торрента!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:476
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "Торрент в \"{0}\" некорректен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:461
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:491
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "Торрент добавлен и запущен: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:463
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:493
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "Торрент добавлен: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:559
|
||||
#, java-format
|
||||
msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
msgstr "Обнаружен не-I2P трекер в торренте \"{0}\", удаляем его из нашего списка трекеров!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:562
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:590
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "Слишком много файлов в торренте \"{0}\" ({1}), удаляем его!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:564
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:592
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr "Торрент \"{0}\" содержит единственный файл заканчивающийся на \".torrent\", удаляем его!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:566
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:594
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "В торренте \"{0}\" не оказалось ни одной части, удаляем его!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:568
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:596
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "Слишком много частей в торренте \"{0}\" (наш предел {1}), удаляем его!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:570
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:598
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "Слишком крупные части в торренте \"{0}\" ({1}B), удаляем его."
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:571
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:599
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "Наш предел {0}B"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:579
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:607
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr "Торренты крупнее чем {0}B пока не поддерживается, удаляем \"{1}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:595
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:623
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "Ошибка: Невозможно удалить торрент {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:616
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:644
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "Торрент остановлен: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:631
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:659
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "Торрент удален: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:664
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#, java-format
|
||||
msgid "Download finished: \"{0}\""
|
||||
msgstr "Завершена загрузка: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:664
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#, java-format
|
||||
msgid "size: {0}B"
|
||||
msgstr "размер: {0}B"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:720
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "Не удалось установить соединение с I2P!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:86
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:169
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark — Анонимный BitTorrent Клиент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:95
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:178
|
||||
msgid "Refresh page"
|
||||
msgstr "Обновить страницу"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:97
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:656
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:180
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:808
|
||||
msgid "I2PSnark"
|
||||
msgstr "I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:99
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:182
|
||||
msgid "Forum"
|
||||
msgstr "Форум"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:125
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:208
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1243
|
||||
msgid "Status"
|
||||
msgstr "Статус"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:131
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:214
|
||||
msgid "Hide Peers"
|
||||
msgstr "спрятать список пиров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:134
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:217
|
||||
msgid "Show Peers"
|
||||
msgstr "показать список пиров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:139
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:222
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1225
|
||||
msgid "Torrent"
|
||||
msgstr "Торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:141
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:224
|
||||
msgid "ETA"
|
||||
msgstr "Осталось"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:143
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:226
|
||||
msgid "Downloaded"
|
||||
msgstr "Получено"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:145
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:228
|
||||
msgid "Uploaded"
|
||||
msgstr "Отдано"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:147
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:230
|
||||
msgid "Down Rate"
|
||||
msgstr "Скорость загрузки"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:149
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:232
|
||||
msgid "Up Rate"
|
||||
msgstr "Скорость отдачи"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:156
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:239
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "Остановить все торренты и закрыть соединение с I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:158
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:241
|
||||
msgid "Stop All"
|
||||
msgstr "Остановить все"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:163
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "Запустить все торренты и открыть соединение с I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:165
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:248
|
||||
msgid "Start All"
|
||||
msgstr "Запустить все"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:182
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:265
|
||||
msgid "No torrents loaded."
|
||||
msgstr "Нет загруженных торрентов."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:187
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:270
|
||||
msgid "Totals"
|
||||
msgstr "Всего"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:189
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:272
|
||||
#, java-format
|
||||
msgid "{0} torrents"
|
||||
msgstr "{0} торрентов"
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "{0} торрент"
|
||||
msgstr[1] "{0} торрента"
|
||||
msgstr[2] "{0} торрентов"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:192
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:275
|
||||
#, java-format
|
||||
msgid "{0} connected peers"
|
||||
msgstr "{0} подсоединенных пиров"
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "{0} подсоединенный пир"
|
||||
msgstr[1] "{0} подсоединенных пиров"
|
||||
msgstr[2] "{0} подсоединенных пиров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:227
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:310
|
||||
#, java-format
|
||||
msgid "Torrent file {0} does not exist"
|
||||
msgstr "Торрент {0} не существует"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:237
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:990
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:320
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1428
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "Торрент уже запущен: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:239
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:992
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:322
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1430
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "Торрент уже в очереди: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:243
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
#, java-format
|
||||
msgid "Copying torrent to {0}"
|
||||
msgstr "Копируем торрент в: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:329
|
||||
#, java-format
|
||||
msgid "Unable to copy the torrent to {0}"
|
||||
msgstr "Не удалось скопировать торрент в: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:329
|
||||
#, java-format
|
||||
msgid "from {0}"
|
||||
msgstr "из: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:254
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:337
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "Получение торрента: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:258
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:341
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "Некорректный URL, должен начинаться с http://"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:288
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:371
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "Запускаем торрент: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:308
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:391
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:409
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "Удален торрент: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:332
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:342
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:415
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:425
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "Файл удален: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:334
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:344
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:417
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:427
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "Не удалось удалить файл: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:353
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:436
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "Директория удалена: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:384
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:467
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "Торрент не создан — вы должны указать трекер"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:399
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:482
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "Создан торрент для \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:402
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:485
|
||||
#, java-format
|
||||
msgid "Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\""
|
||||
msgstr "Многие I2P трекеры требуют зарегистрировать на них торрент перед началом раздачи — пожалуйста проверьте требуется ли это перед запуском \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:404
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:487
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "Ошибка при создании торрента для: \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:407
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:490
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "Невозможно создать торрент для несуществующего файла или директории: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:410
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:493
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr "Торрент не создан — вы должны указать файл или директорию"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:413
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:496
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "Останавливаем все торренты и закрываем соединение с I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:422
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:505
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "Соединение с I2P закрыто."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:425
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:508
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "Соединяемся с I2P и запускаем все торренты."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:502
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:670
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:628
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:822
|
||||
msgid "Unknown"
|
||||
msgstr "Неизвестный"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:505
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:513
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:631
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:636
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:641
|
||||
msgid "TrackerErr"
|
||||
msgstr "ОшибкаТрекера"
|
||||
|
||||
# TODO should replace "uploader limit NN peers" with "global number of upload slots: NN"
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:507
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:519
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:521
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:528
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:530
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:534
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:536
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:862
|
||||
msgid "peers"
|
||||
msgstr "пир."
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:634
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:637
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:648
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:651
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:659
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:662
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:667
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:670
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "{0} пир"
|
||||
msgstr[1] "{0} пира"
|
||||
msgstr[2] "{0} пиров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:517
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:521
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:645
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:650
|
||||
msgid "Seeding"
|
||||
msgstr "Раздается"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:523
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:653
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1281
|
||||
msgid "Complete"
|
||||
msgstr "Завершен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:526
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:530
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:656
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:661
|
||||
msgid "OK"
|
||||
msgstr "Загружается"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:532
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:536
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:664
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:669
|
||||
msgid "Stalled"
|
||||
msgstr "Простаивает"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:538
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:672
|
||||
msgid "No Peers"
|
||||
msgstr "Нет Пиров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:540
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:674
|
||||
msgid "Stopped"
|
||||
msgstr "Остановлен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:553
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:689
|
||||
msgid "View files"
|
||||
msgstr "Открыть директорию"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:555
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:691
|
||||
msgid "Open file"
|
||||
msgstr "Открыть файл"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:579
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:781
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:721
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:933
|
||||
msgid "Tracker"
|
||||
msgstr "Трекер"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:580
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:722
|
||||
msgid "Details"
|
||||
msgstr "Подробнее"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:614
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:756
|
||||
msgid "Stop the torrent"
|
||||
msgstr "Остановить торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:616
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:758
|
||||
msgid "Stop"
|
||||
msgstr "Остановить"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:622
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:764
|
||||
msgid "Start the torrent"
|
||||
msgstr "Запустить торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:624
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
msgid "Start"
|
||||
msgstr "Запустить"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:629
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:771
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr "Удалить торрент из списка и с диска"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:631
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:776
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?"
|
||||
msgstr "Вы действительно хотите удалить \\''{0}.torrent\\''? (загруженные файлы удаляться НЕ будут)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:778
|
||||
msgid "Remove"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:635
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:782
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "Удалить торрент и стереть загруженные файлы"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:637
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:787
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?"
|
||||
msgstr "Вы действительно хотите удалить торрент \\''{0}\\'' и все загруженные файлы?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:789
|
||||
msgid "Delete"
|
||||
msgstr "Стереть"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:680
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:832
|
||||
msgid "Seed"
|
||||
msgstr "Сид"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:698
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:850
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "Uninteresting (У пира нет нужных нам частей торрента)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:700
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:852
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "Choked (Этот пир не позволяет нам запрашивать части торрента)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:714
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:866
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "Uninterested (У нас нужных этому пиру частей торрента)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:716
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:868
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "Choking (Мы не позволяем этому пиру запрашивать у нас части торрента)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:743
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:895
|
||||
msgid "Add Torrent"
|
||||
msgstr "Добавить Торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:745
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:897
|
||||
msgid "From URL"
|
||||
msgstr "Из URL"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:750
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:902
|
||||
msgid "Add torrent"
|
||||
msgstr "Добавить торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:753
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:905
|
||||
#, java-format
|
||||
msgid "Alternately, you can copy .torrent files to the directory {0}."
|
||||
msgstr "Ну или вы можете скопировать .torrent-файлы в директорию {0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:755
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:907
|
||||
msgid "Removing a .torrent file will cause the torrent to stop."
|
||||
msgstr "Удаление .torrent-файла приведет к остановке торрента."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:772
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:924
|
||||
msgid "Create Torrent"
|
||||
msgstr "Создать Торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:775
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:927
|
||||
msgid "Data to seed"
|
||||
msgstr "Файлы для раздачи"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:779
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:931
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr "Файл или директория для раздачи (вводите только название файла или директории, указание абсолютных путей не поддерживается)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:783
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:935
|
||||
msgid "Select a tracker"
|
||||
msgstr "Выбрать трекер"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:796
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:948
|
||||
msgid "or"
|
||||
msgstr "или"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:799
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:951
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "Задать URL анонсера вручную"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:802
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:954
|
||||
msgid "Create torrent"
|
||||
msgstr "Создать торрент"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:820
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:972
|
||||
msgid "Configuration"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:823
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:975
|
||||
msgid "Data directory"
|
||||
msgstr "Директория для файлов"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:826
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:978
|
||||
msgid "Directory to store torrents and data"
|
||||
msgstr "Директория, где будут храниться торренты и загружаемые файлы"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:828
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:980
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "Для изменения отредактируйте файл i2psnark.config и перезагрузите I2PSnark"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:832
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:984
|
||||
msgid "Auto start"
|
||||
msgstr "Автозапуск"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:836
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:988
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "Автоматически запускать торренты после добавления"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:859
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1011
|
||||
msgid "Total uploader limit"
|
||||
msgstr "Ограничение количества слотов отдачи"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:866
|
||||
# TODO should replace "uploader limit NN peers" with "global number of upload slots: NN"
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1014
|
||||
msgid "peers"
|
||||
msgstr "пир."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1018
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "Ограничение скорости отдачи"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:869
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1021
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "Рекомендуется использовать половину от доступной пропускной способности."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:871
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1023
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "Посмотреть/настроить ограничения скорости в маршрутизаторе I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:875
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1027
|
||||
msgid "Use open trackers also"
|
||||
msgstr "Дополнительно использовать открытые трекеры"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:879
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1031
|
||||
msgid "If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"
|
||||
msgstr "Анонсировать торренты на открытых трекерах, дополнительно к тем, что указаны внутри торрента"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:883
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1035
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "URL открытых трекеров"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:894
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1047
|
||||
msgid "Inbound Settings"
|
||||
msgstr "Входящие туннели"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1053
|
||||
msgid "Outbound Settings"
|
||||
msgstr "Исходящие туннели"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1060
|
||||
msgid "I2CP host"
|
||||
msgstr "Адрес I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:899
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1065
|
||||
msgid "I2CP port"
|
||||
msgstr "Порт I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:912
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1077
|
||||
msgid "I2CP options"
|
||||
msgstr "Параметры I2CP"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:917
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1082
|
||||
msgid "Save configuration"
|
||||
msgstr "Сохранить настройки"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:970
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "{0} хоп"
|
||||
msgstr[1] "{0} хопа"
|
||||
msgstr[2] "{0} хопов"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1093
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "{0} туннель"
|
||||
msgstr[1] "{0} туннеля"
|
||||
msgstr[2] "{0} туннелей"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1107
|
||||
#, java-format
|
||||
msgid "1 "
|
||||
msgid_plural "{0} "
|
||||
msgstr[0] "{0} "
|
||||
msgstr[1] "{0} "
|
||||
msgstr[2] "{0} "
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1237
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "Перейти в директорию уровнем выше"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1242
|
||||
msgid "File"
|
||||
msgstr "Файл"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1242
|
||||
msgid "Size"
|
||||
msgstr "Размер"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1265
|
||||
msgid "Directory"
|
||||
msgstr "Директория"
|
||||
|
||||
# This debug error message intentionally left in English
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1270
|
||||
msgid "Torrent not found?"
|
||||
msgstr "Torrent not found?"
|
||||
|
||||
# This debug error message intentionally left in English
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1278
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "File not found in torrent?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1284
|
||||
msgid "complete"
|
||||
msgstr "скачано"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1285
|
||||
msgid "bytes remaining"
|
||||
msgstr "байт осталось"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1408
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "Получен торрент из: {0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:998
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1436
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "Торрент полученный из {0} некорректен"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1003
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1441
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "Не удалось получить торрент из: {0}"
|
||||
|
||||
#~ msgid "{0} torrents"
|
||||
#~ msgstr "{0} торрентов"
|
||||
#~ msgid "hops"
|
||||
#~ msgstr "хопа(-ов)"
|
||||
#~ msgid "tunnels"
|
||||
#~ msgstr "туннеля(-ей)"
|
||||
#~ msgid "Bytes"
|
||||
#~ msgstr "байт"
|
||||
#~ msgid "Cannot change the I2CP settings while torrents are active"
|
||||
#~ msgstr "Невозможно изменить настройки I2CP пока есть активные торренты"
|
||||
|
||||
|
@ -8,643 +8,739 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2psnark\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-01-29 07:17+0000\n"
|
||||
"PO-Revision-Date: 2010-01-29 15:30+0800\n"
|
||||
"Last-Translator: walking <zhazhenzhong@gmail.com>\n"
|
||||
"POT-Creation-Date: 2010-05-29 02:34+0000\n"
|
||||
"PO-Revision-Date: 2010-05-29 10:55+0800\n"
|
||||
"Last-Translator: walking <walking@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Chinese\n"
|
||||
"Plural-Forms: nplurals=1; plural=0\n"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:84
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:87
|
||||
#, java-format
|
||||
msgid "Adding torrents in {0} minutes"
|
||||
msgstr "{0}分钟内完成添加"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:241
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:251
|
||||
#, java-format
|
||||
msgid "Total uploaders limit changed to {0}"
|
||||
msgstr ""
|
||||
msgstr "总上传种子数限制已更新为{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:243
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:253
|
||||
#, java-format
|
||||
msgid "Minimum total uploaders limit is {0}"
|
||||
msgstr ""
|
||||
msgstr "最低上传种子数限制为{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:255
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:265
|
||||
#, java-format
|
||||
msgid "Up BW limit changed to {0}KBps"
|
||||
msgstr "上传带宽限制改为 {0} KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:257
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:267
|
||||
#, java-format
|
||||
msgid "Minimum up bandwidth limit is {0}KBps"
|
||||
msgstr "最小上传带宽限制为 {0} KBps"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:301
|
||||
msgid "Cannot change the I2CP settings while torrents are active"
|
||||
msgstr "正在下载/上传,无法更改I2CP设置"
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:314
|
||||
msgid "I2CP and tunnel changes will take effect after stopping all torrents"
|
||||
msgstr "I2CP与隧道设置的变化在所有种子停止后才能生效"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:307
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:320
|
||||
msgid "Disconnecting old I2CP destination"
|
||||
msgstr "正在断开旧的I2CP目标"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:311
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:324
|
||||
#, java-format
|
||||
msgid "I2CP settings changed to {0}"
|
||||
msgstr "I2CP设置改为{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:315
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:328
|
||||
msgid "Unable to connect with the new settings, reverting to the old I2CP settings"
|
||||
msgstr "无法通过新设置连接,恢复I2CP的旧设置"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:319
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:332
|
||||
msgid "Unable to reconnect with the old settings!"
|
||||
msgstr "旧设置也无法连接!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:321
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:334
|
||||
msgid "Reconnected on the new I2CP destination"
|
||||
msgstr "重新连接新I2CP目标"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:332
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:345
|
||||
#, java-format
|
||||
msgid "I2CP listener restarted for \"{0}\""
|
||||
msgstr "\"{0}\"的I2CP监听端口已启动"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:343
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:356
|
||||
msgid "Enabled autostart"
|
||||
msgstr "启用自动启动"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:345
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:358
|
||||
msgid "Disabled autostart"
|
||||
msgstr "禁用自动启动"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:351
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:364
|
||||
msgid "Enabled open trackers - torrent restart required to take effect."
|
||||
msgstr "启用OpenTracker-重新启动种子后生效"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:353
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:366
|
||||
msgid "Disabled open trackers - torrent restart required to take effect."
|
||||
msgstr "禁用OpenTracker - 重新启动种子后生效"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:360
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:373
|
||||
msgid "Open Tracker list changed - torrent restart required to take effect."
|
||||
msgstr "OpenTracker列表已改变 - 重新启动种子后生效"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:367
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:380
|
||||
msgid "Configuration unchanged."
|
||||
msgstr "设置未改变"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:377
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:390
|
||||
#, java-format
|
||||
msgid "Unable to save the config to {0}"
|
||||
msgstr "无法保存设置到{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:395
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:408
|
||||
msgid "Connecting to I2P"
|
||||
msgstr "正在连接到I2P"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:398
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:411
|
||||
msgid "Error connecting to I2P - check your I2CP settings!"
|
||||
msgstr "连接I2P时发生错误 - 请检查I2CP设置!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:407
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:420
|
||||
#, java-format
|
||||
msgid "Error: Could not add the torrent {0}"
|
||||
msgstr "错误:无法添加种子{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:446
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:442
|
||||
#, java-format
|
||||
msgid "Cannot open \"{0}\""
|
||||
msgstr "无法打开 \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:455
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", will announce to i2p open trackers only"
|
||||
msgstr "警告 - 忽略\"{0}\"文件中I2P网络外的Tracker服务器,文件将仅发布至 I2P 内的 Open Tracker 服务器。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:457
|
||||
#, java-format
|
||||
msgid "Warning - Ignoring non-i2p tracker in \"{0}\", and open trackers are disabled, you must enable open trackers before starting the torrent!"
|
||||
msgstr "警告 - 忽略\"{0}\"文件中I2P网络外的Tracker服务器,OpenTracker已禁用,启动此种子前您必须启用OpenTracker。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:476
|
||||
#, java-format
|
||||
msgid "Torrent in \"{0}\" is invalid"
|
||||
msgstr "无效种子 \"{0}\" "
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:461
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:491
|
||||
#, java-format
|
||||
msgid "Torrent added and started: \"{0}\""
|
||||
msgstr "已添加并启动种子:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:463
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:493
|
||||
#, java-format
|
||||
msgid "Torrent added: \"{0}\""
|
||||
msgstr "已添加种子:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:559
|
||||
#, java-format
|
||||
msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
msgstr "【匿名性警告】\"{0}\" 中含有非I2P Tracker,程序将从Tracker列表中将其删除。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:562
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:590
|
||||
#, java-format
|
||||
msgid "Too many files in \"{0}\" ({1}), deleting it!"
|
||||
msgstr "\"{0}\" ({1}) 含有太多文件,删除之!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:564
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:592
|
||||
#, java-format
|
||||
msgid "Torrent file \"{0}\" cannot end in \".torrent\", deleting it!"
|
||||
msgstr "种子文件 \"{0}\" 不以 \".torrent\"结尾,正在删除!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:566
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:594
|
||||
#, java-format
|
||||
msgid "No pieces in \"{0}\", deleting it!"
|
||||
msgstr "\"{0}\" 中没有数据片,删除之!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:568
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:596
|
||||
#, java-format
|
||||
msgid "Too many pieces in \"{0}\", limit is {1}, deleting it!"
|
||||
msgstr "\"{0}\" 中文件分片太多,限额为{1},删除之!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:570
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:598
|
||||
#, java-format
|
||||
msgid "Pieces are too large in \"{0}\" ({1}B), deleting it."
|
||||
msgstr "\"{0}\" ({1}B) 中文件分片过大,删除之。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:571
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:599
|
||||
#, java-format
|
||||
msgid "Limit is {0}B"
|
||||
msgstr "限额为 {0}B"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:579
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:607
|
||||
#, java-format
|
||||
msgid "Torrents larger than {0}B are not supported yet, deleting \"{1}\""
|
||||
msgstr "目前不支持大于{0}B 的种子,正在删除\"{1}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:595
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:623
|
||||
#, java-format
|
||||
msgid "Error: Could not remove the torrent {0}"
|
||||
msgstr "错误:无法删除种子{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:616
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:644
|
||||
#, java-format
|
||||
msgid "Torrent stopped: \"{0}\""
|
||||
msgstr "种子已停止:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:631
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:659
|
||||
#, java-format
|
||||
msgid "Torrent removed: \"{0}\""
|
||||
msgstr "种子已删除:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:664
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#, java-format
|
||||
msgid "Download finished: \"{0}\""
|
||||
msgstr "下载已完成:\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:664
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#, java-format
|
||||
msgid "size: {0}B"
|
||||
msgstr "大小:{0}B"
|
||||
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:692
|
||||
#: ../java/src/org/klomp/snark/SnarkManager.java:720
|
||||
msgid "Unable to connect to I2P!"
|
||||
msgstr "无法连接至I2P!"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:86
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:169
|
||||
msgid "I2PSnark - Anonymous BitTorrent Client"
|
||||
msgstr "I2PSnark - 匿名BitTorrent客户端"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:95
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:178
|
||||
msgid "Refresh page"
|
||||
msgstr "刷新页面"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:97
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:656
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:180
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:808
|
||||
msgid "I2PSnark"
|
||||
msgstr ""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:99
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:182
|
||||
msgid "Forum"
|
||||
msgstr "论坛"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:125
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:208
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1243
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:131
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:214
|
||||
msgid "Hide Peers"
|
||||
msgstr "隐藏用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:134
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:217
|
||||
msgid "Show Peers"
|
||||
msgstr "显示用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:139
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:222
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1225
|
||||
msgid "Torrent"
|
||||
msgstr "种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:141
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:224
|
||||
msgid "ETA"
|
||||
msgstr ""
|
||||
msgstr "预计剩余时间"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:143
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:226
|
||||
msgid "Downloaded"
|
||||
msgstr "已下载"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:145
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:228
|
||||
msgid "Uploaded"
|
||||
msgstr "已上传"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:147
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:230
|
||||
msgid "Down Rate"
|
||||
msgstr "下载速度"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:149
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:232
|
||||
msgid "Up Rate"
|
||||
msgstr "上传速度"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:156
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:239
|
||||
msgid "Stop all torrents and the I2P tunnel"
|
||||
msgstr "停止全部种子及I2P隧道"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:158
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:241
|
||||
msgid "Stop All"
|
||||
msgstr "停止全部"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:163
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
msgid "Start all torrents and the I2P tunnel"
|
||||
msgstr "启动全部种子及I2P隧道"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:165
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:248
|
||||
msgid "Start All"
|
||||
msgstr "启动全部"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:182
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:265
|
||||
msgid "No torrents loaded."
|
||||
msgstr "未载入任何种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:187
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:270
|
||||
msgid "Totals"
|
||||
msgstr "总计"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:189
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:272
|
||||
#, java-format
|
||||
msgid "{0} torrents"
|
||||
msgstr "{0} 个种子"
|
||||
msgid "1 torrent"
|
||||
msgid_plural "{0} torrents"
|
||||
msgstr[0] "{0}个种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:192
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:275
|
||||
#, java-format
|
||||
msgid "{0} connected peers"
|
||||
msgstr "{0} 已连接用户"
|
||||
msgid "1 connected peer"
|
||||
msgid_plural "{0} connected peers"
|
||||
msgstr[0] "{0}个已连接用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:227
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:310
|
||||
#, java-format
|
||||
msgid "Torrent file {0} does not exist"
|
||||
msgstr "种子文件{0}不存在"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:237
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:990
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:320
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1428
|
||||
#, java-format
|
||||
msgid "Torrent already running: {0}"
|
||||
msgstr "种子已启动:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:239
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:992
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:322
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1430
|
||||
#, java-format
|
||||
msgid "Torrent already in the queue: {0}"
|
||||
msgstr "种子排队中:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:243
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
#, java-format
|
||||
msgid "Copying torrent to {0}"
|
||||
msgstr "正在复制种子到{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:329
|
||||
#, java-format
|
||||
msgid "Unable to copy the torrent to {0}"
|
||||
msgstr "无法复制种子文件到{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:246
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:329
|
||||
#, java-format
|
||||
msgid "from {0}"
|
||||
msgstr "来源{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:254
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:337
|
||||
#, java-format
|
||||
msgid "Fetching {0}"
|
||||
msgstr "正在获取{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:258
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:341
|
||||
msgid "Invalid URL - must start with http://"
|
||||
msgstr "无效链接 - 必须以http:// 开头"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:288
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:371
|
||||
#, java-format
|
||||
msgid "Starting up torrent {0}"
|
||||
msgstr "正在启动种子{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:308
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:326
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:391
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:409
|
||||
#, java-format
|
||||
msgid "Torrent file deleted: {0}"
|
||||
msgstr "种子文件已删除:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:332
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:342
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:415
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:425
|
||||
#, java-format
|
||||
msgid "Data file deleted: {0}"
|
||||
msgstr "数据文件已删除:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:334
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:344
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:417
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:427
|
||||
#, java-format
|
||||
msgid "Data file could not be deleted: {0}"
|
||||
msgstr "无法删除数据文件:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:353
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:436
|
||||
#, java-format
|
||||
msgid "Data dir deleted: {0}"
|
||||
msgstr "数据文件夹已删除:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:384
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:467
|
||||
msgid "Error creating torrent - you must select a tracker"
|
||||
msgstr "创建种子时发生错误 - 您必须选择一个Tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:399
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:482
|
||||
#, java-format
|
||||
msgid "Torrent created for \"{0}\""
|
||||
msgstr "种子创建成功\"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:402
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:485
|
||||
#, java-format
|
||||
msgid "Many I2P trackers require you to register new torrents before seeding - please do so before starting \"{0}\""
|
||||
msgstr "多数I2PTracker需要用户在做种前注册新种子 - 请在启动 \"{0}\"前到所使用的Tracker进行注册。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:404
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:487
|
||||
#, java-format
|
||||
msgid "Error creating a torrent for \"{0}\""
|
||||
msgstr "创建种子时发生错误 \"{0}\""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:407
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:490
|
||||
#, java-format
|
||||
msgid "Cannot create a torrent for the nonexistent data: {0}"
|
||||
msgstr "无法为不存在的数据文件创建种子:{0}"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:410
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:493
|
||||
msgid "Error creating torrent - you must enter a file or directory"
|
||||
msgstr "创建种子时发生错误 - 必须指定文件或文件夹"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:413
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:496
|
||||
msgid "Stopping all torrents and closing the I2P tunnel."
|
||||
msgstr "正在停用所有种子并关闭I2P隧道。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:422
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:505
|
||||
msgid "I2P tunnel closed."
|
||||
msgstr "I2P隧道已关闭"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:425
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:508
|
||||
msgid "Opening the I2P tunnel and starting all torrents."
|
||||
msgstr "正在打开I2P隧道并启动所有种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:502
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:670
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:628
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:822
|
||||
msgid "Unknown"
|
||||
msgstr "未知"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:505
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:513
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:631
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:636
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:641
|
||||
msgid "TrackerErr"
|
||||
msgstr "Tracker错误"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:507
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:509
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:519
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:521
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:528
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:530
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:534
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:536
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:862
|
||||
msgid "peers"
|
||||
msgstr "用户"
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:634
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:637
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:648
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:651
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:659
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:662
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:667
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:670
|
||||
#, java-format
|
||||
msgid "1 peer"
|
||||
msgid_plural "{0} peers"
|
||||
msgstr[0] "{0}个用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:517
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:521
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:645
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:650
|
||||
msgid "Seeding"
|
||||
msgstr "正做种"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:523
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:653
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1281
|
||||
msgid "Complete"
|
||||
msgstr "完成"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:526
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:530
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:656
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:661
|
||||
msgid "OK"
|
||||
msgstr "确定"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:532
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:536
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:664
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:669
|
||||
msgid "Stalled"
|
||||
msgstr "等待"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:538
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:672
|
||||
msgid "No Peers"
|
||||
msgstr "没有用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:540
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:674
|
||||
msgid "Stopped"
|
||||
msgstr "已停用"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:553
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:689
|
||||
msgid "View files"
|
||||
msgstr "浏览文件"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:555
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:691
|
||||
msgid "Open file"
|
||||
msgstr "打开文件"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:579
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:781
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:721
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:933
|
||||
msgid "Tracker"
|
||||
msgstr "Tracker服务器"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:580
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:722
|
||||
msgid "Details"
|
||||
msgstr "详情"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:614
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:756
|
||||
msgid "Stop the torrent"
|
||||
msgstr "停止种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:616
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:758
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:622
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:764
|
||||
msgid "Start the torrent"
|
||||
msgstr "启动种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:624
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:766
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:629
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:771
|
||||
msgid "Remove the torrent from the active list, deleting the .torrent file"
|
||||
msgstr "取消下载任务并删除对应种子文件。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:631
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:776
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the file \\''{0}.torrent\\'' (downloaded data will not be deleted) ?"
|
||||
msgstr "您确定要删除文件“{0}.torrent”(下载的数据文件不会被删除)?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:778
|
||||
msgid "Remove"
|
||||
msgstr "移除"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:635
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:782
|
||||
msgid "Delete the .torrent file and the associated data file(s)"
|
||||
msgstr "删除种子及所下载的文件"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:637
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:787
|
||||
#, java-format
|
||||
msgid "Are you sure you want to delete the torrent \\''{0}\\'' and all downloaded data?"
|
||||
msgstr "您确定要删除种子“{0}”(下载的数据文件会一并被删除)?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:789
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:680
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:832
|
||||
msgid "Seed"
|
||||
msgstr "种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:698
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:850
|
||||
msgid "Uninteresting (The peer has no pieces we need)"
|
||||
msgstr "无需要部分"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:700
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:852
|
||||
msgid "Choked (The peer is not allowing us to request pieces)"
|
||||
msgstr "拒绝请求"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:714
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:866
|
||||
msgid "Uninterested (We have no pieces the peer needs)"
|
||||
msgstr "无需要部分"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:716
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:868
|
||||
msgid "Choking (We are not allowing the peer to request pieces)"
|
||||
msgstr "拒绝请求"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:743
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:895
|
||||
msgid "Add Torrent"
|
||||
msgstr "添加种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:745
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:897
|
||||
msgid "From URL"
|
||||
msgstr "从URL"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:750
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:902
|
||||
msgid "Add torrent"
|
||||
msgstr "添加种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:753
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:905
|
||||
#, java-format
|
||||
msgid "Alternately, you can copy .torrent files to the directory {0}."
|
||||
msgstr "或者您可以将.torrent文件复制到以下目录{0}."
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:755
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:907
|
||||
msgid "Removing a .torrent file will cause the torrent to stop."
|
||||
msgstr "删除种子文件将导致中止该下载任务。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:772
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:924
|
||||
msgid "Create Torrent"
|
||||
msgstr "创建种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:775
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:927
|
||||
msgid "Data to seed"
|
||||
msgstr "做种数据"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:779
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:931
|
||||
msgid "File or directory to seed (must be within the specified path)"
|
||||
msgstr "做种文件或文件夹(必须下面为Snark指定的文件夹中)"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:783
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:935
|
||||
msgid "Select a tracker"
|
||||
msgstr "选择一个Tracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:796
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:948
|
||||
msgid "or"
|
||||
msgstr "或"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:799
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:951
|
||||
msgid "Specify custom tracker announce URL"
|
||||
msgstr "指定Open Tracker发布链接"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:802
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:954
|
||||
msgid "Create torrent"
|
||||
msgstr "创建种子"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:820
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:972
|
||||
msgid "Configuration"
|
||||
msgstr "设置"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:823
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:975
|
||||
msgid "Data directory"
|
||||
msgstr "数据文件夹"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:826
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:978
|
||||
msgid "Directory to store torrents and data"
|
||||
msgstr "种子及被做种文件的保存位置。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:828
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:980
|
||||
msgid "Edit i2psnark.config and restart to change"
|
||||
msgstr "编辑 i2psnark.config 并重启Snark后生效"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:832
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:984
|
||||
msgid "Auto start"
|
||||
msgstr "自动启动"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:836
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:988
|
||||
msgid "If checked, automatically start torrents that are added"
|
||||
msgstr "选中后Snark将自动启动已添加的所有种子。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:859
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1011
|
||||
msgid "Total uploader limit"
|
||||
msgstr ""
|
||||
msgstr "限制总上传种子数为"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:866
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1014
|
||||
msgid "peers"
|
||||
msgstr "用户"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1018
|
||||
msgid "Up bandwidth limit"
|
||||
msgstr "上传带宽限制"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:869
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1021
|
||||
msgid "Half available bandwidth recommended."
|
||||
msgstr "推荐设置为可用带宽的一半。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:871
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1023
|
||||
msgid "View or change router bandwidth"
|
||||
msgstr "浏览或修改路由器带宽"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:875
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1027
|
||||
msgid "Use open trackers also"
|
||||
msgstr "同时使用OpenTracker"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:879
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1031
|
||||
msgid "If checked, announce torrents to open trackers as well as the tracker listed in the torrent file"
|
||||
msgstr "选择后在OpenTracker及种子文件中的Tracker上同时发布。"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:883
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1035
|
||||
msgid "Open tracker announce URLs"
|
||||
msgstr "Open Tracker发布链接"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:894
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1047
|
||||
msgid "Inbound Settings"
|
||||
msgstr "入站设置"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1053
|
||||
msgid "Outbound Settings"
|
||||
msgstr "出站设置"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1060
|
||||
msgid "I2CP host"
|
||||
msgstr "I2CP主机"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:899
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1065
|
||||
msgid "I2CP port"
|
||||
msgstr "I2CP端口"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:912
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1077
|
||||
msgid "I2CP options"
|
||||
msgstr "I2CP选项"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:917
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1082
|
||||
msgid "Save configuration"
|
||||
msgstr "保存设置"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:970
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1092
|
||||
#, java-format
|
||||
msgid "1 hop"
|
||||
msgid_plural "{0} hops"
|
||||
msgstr[0] "{0}跳"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1093
|
||||
#, java-format
|
||||
msgid "1 tunnel"
|
||||
msgid_plural "{0} tunnels"
|
||||
msgstr[0] "{0}隧道"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1107
|
||||
#, java-format
|
||||
msgid "1 "
|
||||
msgid_plural "{0} "
|
||||
msgstr[0] ""
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1237
|
||||
msgid "Up to higher level directory"
|
||||
msgstr "上一层文件夹"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1242
|
||||
msgid "File"
|
||||
msgstr "文件"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1242
|
||||
msgid "Size"
|
||||
msgstr "大小"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1265
|
||||
msgid "Directory"
|
||||
msgstr "文件夹"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1270
|
||||
msgid "Torrent not found?"
|
||||
msgstr "种子未找到"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1278
|
||||
msgid "File not found in torrent?"
|
||||
msgstr "种子中没有发现文件?"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1284
|
||||
msgid "complete"
|
||||
msgstr "完成"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1285
|
||||
msgid "bytes remaining"
|
||||
msgstr "剩余字节数"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1408
|
||||
#, java-format
|
||||
msgid "Torrent fetched from {0}"
|
||||
msgstr "从{0}获取种子成功"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:998
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1436
|
||||
#, java-format
|
||||
msgid "Torrent at {0} was not valid"
|
||||
msgstr "{0}的种子中有错误"
|
||||
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1003
|
||||
#: ../java/src/org/klomp/snark/web/I2PSnarkServlet.java:1441
|
||||
#, java-format
|
||||
msgid "Torrent was not retrieved from {0}"
|
||||
msgstr "从{0}获得种子失败"
|
||||
|
||||
#~ msgid "Cannot change the I2CP settings while torrents are active"
|
||||
#~ msgstr "正在下载/上传,无法更改I2CP设置"
|
||||
#~ msgid "{0} torrents"
|
||||
#~ msgstr "{0} 个种子"
|
||||
#~ msgid "Non-i2p tracker in \"{0}\", deleting it from our list of trackers!"
|
||||
#~ msgstr ""
|
||||
#~ "【匿名性警告】\"{0}\" 中含有非I2P Tracker,程序将从Tracker列表中将其删除。"
|
||||
#~ msgid "Custom tracker URL"
|
||||
#~ msgstr "自定义TrackerURL"
|
||||
#~ msgid "Configure"
|
||||
|
@ -51,7 +51,7 @@ do
|
||||
# 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 \
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
-o ${i}t
|
||||
if [ $? -ne 0 ]
|
||||
|
@ -124,13 +124,13 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
* Tweak that first HTTP response line (HTTP 200 OK, etc)
|
||||
*
|
||||
*/
|
||||
protected String filterResponseLine(String line) {
|
||||
protected static String filterResponseLine(String line) {
|
||||
return line;
|
||||
}
|
||||
|
||||
/** we ignore any potential \r, since we trim it on write anyway */
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
private static boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
/** ok, received, now munge & write it */
|
||||
private void writeHeader() throws IOException {
|
||||
@ -275,7 +275,8 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
_context.statManager().addRateData("i2ptunnel.httpExpanded", (long)expanded, end-start);
|
||||
}
|
||||
}
|
||||
private class InternalGZIPInputStream extends GZIPInputStream {
|
||||
|
||||
private static class InternalGZIPInputStream extends GZIPInputStream {
|
||||
public InternalGZIPInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
@ -318,6 +319,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
return super.toString() + ": " + _in;
|
||||
}
|
||||
|
||||
/*******
|
||||
public static void main(String args[]) {
|
||||
String simple = "HTTP/1.1 200 OK\n" +
|
||||
"foo: bar\n" +
|
||||
@ -367,7 +369,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
"A:\n" +
|
||||
"\n";
|
||||
|
||||
/* */
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
test("Filtered windows", winfilter, true);
|
||||
@ -382,7 +383,6 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
test("Invalid (bad headers)", invalid5, true);
|
||||
test("Invalid (bad headers2)", invalid6, false);
|
||||
test("Invalid (bad headers3)", invalid7, false);
|
||||
/* */
|
||||
}
|
||||
|
||||
private static void test(String name, String orig, boolean shouldPass) {
|
||||
@ -401,4 +401,5 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
System.out.println("Properly fails with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
******/
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.i2ptunnel.socks.I2PSOCKSIRCTunnel;
|
||||
import net.i2p.i2ptunnel.socks.I2PSOCKSTunnel;
|
||||
import net.i2p.i2ptunnel.streamr.StreamrConsumer;
|
||||
import net.i2p.i2ptunnel.streamr.StreamrProducer;
|
||||
@ -895,6 +896,39 @@ public class I2PTunnel implements Logging, EventDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run an SOCKS IRC tunnel on the given port number
|
||||
* @since 0.7.12
|
||||
*/
|
||||
public void runSOCKSIRCTunnel(String args[], Logging l) {
|
||||
if (args.length >= 1 && args.length <= 2) {
|
||||
int _port = -1;
|
||||
try {
|
||||
_port = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
l.log("invalid port");
|
||||
_log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isShared = false;
|
||||
if (args.length > 1)
|
||||
isShared = "true".equalsIgnoreCase(args[1].trim());
|
||||
|
||||
ownDest = !isShared;
|
||||
I2PTunnelTask task;
|
||||
task = new I2PSOCKSIRCTunnel(_port, l, ownDest, (EventDispatcher) this, this);
|
||||
addtask(task);
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
|
||||
} else {
|
||||
l.log("sockstunnel <port>");
|
||||
l.log(" creates a tunnel that distributes SOCKS requests.");
|
||||
notifyEvent("sockstunnelTaskId", Integer.valueOf(-1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streamr client
|
||||
*
|
||||
|
@ -54,7 +54,7 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
if (dests.isEmpty()) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
@ -78,8 +78,9 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets);
|
||||
} catch (Exception ex) {
|
||||
_log.info("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Error connecting", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
|
@ -641,7 +641,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
while (open) {
|
||||
try {
|
||||
synchronized (_waitingSockets) {
|
||||
if (_waitingSockets.size() <= 0)
|
||||
if (_waitingSockets.isEmpty())
|
||||
_waitingSockets.wait();
|
||||
else
|
||||
s = (Socket)_waitingSockets.remove(0);
|
||||
|
@ -26,6 +26,7 @@ import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base32;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
@ -46,8 +47,14 @@ import net.i2p.util.Translate;
|
||||
* $method http://i2p/$b64key/$path $protocolVersion
|
||||
* or
|
||||
* $method /$site/$path $protocolVersion
|
||||
* or (deprecated)
|
||||
* $method /eepproxy/$site/$path $protocolVersion
|
||||
* </pre>
|
||||
*
|
||||
* Note that http://i2p/$b64key/... and /eepproxy/$site/... are not recommended
|
||||
* in browsers or other user-visible applications, as relative links will not
|
||||
* resolve correctly, cookies won't work, etc.
|
||||
*
|
||||
* If the $site resolves with the I2P naming service, then it is directed towards
|
||||
* that eepsite, otherwise it is directed towards this client's outproxy (typically
|
||||
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
|
||||
@ -196,7 +203,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (size <= 0) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Proxy list is empty - no outproxy available");
|
||||
l.log("Proxy list is emtpy - no outproxy available");
|
||||
l.log("Proxy list is empty - no outproxy available");
|
||||
return null;
|
||||
}
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
@ -303,14 +310,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
if (method == null) { // first line (GET /base64/realaddr)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Method is null for [" + line + "]");
|
||||
_log.debug(getPrefix(requestId) + "First line [" + line + "]");
|
||||
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
// TODO use Java URL class to make all this simpler and more robust
|
||||
// That will also fix IPV6 [a:b:c]
|
||||
String request = line.substring(pos + 1);
|
||||
if (request.startsWith("/") && getTunnel().getClientOptions().getProperty("i2ptunnel.noproxy") != null) {
|
||||
// what is this for ???
|
||||
request = "http://i2p" + request;
|
||||
} else if (request.startsWith("/eepproxy/")) {
|
||||
// /eepproxy/foo.i2p/bar/baz.html HTTP/1.0
|
||||
@ -322,6 +331,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
// "http://" + "foo.i2p/bar/baz.html" + " HTTP/1.0"
|
||||
request = "http://" + uri + subRequest.substring(protopos);
|
||||
} else if (request.toLowerCase().startsWith("http://i2p/")) {
|
||||
// http://i2p/b64key/bar/baz.html HTTP/1.0
|
||||
String subRequest = request.substring("http://i2p/".length());
|
||||
int protopos = subRequest.indexOf(" ");
|
||||
String uri = subRequest.substring(0, protopos);
|
||||
if (uri.indexOf("/") == -1) {
|
||||
uri = uri + "/";
|
||||
}
|
||||
// "http://" + "b64key/bar/baz.html" + " HTTP/1.0"
|
||||
request = "http://" + uri + subRequest.substring(protopos);
|
||||
}
|
||||
|
||||
pos = request.indexOf("//");
|
||||
@ -334,6 +353,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
targetRequest = request;
|
||||
|
||||
// pos is the start of the path
|
||||
pos = request.indexOf("/");
|
||||
if (pos == -1) {
|
||||
method = null;
|
||||
@ -346,7 +366,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
int port = 80;
|
||||
if(posPort != -1) {
|
||||
String[] parts = host.split(":");
|
||||
try {
|
||||
host = parts[0];
|
||||
} catch (ArrayIndexOutOfBoundsException ex) {
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
|
||||
}
|
||||
try {
|
||||
port = Integer.parseInt(parts[1]);
|
||||
} catch(Exception exc) {
|
||||
@ -354,9 +384,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
if (host.toLowerCase().equals("proxy.i2p")) {
|
||||
// Go through the various types of host names, set
|
||||
// the host and destination variables accordingly,
|
||||
// and transform the first line.
|
||||
// For all i2p network hosts, ensure that the host is a
|
||||
// Base 32 hostname so that we do not reveal our name for it
|
||||
// in our addressbook (all naming is local),
|
||||
// and it is removed from the request line.
|
||||
|
||||
if (host.length() >= 516 && host.indexOf(".") < 0) {
|
||||
// http://b64key/bar/baz.html
|
||||
destination = host;
|
||||
host = getHostName(destination);
|
||||
line = method + ' ' + request.substring(pos);
|
||||
} else if (host.toLowerCase().equals("proxy.i2p")) {
|
||||
// so we don't do any naming service lookups
|
||||
destination = "proxy.i2p";
|
||||
destination = host;
|
||||
usingInternalServer = true;
|
||||
} else if (host.toLowerCase().endsWith(".i2p")) {
|
||||
// Destination gets the host name
|
||||
@ -392,6 +435,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
// Key contains data, lets not ignore it
|
||||
if (ahelperKey != null) {
|
||||
// ahelperKey will be validated later
|
||||
|
||||
// Host resolvable only with addresshelper
|
||||
if ( (host == null) || ("i2p".equals(host)) )
|
||||
@ -401,12 +445,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
} else {
|
||||
// Host resolvable from database, verify addresshelper key
|
||||
// Silently bypass correct keys, otherwise alert
|
||||
if (!host.equals(ahelperKey))
|
||||
String destB64 = null;
|
||||
try {
|
||||
Destination _dest = I2PTunnel.destFromName(host);
|
||||
if (_dest != null)
|
||||
destB64 = _dest.toBase64();
|
||||
} catch (DataFormatException dfe) {}
|
||||
if (destB64 != null && !destB64.equals(ahelperKey))
|
||||
{
|
||||
// Conflict: handle when URL reconstruction done
|
||||
ahelperConflict = true;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + host + "], specified key [" + ahelperKey + "].");
|
||||
_log.warn(getPrefix(requestId) + "Addresshelper key conflict for site [" + destination + "], trusted key [" + destB64 + "], specified key [" + ahelperKey + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,14 +478,22 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
{
|
||||
|
||||
if (out != null) {
|
||||
long alias = I2PAppContext.getGlobalContext().random().nextLong();
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
String conflictURL = protocol + alias + ".i2p/?" + initialFragments;
|
||||
byte[] header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
|
||||
out.write(header);
|
||||
out.write(_("To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"{1}\">here</a>.", trustedURL, conflictURL).getBytes("UTF-8"));
|
||||
out.write(("<p></div>").getBytes());
|
||||
writeFooter(out);
|
||||
// convert ahelperKey to b32
|
||||
String alias = getHostName(ahelperKey);
|
||||
if (alias.equals("i2p")) {
|
||||
// bad ahelperKey
|
||||
byte[] header = getErrorPage("dnfb", ERR_DESTINATION_UNKNOWN);
|
||||
writeErrorMessage(header, out, targetRequest, false, destination, null);
|
||||
} else {
|
||||
String trustedURL = protocol + uriPath + urlEncoding;
|
||||
// Fixme - any path is lost
|
||||
String conflictURL = protocol + alias + '/' + urlEncoding;
|
||||
byte[] header = getErrorPage("ahelper-conflict", ERR_AHELPER_CONFLICT);
|
||||
out.write(header);
|
||||
out.write(_("To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>.", trustedURL, conflictURL).getBytes("UTF-8"));
|
||||
out.write(("<p></div>").getBytes());
|
||||
writeFooter(out);
|
||||
}
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
@ -450,7 +508,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
line = method + " " + request.substring(pos);
|
||||
} else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1")) {
|
||||
} else if (host.toLowerCase().equals("localhost") || host.equals("127.0.0.1") ||
|
||||
host.startsWith("192.168.")) {
|
||||
// if somebody is trying to get to 192.168.example.com, oh well
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("localhost", ERR_LOCALHOST));
|
||||
writeFooter(out);
|
||||
@ -482,6 +542,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
} else {
|
||||
// what is left for here? a hostname with no dots, and != "i2p"
|
||||
// and not a destination ???
|
||||
// Perhaps something in privatehosts.txt ...
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
if (pos < 0) {
|
||||
@ -494,31 +557,49 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return;
|
||||
}
|
||||
destination = request.substring(0, pos);
|
||||
host = getHostName(destination);
|
||||
line = method + " " + request.substring(pos);
|
||||
} // end host name processing
|
||||
|
||||
if (port != 80 && !usingWWWProxy) {
|
||||
if (out != null) {
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
writeFooter(out);
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isValid = usingWWWProxy || usingInternalServer || isSupportedAddress(host, protocol);
|
||||
if (!isValid) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(requestId) + "notValid(" + host + ")");
|
||||
method = null;
|
||||
destination = null;
|
||||
break;
|
||||
} else if ((!usingWWWProxy) && (!usingInternalServer)) {
|
||||
if (_log.shouldLog(Log.INFO)) _log.info(getPrefix(requestId) + "host=getHostName(" + destination + ")");
|
||||
host = getHostName(destination); // hide original host
|
||||
}
|
||||
|
||||
// don't do this, it forces yet another hostname lookup,
|
||||
// and in all cases host was already set above
|
||||
//if ((!usingWWWProxy) && (!usingInternalServer)) {
|
||||
// String oldhost = host;
|
||||
// host = getHostName(destination); // hide original host
|
||||
// if (_log.shouldLog(Log.INFO))
|
||||
// _log.info(getPrefix(requestId) + " oldhost " + oldhost + " newhost " + host + " dest " + destination);
|
||||
//}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getPrefix(requestId) + "METHOD:" + method + ":");
|
||||
_log.debug(getPrefix(requestId) + "PROTOC:" + protocol + ":");
|
||||
_log.debug(getPrefix(requestId) + "HOST :" + host + ":");
|
||||
_log.debug(getPrefix(requestId) + "DEST :" + destination + ":");
|
||||
_log.debug(getPrefix(requestId) + "METHOD: \"" + method + "\"");
|
||||
_log.debug(getPrefix(requestId) + "PROTOC: \"" + protocol + "\"");
|
||||
_log.debug(getPrefix(requestId) + "HOST : \"" + host + "\"");
|
||||
_log.debug(getPrefix(requestId) + "DEST : \"" + destination + "\"");
|
||||
}
|
||||
|
||||
// end first line processing
|
||||
|
||||
} else {
|
||||
if (lowercaseLine.startsWith("host: ") && !usingWWWProxy) {
|
||||
// Note that we only pass the original Host: line through to the outproxy
|
||||
// But we don't create a Host: line if it wasn't sent to us
|
||||
line = "Host: " + host;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Setting host = " + host);
|
||||
@ -575,7 +656,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
if (method == null || destination == null) {
|
||||
l.log("No HTTP method found in the request.");
|
||||
//l.log("No HTTP method found in the request.");
|
||||
if (out != null) {
|
||||
if ("http://".equalsIgnoreCase(protocol))
|
||||
out.write(getErrorPage("denied", ERR_REQUEST_DENIED));
|
||||
@ -598,7 +679,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
return;
|
||||
}
|
||||
|
||||
Destination clientDest = I2PTunnel.destFromName(destination);
|
||||
// If the host is "i2p", the getHostName() lookup failed, don't try to
|
||||
// look it up again as the naming service does not do negative caching
|
||||
// so it will be slow.
|
||||
|
||||
Destination clientDest;
|
||||
if ("i2p".equals(host))
|
||||
clientDest = null;
|
||||
else
|
||||
clientDest = I2PTunnel.destFromName(destination);
|
||||
|
||||
if (clientDest == null) {
|
||||
//l.log("Could not resolve " + destination + ".");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@ -633,24 +723,27 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
I2PTunnelRunner runner = new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
|
||||
} catch (SocketException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (IOException ex) {
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getPrefix(requestId) + "Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (I2PException ex) {
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) {
|
||||
IOException ex = new IOException("OOM");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
_log.error("getPrefix(requestId) + Error trying to connect", oom);
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
}
|
||||
@ -662,6 +755,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
* We can't use BufferedReader for POST because we can't have readahead,
|
||||
* since we are passing the stream on to I2PTunnelRunner for the POST data.
|
||||
*
|
||||
* Warning - BufferedReader removes \r, DataHelper does not
|
||||
* Warning - DataHelper limits line length, BufferedReader does not
|
||||
* Todo: Limit line length for buffered reads, or go back to unbuffered for all
|
||||
*/
|
||||
private static class InputReader {
|
||||
BufferedReader _br;
|
||||
@ -679,12 +775,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return b32hash.b32.i2p, or "i2p" on lookup failure.
|
||||
* Prior to 0.7.12, returned b64 key
|
||||
*/
|
||||
private final static String getHostName(String host) {
|
||||
if (host == null) return null;
|
||||
if (host.length() == 60 && host.toLowerCase().endsWith(".b32.i2p"))
|
||||
return host;
|
||||
try {
|
||||
Destination dest = I2PTunnel.destFromName(host);
|
||||
if (dest == null) return "i2p";
|
||||
return dest.toBase64();
|
||||
return Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
|
||||
} catch (DataFormatException dfe) {
|
||||
return "i2p";
|
||||
}
|
||||
@ -858,8 +960,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private final static String SUPPORTED_HOSTS[] = { "i2p", "www.i2p.com", "i2p."};
|
||||
|
||||
/** @param host ignored */
|
||||
private static boolean isSupportedAddress(String host, String protocol) {
|
||||
if ((host == null) || (protocol == null)) return false;
|
||||
|
||||
/****
|
||||
* Let's not look up the name _again_
|
||||
* and now that host is a b32, this was failing
|
||||
*
|
||||
boolean found = false;
|
||||
String lcHost = host.toLowerCase();
|
||||
for (int i = 0; i < SUPPORTED_HOSTS.length; i++) {
|
||||
@ -876,7 +984,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
} catch (DataFormatException dfe) {
|
||||
}
|
||||
}
|
||||
|
||||
****/
|
||||
return protocol.equalsIgnoreCase("http://");
|
||||
}
|
||||
|
||||
|
@ -321,6 +321,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** ridiculously long, just to prevent OOM DOS @since 0.7.13 */
|
||||
private static final int MAX_HEADERS = 60;
|
||||
|
||||
private Properties readHeaders(InputStream in, StringBuilder command) throws IOException {
|
||||
Properties headers = new Properties();
|
||||
StringBuilder buf = new StringBuilder(128);
|
||||
@ -344,7 +347,10 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (trimmed > 0)
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed, 0);
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
if (++i > MAX_HEADERS)
|
||||
throw new IOException("Too many header lines - max " + MAX_HEADERS);
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
|
@ -61,7 +61,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
if (dests.size() <= 0) {
|
||||
if (dests.isEmpty()) {
|
||||
l.log("No target destinations found");
|
||||
notifyEvent("openClientResult", "error");
|
||||
return;
|
||||
@ -82,15 +82,15 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
try {
|
||||
i2ps = createI2PSocket(clientDest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
StringBuilder expectedPong = new StringBuilder();
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " in");
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PAppThread(new IrcInboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " out");
|
||||
Thread out = new I2PAppThread(new IrcOutboundFilter(s,i2ps, expectedPong), "IRC Client " + __clientId + " out", true);
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error connecting", ex);
|
||||
l.log(ex.getMessage());
|
||||
//l.log("Error connecting: " + ex.getMessage());
|
||||
closeSocket(s);
|
||||
if (i2ps != null) {
|
||||
synchronized (sockLock) {
|
||||
@ -117,13 +117,13 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
/*************************************************************************
|
||||
*
|
||||
*/
|
||||
private class IrcInboundFilter implements Runnable {
|
||||
public static class IrcInboundFilter implements Runnable {
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuilder expectedPong;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuilder pong) {
|
||||
public IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
@ -191,13 +191,13 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
/*************************************************************************
|
||||
*
|
||||
*/
|
||||
private class IrcOutboundFilter implements Runnable {
|
||||
public static class IrcOutboundFilter implements Runnable {
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuilder expectedPong;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuilder pong) {
|
||||
public IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
@ -266,7 +266,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
|
||||
public String inboundFilter(String s, StringBuilder expectedPong) {
|
||||
public static String inboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",4);
|
||||
String command;
|
||||
@ -353,7 +353,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return null;
|
||||
}
|
||||
|
||||
public String outboundFilter(String s, StringBuilder expectedPong) {
|
||||
public static String outboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",3);
|
||||
String command;
|
||||
@ -378,7 +378,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"KICK",
|
||||
"HELPME",
|
||||
"RULES",
|
||||
"TOPIC"
|
||||
"TOPIC",
|
||||
"ISON" // jIRCii uses this for a ping (response is 303)
|
||||
};
|
||||
|
||||
if(field[0].length()==0)
|
||||
@ -390,7 +391,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
command = field[0].toUpperCase();
|
||||
|
||||
if ("PING".equalsIgnoreCase(command)) {
|
||||
if ("PING".equals(command)) {
|
||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||
// like BitchX, actually expect certain behavior. It sends two different pings:
|
||||
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
||||
@ -426,19 +427,19 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
return rv;
|
||||
}
|
||||
if ("PONG".equalsIgnoreCase(command))
|
||||
if ("PONG".equals(command))
|
||||
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++)
|
||||
{
|
||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||
if(allowedCommands[i].equals(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// mIRC sends "NOTICE user :DCC Send file (IP)"
|
||||
// in addition to the CTCP version
|
||||
if("NOTICE".equalsIgnoreCase(command))
|
||||
if("NOTICE".equals(command))
|
||||
{
|
||||
String msg = field[2];
|
||||
if(msg.startsWith(":DCC "))
|
||||
@ -447,7 +448,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||
if("PRIVMSG".equals(command) || "NOTICE".equals(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[2];
|
||||
@ -465,14 +466,16 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return s;
|
||||
}
|
||||
|
||||
if("USER".equalsIgnoreCase(command)) {
|
||||
if("USER".equals(command)) {
|
||||
int idx = field[2].lastIndexOf(":");
|
||||
if(idx<0)
|
||||
return "USER user hostname localhost :realname";
|
||||
String realname = field[2].substring(idx+1);
|
||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||
return ret;
|
||||
} else if ("QUIT".equalsIgnoreCase(command)) {
|
||||
}
|
||||
|
||||
if ("QUIT".equals(command)) {
|
||||
return "QUIT :leaving";
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,9 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* There are three options for mangling the desthash. Put the option in the
|
||||
* "custom options" section of i2ptunnel.
|
||||
* - ircserver.method unset: Defaults to user.
|
||||
* - ircserver.method=user: Use method described above.
|
||||
* - ircserver.method=webirc: Use the WEBIRC protocol.
|
||||
* - ircserver.cloakKey unset: Cloak with a random value that is persistent for
|
||||
* the life of this tunnel. This is the default.
|
||||
* - ircserver.cloakKey=somepassphrase: Cloak with the hash of the passphrase. Use this to
|
||||
@ -39,6 +42,8 @@ import net.i2p.util.Log;
|
||||
* be able to track users even when they switch servers.
|
||||
* Note: don't quote or put spaces in the passphrase,
|
||||
* the i2ptunnel gui can't handle it.
|
||||
* - ircserver.webircPassword=password The password to use for the WEBIRC protocol.
|
||||
* - ircserver.webircSpoofIP=IP The IP
|
||||
* - ircserver.fakeHostname=%f.b32.i2p: Set the fake hostname sent by I2PTunnel,
|
||||
* %f is the full B32 destination hash
|
||||
* %c is the cloaked hash.
|
||||
@ -48,7 +53,12 @@ import net.i2p.util.Log;
|
||||
* @author zzz
|
||||
*/
|
||||
public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
public static final String PROP_METHOD="ircserver.method";
|
||||
public static final String PROP_METHOD_DEFAULT="user";
|
||||
public static final String PROP_CLOAK="ircserver.cloakKey";
|
||||
public static final String PROP_WEBIRC_PASSWORD="ircserver.webircPassword";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP="ircserver.webircSpoofIP";
|
||||
public static final String PROP_WEBIRC_SPOOF_IP_DEFAULT="127.0.0.1";
|
||||
public static final String PROP_HOSTNAME="ircserver.fakeHostname";
|
||||
public static final String PROP_HOSTNAME_DEFAULT="%f.b32.i2p";
|
||||
|
||||
@ -67,7 +77,20 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
|
||||
/** generate a random 32 bytes, or the hash of the passphrase */
|
||||
private void initCloak(I2PTunnel tunnel) {
|
||||
// get the properties of this server-tunnel
|
||||
Properties opts = tunnel.getClientOptions();
|
||||
|
||||
// get method of host faking
|
||||
this.method = opts.getProperty(PROP_METHOD, PROP_METHOD_DEFAULT);
|
||||
assert this.method != null;
|
||||
|
||||
// get the password for the webirc method
|
||||
this.webircPassword = opts.getProperty(PROP_WEBIRC_PASSWORD);
|
||||
|
||||
// get the spoof IP for the webirc method
|
||||
this.webircSpoofIP = opts.getProperty(PROP_WEBIRC_SPOOF_IP, PROP_WEBIRC_SPOOF_IP_DEFAULT);
|
||||
|
||||
// get the cloaking passphrase
|
||||
String passphrase = opts.getProperty(PROP_CLOAK);
|
||||
if (passphrase == null) {
|
||||
this.cloakKey = new byte[Hash.HASH_LENGTH];
|
||||
@ -76,17 +99,30 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
this.cloakKey = SHA256Generator.getInstance().calculateHash(passphrase.trim().getBytes()).getData();
|
||||
}
|
||||
|
||||
// get the fake hostmask to use
|
||||
this.hostname = opts.getProperty(PROP_HOSTNAME, PROP_HOSTNAME_DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
try {
|
||||
// give them 15 seconds to send in the request
|
||||
socket.setReadTimeout(15*1000);
|
||||
InputStream in = socket.getInputStream();
|
||||
String modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
String modifiedRegistration;
|
||||
if(!this.method.equals("webirc")) {
|
||||
// give them 15 seconds to send in the request
|
||||
socket.setReadTimeout(15*1000);
|
||||
InputStream in = socket.getInputStream();
|
||||
modifiedRegistration = filterRegistration(in, cloakDest(socket.getPeerDestination()));
|
||||
socket.setReadTimeout(readTimeout);
|
||||
} else {
|
||||
StringBuffer buf = new StringBuffer("WEBIRC ");
|
||||
buf.append(this.webircPassword);
|
||||
buf.append(" cgiirc ");
|
||||
buf.append(cloakDest(socket.getPeerDestination()));
|
||||
buf.append(' ');
|
||||
buf.append(this.webircSpoofIP);
|
||||
buf.append("\r\n");
|
||||
modifiedRegistration = buf.toString();
|
||||
}
|
||||
Socket s = new Socket(remoteHost, remotePort);
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedRegistration.getBytes(), null);
|
||||
} catch (SocketException ex) {
|
||||
@ -185,4 +221,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
|
||||
|
||||
private byte[] cloakKey; // 32 bytes of stuff to scramble the dest with
|
||||
private String hostname;
|
||||
private String method;
|
||||
private String webircPassword;
|
||||
private String webircSpoofIP;
|
||||
}
|
||||
|
@ -124,11 +124,14 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
OutputStream i2pout = i2ps.getOutputStream(); //new BufferedOutputStream(i2ps.getOutputStream(), MAX_PACKET_SIZE);
|
||||
if (initialI2PData != null) {
|
||||
synchronized (slock) {
|
||||
// this does not increment totalSent
|
||||
i2pout.write(initialI2PData);
|
||||
i2pout.flush();
|
||||
// do NOT flush here, it will block and then onTimeout.run() won't happen on fail.
|
||||
//i2pout.flush();
|
||||
}
|
||||
}
|
||||
if (initialSocketData != null) {
|
||||
// this does not increment totalReceived
|
||||
out.write(initialSocketData);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -150,6 +153,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("runner has a timeout job, totalReceived = " + totalReceived
|
||||
+ " totalSent = " + totalSent + " job = " + onTimeout);
|
||||
// should we only look at totalReceived?
|
||||
if ( (totalSent <= 0) && (totalReceived <= 0) )
|
||||
onTimeout.run();
|
||||
}
|
||||
@ -271,7 +275,7 @@ public class I2PTunnelRunner extends I2PAppThread implements I2PSocket.SocketErr
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Flushing after sending " + len + " bytes through");
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(direction + ": " + len + " bytes flushed through to "
|
||||
_log.debug(direction + ": " + len + " bytes flushed through " + (_toI2P ? "to " : "from ")
|
||||
+ i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6));
|
||||
try {
|
||||
Thread.sleep(I2PTunnel.PACKET_DELAY);
|
||||
|
@ -213,7 +213,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
sockMgr.getSession().destroySession();
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error destroying the session", ex);
|
||||
System.exit(1);
|
||||
//System.exit(1);
|
||||
}
|
||||
l.log("Server shut down.");
|
||||
open = false;
|
||||
@ -294,6 +294,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
}
|
||||
|
||||
protected void blockingHandle(I2PSocket socket) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Incoming connection to '" + toString() + "' from: " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
|
||||
long afterSocket = -1;
|
||||
//local is fast, so synchronously. Does not need that many
|
||||
|
@ -144,6 +144,8 @@ public class TunnelController implements Logging {
|
||||
startIrcClient();
|
||||
} else if("sockstunnel".equals(type)) {
|
||||
startSocksClient();
|
||||
} else if("socksirctunnel".equals(type)) {
|
||||
startSocksIRCClient();
|
||||
} else if("connectclient".equals(type)) {
|
||||
startConnectClient();
|
||||
} else if ("client".equals(type)) {
|
||||
@ -211,6 +213,14 @@ public class TunnelController implements Logging {
|
||||
_tunnel.runSOCKSTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
}
|
||||
|
||||
/** @since 0.7.12 */
|
||||
private void startSocksIRCClient() {
|
||||
setListenOn();
|
||||
String listenPort = getListenPort();
|
||||
String sharedClient = getSharedClient();
|
||||
_tunnel.runSOCKSIRCTunnel(new String[] { listenPort, sharedClient }, this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Streamr client is a UDP server, use the listenPort field for targetPort
|
||||
* and the listenOnInterface field for the targetHost
|
||||
|
@ -329,7 +329,7 @@ public class TunnelControllerGroup {
|
||||
Set owners = (Set)_sessions.get(session);
|
||||
if (owners != null) {
|
||||
owners.remove(controller);
|
||||
if (owners.size() <= 0) {
|
||||
if (owners.isEmpty()) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("After releasing session " + session + " by " + controller + ", no more owners remain");
|
||||
shouldClose = true;
|
||||
|
@ -0,0 +1,62 @@
|
||||
/* I2PSOCKSTunnel is released under the terms of the GNU GPL,
|
||||
* with an additional exception. For further details, see the
|
||||
* licensing terms in I2PTunnel.java.
|
||||
*
|
||||
* Copyright (c) 2004 by human
|
||||
*/
|
||||
package net.i2p.i2ptunnel.socks;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.i2ptunnel.I2PTunnel;
|
||||
import net.i2p.i2ptunnel.I2PTunnelIRCClient;
|
||||
import net.i2p.i2ptunnel.Logging;
|
||||
import net.i2p.util.EventDispatcher;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/*
|
||||
* Pipe SOCKS IRC connections through I2PTunnelIRCClient filtering,
|
||||
* to get the best of both worlds:
|
||||
*
|
||||
* - SOCKS lets you specify the host so you don't have to set up
|
||||
* a tunnel for each IRC server in advance
|
||||
* - IRC filtering for security
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class I2PSOCKSIRCTunnel extends I2PSOCKSTunnel {
|
||||
|
||||
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(I2PSOCKSIRCTunnel.class);
|
||||
private static int __clientId = 0;
|
||||
|
||||
public I2PSOCKSIRCTunnel(int localPort, Logging l, boolean ownDest, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(localPort, l, ownDest, notifyThis, tunnel);
|
||||
setName(getLocalPort() + " -> SOCKSIRCTunnel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as in I2PSOCKSTunnel, but run the filters from I2PTunnelIRCClient
|
||||
* instead of I2PTunnelRunner
|
||||
*/
|
||||
@Override
|
||||
protected void clientConnectionRun(Socket s) {
|
||||
try {
|
||||
_log.error("SOCKS IRC Tunnel Start");
|
||||
SOCKSServer serv = SOCKSServerFactory.createSOCKSServer(s);
|
||||
Socket clientSock = serv.getClientSocket();
|
||||
I2PSocket destSock = serv.getDestinationI2PSocket(this);
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PAppThread(new I2PTunnelIRCClient.IrcInboundFilter(clientSock, destSock, expectedPong), "SOCKS IRC Client " + (++__clientId) + " in", true);
|
||||
in.start();
|
||||
Thread out = new I2PAppThread(new I2PTunnelIRCClient.IrcOutboundFilter(clientSock, destSock, expectedPong), "SOCKS IRC Client " + __clientId + " out", true);
|
||||
out.start();
|
||||
} catch (SOCKSException e) {
|
||||
_log.error("Error from SOCKS connection", e);
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
}
|
@ -224,7 +224,7 @@ public class SOCKS4aServer extends SOCKSServer {
|
||||
throw new SOCKSException(err);
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
if (proxies == null || proxies.isEmpty()) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either - host: " + connHostName;
|
||||
_log.error(err);
|
||||
try {
|
||||
|
@ -89,10 +89,10 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
int method = Method.NO_ACCEPTABLE_METHODS;
|
||||
|
||||
for (int i = 0; i < nMethods; ++i) {
|
||||
method = in.readByte() & 0xff;
|
||||
if (method == Method.NO_AUTH_REQUIRED) {
|
||||
int meth = in.readByte() & 0xff;
|
||||
if (meth == Method.NO_AUTH_REQUIRED) {
|
||||
// That's fine, we do support this method
|
||||
break;
|
||||
method = meth;
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +119,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
int socksVer = in.readByte() & 0xff;
|
||||
if (socksVer != SOCKS_VERSION_5) {
|
||||
_log.debug("error in SOCKS5 request (protocol != 5? wtf?)");
|
||||
throw new SOCKSException("Invalid protocol version in request");
|
||||
throw new SOCKSException("Invalid protocol version in request: " + socksVer);
|
||||
}
|
||||
|
||||
int command = in.readByte() & 0xff;
|
||||
@ -332,7 +332,7 @@ public class SOCKS5Server extends SOCKSServer {
|
||||
throw new SOCKSException(err);
|
||||
} else {
|
||||
List<String> proxies = t.getProxies(connPort);
|
||||
if (proxies == null || proxies.size() <= 0) {
|
||||
if (proxies == null || proxies.isEmpty()) {
|
||||
String err = "No outproxy configured for port " + connPort + " and no default configured either";
|
||||
_log.error(err);
|
||||
try {
|
||||
|
@ -50,7 +50,7 @@ public class SOCKSUDPUnwrapper implements Source, Sink {
|
||||
|
||||
int headerlen = h.getBytes().length;
|
||||
byte unwrapped[] = new byte[data.length - headerlen];
|
||||
System.arraycopy(unwrapped, 0, data, headerlen, unwrapped.length);
|
||||
System.arraycopy(data, headerlen, unwrapped, 0, unwrapped.length);
|
||||
this.sink.send(dest, unwrapped);
|
||||
}
|
||||
|
||||
|
@ -39,8 +39,8 @@ public class SOCKSUDPWrapper implements Source, Sink {
|
||||
|
||||
byte[] header = h.getBytes();
|
||||
byte wrapped[] = new byte[header.length + data.length];
|
||||
System.arraycopy(wrapped, 0, header, 0, header.length);
|
||||
System.arraycopy(wrapped, header.length, data, 0, data.length);
|
||||
System.arraycopy(header, 0, wrapped, 0, header.length);
|
||||
System.arraycopy(data, 0, wrapped, header.length, data.length);
|
||||
this.sink.send(from, wrapped);
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,9 @@ public class IndexBean {
|
||||
private String _action;
|
||||
private int _tunnel;
|
||||
private long _prevNonce;
|
||||
private long _prevNonce2;
|
||||
private long _curNonce;
|
||||
private long _nextNonce;
|
||||
private String _passphrase;
|
||||
|
||||
private String _type;
|
||||
private String _name;
|
||||
@ -79,8 +79,11 @@ public class IndexBean {
|
||||
public static final int NOT_RUNNING = 3;
|
||||
public static final int STANDBY = 4;
|
||||
|
||||
public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
|
||||
/** deprecated unimplemented, now using routerconsole realm */
|
||||
//public static final String PROP_TUNNEL_PASSPHRASE = "i2ptunnel.passphrase";
|
||||
public static final String PROP_TUNNEL_PASSPHRASE = "consolePassword";
|
||||
static final String PROP_NONCE = IndexBean.class.getName() + ".nonce";
|
||||
static final String PROP_NONCE_OLD = PROP_NONCE + '2';
|
||||
static final String CLIENT_NICKNAME = "shared clients";
|
||||
|
||||
public static final String PROP_THEME_NAME = "routerconsole.theme";
|
||||
@ -96,10 +99,16 @@ public class IndexBean {
|
||||
_tunnel = -1;
|
||||
_curNonce = -1;
|
||||
_prevNonce = -1;
|
||||
_prevNonce2 = -1;
|
||||
try {
|
||||
String nonce2 = System.getProperty(PROP_NONCE_OLD);
|
||||
if (nonce2 != null)
|
||||
_prevNonce2 = Long.parseLong(nonce2);
|
||||
String nonce = System.getProperty(PROP_NONCE);
|
||||
if (nonce != null)
|
||||
if (nonce != null) {
|
||||
_prevNonce = Long.parseLong(nonce);
|
||||
System.setProperty(PROP_NONCE_OLD, nonce);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {}
|
||||
_nextNonce = _context.random().nextLong();
|
||||
System.setProperty(PROP_NONCE, Long.toString(_nextNonce));
|
||||
@ -117,8 +126,8 @@ public class IndexBean {
|
||||
}
|
||||
}
|
||||
|
||||
/** deprecated unimplemented, now using routerconsole realm */
|
||||
public void setPassphrase(String phrase) {
|
||||
_passphrase = phrase;
|
||||
}
|
||||
|
||||
public void setAction(String action) {
|
||||
@ -134,19 +143,16 @@ public class IndexBean {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validPassphrase(String proposed) {
|
||||
if (proposed == null) return false;
|
||||
/** just check if console password option is set, jetty will do auth */
|
||||
private boolean validPassphrase() {
|
||||
String pass = _context.getProperty(PROP_TUNNEL_PASSPHRASE);
|
||||
if ( (pass != null) && (pass.trim().length() > 0) )
|
||||
return pass.trim().equals(proposed.trim());
|
||||
else
|
||||
return false;
|
||||
return pass != null && pass.trim().length() > 0;
|
||||
}
|
||||
|
||||
private String processAction() {
|
||||
if ( (_action == null) || (_action.trim().length() <= 0) || ("Cancel".equals(_action)))
|
||||
return "";
|
||||
if ( (_prevNonce != _curNonce) && (!validPassphrase(_passphrase)) )
|
||||
if ( (_prevNonce != _curNonce) && (_prevNonce2 != _curNonce) && (!validPassphrase()) )
|
||||
return "Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.";
|
||||
if ("Stop all".equals(_action))
|
||||
return stopAll();
|
||||
@ -349,6 +355,7 @@ public class IndexBean {
|
||||
return ( ("client".equals(type)) ||
|
||||
("httpclient".equals(type)) ||
|
||||
("sockstunnel".equals(type)) ||
|
||||
("socksirctunnel".equals(type)) ||
|
||||
("connectclient".equals(type)) ||
|
||||
("streamrclient".equals(type)) ||
|
||||
("ircclient".equals(type)));
|
||||
@ -385,6 +392,7 @@ public class IndexBean {
|
||||
else if ("server".equals(internalType)) return _("Standard server");
|
||||
else if ("httpserver".equals(internalType)) return _("HTTP server");
|
||||
else if ("sockstunnel".equals(internalType)) return _("SOCKS 4/4a/5 proxy");
|
||||
else if ("socksirctunnel".equals(internalType)) return _("SOCKS IRC proxy");
|
||||
else if ("connectclient".equals(internalType)) return _("CONNECT/SSL/HTTPS proxy");
|
||||
else if ("ircserver".equals(internalType)) return _("IRC server");
|
||||
else if ("streamrclient".equals(internalType)) return _("Streamr client");
|
||||
|
@ -424,9 +424,9 @@
|
||||
<span class="comment"><%=intl._("NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted.")%></span>
|
||||
<div class="separator"><hr /></div>
|
||||
<input type="hidden" value="true" name="removeConfirm" />
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><%=intl._("Save")%>(<span class="accessKey">S</span>)</button>
|
||||
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><%=intl._("Delete")%>(<span class="accessKey">D</span>)</button>
|
||||
<button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel"><%=intl._("Cancel")%></button>
|
||||
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><%=intl._("Delete")%>(<span class="accessKey">D</span>)</button>
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><%=intl._("Save")%>(<span class="accessKey">S</span>)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -341,7 +341,7 @@
|
||||
|
||||
<div id="optionsField" class="rowItem">
|
||||
<label for="access" accesskey="s">
|
||||
<%=intl._("Restricted Access List")%>(<span class="accessKey">s</span>): <i><%=intl._("Unimplemented")%></i>
|
||||
<%=intl._("Restricted Access List")%>(<span class="accessKey">s</span>):
|
||||
</label>
|
||||
</div>
|
||||
<div id="portField" class="rowItem">
|
||||
@ -457,9 +457,9 @@
|
||||
<span class="comment"><%=intl._("NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted.")%></span>
|
||||
<div class="separator"><hr /></div>
|
||||
<input type="hidden" value="true" name="removeConfirm" />
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><%=intl._("Save")%>(<span class="accessKey">S</span>)</button>
|
||||
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><%=intl._("Delete")%>(<span class="accessKey">D</span>)</button>
|
||||
<button id="controlCancel" class="control" type="submit" name="action" value="" title="Cancel"><%=intl._("Cancel")%></button>
|
||||
<button id="controlDelete" <%=(editBean.allowJS() ? "onclick=\"if (!confirm('Are you sure you want to delete?')) { return false; }\" " : "")%>accesskey="D" class="control" type="submit" name="action" value="Delete this proxy" title="Delete this Proxy"><%=intl._("Delete")%>(<span class="accessKey">D</span>)</button>
|
||||
<button id="controlSave" accesskey="S" class="control" type="submit" name="action" value="Save changes" title="Save Changes"><%=intl._("Save")%>(<span class="accessKey">S</span>)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -250,7 +250,8 @@
|
||||
}
|
||||
%></div>
|
||||
|
||||
<% if (!"sockstunnel".equals(indexBean.getInternalType(curClient))) { %>
|
||||
<% if (!("sockstunnel".equals(indexBean.getInternalType(curClient)) ||
|
||||
"socksirctunnel".equals(indexBean.getInternalType(curClient)))) { %>
|
||||
<div class="destinationField rowItem">
|
||||
<label>
|
||||
<% if ("httpclient".equals(indexBean.getInternalType(curClient)) || "connectclient".equals(indexBean.getInternalType(curClient))) { %>
|
||||
@ -288,6 +289,7 @@
|
||||
<option value="httpclient">HTTP</option>
|
||||
<option value="ircclient">IRC</option>
|
||||
<option value="sockstunnel">SOCKS 4/4a/5</option>
|
||||
<option value="socksirctunnel">SOCKS IRC</option>
|
||||
<option value="connectclient">CONNECT</option>
|
||||
<option value="streamrclient">Streamr</option>
|
||||
</select>
|
||||
|
@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2ptunnel\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-01-14 06:24+0000\n"
|
||||
"PO-Revision-Date: 2010-01-18 18:40+0000\n"
|
||||
"POT-Creation-Date: 2010-03-06 00:05+0000\n"
|
||||
"PO-Revision-Date: 2010-03-06 00:08+0000\n"
|
||||
"Last-Translator: 4get <forget@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -17,60 +17,64 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Russian\n"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:436
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:483
|
||||
#, java-format
|
||||
msgid "To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"{1}\">here</a>."
|
||||
msgstr "Для перехода по ссылке из локальной адресной книги, нажмите <a href=\"{0}\">здесь</a>. Для перехода по новой addresshelper-ссылке с временным присвоением ей случайного имени, нажмите <a href=\"{1}\">здесь</a>."
|
||||
msgid "To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>."
|
||||
msgstr "Для перехода по ссылке из локальной адресной книги, нажмите <a href=\"{0}\">здесь</a>. Для перехода по новой addresshelper-ссылке, нажмите <a href=\"{1}\">здесь</a>."
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:802
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:886
|
||||
msgid "Click a link below to look for an address helper by using a \"jump\" service:"
|
||||
msgstr "Jump-сервисы, которые, возможно, знают нужную Вам addresshelper-ссылку:"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:362
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:363
|
||||
msgid "New Tunnel"
|
||||
msgstr "Новый туннель"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:382
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:383
|
||||
msgid "Standard client"
|
||||
msgstr "Обычный клиент"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:383
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:384
|
||||
msgid "HTTP client"
|
||||
msgstr "HTTP-клиент"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:384
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:385
|
||||
msgid "IRC client"
|
||||
msgstr "IRC-клиент"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:385
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:386
|
||||
msgid "Standard server"
|
||||
msgstr "Обычный сервер"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:386
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:387
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTP-сервер"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:387
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:388
|
||||
msgid "SOCKS 4/4a/5 proxy"
|
||||
msgstr "SOCKS 4/4a/5 прокси"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:388
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:389
|
||||
msgid "SOCKS IRC proxy"
|
||||
msgstr "SOCKS IRC прокси"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:390
|
||||
msgid "CONNECT/SSL/HTTPS proxy"
|
||||
msgstr "CONNECT/SSL/HTTPS прокси"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:389
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:391
|
||||
msgid "IRC server"
|
||||
msgstr "IRC-сервер"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:390
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:392
|
||||
msgid "Streamr client"
|
||||
msgstr "Streamr-клиент"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:391
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:393
|
||||
msgid "Streamr server"
|
||||
msgstr "Streamr-сервер"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:392
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:394
|
||||
msgid "HTTP bidir"
|
||||
msgstr "HTTP bidir (экспериментальный двунаправленный режим, инструкцию спрашивайте у sponge)"
|
||||
|
||||
@ -105,7 +109,7 @@ msgstr "Тип"
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:120
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:120
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:226
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:357
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:358
|
||||
msgid "Description"
|
||||
msgstr "Описание"
|
||||
|
||||
@ -638,12 +642,12 @@ msgid "New server tunnel"
|
||||
msgstr "Новый серверный туннель"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:236
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:367
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:368
|
||||
msgid "Standard"
|
||||
msgstr "Стандартный"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:238
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:369
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:370
|
||||
msgid "Create"
|
||||
msgstr "Создать"
|
||||
|
||||
@ -661,15 +665,15 @@ msgid "Standby"
|
||||
msgstr "Режим ожидания"
|
||||
|
||||
# This term intentionally left in English
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:345
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:346
|
||||
msgid "Outproxy"
|
||||
msgstr "Outproxy"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:349
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:350
|
||||
msgid "Destination"
|
||||
msgstr "Адрес назначения"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:365
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:366
|
||||
msgid "New client tunnel"
|
||||
msgstr "Новый клиентский туннель"
|
||||
|
||||
|
@ -8,69 +8,73 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: I2P i2ptunnel\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-01-29 07:17+0000\n"
|
||||
"PO-Revision-Date: 2010-01-29 15:31+0800\n"
|
||||
"Last-Translator: walking <zhazhenzhong@gmail.com>\n"
|
||||
"POT-Creation-Date: 2010-05-29 02:35+0000\n"
|
||||
"PO-Revision-Date: 2010-05-29 10:57+0800\n"
|
||||
"Last-Translator: walking <walking@mail.i2p>\n"
|
||||
"Language-Team: foo <foo@bar>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Poedit-Language: Chinese\n"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:436
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:493
|
||||
#, java-format
|
||||
msgid "To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper link by temporarily giving it a random alias, click <a href=\"{1}\">here</a>."
|
||||
msgstr "要访问您本地【地址簿】中规定的主机(相当与IP),请点击<a href=\"{0}\">这里</a>。要访问【地址助手】返回的主机请点<a href=\"{1}\">这里</a>(主机的域名会被临时强制替换)。"
|
||||
msgid "To visit the destination in your host database, click <a href=\"{0}\">here</a>. To visit the conflicting addresshelper destination, click <a href=\"{1}\">here</a>."
|
||||
msgstr "域名冲突:要访问您本地【地址簿】中设置的目标主机(相当与IP),请点击<a href=\"{0}\">这里</a>。要访问【地址助手】返回的目标主机请点<a href=\"{1}\">这里</a>。"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:802
|
||||
#: ../java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java:904
|
||||
msgid "Click a link below to look for an address helper by using a \"jump\" service:"
|
||||
msgstr "请点击下面的链接通过【跳转(Jump)】服务提供的【地址助手】链接跳转至域名对应的主机:"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:362
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:369
|
||||
msgid "New Tunnel"
|
||||
msgstr "新建隧道"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:382
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:389
|
||||
msgid "Standard client"
|
||||
msgstr "标准客户端"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:383
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:390
|
||||
msgid "HTTP client"
|
||||
msgstr "HTTP 客户端"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:384
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:391
|
||||
msgid "IRC client"
|
||||
msgstr "IRC 客户端"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:385
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:392
|
||||
msgid "Standard server"
|
||||
msgstr "标准服务器"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:386
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:393
|
||||
msgid "HTTP server"
|
||||
msgstr "HTTP 服务器"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:387
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:394
|
||||
msgid "SOCKS 4/4a/5 proxy"
|
||||
msgstr "SOCKS4/4A/5 代理"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:388
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:395
|
||||
msgid "SOCKS IRC proxy"
|
||||
msgstr "SOCKS IRC 代理"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:396
|
||||
msgid "CONNECT/SSL/HTTPS proxy"
|
||||
msgstr "CONNECT/SSL/HTTPS 代理"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:389
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:397
|
||||
msgid "IRC server"
|
||||
msgstr "IRC 服务器"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:390
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:398
|
||||
msgid "Streamr client"
|
||||
msgstr "Streamr 客户端"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:391
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:399
|
||||
msgid "Streamr server"
|
||||
msgstr "Streamr 服务器"
|
||||
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:392
|
||||
#: ../java/src/net/i2p/i2ptunnel/web/IndexBean.java:400
|
||||
msgid "HTTP bidir"
|
||||
msgstr "双向http"
|
||||
|
||||
@ -105,7 +109,7 @@ msgstr "类型"
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:120
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:120
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:226
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:357
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:358
|
||||
msgid "Description"
|
||||
msgstr "描述"
|
||||
|
||||
@ -362,7 +366,7 @@ msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:406
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:445
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:443
|
||||
msgid "Reduce tunnel quantity when idle"
|
||||
msgstr "空闲时缩减隧道数量"
|
||||
|
||||
@ -372,19 +376,19 @@ msgstr "空闲时缩减隧道数量"
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:442
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:452
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:417
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:435
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:447
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:433
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:445
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:412
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:451
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:449
|
||||
msgid "Reduced tunnel count"
|
||||
msgstr "削减后的隧道数量"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:416
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:436
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:455
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:453
|
||||
msgid "Idle minutes"
|
||||
msgstr "空闲时间(分钟)"
|
||||
|
||||
@ -422,29 +426,29 @@ msgid "(if known)"
|
||||
msgstr "(如果已知)"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:468
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:491
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:489
|
||||
msgid "Custom options"
|
||||
msgstr "自定义选项"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:472
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:495
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:493
|
||||
msgid "NOTE: If tunnel is currently running, most changes will not take effect until tunnel is stopped and restarted."
|
||||
msgstr "注意:如果当前隧道已经启动,设置需要【停止】并重新【启动】相应隧道后才能生效。"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:474
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:497
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:495
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:478
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:501
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:499
|
||||
msgid "Delete"
|
||||
msgstr "删除"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editClient_jsp.java:480
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:503
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:501
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:73
|
||||
msgid "I2P Tunnel Manager - Edit Server Tunnel"
|
||||
@ -491,7 +495,7 @@ msgid "Generate"
|
||||
msgstr "生成"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:429
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:489
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:487
|
||||
msgid "(Tunnel must be stopped first)"
|
||||
msgstr "(必须先停止隧道)"
|
||||
|
||||
@ -499,51 +503,47 @@ msgstr "(必须先停止隧道)"
|
||||
msgid "Restricted Access List"
|
||||
msgstr "限制访问列表"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:433
|
||||
msgid "Unimplemented"
|
||||
msgstr "尚未实现"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:439
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:437
|
||||
msgid "Access List"
|
||||
msgstr "访问列表"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:443
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:441
|
||||
msgid "(Restrict to these clients only)"
|
||||
msgstr "(仅允许这些客户访问)"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:459
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:457
|
||||
msgid "New Certificate type"
|
||||
msgstr "新建证书类型"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:461
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:459
|
||||
msgid "None"
|
||||
msgstr "无"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:465
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:463
|
||||
msgid "Hashcash (effort)"
|
||||
msgstr "Hashcash (强度)"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:471
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:469
|
||||
msgid "Hashcash Calc Time"
|
||||
msgstr "Hashcash 计算时间"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:473
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:471
|
||||
msgid "Estimate"
|
||||
msgstr "估算"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:475
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:473
|
||||
msgid "Hidden"
|
||||
msgstr "隐藏"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:479
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:477
|
||||
msgid "Signed (signed by)"
|
||||
msgstr "签名(签名者)"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:485
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:483
|
||||
msgid "Modify Certificate"
|
||||
msgstr "修改证书"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:487
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/editServer_jsp.java:485
|
||||
msgid "Modify"
|
||||
msgstr "修改"
|
||||
|
||||
@ -638,12 +638,12 @@ msgid "New server tunnel"
|
||||
msgstr "新建服务器隧道"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:236
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:367
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:368
|
||||
msgid "Standard"
|
||||
msgstr "标准"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:238
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:369
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:370
|
||||
msgid "Create"
|
||||
msgstr "创建"
|
||||
|
||||
@ -660,15 +660,18 @@ msgstr "网络接口"
|
||||
msgid "Standby"
|
||||
msgstr "等待"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:345
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:346
|
||||
msgid "Outproxy"
|
||||
msgstr "出口代理"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:349
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:350
|
||||
msgid "Destination"
|
||||
msgstr "目标"
|
||||
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:365
|
||||
#: ../jsp/WEB-INF/classes/net/i2p/i2ptunnel/jsp/index_jsp.java:366
|
||||
msgid "New client tunnel"
|
||||
msgstr "新建客户隧道"
|
||||
|
||||
#~ msgid "Unimplemented"
|
||||
#~ msgstr "尚未实现"
|
||||
|
||||
|
@ -1301,7 +1301,7 @@ public class HttpContext extends Container
|
||||
|
||||
List scss= _constraintMap.getMatches(pathInContext);
|
||||
String pattern=null;
|
||||
if (scss != null && scss.size() > 0)
|
||||
if (scss != null && !scss.isEmpty())
|
||||
{
|
||||
Object constraints= null;
|
||||
|
||||
|
431
apps/jetty/java/src/org/mortbay/util/Resource.java
Normal file
@ -0,0 +1,431 @@
|
||||
// ========================================================================
|
||||
// $Id: Resource.java,v 1.32 2009/05/16 01:53:36 gregwilkins Exp $
|
||||
// Copyright 1996-2004 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ========================================================================
|
||||
package org.mortbay.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.mortbay.log.LogFactory;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Abstract resource class.
|
||||
*
|
||||
* @version $Id: Resource.java,v 1.32 2009/05/16 01:53:36 gregwilkins Exp $
|
||||
* @author Nuno Preguica
|
||||
* @author Greg Wilkins (gregw)
|
||||
*/
|
||||
public abstract class Resource implements Serializable
|
||||
{
|
||||
private static Log log = LogFactory.getLog(Resource.class);
|
||||
|
||||
Object _associate;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a url.
|
||||
* @param url A URL.
|
||||
* @return A Resource object.
|
||||
*/
|
||||
public static Resource newResource(URL url)
|
||||
throws IOException
|
||||
{
|
||||
if (url==null)
|
||||
return null;
|
||||
|
||||
String urls=url.toExternalForm();
|
||||
if( urls.startsWith( "file:"))
|
||||
{
|
||||
try
|
||||
{
|
||||
FileResource fileResource= new FileResource(url);
|
||||
return fileResource;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
log.debug(LogSupport.EXCEPTION,e);
|
||||
return new BadResource(url,e.toString());
|
||||
}
|
||||
}
|
||||
else if( urls.startsWith( "jar:file:"))
|
||||
{
|
||||
return new JarFileResource(url);
|
||||
}
|
||||
else if( urls.startsWith( "jar:"))
|
||||
{
|
||||
return new JarResource(url);
|
||||
}
|
||||
|
||||
return new URLResource(url,null);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a resource from a string.
|
||||
* @param resource A URL or filename.
|
||||
* @return A Resource object.
|
||||
*/
|
||||
public static Resource newResource(String resource)
|
||||
throws MalformedURLException, IOException
|
||||
{
|
||||
URL url=null;
|
||||
try
|
||||
{
|
||||
// Try to format as a URL?
|
||||
url = new URL(resource);
|
||||
}
|
||||
catch(MalformedURLException e)
|
||||
{
|
||||
if(!resource.startsWith("ftp:") &&
|
||||
!resource.startsWith("file:") &&
|
||||
!resource.startsWith("jar:"))
|
||||
{
|
||||
try
|
||||
{
|
||||
// It's a file.
|
||||
if (resource.startsWith("./"))
|
||||
resource=resource.substring(2);
|
||||
|
||||
File file=new File(resource).getCanonicalFile();
|
||||
url=file.toURI().toURL();
|
||||
|
||||
URLConnection connection=url.openConnection();
|
||||
FileResource fileResource= new FileResource(url,connection,file);
|
||||
return fileResource;
|
||||
}
|
||||
catch(Exception e2)
|
||||
{
|
||||
log.debug(LogSupport.EXCEPTION,e2);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log.warn("Bad Resource: "+resource);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
String nurl=url.toString();
|
||||
if (nurl.length()>0 &&
|
||||
nurl.charAt(nurl.length()-1)!=
|
||||
resource.charAt(resource.length()-1))
|
||||
{
|
||||
if ((nurl.charAt(nurl.length()-1)!='/' ||
|
||||
nurl.charAt(nurl.length()-2)!=resource.charAt(resource.length()-1))
|
||||
&&
|
||||
(resource.charAt(resource.length()-1)!='/' ||
|
||||
resource.charAt(resource.length()-2)!=nurl.charAt(nurl.length()-1)
|
||||
))
|
||||
{
|
||||
return new BadResource(url,"Trailing special characters stripped by URL in "+resource);
|
||||
}
|
||||
}
|
||||
return newResource(url);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Construct a system resource from a string.
|
||||
* The resource is tried as classloader resource before being
|
||||
* treated as a normal resource.
|
||||
*/
|
||||
public static Resource newSystemResource(String resource)
|
||||
throws IOException
|
||||
{
|
||||
URL url=null;
|
||||
// Try to format as a URL?
|
||||
ClassLoader
|
||||
loader=Thread.currentThread().getContextClassLoader();
|
||||
if (loader!=null)
|
||||
{
|
||||
url=loader.getResource(resource);
|
||||
if (url==null && resource.startsWith("/"))
|
||||
url=loader.getResource(resource.substring(1));
|
||||
}
|
||||
if (url==null)
|
||||
{
|
||||
loader=Resource.class.getClassLoader();
|
||||
if (loader!=null)
|
||||
{
|
||||
url=loader.getResource(resource);
|
||||
if (url==null && resource.startsWith("/"))
|
||||
url=loader.getResource(resource.substring(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (url==null)
|
||||
{
|
||||
url=ClassLoader.getSystemResource(resource);
|
||||
if (url==null && resource.startsWith("/"))
|
||||
url=loader.getResource(resource.substring(1));
|
||||
}
|
||||
|
||||
if (url==null)
|
||||
return null;
|
||||
|
||||
return newResource(url);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void finalize()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Release any resources held by the resource.
|
||||
*/
|
||||
public abstract void release();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns true if the respresened resource exists.
|
||||
*/
|
||||
public abstract boolean exists();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns true if the respresenetd resource is a container/directory.
|
||||
* If the resource is not a file, resources ending with "/" are
|
||||
* considered directories.
|
||||
*/
|
||||
public abstract boolean isDirectory();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns the last modified time
|
||||
*/
|
||||
public abstract long lastModified();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Return the length of the resource
|
||||
*/
|
||||
public abstract long length();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns an URL representing the given resource
|
||||
*/
|
||||
public abstract URL getURL();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns an File representing the given resource or NULL if this
|
||||
* is not possible.
|
||||
*/
|
||||
public abstract File getFile()
|
||||
throws IOException;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns the name of the resource
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns an input stream to the resource
|
||||
*/
|
||||
public abstract InputStream getInputStream()
|
||||
throws java.io.IOException;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns an output stream to the resource
|
||||
*/
|
||||
public abstract OutputStream getOutputStream()
|
||||
throws java.io.IOException, SecurityException;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Deletes the given resource
|
||||
*/
|
||||
public abstract boolean delete()
|
||||
throws SecurityException;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Rename the given resource
|
||||
*/
|
||||
public abstract boolean renameTo( Resource dest)
|
||||
throws SecurityException;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns a list of resource names contained in the given resource
|
||||
* The resource names are not URL encoded.
|
||||
*/
|
||||
public abstract String[] list();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Returns the resource contained inside the current resource with the
|
||||
* given name.
|
||||
* @param path The path segment to add, which should be encoded by the
|
||||
* encode method.
|
||||
*/
|
||||
public abstract Resource addPath(String path)
|
||||
throws IOException,MalformedURLException;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Encode according to this resource type.
|
||||
* The default implementation calls URI.encodePath(uri)
|
||||
* @param uri
|
||||
* @return String encoded for this resource type.
|
||||
*/
|
||||
public String encode(String uri)
|
||||
{
|
||||
return URI.encodePath(uri);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Object getAssociate()
|
||||
{
|
||||
return _associate;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setAssociate(Object o)
|
||||
{
|
||||
_associate=o;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return The canonical Alias of this resource or null if none.
|
||||
*/
|
||||
public URL getAlias()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public CachedResource cache()
|
||||
throws IOException
|
||||
{
|
||||
return new CachedResource(this);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the resource list as a HTML directory listing.
|
||||
* @param base The base URL
|
||||
* @param parent True if the parent directory should be included
|
||||
* @return String of HTML
|
||||
*/
|
||||
public String getListHTML(String base,
|
||||
boolean parent)
|
||||
throws IOException
|
||||
{
|
||||
if (!isDirectory())
|
||||
return null;
|
||||
|
||||
|
||||
String[] ls = list();
|
||||
if (ls==null)
|
||||
return null;
|
||||
Arrays.sort(ls);
|
||||
|
||||
String title = "Directory: "+URI.decodePath(base);
|
||||
title=StringUtil.replace(StringUtil.replace(title,"<","<"),">",">");
|
||||
StringBuffer buf=new StringBuffer(4096);
|
||||
buf.append("<HTML><HEAD><TITLE>");
|
||||
buf.append(title);
|
||||
buf.append("</TITLE></HEAD><BODY>\n<H1>");
|
||||
buf.append(title);
|
||||
buf.append("</H1><TABLE BORDER=0>");
|
||||
|
||||
if (parent)
|
||||
{
|
||||
buf.append("<TR><TD><A HREF=");
|
||||
buf.append(URI.encodePath(URI.addPaths(base,"../")));
|
||||
buf.append(">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
|
||||
}
|
||||
|
||||
DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
|
||||
DateFormat.MEDIUM);
|
||||
for (int i=0 ; i< ls.length ; i++)
|
||||
{
|
||||
String encoded=URI.encodePath(ls[i]);
|
||||
// bugfix for I2P - Backport from Jetty 6 (zero file lengths and last-modified times)
|
||||
// http://jira.codehaus.org/browse/JETTY-361?page=com.atlassian.jira.plugin.system.issuetabpanels%3Achangehistory-tabpanel#issue-tabs
|
||||
// See resource.diff attachment
|
||||
//Resource item = addPath(encoded);
|
||||
Resource item = addPath(ls[i]);
|
||||
|
||||
buf.append("<TR><TD><A HREF=\"");
|
||||
|
||||
String path=URI.addPaths(base,encoded);
|
||||
|
||||
if (item.isDirectory() && !path.endsWith("/"))
|
||||
path=URI.addPaths(path,"/");
|
||||
buf.append(path);
|
||||
buf.append("\">");
|
||||
buf.append(StringUtil.replace(StringUtil.replace(ls[i],"<","<"),">",">"));
|
||||
buf.append(" ");
|
||||
buf.append("</TD><TD ALIGN=right>");
|
||||
buf.append(item.length());
|
||||
buf.append(" bytes </TD><TD>");
|
||||
buf.append(dfmt.format(new Date(item.lastModified())));
|
||||
buf.append("</TD></TR>\n");
|
||||
}
|
||||
buf.append("</TABLE>\n");
|
||||
buf.append("</BODY></HTML>\n");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param out
|
||||
* @param start First byte to write
|
||||
* @param count Bytes to write or -1 for all of them.
|
||||
*/
|
||||
public void writeTo(OutputStream out,long start,long count)
|
||||
throws IOException
|
||||
{
|
||||
InputStream in = getInputStream();
|
||||
try
|
||||
{
|
||||
in.skip(start);
|
||||
if (count<0)
|
||||
IO.copy(in,out);
|
||||
else
|
||||
IO.copy(in,out,(int)count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ class I2PServerSocketImpl implements I2PServerSocket {
|
||||
I2PSocket ret = null;
|
||||
|
||||
while ( (ret == null) && (!closing) ){
|
||||
while (pendingSockets.size() <= 0) {
|
||||
while (pendingSockets.isEmpty()) {
|
||||
if (closing) throw new ConnectException("I2PServerSocket closed");
|
||||
try {
|
||||
synchronized(socketAddedLock) {
|
||||
@ -78,7 +78,7 @@ class I2PServerSocketImpl implements I2PServerSocket {
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
synchronized (pendingSockets) {
|
||||
if (pendingSockets.size() > 0) {
|
||||
if (!pendingSockets.isEmpty()) {
|
||||
ret = (I2PSocket)pendingSockets.remove(0);
|
||||
}
|
||||
}
|
||||
|
@ -64,13 +64,16 @@
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/routerconsole.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Class-Path" value="i2p.jar router.jar" />
|
||||
<!-- top level installer will rename to jrobin.jar -->
|
||||
<attribute name="Class-Path" value="i2p.jar router.jar jrobin.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<delete dir="./tmpextract" />
|
||||
<!-- jrobin taken out of routerconsole.jar in 0.7.12
|
||||
<unjar src="../../jrobin/jrobin-1.4.0.jar" dest="./tmpextract" />
|
||||
<jar destfile="./build/routerconsole.jar" basedir="./tmpextract" update="true" />
|
||||
<delete dir="./tmpextract" />
|
||||
-->
|
||||
|
||||
<ant target="war" />
|
||||
|
||||
|
@ -39,7 +39,11 @@ ROUTERFILES="\
|
||||
../../../router/java/src/net/i2p/router/transport/TransportManager.java \
|
||||
../../../router/java/src/net/i2p/router/transport/GetBidsJob.java \
|
||||
../../../router/java/src/net/i2p/router/Blocklist.java \
|
||||
../../../router/java/src/net/i2p/router/transport/ntcp/EstablishState.java"
|
||||
../../../router/java/src/net/i2p/router/transport/ntcp/EstablishState.java \
|
||||
../../../router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java \
|
||||
../../../router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java \
|
||||
../../../router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java \
|
||||
../../../router/java/src/net/i2p/router/transport/udp/UDPTransport.java"
|
||||
|
||||
# add ../java/ so the refs will work in the po file
|
||||
JPATHS="../java/src ../jsp/WEB-INF ../java/strings $JFILE $ROUTERFILES"
|
||||
@ -78,7 +82,7 @@ do
|
||||
# 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 \
|
||||
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
|
||||
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
|
||||
--keyword=handler._ --keyword=formhandler._ \
|
||||
--keyword=net.i2p.router.web.Messages.getString \
|
||||
|
@ -19,6 +19,9 @@ public class CSSHelper extends HelperBase {
|
||||
if (userAgent != null && userAgent.contains("MSIE")) {
|
||||
url += FORCE + "/";
|
||||
} else {
|
||||
// This is the first thing to use _context on most pages
|
||||
if (_context == null)
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
String theme = _context.getProperty(PROP_THEME_NAME, DEFAULT_THEME);
|
||||
url += theme + "/";
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -11,24 +11,26 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.mortbay.http.HttpListener;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
/**
|
||||
* Saves changes to clients.config or webapps.config
|
||||
*/
|
||||
public class ConfigClientsHandler extends FormHandler {
|
||||
private Log configClient_log;
|
||||
private Map _settings;
|
||||
|
||||
public ConfigClientsHandler() {
|
||||
configClient_log = ContextHelper.getContext(null).logManager().getLog(ConfigClientsHandler.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processForm() {
|
||||
// set action for when CR is hit in a text input box
|
||||
if (_action.length() <= 0) {
|
||||
String url = getJettyString("pluginURL");
|
||||
if (url != null && url.length() > 0)
|
||||
_action = "Install Plugin";
|
||||
else
|
||||
_action = "Save Client Configuration";
|
||||
}
|
||||
|
||||
if (_action.equals(_("Save Client Configuration"))) {
|
||||
saveClientChanges();
|
||||
return;
|
||||
@ -37,6 +39,14 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
saveWebAppChanges();
|
||||
return;
|
||||
}
|
||||
if (_action.equals(_("Save Plugin Configuration"))) {
|
||||
savePluginChanges();
|
||||
return;
|
||||
}
|
||||
if (_action.equals(_("Install Plugin"))) {
|
||||
installPlugin();
|
||||
return;
|
||||
}
|
||||
// value
|
||||
if (_action.startsWith("Start ")) {
|
||||
String app = _action.substring(6);
|
||||
@ -44,10 +54,15 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
try {
|
||||
appnum = Integer.parseInt(app);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (appnum >= 0)
|
||||
if (appnum >= 0) {
|
||||
startClient(appnum);
|
||||
else
|
||||
startWebApp(app);
|
||||
} else {
|
||||
List<String> plugins = PluginStarter.getPlugins();
|
||||
if (plugins.contains(app))
|
||||
startPlugin(app);
|
||||
else
|
||||
startWebApp(app);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -58,10 +73,48 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
try {
|
||||
appnum = Integer.parseInt(app);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (appnum >= 0)
|
||||
if (appnum >= 0) {
|
||||
deleteClient(appnum);
|
||||
} else {
|
||||
try {
|
||||
PluginStarter.stopPlugin(_context, app);
|
||||
PluginStarter.deletePlugin(_context, app);
|
||||
addFormNotice(_("Deleted plugin {0}", app));
|
||||
} catch (Throwable e) {
|
||||
addFormError(_("Error deleting plugin {0}", app) + ": " + e);
|
||||
_log.error("Error deleting plugin " + app, e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// value
|
||||
if (_action.startsWith("Stop ")) {
|
||||
String app = _action.substring(5);
|
||||
try {
|
||||
PluginStarter.stopPlugin(_context, app);
|
||||
addFormNotice(_("Stopped plugin {0}", app));
|
||||
} catch (Throwable e) {
|
||||
addFormError(_("Error stopping plugin {0}", app) + ": " + e);
|
||||
_log.error("Error stopping plugin " + app, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// value
|
||||
if (_action.startsWith("Update ")) {
|
||||
String app = _action.substring(7);
|
||||
updatePlugin(app);
|
||||
return;
|
||||
}
|
||||
|
||||
// value
|
||||
if (_action.startsWith("Check ")) {
|
||||
String app = _action.substring(6);
|
||||
checkPlugin(app);
|
||||
return;
|
||||
}
|
||||
|
||||
// label (IE)
|
||||
String xStart = _("Start");
|
||||
if (_action.toLowerCase().startsWith(xStart + "<span class=hide> ") &&
|
||||
@ -72,13 +125,19 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
try {
|
||||
appnum = Integer.parseInt(app);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (appnum >= 0)
|
||||
if (appnum >= 0) {
|
||||
startClient(appnum);
|
||||
else
|
||||
startWebApp(app);
|
||||
} else {
|
||||
List<String> plugins = PluginStarter.getPlugins();
|
||||
if (plugins.contains(app))
|
||||
startPlugin(app);
|
||||
else
|
||||
startWebApp(app);
|
||||
}
|
||||
} else {
|
||||
addFormError(_("Unsupported") + ' ' + _action + '.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setSettings(Map settings) { _settings = new HashMap(settings); }
|
||||
@ -91,7 +150,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
if (! ("webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName)))
|
||||
ca.disabled = val == null;
|
||||
// edit of an existing entry
|
||||
String desc = getString("desc" + cur);
|
||||
String desc = getJettyString("desc" + cur);
|
||||
if (desc != null) {
|
||||
int spc = desc.indexOf(" ");
|
||||
String clss = desc;
|
||||
@ -102,12 +161,12 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
}
|
||||
ca.className = clss;
|
||||
ca.args = args;
|
||||
ca.clientName = getString("name" + cur);
|
||||
ca.clientName = getJettyString("name" + cur);
|
||||
}
|
||||
}
|
||||
|
||||
int newClient = clients.size();
|
||||
String newDesc = getString("desc" + newClient);
|
||||
String newDesc = getJettyString("desc" + newClient);
|
||||
if (newDesc != null && newDesc.trim().length() > 0) {
|
||||
// new entry
|
||||
int spc = newDesc.indexOf(" ");
|
||||
@ -117,7 +176,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
clss = newDesc.substring(0, spc);
|
||||
args = newDesc.substring(spc + 1);
|
||||
}
|
||||
String name = getString("name" + newClient);
|
||||
String name = getJettyString("name" + newClient);
|
||||
if (name == null || name.trim().length() <= 0) name = "new client";
|
||||
ClientAppConfig ca = new ClientAppConfig(clss, name, args, 2*60*1000,
|
||||
_settings.get(newClient + ".enabled") != null);
|
||||
@ -130,7 +189,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
}
|
||||
|
||||
/** curses Jetty for returning arrays */
|
||||
private String getString(String key) {
|
||||
private String getJettyString(String key) {
|
||||
String[] arr = (String[]) _settings.get(key);
|
||||
if (arr == null)
|
||||
return null;
|
||||
@ -144,7 +203,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
return;
|
||||
}
|
||||
ClientAppConfig ca = clients.get(i);
|
||||
LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), configClient_log);
|
||||
LoadClientAppsJob.runClient(ca.className, ca.clientName, LoadClientAppsJob.parseArgs(ca.args), _log);
|
||||
addFormNotice(_("Client") + ' ' + _(ca.clientName) + ' ' + _("started") + '.');
|
||||
}
|
||||
|
||||
@ -173,32 +232,109 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
props.setProperty(name, "" + (val != null));
|
||||
}
|
||||
RouterConsoleRunner.storeWebAppProperties(props);
|
||||
addFormNotice(_("WebApp configuration saved successfully - restart required to take effect."));
|
||||
addFormNotice(_("WebApp configuration saved."));
|
||||
}
|
||||
|
||||
// Big hack for the moment, not using properties for directory and port
|
||||
// Go through all the Jetty servers, find the one serving port 7657,
|
||||
// requested and add the .war to that one
|
||||
private void savePluginChanges() {
|
||||
Properties props = PluginStarter.pluginProperties();
|
||||
Set keys = props.keySet();
|
||||
int cur = 0;
|
||||
for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (! (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)))
|
||||
continue;
|
||||
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
||||
Object val = _settings.get(app + ".enabled");
|
||||
props.setProperty(name, "" + (val != null));
|
||||
}
|
||||
PluginStarter.storePluginProperties(props);
|
||||
addFormNotice(_("Plugin configuration saved."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Big hack for the moment, not using properties for directory and port
|
||||
* Go through all the Jetty servers, find the one serving port 7657,
|
||||
* requested and add the .war to that one
|
||||
*/
|
||||
private void startWebApp(String app) {
|
||||
Collection c = Server.getHttpServers();
|
||||
for (int i = 0; i < c.size(); i++) {
|
||||
Server s = (Server) c.toArray()[i];
|
||||
HttpListener[] hl = s.getListeners();
|
||||
for (int j = 0; j < hl.length; j++) {
|
||||
if (hl[j].getPort() == 7657) {
|
||||
Server s = WebAppStarter.getConsoleServer();
|
||||
if (s != null) {
|
||||
try {
|
||||
File path = new File(_context.getBaseDir(), "webapps");
|
||||
path = new File(path, app + ".war");
|
||||
s.addWebApplication("/"+ app, path.getAbsolutePath()).start();
|
||||
// no passwords... initialize(wac);
|
||||
WebAppStarter.startWebApp(_context, s, app, path.getAbsolutePath());
|
||||
addFormNotice(_("WebApp") + " <a href=\"/" + app + "/\">" + _(app) + "</a> " + _("started") + '.');
|
||||
} catch (Exception ioe) {
|
||||
addFormError(_("Failed to start") + ' ' + _(app) + " " + ioe + '.');
|
||||
} catch (Throwable e) {
|
||||
addFormError(_("Failed to start") + ' ' + _(app) + " " + e + '.');
|
||||
_log.error("Failed to start webapp " + app, e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
addFormError(_("Failed to find server."));
|
||||
}
|
||||
|
||||
private void installPlugin() {
|
||||
String url = getJettyString("pluginURL");
|
||||
if (url == null || url.length() <= 0) {
|
||||
addFormError(_("No plugin URL specified."));
|
||||
return;
|
||||
}
|
||||
installPlugin(url);
|
||||
}
|
||||
|
||||
private void updatePlugin(String app) {
|
||||
Properties props = PluginStarter.pluginProperties(_context, app);
|
||||
String url = props.getProperty("updateURL");
|
||||
if (url == null) {
|
||||
addFormError(_("No update URL specified for {0}",app));
|
||||
return;
|
||||
}
|
||||
installPlugin(url);
|
||||
}
|
||||
|
||||
private void installPlugin(String url) {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
PluginUpdateHandler puh = PluginUpdateHandler.getInstance(_context);
|
||||
if (puh.isRunning()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
puh.update(url);
|
||||
addFormNotice(_("Downloading plugin from {0}", url));
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private void checkPlugin(String app) {
|
||||
if ("true".equals(System.getProperty(UpdateHandler.PROP_UPDATE_IN_PROGRESS))) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
PluginUpdateChecker puc = PluginUpdateChecker.getInstance(_context);
|
||||
if (puc.isRunning()) {
|
||||
addFormError(_("Plugin or update download already in progress."));
|
||||
return;
|
||||
}
|
||||
puc.update(app);
|
||||
addFormNotice(_("Checking plugin {0} for updates", app));
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private void startPlugin(String app) {
|
||||
try {
|
||||
PluginStarter.startPlugin(_context, app);
|
||||
addFormNotice(_("Started plugin {0}", app));
|
||||
} catch (Throwable e) {
|
||||
addFormError(_("Error starting plugin {0}", app) + ": " + e);
|
||||
_log.error("Error starting plugin " + app, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
@ -33,18 +35,19 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
public String getForm1() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("Client") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Start Now") + "</th><th align=\"left\">" + _("Class and arguments") + "</th></tr>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("Client") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Control") + "</th><th align=\"left\">" + _("Class and arguments") + "</th></tr>\n");
|
||||
|
||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(_context);
|
||||
for (int cur = 0; cur < clients.size(); cur++) {
|
||||
ClientAppConfig ca = clients.get(cur);
|
||||
renderForm(buf, ""+cur, ca.clientName, false, !ca.disabled,
|
||||
"webConsole".equals(ca.clientName) || "Web console".equals(ca.clientName),
|
||||
ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit), true);
|
||||
ca.className + ((ca.args != null) ? " " + ca.args : ""), (""+cur).equals(_edit),
|
||||
true, false, false, true, ca.disabled);
|
||||
}
|
||||
|
||||
if ("new".equals(_edit))
|
||||
renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false);
|
||||
renderForm(buf, "" + clients.size(), "", false, false, false, "", true, false, false, false, false, false);
|
||||
buf.append("</table>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
@ -52,7 +55,7 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
public String getForm2() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("WebApp") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Start Now") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("WebApp") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Control") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
|
||||
Properties props = RouterConsoleRunner.webAppProperties();
|
||||
Set<String> keys = new TreeSet(props.keySet());
|
||||
for (Iterator<String> iter = keys.iterator(); iter.hasNext(); ) {
|
||||
@ -61,7 +64,94 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
String app = name.substring(RouterConsoleRunner.PREFIX.length(), name.lastIndexOf(RouterConsoleRunner.ENABLED));
|
||||
String val = props.getProperty(name);
|
||||
renderForm(buf, app, app, !"addressbook".equals(app),
|
||||
"true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war", false, false);
|
||||
"true".equals(val), RouterConsoleRunner.ROUTERCONSOLE.equals(app), app + ".war",
|
||||
false, false, false, false, false, true);
|
||||
}
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public boolean showPlugins() {
|
||||
return PluginStarter.pluginsEnabled(_context);
|
||||
}
|
||||
|
||||
public String getForm3() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th align=\"right\">" + _("Plugin") + "</th><th>" + _("Run at Startup?") + "</th><th>" + _("Control") + "</th><th align=\"left\">" + _("Description") + "</th></tr>\n");
|
||||
Properties props = PluginStarter.pluginProperties();
|
||||
Set<String> keys = new TreeSet(props.keySet());
|
||||
for (Iterator<String> iter = keys.iterator(); iter.hasNext(); ) {
|
||||
String name = iter.next();
|
||||
if (name.startsWith(PluginStarter.PREFIX) && name.endsWith(PluginStarter.ENABLED)) {
|
||||
String app = name.substring(PluginStarter.PREFIX.length(), name.lastIndexOf(PluginStarter.ENABLED));
|
||||
String val = props.getProperty(name);
|
||||
Properties appProps = PluginStarter.pluginProperties(_context, app);
|
||||
if (appProps.isEmpty())
|
||||
continue;
|
||||
StringBuilder desc = new StringBuilder(256);
|
||||
desc.append("<table border=\"0\">")
|
||||
.append("<tr><td><b>").append(_("Version")).append("<td>").append(stripHTML(appProps, "version"))
|
||||
.append("<tr><td><b>")
|
||||
.append(_("Signed by")).append("<td>");
|
||||
String s = stripHTML(appProps, "signer");
|
||||
if (s != null) {
|
||||
if (s.indexOf("@") > 0)
|
||||
desc.append("<a href=\"mailto:").append(s).append("\">").append(s).append("</a>");
|
||||
else
|
||||
desc.append(s);
|
||||
}
|
||||
s = stripHTML(appProps, "date");
|
||||
if (s != null) {
|
||||
long ms = 0;
|
||||
try {
|
||||
ms = Long.parseLong(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
if (ms > 0) {
|
||||
String date = (new SimpleDateFormat("yyyy-MM-dd HH:mm")).format(new Date(ms));
|
||||
desc.append("<tr><td><b>")
|
||||
.append(_("Date")).append("<td>").append(date);
|
||||
}
|
||||
}
|
||||
s = stripHTML(appProps, "author");
|
||||
if (s != null) {
|
||||
desc.append("<tr><td><b>")
|
||||
.append(_("Author")).append("<td>");
|
||||
if (s.indexOf("@") > 0)
|
||||
desc.append("<a href=\"mailto:").append(s).append("\">").append(s).append("</a>");
|
||||
else
|
||||
desc.append(s);
|
||||
}
|
||||
s = stripHTML(appProps, "description_" + Messages.getLanguage(_context));
|
||||
if (s == null)
|
||||
s = stripHTML(appProps, "description");
|
||||
if (s != null) {
|
||||
desc.append("<tr><td><b>")
|
||||
.append(_("Description")).append("<td>").append(s);
|
||||
}
|
||||
s = stripHTML(appProps, "license");
|
||||
if (s != null) {
|
||||
desc.append("<tr><td><b>")
|
||||
.append(_("License")).append("<td>").append(s);
|
||||
}
|
||||
s = stripHTML(appProps, "websiteURL");
|
||||
if (s != null) {
|
||||
desc.append("<tr><td>")
|
||||
.append("<a href=\"").append(s).append("\">").append(_("Website")).append("</a><td> ");
|
||||
}
|
||||
String updateURL = stripHTML(appProps, "updateURL");
|
||||
if (updateURL != null) {
|
||||
desc.append("<tr><td>")
|
||||
.append("<a href=\"").append(updateURL).append("\">").append(_("Update link")).append("</a><td> ");
|
||||
}
|
||||
desc.append("</table>");
|
||||
boolean enableStop = !Boolean.valueOf(appProps.getProperty("disableStop")).booleanValue();
|
||||
enableStop &= PluginStarter.isPluginRunning(app, _context);
|
||||
boolean enableStart = !PluginStarter.isPluginRunning(app, _context);
|
||||
renderForm(buf, app, app, false,
|
||||
"true".equals(val), false, desc.toString(), false, false,
|
||||
updateURL != null, enableStop, true, enableStart);
|
||||
}
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
@ -70,7 +160,9 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
|
||||
/** ro trumps edit and showEditButton */
|
||||
private void renderForm(StringBuilder buf, String index, String name, boolean urlify,
|
||||
boolean enabled, boolean ro, String desc, boolean edit, boolean showEditButton) {
|
||||
boolean enabled, boolean ro, String desc, boolean edit,
|
||||
boolean showEditButton, boolean showUpdateButton, boolean showStopButton,
|
||||
boolean showDeleteButton, boolean showStartButton) {
|
||||
buf.append("<tr><td class=\"mediumtags\" align=\"right\" width=\"25%\">");
|
||||
if (urlify && enabled) {
|
||||
String link = "/";
|
||||
@ -92,13 +184,24 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
if (ro)
|
||||
buf.append("disabled=\"true\" ");
|
||||
}
|
||||
buf.append("/></td><td align=\"center\" width=\"15%\">");
|
||||
if ((!enabled) && !edit) {
|
||||
buf.append("></td><td align=\"center\" width=\"15%\">");
|
||||
if (showStartButton && (!ro) && !edit) {
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Start ").append(index).append("\" >" + _("Start") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
}
|
||||
if (showEditButton && (!edit) && !ro) {
|
||||
if (showEditButton && (!edit) && !ro)
|
||||
buf.append("<button type=\"submit\" name=\"edit\" value=\"Edit ").append(index).append("\" >" + _("Edit") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Delete ").append(index).append("\" >" + _("Delete") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
if (showStopButton && (!edit))
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Stop ").append(index).append("\" >" + _("Stop") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
if (showUpdateButton && (!edit) && !ro) {
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Check ").append(index).append("\" >" + _("Check for updates") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Update ").append(index).append("\" >" + _("Update") + "<span class=hide> ").append(index).append("</span></button>");
|
||||
}
|
||||
if (showDeleteButton && (!edit) && !ro) {
|
||||
buf.append("<button type=\"submit\" name=\"action\" value=\"Delete ").append(index)
|
||||
.append("\" onclick=\"if (!confirm('")
|
||||
.append(_("Are you sure you want to delete {0}?", _(name)))
|
||||
.append("')) { return false; }\">")
|
||||
.append(_("Delete")).append("<span class=hide> ").append(index).append("</span></button>");
|
||||
}
|
||||
buf.append("</td><td align=\"left\" width=\"50%\">");
|
||||
if (edit && !ro) {
|
||||
@ -110,4 +213,16 @@ public class ConfigClientsHelper extends HelperBase {
|
||||
}
|
||||
buf.append("</td></tr>\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Like in DataHelper but doesn't convert null to ""
|
||||
* There's a lot worse things a plugin could do but...
|
||||
*/
|
||||
static String stripHTML(Properties props, String key) {
|
||||
String orig = props.getProperty(key);
|
||||
if (orig == null) return null;
|
||||
String t1 = orig.replace('<', ' ');
|
||||
String rv = t1.replace('>', ' ');
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,10 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_SOURCES, _udpAutoIP);
|
||||
// Todo: Catch local IPs right here rather than complaining later
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_EXTERNAL_HOST, uhost);
|
||||
if (uhost.length() > 0)
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_EXTERNAL_HOST, uhost);
|
||||
else
|
||||
_context.router().removeConfigSetting(UDPTransport.PROP_EXTERNAL_HOST);
|
||||
if ((!oldUdp.equals(_udpAutoIP)) || (!oldUHost.equals(uhost))) {
|
||||
addFormNotice(_("Updating IP address"));
|
||||
restartRequired = true;
|
||||
@ -298,6 +301,9 @@ public class ConfigNetHandler extends FormHandler {
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
private static final int DEF_BURST_PCT = 10;
|
||||
private static final int DEF_BURST_TIME = 20;
|
||||
|
||||
private void updateRates() {
|
||||
boolean updated = false;
|
||||
|
||||
@ -310,14 +316,27 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Since burst is now hidden in the gui, set burst to +10% for 20 seconds
|
||||
if ( (_inboundRate != null) && (_inboundRate.length() > 0) &&
|
||||
!_inboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_INBOUND_BANDWIDTH))) {
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, _inboundRate);
|
||||
try {
|
||||
int rate = Integer.parseInt(_inboundRate) * (100 + DEF_BURST_PCT) / 100;
|
||||
int kb = DEF_BURST_TIME * rate;
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + rate);
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH_PEAK, "" + kb);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
updated = true;
|
||||
}
|
||||
if ( (_outboundRate != null) && (_outboundRate.length() > 0) &&
|
||||
!_outboundRate.equals(_context.getProperty(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + FIFOBandwidthRefiller.DEFAULT_OUTBOUND_BANDWIDTH))) {
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, _outboundRate);
|
||||
try {
|
||||
int rate = Integer.parseInt(_outboundRate) * (100 + DEF_BURST_PCT) / 100;
|
||||
int kb = DEF_BURST_TIME * rate;
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + rate);
|
||||
_context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH_PEAK, "" + kb);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
updated = true;
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,8 @@ public class ConfigNetHelper extends HelperBase {
|
||||
return kbytesToBits(getShareBandwidth());
|
||||
}
|
||||
private String kbytesToBits(int kbytes) {
|
||||
return DataHelper.formatSize(kbytes * 8 * 1024) + ' ' + _("bits per second");
|
||||
return DataHelper.formatSize(kbytes * 8 * 1024) + ' ' + _("bits per second") +
|
||||
' ' + _("or {0} bytes per month maximum", DataHelper.formatSize(kbytes * 1024l * 60 * 60 * 24 * 31));
|
||||
}
|
||||
public String getInboundBurstRate() {
|
||||
return "" + _context.bandwidthLimiter().getInboundBurstKBytesPerSecond();
|
||||
|
@ -58,7 +58,7 @@ public class ConfigPeerHandler extends FormHandler {
|
||||
}
|
||||
addFormError(_("Invalid peer"));
|
||||
} else if (_action.startsWith("Check")) {
|
||||
addFormError("Unsupported");
|
||||
addFormError(_("Unsupported"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class ConfigStatsHelper extends HelperBase {
|
||||
* @return true if a valid stat is available, otherwise false
|
||||
*/
|
||||
public boolean hasMoreStats() {
|
||||
if (_stats.size() <= 0)
|
||||
if (_stats.isEmpty())
|
||||
return false;
|
||||
_currentIsGraphed = false;
|
||||
_currentStatName = (String)_stats.remove(0);
|
||||
@ -139,9 +139,6 @@ public class ConfigStatsHelper extends HelperBase {
|
||||
public boolean getCurrentCanBeGraphed() { return _currentCanBeGraphed; }
|
||||
public String getExplicitFilter() { return _filter; }
|
||||
public boolean getIsFull() {
|
||||
String f = _context.getProperty(StatManager.PROP_STAT_FULL);
|
||||
if (f != null && f.equals("true"))
|
||||
return true;
|
||||
return false;
|
||||
return _context.getBooleanPropertyDefaultTrue(StatManager.PROP_STAT_FULL);
|
||||
}
|
||||
}
|
||||
|
@ -8,11 +8,11 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
|
||||
public class ConfigTunnelsHelper extends HelperBase {
|
||||
static final String HOP = _x("hop");
|
||||
static final String TUNNEL = _x("tunnel");
|
||||
private static final String HOP = "hop";
|
||||
private static final String TUNNEL = "tunnel";
|
||||
/** dummies for translation */
|
||||
static final String HOPS = _x("hops");
|
||||
static final String TUNNELS = _x("tunnels");
|
||||
private static final String HOPS = ngettext("1 hop", "{0} hops");
|
||||
private static final String TUNNELS = ngettext("1 tunnel", "{0} tunnels");
|
||||
|
||||
public ConfigTunnelsHelper() {}
|
||||
|
||||
@ -163,7 +163,7 @@ public class ConfigTunnelsHelper extends HelperBase {
|
||||
// TunnelPoolOptions, so make the boxes readonly.
|
||||
// And let's not display them at all unless they have contents, which should be rare.
|
||||
Properties props = in.getUnknownOptions();
|
||||
if (props.size() > 0) {
|
||||
if (!props.isEmpty()) {
|
||||
buf.append("<tr><td align=\"right\" class=\"mediumtags\">" + _("Inbound options") + ":</td>\n" +
|
||||
"<td colspan=\"2\" align=\"center\"><input name=\"").append(index);
|
||||
buf.append(".inboundOptions\" type=\"text\" size=\"32\" disabled=\"true\" " +
|
||||
@ -176,7 +176,7 @@ public class ConfigTunnelsHelper extends HelperBase {
|
||||
buf.append("\"></td></tr>\n");
|
||||
}
|
||||
props = out.getUnknownOptions();
|
||||
if (props.size() > 0) {
|
||||
if (!props.isEmpty()) {
|
||||
buf.append("<tr><td align=\"right\" class=\"mediumtags\">" + _("Outbound options") + ":</td>\n" +
|
||||
"<td colspan=\"2\" align=\"center\"><input name=\"").append(index);
|
||||
buf.append(".outboundOptions\" type=\"text\" size=\"32\" disabled=\"true\" " +
|
||||
@ -196,14 +196,13 @@ public class ConfigTunnelsHelper extends HelperBase {
|
||||
buf.append("<option value=\"").append(i).append("\" ");
|
||||
if (i == now)
|
||||
buf.append("selected=\"true\" ");
|
||||
String pname;
|
||||
// pluralize and then translate
|
||||
if (i != 1 && i != -1)
|
||||
pname = name + 's';
|
||||
else
|
||||
pname = name;
|
||||
buf.append(">").append(prefix).append(i).append(' ').append(_(pname));
|
||||
buf.append(">").append(_(i, "1 " + name, "{0} " + name + 's'));
|
||||
buf.append("</option>\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** dummy for tagging */
|
||||
private static String ngettext(String s, String p) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Set;
|
||||
|
||||
@ -19,6 +20,8 @@ public class ConfigUIHelper extends HelperBase {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
static final String PROP_THEME_PFX = "routerconsole.theme.";
|
||||
|
||||
/** @return standard and user-installed themes, sorted (untranslated) */
|
||||
private Set<String> themeSet() {
|
||||
Set<String> rv = new TreeSet();
|
||||
@ -33,6 +36,13 @@ public class ConfigUIHelper extends HelperBase {
|
||||
if (files[i].isDirectory() && ! name.equals("images"))
|
||||
rv.add(name);
|
||||
}
|
||||
// user themes
|
||||
Set props = _context.getPropertyNames();
|
||||
for (Iterator iter = props.iterator(); iter.hasNext(); ) {
|
||||
String prop = (String) iter.next();
|
||||
if (prop.startsWith(PROP_THEME_PFX) && prop.length() > PROP_THEME_PFX.length())
|
||||
rv.add(prop.substring(PROP_THEME_PFX.length()));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -42,11 +42,15 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
public static final String PROP_ZIP_URL = "router.updateUnsignedURL";
|
||||
|
||||
public static final String PROP_UPDATE_URL = "router.updateURL";
|
||||
/**
|
||||
* Changed as of release 0.7.14 from .sud to .su2
|
||||
* Update hosts must maintain both for several releases
|
||||
*/
|
||||
public static final String DEFAULT_UPDATE_URL =
|
||||
"http://echelon.i2p/i2p/i2pupdate.sud\r\n" +
|
||||
"http://stats.i2p/i2p/i2pupdate.sud\r\n" +
|
||||
"http://www.i2p2.i2p/_static/i2pupdate.sud\r\n" +
|
||||
"http://update.postman.i2p/i2pupdate.sud" ;
|
||||
"http://echelon.i2p/i2p/i2pupdate.su2\r\n" +
|
||||
"http://stats.i2p/i2p/i2pupdate.su2\r\n" +
|
||||
"http://www.i2p2.i2p/_static/i2pupdate.su2\r\n" +
|
||||
"http://update.postman.i2p/i2pupdate.su2" ;
|
||||
|
||||
public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
|
||||
|
||||
@ -65,6 +69,10 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
addFormNotice(_("Update available, attempting to download now"));
|
||||
else
|
||||
addFormNotice(_("Update available, click button on left to download"));
|
||||
// So that update() will post a status to the summary bar before we reload
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) {}
|
||||
} else
|
||||
addFormNotice(_("No update available"));
|
||||
return;
|
||||
|
@ -6,9 +6,11 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
class ContextHelper {
|
||||
|
||||
/** @throws IllegalStateException if no context available */
|
||||
public static RouterContext getContext(String contextId) {
|
||||
List contexts = RouterContext.listContexts();
|
||||
if ( (contexts == null) || (contexts.size() <= 0) )
|
||||
if ( (contexts == null) || (contexts.isEmpty()) )
|
||||
throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down.");
|
||||
if ( (contextId == null) || (contextId.trim().length() <= 0) )
|
||||
return (RouterContext)contexts.get(0);
|
||||
|
@ -85,16 +85,16 @@ public class FormHandler {
|
||||
public String getAllMessages() {
|
||||
validate();
|
||||
process();
|
||||
if (_errors.size() <= 0 && _notices.size() <= 0)
|
||||
if (_errors.isEmpty() && _notices.isEmpty())
|
||||
return "";
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
buf.append("<div class=\"messages\" id=\"messages\"><p>");
|
||||
if (_errors.size() > 0) {
|
||||
if (!_errors.isEmpty()) {
|
||||
buf.append("<span class=\"error\">");
|
||||
buf.append(render(_errors));
|
||||
buf.append("</span>");
|
||||
}
|
||||
if (_notices.size() > 0) {
|
||||
if (!_notices.isEmpty()) {
|
||||
buf.append("<span class=\"notice\">");
|
||||
buf.append(render(_notices));
|
||||
buf.append("</span>");
|
||||
@ -160,7 +160,7 @@ public class FormHandler {
|
||||
if ( (expected != null) && (expected.trim().length() > 0) && (expected.equals(_passphrase)) ) {
|
||||
// ok
|
||||
} else {
|
||||
addFormError("Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit.");
|
||||
addFormError(_("Invalid form submission, probably because you used the 'back' or 'reload' button on your browser. Please resubmit."));
|
||||
_valid = false;
|
||||
}
|
||||
}
|
||||
@ -174,8 +174,8 @@ public class FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private String render(List<String> source) {
|
||||
if (source.size() <= 0) {
|
||||
private static String render(List<String> source) {
|
||||
if (source.isEmpty()) {
|
||||
return "";
|
||||
} else {
|
||||
StringBuilder buf = new StringBuilder(512);
|
||||
|
@ -79,19 +79,21 @@ public class GraphHelper extends HelperBase {
|
||||
+ "&width=" + (3 * _width)
|
||||
+ "&height=" + (3 * _height)
|
||||
+ "\" / target=\"_blank\">");
|
||||
String title = _("Combined bandwidth graph");
|
||||
_out.write("<img class=\"statimage\" width=\""
|
||||
+ (_width + 83) + "\" height=\"" + (_height + 92)
|
||||
+ "\" src=\"viewstat.jsp?stat=bw.combined"
|
||||
+ "&periodCount=" + _periodCount
|
||||
+ "&width=" + _width
|
||||
+ "&height=" + (_height - 14)
|
||||
+ "\" alt=\"Combined bandwidth graph\" title=\"Combined bandwidth graph\"></a>\n");
|
||||
+ "\" alt=\"" + title + "\" title=\"" + title + "\"></a>\n");
|
||||
}
|
||||
|
||||
for (Iterator iter = ordered.iterator(); iter.hasNext(); ) {
|
||||
SummaryListener lsnr = (SummaryListener)iter.next();
|
||||
Rate r = lsnr.getRate();
|
||||
String title = r.getRateStat().getName() + " for " + DataHelper.formatDuration(_periodCount * r.getPeriod());
|
||||
// e.g. "statname for 60m"
|
||||
String title = _("{0} for {1}", r.getRateStat().getName(), DataHelper.formatDuration(_periodCount * r.getPeriod()));
|
||||
_out.write("<a href=\"viewstat.jsp?stat="
|
||||
+ r.getRateStat().getName()
|
||||
+ "&showEvents=" + _showEvents
|
||||
|
@ -51,6 +51,16 @@ public abstract class HelperBase {
|
||||
return Messages.getString(s, o, _context);
|
||||
}
|
||||
|
||||
/** two params @since 0.7.14 */
|
||||
public String _(String s, Object o, Object o2) {
|
||||
return Messages.getString(s, o, o2, _context);
|
||||
}
|
||||
|
||||
/** translate (ngettext) @since 0.7.14 */
|
||||
public String _(int n, String s, String p) {
|
||||
return Messages.getString(n, s, p, _context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a string for extraction by xgettext and translation.
|
||||
* Use this only in static initializers.
|
||||
|
@ -29,4 +29,14 @@ public class Messages extends Translate {
|
||||
public static String getString(String s, Object o, I2PAppContext ctx) {
|
||||
return Translate.getString(s, o, ctx, BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** two params @since 0.7.14 */
|
||||
public static String getString(String s, Object o, Object o2, I2PAppContext ctx) {
|
||||
return Translate.getString(s, o, o2, ctx, BUNDLE_NAME);
|
||||
}
|
||||
|
||||
/** translate (ngettext) @since 0.7.14 */
|
||||
public static String getString(int n, String s, String p, I2PAppContext ctx) {
|
||||
return Translate.getString(n, s, p, ctx, BUNDLE_NAME);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
public class NavHelper extends HelperBase {
|
||||
private static Map _apps = new HashMap();
|
||||
|
||||
public NavHelper() {}
|
||||
public class NavHelper {
|
||||
private static Map<String, String> _apps = new ConcurrentHashMap(4);
|
||||
private static Map<String, String> _tooltips = new ConcurrentHashMap(4);
|
||||
|
||||
/**
|
||||
* To register a new client application so that it shows up on the router
|
||||
@ -21,17 +23,35 @@ public class NavHelper extends HelperBase {
|
||||
public static void registerApp(String name, String path) {
|
||||
_apps.put(name, path);
|
||||
}
|
||||
|
||||
public static void registerApp(String name, String path, String tooltip) {
|
||||
_apps.put(name, path);
|
||||
_tooltips.put(name, tooltip);
|
||||
}
|
||||
|
||||
public static void unregisterApp(String name) {
|
||||
_apps.remove(name);
|
||||
_tooltips.remove(name);
|
||||
}
|
||||
|
||||
public String getClientAppLinks() {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
for (Iterator iter = _apps.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String path = (String)_apps.get(name);
|
||||
buf.append("<a href=\"").append(path).append("\">");
|
||||
buf.append(name).append("</a> |");
|
||||
/**
|
||||
* Translated string is loaded by PluginStarter
|
||||
*/
|
||||
public static String getClientAppLinks(I2PAppContext ctx) {
|
||||
if (_apps.isEmpty())
|
||||
return "";
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
List<String> l = new ArrayList(_apps.keySet());
|
||||
Collections.sort(l);
|
||||
for (String name : l) {
|
||||
String path = _apps.get(name);
|
||||
if (path == null)
|
||||
continue;
|
||||
buf.append(" <a target=\"_blank\" href=\"").append(path).append("\" ");
|
||||
String tip = _tooltips.get(name);
|
||||
if (tip != null)
|
||||
buf.append("title=\"").append(tip).append("\" ");
|
||||
buf.append('>').append(name).append("</a>");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import net.i2p.data.DataHelper;
|
||||
public class NetDbHelper extends HelperBase {
|
||||
private String _routerPrefix;
|
||||
private int _full;
|
||||
private boolean _lease = false;
|
||||
private boolean _lease;
|
||||
private boolean _debug;
|
||||
|
||||
public NetDbHelper() {}
|
||||
|
||||
@ -23,7 +24,11 @@ public class NetDbHelper extends HelperBase {
|
||||
_full = Integer.parseInt(f);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
public void setLease(String l) { _lease = "1".equals(l); }
|
||||
|
||||
public void setLease(String l) {
|
||||
_debug = "2".equals(l);
|
||||
_lease = _debug || "1".equals(l);
|
||||
}
|
||||
|
||||
public String getNetDbSummary() {
|
||||
NetDbRenderer renderer = new NetDbRenderer(_context);
|
||||
@ -32,7 +37,7 @@ public class NetDbHelper extends HelperBase {
|
||||
if (_routerPrefix != null)
|
||||
renderer.renderRouterInfoHTML(_out, _routerPrefix);
|
||||
else if (_lease)
|
||||
renderer.renderLeaseSetHTML(_out);
|
||||
renderer.renderLeaseSetHTML(_out, _debug);
|
||||
else
|
||||
renderer.renderStatusHTML(_out, _full);
|
||||
return "";
|
||||
@ -41,7 +46,7 @@ public class NetDbHelper extends HelperBase {
|
||||
if (_routerPrefix != null)
|
||||
renderer.renderRouterInfoHTML(new OutputStreamWriter(baos), _routerPrefix);
|
||||
else if (_lease)
|
||||
renderer.renderLeaseSetHTML(new OutputStreamWriter(baos));
|
||||
renderer.renderLeaseSetHTML(new OutputStreamWriter(baos), _debug);
|
||||
else
|
||||
renderer.renderStatusHTML(new OutputStreamWriter(baos), _full);
|
||||
return new String(baos.toByteArray());
|
||||
|
@ -10,6 +10,8 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.math.BigInteger; // debug
|
||||
import java.text.DecimalFormat; // debug
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@ -27,6 +29,8 @@ import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.router.networkdb.kademlia.HashDistance; // debug
|
||||
import net.i2p.util.HexDump; // debug
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
@ -37,10 +41,10 @@ public class NetDbRenderer {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
private class LeaseSetComparator implements Comparator {
|
||||
public int compare(Object l, Object r) {
|
||||
Destination dl = ((LeaseSet)l).getDestination();
|
||||
Destination dr = ((LeaseSet)r).getDestination();
|
||||
private class LeaseSetComparator implements Comparator<LeaseSet> {
|
||||
public int compare(LeaseSet l, LeaseSet r) {
|
||||
Destination dl = l.getDestination();
|
||||
Destination dr = r.getDestination();
|
||||
boolean locall = _context.clientManager().isLocal(dl);
|
||||
boolean localr = _context.clientManager().isLocal(dr);
|
||||
if (locall && !localr) return -1;
|
||||
@ -49,9 +53,20 @@ public class NetDbRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static class RouterInfoComparator implements Comparator {
|
||||
public int compare(Object l, Object r) {
|
||||
return ((RouterInfo)l).getIdentity().getHash().toBase64().compareTo(((RouterInfo)r).getIdentity().getHash().toBase64());
|
||||
/** for debugging @since 0.7.14 */
|
||||
private static class LeaseSetRoutingKeyComparator implements Comparator<LeaseSet> {
|
||||
private final Hash _us;
|
||||
public LeaseSetRoutingKeyComparator(Hash us) {
|
||||
_us = us;
|
||||
}
|
||||
public int compare(LeaseSet l, LeaseSet r) {
|
||||
return HashDistance.getDistance(_us, l.getRoutingKey()).subtract(HashDistance.getDistance(_us, r.getRoutingKey())).signum();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RouterInfoComparator implements Comparator<RouterInfo> {
|
||||
public int compare(RouterInfo l, RouterInfo r) {
|
||||
return l.getIdentity().getHash().toBase64().compareTo(r.getIdentity().getHash().toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,13 +93,31 @@ public class NetDbRenderer {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void renderLeaseSetHTML(Writer out) throws IOException {
|
||||
/**
|
||||
* @param debug @since 0.7.14 sort by distance from us, display
|
||||
* median distance, and other stuff, useful when floodfill
|
||||
*/
|
||||
public void renderLeaseSetHTML(Writer out, boolean debug) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(4*1024);
|
||||
buf.append("<h2>" + _("Network Database Contents") + "</h2>\n");
|
||||
buf.append("<a href=\"netdb.jsp\">" + _("View RouterInfo") + "</a>");
|
||||
buf.append("<h3>").append(_("LeaseSets")).append("</h3>\n");
|
||||
Set leases = new TreeSet(new LeaseSetComparator());
|
||||
Hash ourRKey;
|
||||
Set<LeaseSet> leases;
|
||||
DecimalFormat fmt;
|
||||
if (debug) {
|
||||
ourRKey = _context.routerHash();
|
||||
leases = new TreeSet(new LeaseSetRoutingKeyComparator(ourRKey));
|
||||
fmt = new DecimalFormat("#0.00");
|
||||
} else {
|
||||
ourRKey = null;
|
||||
leases = new TreeSet(new LeaseSetComparator());
|
||||
fmt = null;
|
||||
}
|
||||
leases.addAll(_context.netDb().getLeases());
|
||||
int medianCount = leases.size() / 2;
|
||||
BigInteger median = null;
|
||||
int c = 0;
|
||||
long now = _context.clock().now();
|
||||
for (Iterator iter = leases.iterator(); iter.hasNext(); ) {
|
||||
LeaseSet ls = (LeaseSet)iter.next();
|
||||
@ -115,6 +148,16 @@ public class NetDbRenderer {
|
||||
buf.append(_("Expires in {0}", DataHelper.formatDuration(exp))).append("<br>\n");
|
||||
else
|
||||
buf.append(_("Expired {0} ago", DataHelper.formatDuration(0-exp))).append("<br>\n");
|
||||
if (debug) {
|
||||
buf.append("RAP? " + ls.getReceivedAsPublished() + ' ');
|
||||
buf.append("RAR? " + ls.getReceivedAsReply() + ' ');
|
||||
BigInteger dist = HashDistance.getDistance(ourRKey, ls.getRoutingKey());
|
||||
if (c++ == medianCount)
|
||||
median = dist;
|
||||
buf.append("Dist: <b>" + fmt.format(biLog2(dist)) + "</b> ");
|
||||
buf.append("RKey: " + ls.getRoutingKey().toBase64() + ' ');
|
||||
buf.append("<br>");
|
||||
}
|
||||
for (int i = 0; i < ls.getLeaseCount(); i++) {
|
||||
buf.append(_("Lease")).append(' ').append(i + 1).append(": " + _("Gateway") + ' ');
|
||||
buf.append(_context.commSystem().renderPeerHTML(ls.getLease(i).getGateway()));
|
||||
@ -124,10 +167,42 @@ public class NetDbRenderer {
|
||||
out.write(buf.toString());
|
||||
buf.setLength(0);
|
||||
}
|
||||
if (debug) {
|
||||
buf.append("<p><b>Total Leasesets: " + leases.size());
|
||||
buf.append("<p><b>Published (RAP) Leasesets: " + _context.netDb().getKnownLeaseSets());
|
||||
buf.append("<p>Mod Data: " + HexDump.dump(_context.routingKeyGenerator().getModData()) + "<p>");
|
||||
buf.append("<p>Network data (only valid if floodfill):");
|
||||
buf.append("<p>Center of Key Space (router hash): " + ourRKey.toBase64() + "<p>");
|
||||
if (median != null) {
|
||||
double log2 = biLog2(median);
|
||||
buf.append("<p>Median distance (bits): " + fmt.format(log2));
|
||||
// 3 for 8 floodfills... -1 for median
|
||||
int total = (int) Math.round(Math.pow(2, 3 + 256 - 1 - log2));
|
||||
buf.append("<p>Estimated total floodfills: " + total);
|
||||
buf.append("<p>Estimated network total leasesets: " + (total * leases.size() / 8));
|
||||
}
|
||||
}
|
||||
out.write(buf.toString());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging
|
||||
* http://forums.sun.com/thread.jspa?threadID=597652
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static double biLog2(BigInteger a) {
|
||||
int b = a.bitLength() - 1;
|
||||
double c = 0;
|
||||
double d = 0.5;
|
||||
for (int i = b; i >= 0; --i) {
|
||||
if (a.testBit(i))
|
||||
c += d;
|
||||
d /= 2;
|
||||
}
|
||||
return b + c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mode 0: our info and charts only; 1: full routerinfos and charts; 2: abbreviated routerinfos and charts
|
||||
*/
|
||||
@ -160,7 +235,7 @@ public class NetDbRenderer {
|
||||
ObjectCounter<String> countries = new ObjectCounter();
|
||||
int[] transportCount = new int[8];
|
||||
|
||||
Set routers = new TreeSet(new RouterInfoComparator());
|
||||
Set<RouterInfo> routers = new TreeSet(new RouterInfoComparator());
|
||||
routers.addAll(_context.netDb().getRouters());
|
||||
for (Iterator iter = routers.iterator(); iter.hasNext(); ) {
|
||||
RouterInfo ri = (RouterInfo)iter.next();
|
||||
@ -185,7 +260,7 @@ public class NetDbRenderer {
|
||||
buf.append("<table border=\"0\" cellspacing=\"30\"><tr><th colspan=\"3\">").append(_("Network Database Router Statistics")).append("</th><tr><td>");
|
||||
// versions table
|
||||
List<String> versionList = new ArrayList(versions.objects());
|
||||
if (versionList.size() > 0) {
|
||||
if (!versionList.isEmpty()) {
|
||||
Collections.sort(versionList, Collections.reverseOrder(new VersionComparator()));
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th>" + _("Version") + "</th><th>" + _("Count") + "</th></tr>\n");
|
||||
@ -217,7 +292,7 @@ public class NetDbRenderer {
|
||||
|
||||
// country table
|
||||
List<String> countryList = new ArrayList(countries.objects());
|
||||
if (countryList.size() > 0) {
|
||||
if (!countryList.isEmpty()) {
|
||||
Collections.sort(countryList, new CountryComparator());
|
||||
buf.append("<table>\n");
|
||||
buf.append("<tr><th align=\"left\">" + _("Country") + "</th><th>" + _("Count") + "</th></tr>\n");
|
||||
@ -282,11 +357,15 @@ public class NetDbRenderer {
|
||||
}
|
||||
for (Iterator iter = info.getAddresses().iterator(); iter.hasNext(); ) {
|
||||
RouterAddress addr = (RouterAddress)iter.next();
|
||||
buf.append("<b>").append(DataHelper.stripHTML(addr.getTransportStyle())).append(":</b> ");
|
||||
String style = addr.getTransportStyle();
|
||||
buf.append("<b>").append(DataHelper.stripHTML(style)).append(":</b> ");
|
||||
int cost = addr.getCost();
|
||||
if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10)))
|
||||
buf.append('[').append(_("cost")).append('=').append("" + cost).append("] ");
|
||||
for (Iterator optIter = addr.getOptions().keySet().iterator(); optIter.hasNext(); ) {
|
||||
String name = (String)optIter.next();
|
||||
String val = addr.getOptions().getProperty(name);
|
||||
buf.append('[').append(DataHelper.stripHTML(name)).append('=').append(DataHelper.stripHTML(val)).append("] ");
|
||||
buf.append('[').append(_(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] ");
|
||||
}
|
||||
}
|
||||
buf.append("</td></tr>\n");
|
||||
|
@ -47,6 +47,8 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
|
||||
private static final String NEWS_FILE = "docs/news.xml";
|
||||
private static final String TEMP_NEWS_FILE = "news.xml.temp";
|
||||
/** @since 0.7.14 not configurable */
|
||||
private static final String BACKUP_NEWS_URL = "http://www.i2p2.i2p/_static/news/news.xml";
|
||||
|
||||
private NewsFetcher(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
@ -90,8 +92,11 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final long INITIAL_DELAY = 5*60*1000;
|
||||
private static final long RUN_DELAY = 10*60*1000;
|
||||
|
||||
public void run() {
|
||||
try { Thread.sleep(_context.random().nextLong(5*60*1000)); } catch (InterruptedException ie) {}
|
||||
try { Thread.sleep(INITIAL_DELAY + _context.random().nextLong(INITIAL_DELAY)); } catch (InterruptedException ie) {}
|
||||
while (true) {
|
||||
if (!_updateAvailable) checkForUpdates();
|
||||
if (shouldFetchNews()) {
|
||||
@ -99,7 +104,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
if (shouldFetchUnsigned())
|
||||
fetchUnsignedHead();
|
||||
}
|
||||
try { Thread.sleep(10*60*1000); } catch (InterruptedException ie) {}
|
||||
try { Thread.sleep(RUN_DELAY); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,12 +156,20 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
try {
|
||||
EepGet get = null;
|
||||
if (shouldProxy)
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 2, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
else
|
||||
get = new EepGet(_context, false, null, 0, 0, _tempFile.getAbsolutePath(), newsURL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch())
|
||||
if (get.fetch()) {
|
||||
_lastModified = get.getLastModified();
|
||||
} else {
|
||||
// backup news location - always proxied
|
||||
_tempFile.delete();
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 0, _tempFile.getAbsolutePath(), BACKUP_NEWS_URL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
if (get.fetch())
|
||||
_lastModified = get.getLastModified();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
@ -322,7 +335,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
handler = new UpdateHandler((RouterContext)_context);
|
||||
} else {
|
||||
List contexts = RouterContext.listContexts();
|
||||
if (contexts.size() > 0)
|
||||
if (!contexts.isEmpty())
|
||||
handler = new UpdateHandler((RouterContext)contexts.get(0));
|
||||
else
|
||||
_log.log(Log.CRIT, "No router context to update with?");
|
||||
|
@ -0,0 +1,573 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ClassLoader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.startup.ClientAppConfig;
|
||||
import net.i2p.router.startup.LoadClientAppsJob;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Translate;
|
||||
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
|
||||
/**
|
||||
* Start/stop/delete plugins that are already installed
|
||||
* Get properties of installed plugins
|
||||
* Get or change settings in plugins.config
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class PluginStarter implements Runnable {
|
||||
protected RouterContext _context;
|
||||
static final String PREFIX = "plugin.";
|
||||
static final String ENABLED = ".startOnLoad";
|
||||
private static final String[] STANDARD_WEBAPPS = { "i2psnark", "i2ptunnel", "susidns",
|
||||
"susimail", "addressbook", "routerconsole" };
|
||||
private static final String[] STANDARD_THEMES = { "images", "light", "dark", "classic",
|
||||
"midnight" };
|
||||
private static Map<String, ThreadGroup> pluginThreadGroups = new ConcurrentHashMap<String, ThreadGroup>(); // one thread group per plugin (map key=plugin name)
|
||||
private static Map<String, Collection<Job>> pluginJobs = new ConcurrentHashMap<String, Collection<Job>>();
|
||||
private static Map<String, ClassLoader> _clCache = new ConcurrentHashMap();
|
||||
|
||||
public PluginStarter(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
}
|
||||
|
||||
static boolean pluginsEnabled(I2PAppContext ctx) {
|
||||
return Boolean.valueOf(ctx.getProperty("router.enablePlugins", "true")).booleanValue();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
startPlugins(_context);
|
||||
}
|
||||
|
||||
/** this shouldn't throw anything */
|
||||
static void startPlugins(RouterContext ctx) {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
Properties props = pluginProperties();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (name.startsWith(PREFIX) && name.endsWith(ENABLED)) {
|
||||
if (Boolean.valueOf(props.getProperty(name)).booleanValue()) {
|
||||
String app = name.substring(PREFIX.length(), name.lastIndexOf(ENABLED));
|
||||
try {
|
||||
if (!startPlugin(ctx, app))
|
||||
log.error("Failed to start plugin: " + app);
|
||||
} catch (Throwable e) {
|
||||
log.error("Failed to start plugin: " + app, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true on success
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
*/
|
||||
static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot start nonexistent plugin: " + appName);
|
||||
return false;
|
||||
}
|
||||
//log.error("Starting plugin: " + appName);
|
||||
|
||||
// register themes
|
||||
File dir = new File(pluginDir, "console/themes");
|
||||
File[] tfiles = dir.listFiles();
|
||||
if (tfiles != null) {
|
||||
for (int i = 0; i < tfiles.length; i++) {
|
||||
String name = tfiles[i].getName();
|
||||
if (tfiles[i].isDirectory() && (!Arrays.asList(STANDARD_THEMES).contains(tfiles[i])))
|
||||
ctx.router().setConfigSetting(ConfigUIHelper.PROP_THEME_PFX + name, tfiles[i].getAbsolutePath());
|
||||
// we don't need to save
|
||||
}
|
||||
}
|
||||
|
||||
// load and start things in clients.config
|
||||
File clientConfig = new File(pluginDir, "clients.config");
|
||||
if (clientConfig.exists()) {
|
||||
Properties props = new Properties();
|
||||
DataHelper.loadProps(props, clientConfig);
|
||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
||||
runClientApps(ctx, pluginDir, clients, "start");
|
||||
}
|
||||
|
||||
// start console webapps in console/webapps
|
||||
Server server = WebAppStarter.getConsoleServer();
|
||||
if (server != null) {
|
||||
File consoleDir = new File(pluginDir, "console");
|
||||
Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
|
||||
File webappDir = new File(consoleDir, "webapps");
|
||||
String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
|
||||
if (fileNames != null) {
|
||||
for (int i = 0; i < fileNames.length; i++) {
|
||||
try {
|
||||
String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
|
||||
//log.error("Found webapp: " + warName);
|
||||
// check for duplicates in $I2P
|
||||
if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
|
||||
log.error("Skipping duplicate webapp " + warName + " in plugin " + appName);
|
||||
continue;
|
||||
}
|
||||
String enabled = props.getProperty(RouterConsoleRunner.PREFIX + warName + ENABLED);
|
||||
if (! "false".equals(enabled)) {
|
||||
//log.error("Starting webapp: " + warName);
|
||||
String path = new File(webappDir, fileNames[i]).getCanonicalPath();
|
||||
WebAppStarter.startWebApp(ctx, server, warName, path);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.error("Error resolving '" + fileNames[i] + "' in '" + webappDir, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add translation jars in console/locale
|
||||
// These will not override existing resource bundles since we are adding them
|
||||
// later in the classpath.
|
||||
File localeDir = new File(pluginDir, "console/locale");
|
||||
if (localeDir.exists() && localeDir.isDirectory()) {
|
||||
File[] files = localeDir.listFiles();
|
||||
if (files != null) {
|
||||
boolean added = false;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File f = files[i];
|
||||
if (f.getName().endsWith(".jar")) {
|
||||
try {
|
||||
addPath(f.toURI().toURL());
|
||||
log.error("INFO: Adding translation plugin to classpath: " + f);
|
||||
added = true;
|
||||
} catch (Exception e) {
|
||||
log.error("Plugin " + appName + " bad classpath element: " + f, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (added)
|
||||
Translate.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
// add summary bar link
|
||||
Properties props = pluginProperties(ctx, appName);
|
||||
String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx));
|
||||
if (name == null)
|
||||
name = ConfigClientsHelper.stripHTML(props, "consoleLinkName");
|
||||
String url = ConfigClientsHelper.stripHTML(props, "consoleLinkURL");
|
||||
if (name != null && url != null && name.length() > 0 && url.length() > 0) {
|
||||
String tip = ConfigClientsHelper.stripHTML(props, "consoleLinkTooltip_" + Messages.getLanguage(ctx));
|
||||
if (tip == null)
|
||||
tip = ConfigClientsHelper.stripHTML(props, "consoleLinkTooltip");
|
||||
if (tip != null)
|
||||
NavHelper.registerApp(name, url, tip);
|
||||
else
|
||||
NavHelper.registerApp(name, url);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true on success
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
*/
|
||||
static boolean stopPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot stop nonexistent plugin: " + appName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// stop things in clients.config
|
||||
File clientConfig = new File(pluginDir, "clients.config");
|
||||
if (clientConfig.exists()) {
|
||||
Properties props = new Properties();
|
||||
DataHelper.loadProps(props, clientConfig);
|
||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
||||
runClientApps(ctx, pluginDir, clients, "stop");
|
||||
}
|
||||
|
||||
// stop console webapps in console/webapps
|
||||
Server server = WebAppStarter.getConsoleServer();
|
||||
if (server != null) {
|
||||
File consoleDir = new File(pluginDir, "console");
|
||||
Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath());
|
||||
File webappDir = new File(consoleDir, "webapps");
|
||||
String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance());
|
||||
if (fileNames != null) {
|
||||
for (int i = 0; i < fileNames.length; i++) {
|
||||
String warName = fileNames[i].substring(0, fileNames[i].lastIndexOf(".war"));
|
||||
if (Arrays.asList(STANDARD_WEBAPPS).contains(warName)) {
|
||||
continue;
|
||||
}
|
||||
WebAppStarter.stopWebApp(server, warName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove summary bar link
|
||||
Properties props = pluginProperties(ctx, appName);
|
||||
String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx));
|
||||
if (name == null)
|
||||
name = ConfigClientsHelper.stripHTML(props, "consoleLinkName");
|
||||
if (name != null && name.length() > 0)
|
||||
NavHelper.unregisterApp(name);
|
||||
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Stopping plugin: " + appName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return true on success - caller should call stopPlugin() first */
|
||||
static boolean deletePlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot delete nonexistent plugin: " + appName);
|
||||
return false;
|
||||
}
|
||||
// uninstall things in clients.config
|
||||
File clientConfig = new File(pluginDir, "clients.config");
|
||||
if (clientConfig.exists()) {
|
||||
Properties props = new Properties();
|
||||
DataHelper.loadProps(props, clientConfig);
|
||||
List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig);
|
||||
runClientApps(ctx, pluginDir, clients, "uninstall");
|
||||
}
|
||||
|
||||
// unregister themes, and switch to default if we are unregistering the current theme
|
||||
File dir = new File(pluginDir, "console/themes");
|
||||
File[] tfiles = dir.listFiles();
|
||||
if (tfiles != null) {
|
||||
String current = ctx.getProperty(CSSHelper.PROP_THEME_NAME);
|
||||
for (int i = 0; i < tfiles.length; i++) {
|
||||
String name = tfiles[i].getName();
|
||||
if (tfiles[i].isDirectory() && (!Arrays.asList(STANDARD_THEMES).contains(tfiles[i]))) {
|
||||
ctx.router().removeConfigSetting(ConfigUIHelper.PROP_THEME_PFX + name);
|
||||
if (name.equals(current))
|
||||
ctx.router().setConfigSetting(CSSHelper.PROP_THEME_NAME, CSSHelper.DEFAULT_THEME);
|
||||
}
|
||||
}
|
||||
ctx.router().saveConfig();
|
||||
}
|
||||
|
||||
FileUtil.rmdir(pluginDir, false);
|
||||
Properties props = pluginProperties();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (name.startsWith(PREFIX + appName))
|
||||
iter.remove();
|
||||
}
|
||||
storePluginProperties(props);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** plugin.config */
|
||||
public static Properties pluginProperties(I2PAppContext ctx, String appName) {
|
||||
File cfgFile = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
||||
Properties rv = new Properties();
|
||||
try {
|
||||
DataHelper.loadProps(rv, cfgFile);
|
||||
} catch (IOException ioe) {}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* plugins.config
|
||||
* this auto-adds a propery for every dir in the plugin directory
|
||||
*/
|
||||
public static Properties pluginProperties() {
|
||||
File dir = I2PAppContext.getGlobalContext().getConfigDir();
|
||||
Properties rv = new Properties();
|
||||
File cfgFile = new File(dir, "plugins.config");
|
||||
|
||||
try {
|
||||
DataHelper.loadProps(rv, cfgFile);
|
||||
} catch (IOException ioe) {}
|
||||
|
||||
List<String> names = getPlugins();
|
||||
for (String name : names) {
|
||||
String prop = PREFIX + name + ENABLED;
|
||||
if (rv.getProperty(prop) == null)
|
||||
rv.setProperty(prop, "true");
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* all installed plugins whether enabled or not
|
||||
*/
|
||||
public static List<String> getPlugins() {
|
||||
List<String> rv = new ArrayList();
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File[] files = pluginDir.listFiles();
|
||||
if (files == null)
|
||||
return rv;
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isDirectory())
|
||||
rv.add(files[i].getName());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* The signing keys from all the plugins
|
||||
* @return Map of key to keyname
|
||||
* Last one wins if a dup (installer should prevent dups)
|
||||
*/
|
||||
public static Map<String, String> getPluginKeys(I2PAppContext ctx) {
|
||||
Map<String, String> rv = new HashMap();
|
||||
List<String> names = getPlugins();
|
||||
for (String name : names) {
|
||||
Properties props = pluginProperties(ctx, name);
|
||||
String pubkey = props.getProperty("key");
|
||||
String signer = props.getProperty("signer");
|
||||
if (pubkey != null && signer != null && pubkey.length() == 172 && signer.length() > 0)
|
||||
rv.put(pubkey, signer);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* plugins.config
|
||||
*/
|
||||
public static void storePluginProperties(Properties props) {
|
||||
File cfgFile = new File(I2PAppContext.getGlobalContext().getConfigDir(), "plugins.config");
|
||||
try {
|
||||
DataHelper.storeProps(props, cfgFile);
|
||||
} catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param action "start" or "stop" or "uninstall"
|
||||
* @throws just about anything if an app has a delay less than zero, caller would be wise to catch Throwable
|
||||
* If no apps have a delay less than zero, it shouldn't throw anything
|
||||
*/
|
||||
private static void runClientApps(RouterContext ctx, File pluginDir, List<ClientAppConfig> apps, String action) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
|
||||
// initialize pluginThreadGroup and pluginJobs
|
||||
String pluginName = pluginDir.getName();
|
||||
if (!pluginThreadGroups.containsKey(pluginName))
|
||||
pluginThreadGroups.put(pluginName, new ThreadGroup(pluginName));
|
||||
ThreadGroup pluginThreadGroup = pluginThreadGroups.get(pluginName);
|
||||
if (action.equals("start"))
|
||||
pluginJobs.put(pluginName, new ConcurrentHashSet<Job>());
|
||||
|
||||
for(ClientAppConfig app : apps) {
|
||||
if (action.equals("start") && app.disabled)
|
||||
continue;
|
||||
String argVal[];
|
||||
if (action.equals("start")) {
|
||||
// start
|
||||
argVal = LoadClientAppsJob.parseArgs(app.args);
|
||||
} else {
|
||||
String args;
|
||||
if (action.equals("stop"))
|
||||
args = app.stopargs;
|
||||
else if (action.equals("uninstall"))
|
||||
args = app.uninstallargs;
|
||||
else
|
||||
throw new IllegalArgumentException("bad action");
|
||||
// args must be present
|
||||
if (args == null || args.length() <= 0)
|
||||
continue;
|
||||
argVal = LoadClientAppsJob.parseArgs(args);
|
||||
}
|
||||
// do this after parsing so we don't need to worry about quoting
|
||||
for (int i = 0; i < argVal.length; i++) {
|
||||
if (argVal[i].indexOf("$") >= 0) {
|
||||
argVal[i] = argVal[i].replace("$I2P", ctx.getBaseDir().getAbsolutePath());
|
||||
argVal[i] = argVal[i].replace("$CONFIG", ctx.getConfigDir().getAbsolutePath());
|
||||
argVal[i] = argVal[i].replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
ClassLoader cl = null;
|
||||
if (app.classpath != null) {
|
||||
String cp = new String(app.classpath);
|
||||
if (cp.indexOf("$") >= 0) {
|
||||
cp = cp.replace("$I2P", ctx.getBaseDir().getAbsolutePath());
|
||||
cp = cp.replace("$CONFIG", ctx.getConfigDir().getAbsolutePath());
|
||||
cp = cp.replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
// Old way - add for the whole JVM
|
||||
//addToClasspath(cp, app.clientName, log);
|
||||
|
||||
// New way - add only for this client
|
||||
// We cache the ClassLoader we start the client with, so
|
||||
// we can reuse it for stopping and uninstalling.
|
||||
// If we don't, the client won't be able to find its
|
||||
// static members.
|
||||
String clCacheKey = pluginName + app.className + app.args;
|
||||
if (!action.equals("start"))
|
||||
cl = _clCache.get(clCacheKey);
|
||||
if (cl == null) {
|
||||
URL[] urls = classpathToURLArray(cp, app.clientName, log);
|
||||
if (urls != null) {
|
||||
cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
|
||||
if (action.equals("start"))
|
||||
_clCache.put(clCacheKey, cl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app.delay < 0 && action.equals("start")) {
|
||||
// this will throw exceptions
|
||||
LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log, cl);
|
||||
} else if (app.delay == 0 || !action.equals("start")) {
|
||||
// quick check, will throw ClassNotFoundException on error
|
||||
LoadClientAppsJob.testClient(app.className, cl);
|
||||
// run this guy now
|
||||
LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log, pluginThreadGroup, cl);
|
||||
} else {
|
||||
// If there is some delay, there may be a really good reason for it.
|
||||
// Loading a class would be one of them!
|
||||
// So we do a quick check first, If it bombs out, we delay and try again.
|
||||
// If it bombs after that, then we throw the ClassNotFoundException.
|
||||
try {
|
||||
// quick check
|
||||
LoadClientAppsJob.testClient(app.className, cl);
|
||||
} catch(ClassNotFoundException ex) {
|
||||
// Try again 1 or 2 seconds later.
|
||||
// This should be enough time. Although it is a lousy hack
|
||||
// it should work for most cases.
|
||||
// Perhaps it may be even better to delay a percentage
|
||||
// if > 1, and reduce the delay time.
|
||||
// Under normal circumstances there will be no delay at all.
|
||||
if(app.delay > 1) {
|
||||
Thread.sleep(2000);
|
||||
} else {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
// quick check, will throw ClassNotFoundException on error
|
||||
LoadClientAppsJob.testClient(app.className, cl);
|
||||
// wait before firing it up
|
||||
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup, cl);
|
||||
ctx.jobQueue().addJob(job);
|
||||
pluginJobs.get(pluginName).add(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPluginRunning(String pluginName, RouterContext ctx) {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
|
||||
boolean isJobRunning = false;
|
||||
if (pluginJobs.containsKey(pluginName))
|
||||
for (Job job: pluginJobs.get(pluginName))
|
||||
if (ctx.jobQueue().isJobActive(job)) {
|
||||
isJobRunning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("plugin name = <" + pluginName + ">; threads running? " + isClientThreadRunning(pluginName) + "; webapp runing? " + WebAppStarter.isWebAppRunning(pluginName) + "; jobs running? " + isJobRunning);
|
||||
return isClientThreadRunning(pluginName) || WebAppStarter.isWebAppRunning(pluginName) || isJobRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if one or more client threads are running in a given plugin.
|
||||
* @param pluginName
|
||||
* @return
|
||||
*/
|
||||
private static boolean isClientThreadRunning(String pluginName) {
|
||||
ThreadGroup group = pluginThreadGroups.get(pluginName);
|
||||
if (group == null)
|
||||
return false;
|
||||
|
||||
Thread[] activeThreads = new Thread[1];
|
||||
group.enumerate(activeThreads);
|
||||
return activeThreads[0] != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perhaps there's an easy way to use Thread.setContextClassLoader()
|
||||
* but I don't see how to make it magically get used for everything.
|
||||
* So add this to the whole JVM's classpath.
|
||||
*/
|
||||
/******
|
||||
private static void addToClasspath(String classpath, String clientName, Log log) {
|
||||
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String elem = tok.nextToken().trim();
|
||||
File f = new File(elem);
|
||||
if (!f.isAbsolute()) {
|
||||
log.error("Plugin client " + clientName + " classpath element is not absolute: " + f);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
addPath(f.toURI().toURL());
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("INFO: Adding plugin to classpath: " + f);
|
||||
} catch (Exception e) {
|
||||
log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
*****/
|
||||
|
||||
/**
|
||||
* @return null if no valid elements
|
||||
*/
|
||||
private static URL[] classpathToURLArray(String classpath, String clientName, Log log) {
|
||||
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
||||
List<URL> urls = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String elem = tok.nextToken().trim();
|
||||
File f = new File(elem);
|
||||
if (!f.isAbsolute()) {
|
||||
log.error("Plugin client " + clientName + " classpath element is not absolute: " + f);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
urls.add(f.toURI().toURL());
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("INFO: Adding plugin to classpath: " + f);
|
||||
} catch (Exception e) {
|
||||
log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
|
||||
}
|
||||
}
|
||||
if (urls.isEmpty())
|
||||
return null;
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://jimlife.wordpress.com/2007/12/19/java-adding-new-classpath-at-runtime/
|
||||
*/
|
||||
public static void addPath(URL u) throws Exception {
|
||||
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
|
||||
Class urlClass = URLClassLoader.class;
|
||||
Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
|
||||
method.setAccessible(true);
|
||||
method.invoke(urlClassLoader, new Object[]{u});
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Stop all plugins that are installed
|
||||
*
|
||||
* @since 0.7.13
|
||||
* @author zzz
|
||||
*/
|
||||
public class PluginStopper extends PluginStarter {
|
||||
|
||||
public PluginStopper(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
stopPlugins(_context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all plugins
|
||||
* (whether or not they were ever started)
|
||||
*
|
||||
* this shouldn't throw anything
|
||||
*/
|
||||
static void stopPlugins(RouterContext ctx) {
|
||||
Log log = ctx.logManager().getLog(PluginStopper.class);
|
||||
for (String app : getPlugins()) {
|
||||
try {
|
||||
stopPlugin(ctx, app);
|
||||
} catch (Throwable e) {
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("Failed to stop plugin: " + app, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PartialEepGet;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.VersionComparator;
|
||||
|
||||
/**
|
||||
* Check for an updated version of a plugin.
|
||||
* A plugin is a standard .sud file with a 40-byte signature,
|
||||
* a 16-byte version, and a .zip file.
|
||||
*
|
||||
* So we get the current version and update URL for the installed plugin,
|
||||
* then fetch the first 56 bytes of the URL, extract the version,
|
||||
* and compare.
|
||||
*
|
||||
* @since 0.7.12
|
||||
* @author zzz
|
||||
*/
|
||||
public class PluginUpdateChecker extends UpdateHandler {
|
||||
private static PluginUpdateCheckerRunner _pluginUpdateCheckerRunner;
|
||||
private String _appName;
|
||||
private String _oldVersion;
|
||||
private String _xpi2pURL;
|
||||
|
||||
private static PluginUpdateChecker _instance;
|
||||
public static final synchronized PluginUpdateChecker getInstance(RouterContext ctx) {
|
||||
if (_instance != null)
|
||||
return _instance;
|
||||
_instance = new PluginUpdateChecker(ctx);
|
||||
return _instance;
|
||||
}
|
||||
|
||||
private PluginUpdateChecker(RouterContext ctx) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
/** check all plugins */
|
||||
public void update() {
|
||||
Thread t = new I2PAppThread(new AllCheckerRunner(), "AllAppChecker", true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public class AllCheckerRunner implements Runnable {
|
||||
public void run() {
|
||||
List<String> plugins = PluginStarter.getPlugins();
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
/** check a single plugin */
|
||||
public void update(String appName) {
|
||||
// don't block waiting for the other one to finish
|
||||
if ("true".equals(System.getProperty(PROP_UPDATE_IN_PROGRESS))) {
|
||||
_log.error("Update already running");
|
||||
return;
|
||||
}
|
||||
synchronized (UpdateHandler.class) {
|
||||
Properties props = PluginStarter.pluginProperties(_context, appName);
|
||||
String oldVersion = props.getProperty("version");
|
||||
String xpi2pURL = props.getProperty("updateURL");
|
||||
if (oldVersion == null || xpi2pURL == null) {
|
||||
updateStatus("<b>" + _("Cannot check, plugin {0} is not installed", appName) + "</b>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pluginUpdateCheckerRunner == null)
|
||||
_pluginUpdateCheckerRunner = new PluginUpdateCheckerRunner();
|
||||
if (_pluginUpdateCheckerRunner.isRunning())
|
||||
return;
|
||||
_xpi2pURL = xpi2pURL;
|
||||
_appName = appName;
|
||||
_oldVersion = oldVersion;
|
||||
System.setProperty(PROP_UPDATE_IN_PROGRESS, "true");
|
||||
I2PAppThread update = new I2PAppThread(_pluginUpdateCheckerRunner, "AppChecker", true);
|
||||
update.start();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return _pluginUpdateCheckerRunner != null && _pluginUpdateCheckerRunner.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
// FIXME
|
||||
return false;
|
||||
}
|
||||
|
||||
private void scheduleStatusClean(String msg) {
|
||||
SimpleScheduler.getInstance().addEvent(new Cleaner(msg), 60*60*1000);
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
private String _msg;
|
||||
public Cleaner(String msg) {
|
||||
_msg = msg;
|
||||
}
|
||||
public void timeReached() {
|
||||
if (_msg.equals(getStatus()))
|
||||
updateStatus("");
|
||||
}
|
||||
}
|
||||
|
||||
public class PluginUpdateCheckerRunner extends UpdateRunner implements Runnable, EepGet.StatusListener {
|
||||
ByteArrayOutputStream _baos;
|
||||
|
||||
public PluginUpdateCheckerRunner() {
|
||||
super();
|
||||
_baos = new ByteArrayOutputStream(TrustedUpdate.HEADER_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void update() {
|
||||
updateStatus("<b>" + _("Checking for update of plugin {0}", _appName) + "</b>");
|
||||
// use the same settings as for updater
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
int proxyPort = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT_INT);
|
||||
_baos.reset();
|
||||
try {
|
||||
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _xpi2pURL, TrustedUpdate.HEADER_BYTES);
|
||||
_get.addStatusListener(PluginUpdateCheckerRunner.this);
|
||||
_get.fetch();
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error checking update for plugin", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
String newVersion = TrustedUpdate.getVersionString(new ByteArrayInputStream(_baos.toByteArray()));
|
||||
boolean newer = (new VersionComparator()).compare(newVersion, _oldVersion) > 0;
|
||||
String msg;
|
||||
if (newer)
|
||||
msg = "<b>" + _("New plugin version {0} is available", newVersion) + "</b>";
|
||||
else
|
||||
msg = "<b>" + _("No new version is available for plugin {0}", _appName) + "</b>";
|
||||
updateStatus(msg);
|
||||
scheduleStatusClean(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
File f = new File(_updateFile);
|
||||
f.delete();
|
||||
String msg = "<b>" + _("Update check failed for plugin {0}", _appName) + "</b>";
|
||||
updateStatus(msg);
|
||||
scheduleStatusClean(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|