propagate from branch 'i2p.i2p.unittests' (head 0c5ea65761d9127f160bccb3d1d157f8947ca050)

to branch 'i2p.i2p' (head e36d5669f32ad1a0f66ab84f7f9ff8fa2937680b)
This commit is contained in:
str4d
2012-07-31 21:49:31 +00:00
488 changed files with 98572 additions and 53525 deletions

View File

@@ -7,6 +7,7 @@ trans.da = apps/i2ptunnel/locale/messages_da.po
trans.de = apps/i2ptunnel/locale/messages_de.po
trans.es = apps/i2ptunnel/locale/messages_es.po
trans.fr = apps/i2ptunnel/locale/messages_fr.po
trans.hu = apps/i2ptunnel/locale/messages_hu.po
trans.it = apps/i2ptunnel/locale/messages_it.po
trans.nl = apps/i2ptunnel/locale/messages_nl.po
trans.ru = apps/i2ptunnel/locale/messages_ru.po
@@ -22,10 +23,12 @@ trans.ar = apps/routerconsole/locale/messages_ar.po
trans.cs = apps/routerconsole/locale/messages_cs.po
trans.da = apps/routerconsole/locale/messages_da.po
trans.de = apps/routerconsole/locale/messages_de.po
trans.et_EE = apps/routerconsole/locale/messages_ee.po
trans.el = apps/routerconsole/locale/messages_el.po
trans.es = apps/routerconsole/locale/messages_es.po
trans.et_EE = apps/routerconsole/locale/messages_ee.po
trans.fi = apps/routerconsole/locale/messages_fi.po
trans.fr = apps/routerconsole/locale/messages_fr.po
trans.hu = apps/routerconsole/locale/messages_hu.po
trans.it = apps/routerconsole/locale/messages_it.po
trans.nl = apps/routerconsole/locale/messages_nl.po
trans.pl = apps/routerconsole/locale/messages_pl.po
@@ -43,6 +46,7 @@ trans.cs = apps/i2psnark/locale/messages_cs.po
trans.de = apps/i2psnark/locale/messages_de.po
trans.es = apps/i2psnark/locale/messages_es.po
trans.fr = apps/i2psnark/locale/messages_fr.po
trans.hu = apps/i2psnark/locale/messages_hu.po
trans.it = apps/i2psnark/locale/messages_it.po
trans.nl = apps/i2psnark/locale/messages_nl.po
trans.pl = apps/i2psnark/locale/messages_pl.po
@@ -59,8 +63,10 @@ trans.ar = apps/susidns/locale/messages_ar.po
trans.cs = apps/susidns/locale/messages_cs.po
trans.da = apps/susidns/locale/messages_da.po
trans.de = apps/susidns/locale/messages_de.po
trans.el = apps/susidns/locale/messages_el.po
trans.es = apps/susidns/locale/messages_es.po
trans.fr = apps/susidns/locale/messages_fr.po
trans.hu = apps/susidns/locale/messages_hu.po
trans.it = apps/susidns/locale/messages_it.po
trans.nl = apps/susidns/locale/messages_nl.po
trans.pl = apps/susidns/locale/messages_pl.po
@@ -77,8 +83,10 @@ trans.ar = apps/desktopgui/locale/messages_ar.po
trans.cs = apps/desktopgui/locale/messages_cs.po
trans.da = apps/desktopgui/locale/messages_da.po
trans.de = apps/desktopgui/locale/messages_de.po
trans.el = apps/desktopgui/locale/messages_el.po
trans.es = apps/desktopgui/locale/messages_es.po
trans.fr = apps/desktopgui/locale/messages_fr.po
trans.hu = apps/desktopgui/locale/messages_hu.po
trans.it = apps/desktopgui/locale/messages_it.po
trans.nl = apps/desktopgui/locale/messages_nl.po
trans.pl = apps/desktopgui/locale/messages_pl.po
@@ -95,6 +103,7 @@ trans.cs = apps/susimail/locale/messages_cs.po
trans.de = apps/susimail/locale/messages_de.po
trans.es = apps/susimail/locale/messages_es.po
trans.fr = apps/susimail/locale/messages_fr.po
trans.hu = apps/susimail/locale/messages_hu.po
trans.it = apps/susimail/locale/messages_it.po
trans.nl = apps/susimail/locale/messages_nl.po
trans.ru = apps/susimail/locale/messages_ru.po
@@ -109,7 +118,10 @@ source_file = debian/po/templates.pot
source_lang = en
trans.cs = debian/po/cs.po
trans.de = debian/po/de.po
trans.el = debian/po/el.po
trans.es = debian/po/es.po
trans.it = debian/po/it.po
trans.hu = debian/po/hu.po
trans.pl = debian/po/pl.po
trans.ru = debian/po/ru.po
trans.sv_SE = debian/po/sv.po

View File

@@ -197,6 +197,7 @@ Applications:
- Guernsey and Isle of Man flags from the Open Clip Art Library, released into the public domain
- All other flag icons: public domain, courtesy mjames@gmail.com http://www.famfamfam.com/
Silk icons: See licenses/LICENSE-SilkIcons.txt
FatCow icons: See licenses/LICENSE-FatCowIcons.txt
GeoIP Data:
Copyright (c) 2008 MaxMind, Inc. All Rights Reserved.
@@ -206,6 +207,10 @@ Applications:
"Man with hat over face" & related images licensed under a Creative Commons 2.0 license.
Original photos by Florian Kuhlmann. http://www.flickr.com/photos/floriankuhlmann/3117758155
I2PSnark light theme:
"Creative Commons Cat" licensed under a Creative Commons Attribution 3.0 Unported License.
Original photo by Boaz Arad. http://www.luxphile.com/2011/01/creative-commons-cat.html
SAM:
Public domain.

View File

@@ -115,7 +115,6 @@ import net.i2p.util.SimpleTimer2;
*/
public class BOB {
private final static Log _log = new Log(BOB.class);
public final static String PROP_CONFIG_LOCATION = "BOB.config";
public final static String PROP_BOB_PORT = "BOB.port";
public final static String PROP_BOB_HOST = "BOB.host";
@@ -137,7 +136,7 @@ public class BOB {
*/
public static void info(String arg) {
System.out.println("INFO:" + arg);
_log.info(arg);
(new Log(BOB.class)).info(arg);
}
/**
@@ -147,7 +146,7 @@ public class BOB {
*/
public static void warn(String arg) {
System.out.println("WARNING:" + arg);
_log.warn(arg);
(new Log(BOB.class)).warn(arg);
}
/**
@@ -157,7 +156,7 @@ public class BOB {
*/
public static void error(String arg) {
System.out.println("ERROR: " + arg);
_log.error(arg);
(new Log(BOB.class)).error(arg);
}
/**
@@ -185,6 +184,7 @@ public class BOB {
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
i = Y1.hashCode();
i = Y2.hashCode();
Log _log = new Log(BOB.class);
try {
{
File cfg = new File(configLocation);
@@ -260,6 +260,7 @@ public class BOB {
i = 0;
boolean g = false;
spin.set(true);
try {
info("BOB is now running.");
listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST)));

View File

@@ -54,7 +54,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 = "0F", BEXT = "";
public static final String BMAJ = "00", BMIN = "00", BREV = "10", BEXT = "";
public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
private Socket server;
private Properties props;

View File

@@ -0,0 +1,56 @@
# I2P
# Copyright (C) 2009 The I2P Project
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# Translators:
# <lixtetrax@grhack.net>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2012-07-02 11:28+0000\n"
"Last-Translator: lixtetrax <lixtetrax@grhack.net>\n"
"Language-Team: Greek (http://www.transifex.com/projects/p/I2P/language/el/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: el\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
msgid "Start I2P"
msgstr "Έναρξη Ι2Ρ"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "I2P is starting!"
msgstr "Το Ι2Ρ ξεκίνησε!"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "Starting"
msgstr "Έναρξη"
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
msgid "Launch I2P Browser"
msgstr "Έναρξη φυλλομετρητή Ι2Ρ"
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
msgid "Configure desktopgui"
msgstr "Παραμετροποίηση desktopgui"
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
msgid "Restart I2P"
msgstr "Επανεκκίνηση Ι2Ρ"
#: src/net/i2p/desktopgui/InternalTrayManager.java:85
msgid "Stop I2P"
msgstr "Τερματισμός Ι2Ρ"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
msgid "Tray icon configuration"
msgstr "Παραμετροποίηση εικονιδίου"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
msgid "Should tray icon be enabled?"
msgstr "Ενεργοποίηση εικονιδίου;"

View File

@@ -0,0 +1,55 @@
# I2P
# Copyright (C) 2009 The I2P Project
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2012-06-01 16:28+0000\n"
"Last-Translator: AdminLMH <lehetmashogy@i2pmail.org>\n"
"Language-Team: Hungarian (http://www.transifex.net/projects/p/I2P/language/hu/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: hu\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
msgid "Start I2P"
msgstr "I2P indítása"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "I2P is starting!"
msgstr "I2P indul!"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "Starting"
msgstr "indítás"
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
msgid "Launch I2P Browser"
msgstr "I2P Böngésző Indítása"
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
msgid "Configure desktopgui"
msgstr "Asztali Grafikus Felület Beállítása"
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
msgid "Restart I2P"
msgstr "I2P Újraindítása"
#: src/net/i2p/desktopgui/InternalTrayManager.java:85
msgid "Stop I2P"
msgstr "I2P Leállítása"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
msgid "Tray icon configuration"
msgstr "Tálcaikon beállítása"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
msgid "Should tray icon be enabled?"
msgstr "Tálcaikon engedélyezve legyen?"

View File

@@ -3,15 +3,17 @@
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# Translators:
# <bovas85@gmail.com>, 2012.
# <jokjok@hotmail.it>, 2011.
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2011-06-09 17:09+0000\n"
"Last-Translator: mkkid <jokjok@hotmail.it>\n"
"Language-Team: Italian (http://www.transifex.net/projects/p/I2P/team/it/)\n"
"PO-Revision-Date: 2012-06-01 12:21+0000\n"
"Last-Translator: Leelium <bovas85@gmail.com>\n"
"Language-Team: Italian (http://www.transifex.net/projects/p/I2P/language/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -48,10 +50,8 @@ msgstr "Ferma I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
msgid "Tray icon configuration"
msgstr "Configurazione dell'icona nell'Area di notifica"
msgstr "Configurazione dell'icona nell'area di notifica"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
msgid "Should tray icon be enabled?"
msgstr "Vuoi che l'icona nell'Area di notifica venga abilitata?"
msgstr "Vuoi che l'icona nelll'rea di notifica venga abilitata?"

View File

@@ -3,7 +3,8 @@
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# 123hund123 <M8R-ra4r1r@mailinator.com>, 2011
# Translators:
# 123hund123 <M8R-ra4r1r@mailinator.com>, 2011.
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
@@ -11,7 +12,7 @@ msgstr ""
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2011-03-22 15:49+0000\n"
"Last-Translator: 123hund123 <M8R-ra4r1r@mailinator.com>\n"
"Language-Team: Swedish (Sweden) (http://www.transifex.net/projects/p/I2P/team/sv_SE/)\n"
"Language-Team: Swedish (Sweden) (http://www.transifex.net/projects/p/I2P/language/sv_SE/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -53,5 +54,3 @@ msgstr "Ikonpanelskonfiguration"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
msgid "Should tray icon be enabled?"
msgstr "Ska ikonpanelen vara aktiverad?"

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

View File

@@ -57,7 +57,7 @@
<target name="jar" depends="builddep, compile, jarUpToDate, listChangedFiles" unless="jar.uptodate" >
<!-- set if unset -->
<property name="workspace.changes.tr" value="" />
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/messages_*.class">
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/FetchAndAdd*.class **/messages_*.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
@@ -72,7 +72,7 @@
<target name="jarUpToDate">
<uptodate property="jar.uptodate" targetfile="build/i2psnark.jar" >
<srcfiles dir= "build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/messages_*.class" />
<srcfiles dir= "build/obj" includes="**/*.class" excludes="**/I2PSnarkServlet*.class **/FetchAndAdd*.class **/messages_*.class" />
</uptodate>
<condition property="shouldListChanges" >
<and>

View File

@@ -29,8 +29,12 @@ import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.Hash;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Accepts connections on a TCP port and routes them to sub-acceptors.
@@ -41,11 +45,15 @@ public class ConnectionAcceptor implements Runnable
private I2PServerSocket serverSocket;
private PeerAcceptor peeracceptor;
private Thread thread;
private I2PSnarkUtil _util;
private final I2PSnarkUtil _util;
private final ObjectCounter<Hash> _badCounter = new ObjectCounter();
private boolean stop;
private boolean socketChanged;
private static final int MAX_BAD = 2;
private static final long BAD_CLEAN_INTERVAL = 30*60*1000;
public ConnectionAcceptor(I2PSnarkUtil util) { _util = util; }
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
@@ -59,6 +67,7 @@ public class ConnectionAcceptor implements Runnable
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
}
}
}
@@ -70,11 +79,10 @@ public class ConnectionAcceptor implements Runnable
this.peeracceptor = peeracceptor;
_util = util;
socketChanged = false;
stop = false;
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), BAD_CLEAN_INTERVAL);
}
public void halt()
@@ -142,6 +150,12 @@ public class ConnectionAcceptor implements Runnable
try { socket.close(); } catch (IOException ioe) {}
continue;
}
if (_badCounter.count(socket.getPeerDestination().calculateHash()) >= MAX_BAD) {
if (_log.shouldLog(Log.WARN))
_log.warn("Rejecting connection from " + socket.getPeerDestination().calculateHash() + " after " + MAX_BAD + " failures");
try { socket.close(); } catch (IOException ioe) {}
continue;
}
Thread t = new I2PAppThread(new Handler(socket), "I2PSnark incoming connection");
t.start();
}
@@ -171,10 +185,12 @@ public class ConnectionAcceptor implements Runnable
}
private class Handler implements Runnable {
private I2PSocket _socket;
private final I2PSocket _socket;
public Handler(I2PSocket socket) {
_socket = socket;
}
public void run() {
try {
InputStream in = _socket.getInputStream();
@@ -182,13 +198,23 @@ public class ConnectionAcceptor implements Runnable
// this is for the readahead in PeerAcceptor.connection()
in = new BufferedInputStream(in);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash());
peeracceptor.connection(_socket, in, out);
} catch (PeerAcceptor.ProtocolException ihe) {
_badCounter.increment(_socket.getPeerDestination().calculateHash());
if (_log.shouldLog(Log.INFO))
_log.info("Protocol error from " + _socket.getPeerDestination().calculateHash(), ihe);
try { _socket.close(); } catch (IOException ignored) { }
} catch (IOException ioe) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash(), ioe);
try { _socket.close(); } catch (IOException ignored) { }
}
}
}
/** @since 0.9.1 */
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() { _badCounter.clear(); }
}
}

View File

@@ -24,7 +24,7 @@ package org.klomp.snark;
/**
* Callback used when some peer changes state.
*/
public interface CoordinatorListener
interface CoordinatorListener
{
/**
* Called when the PeerCoordinator notices a change in the state of a peer.

View File

@@ -310,7 +310,10 @@ abstract class ExtensionHandler {
BDecoder dec = new BDecoder(is);
BEValue bev = dec.bdecodeMap();
Map<String, BEValue> map = bev.getMap();
byte[] ids = map.get("added").getBytes();
bev = map.get("added");
if (bev == null)
return;
byte[] ids = bev.getBytes();
if (ids.length < HASH_LENGTH)
return;
int len = Math.min(ids.length, (I2PSnarkUtil.MAX_CONNECTIONS - 1) * HASH_LENGTH);

View File

@@ -3,6 +3,7 @@ package org.klomp.snark;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -20,6 +21,7 @@ import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketEepGet;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.Base32;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@@ -53,8 +55,9 @@ public class I2PSnarkUtil {
private String _i2cpHost;
private int _i2cpPort;
private final Map<String, String> _opts;
private I2PSocketManager _manager;
private volatile I2PSocketManager _manager;
private boolean _configured;
private volatile boolean _connecting;
private final Set<Hash> _shitlist;
private int _maxUploaders;
private int _maxUpBW;
@@ -63,9 +66,11 @@ public class I2PSnarkUtil {
private int _startupDelay;
private boolean _shouldUseOT;
private boolean _areFilesPublic;
private String _openTrackerString;
private List<String> _openTrackers;
private DHT _dht;
private static final int EEPGET_CONNECT_TIMEOUT = 45*1000;
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
public static final int DEFAULT_STARTUP_DELAY = 3;
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
@@ -87,6 +92,8 @@ public class I2PSnarkUtil {
_maxConnections = MAX_CONNECTIONS;
_startupDelay = DEFAULT_STARTUP_DELAY;
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
// FIXME split if default has more than one
_openTrackers = Collections.singletonList(DEFAULT_OPENTRACKERS);
// This is used for both announce replies and .torrent file downloads,
// so it must be available even if not connected to I2CP.
// so much for multiple instances
@@ -115,6 +122,9 @@ public class I2PSnarkUtil {
}
******/
/** @since 0.9.1 */
public I2PAppContext getContext() { return _context; }
public boolean configured() { return _configured; }
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
@@ -184,11 +194,15 @@ public class I2PSnarkUtil {
/** @since 0.8.9 */
public void setFilesPublic(boolean yes) { _areFilesPublic = yes; }
/** @since 0.9.1 */
public File getTempDir() { return _tmpDir; }
/**
* Connect to the router, if we aren't already
*/
synchronized public boolean connect() {
if (_manager == null) {
_connecting = true;
// try to find why reconnecting after stop
if (_log.shouldLog(Log.DEBUG))
_log.debug("Connecting to I2P", new Exception("I did it"));
@@ -207,6 +221,8 @@ public class I2PSnarkUtil {
// we don't need fast handshake for peer connections.
//if (opts.getProperty("i2p.streaming.connectDelay") == null)
// opts.setProperty("i2p.streaming.connectDelay", "500");
if (opts.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
opts.setProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT, "75000");
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
@@ -222,8 +238,11 @@ public class I2PSnarkUtil {
if (opts.getProperty("i2p.streaming.maxConnsPerMinute") == null)
opts.setProperty("i2p.streaming.maxConnsPerMinute", "2");
if (opts.getProperty("i2p.streaming.maxTotalConnsPerMinute") == null)
opts.setProperty("i2p.streaming.maxTotalConnsPerMinute", "6");
opts.setProperty("i2p.streaming.maxTotalConnsPerMinute", "8");
if (opts.getProperty("i2p.streaming.maxConnsPerHour") == null)
opts.setProperty("i2p.streaming.maxConnsPerHour", "20");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
_connecting = false;
}
// FIXME this only instantiates krpc once, left stuck with old manager
//if (ENABLE_DHT && _manager != null && _dht == null)
@@ -239,6 +258,18 @@ public class I2PSnarkUtil {
public boolean connected() { return _manager != null; }
/** @since 0.9.1 */
public boolean isConnecting() { return _manager == null && _connecting; }
/**
* For FetchAndAdd
* @return null if not connected
* @since 0.9.1
*/
public I2PSocketManager getSocketManager() {
return _manager;
}
/**
* Destroy the destination itself
*/
@@ -247,7 +278,11 @@ public class I2PSnarkUtil {
// FIXME this can cause race NPEs elsewhere
_manager = null;
_shitlist.clear();
mgr.destroySocketManager();
if (mgr != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Disconnecting from I2P", new Exception("I did it"));
mgr.destroySocketManager();
}
// this will delete a .torrent file d/l in progress so don't do that...
FileUtil.rmdir(_tmpDir, false);
// in case the user will d/l a .torrent file next...
@@ -286,11 +321,24 @@ public class I2PSnarkUtil {
}
/**
* fetch the given URL, returning the file it is stored in, or null on error
* Fetch the given URL, returning the file it is stored in, or null on error.
* No retries.
*/
public File get(String url) { return get(url, true, 0); }
/**
* @param rewrite if true, convert http://KEY.i2p/foo/announce to http://i2p/KEY/foo/announce
*/
public File get(String url, boolean rewrite) { return get(url, rewrite, 0); }
/**
* @param retries if < 0, set timeout to a few seconds
*/
public File get(String url, int retries) { return get(url, true, retries); }
/**
* @param retries if < 0, set timeout to a few seconds
*/
public File get(String url, boolean rewrite, int retries) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
@@ -299,7 +347,7 @@ public class I2PSnarkUtil {
// we could use the system tmp dir but deleteOnExit() doesn't seem to work on all platforms...
out = SecureFile.createTempFile("i2psnark", null, _tmpDir);
} catch (IOException ioe) {
ioe.printStackTrace();
_log.error("temp file error", ioe);
if (out != null)
out.delete();
return null;
@@ -311,12 +359,21 @@ public class I2PSnarkUtil {
//_log.debug("Rewritten url [" + fetchURL + "]");
//EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, retries, out.getAbsolutePath(), fetchURL);
// Use our tunnel for announces and .torrent fetches too! Make sure we're connected first...
if (!connected()) {
if (!connect())
int timeout;
if (retries < 0) {
if (!connected())
return null;
timeout = EEPGET_CONNECT_TIMEOUT_SHORT;
retries = 0;
} else {
timeout = EEPGET_CONNECT_TIMEOUT;
if (!connected()) {
if (!connect())
return null;
}
}
EepGet get = new I2PSocketEepGet(_context, _manager, retries, out.getAbsolutePath(), fetchURL);
if (get.fetch()) {
if (get.fetch(timeout)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
return out;
@@ -445,30 +502,17 @@ public class I2PSnarkUtil {
}
/** @param ot non-null */
public void setOpenTrackerString(String ot) {
_openTrackerString = ot;
public void setOpenTrackers(List<String> ot) {
_openTrackers = ot;
}
public String getOpenTrackerString() {
if (_openTrackerString == null)
return DEFAULT_OPENTRACKERS;
return _openTrackerString;
}
/** comma delimited list open trackers to use as backups */
/** sorted map of name to announceURL=baseURL */
/** List of open trackers to use as backups
* @return non-null, possibly unmodifiable, empty if disabled
*/
public List<String> getOpenTrackers() {
if (!shouldUseOpenTrackers())
return null;
List<String> rv = new ArrayList(1);
String trackers = getOpenTrackerString();
StringTokenizer tok = new StringTokenizer(trackers, ", ");
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
if (rv.isEmpty())
return null;
return rv;
return Collections.EMPTY_LIST;
return _openTrackers;
}
public void setUseOpenTrackers(boolean yes) {

View File

@@ -5,10 +5,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.RandomSource;
import org.klomp.snark.bencode.BDecoder;
import org.klomp.snark.bencode.BEValue;
@@ -27,7 +27,6 @@ import org.klomp.snark.bencode.BEValue;
*/
class MagnetState {
public static final int CHUNK_SIZE = 16*1024;
private static final Random random = I2PAppContext.getGlobalContext().random();
private final byte[] infohash;
private boolean complete;
@@ -129,7 +128,7 @@ class MagnetState {
throw new IllegalArgumentException("not initialized");
if (complete)
throw new IllegalArgumentException("complete");
int rand = random.nextInt(totalChunks);
int rand = RandomSource.getInstance().nextInt(totalChunks);
for (int i = 0; i < totalChunks; i++) {
int chk = (i + rand) % totalChunks;
if (!(have.get(chk) || requested.get(chk))) {

View File

@@ -422,6 +422,29 @@ public class MetaInfo
return false;
return true;
}
/**
* @return good
* @since 0.9.1
*/
boolean checkPiece(PartialPiece pp) {
MessageDigest sha1 = SHA1.getInstance();
int piece = pp.getPiece();
byte[] hash;
try {
hash = pp.getHash();
} catch (IOException ioe) {
// Could be caused by closing a peer connnection
// we don't want the exception to propagate through
// to Storage.putPiece()
_log.warn("Error checking", ioe);
return false;
}
for (int i = 0; i < 20; i++)
if (hash[i] != piece_hashes[20 * piece + i])
return false;
return true;
}
/**
* Returns the total length of the torrent in bytes.

View File

@@ -1,6 +1,22 @@
package org.klomp.snark;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA1;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
/**
* Store the received data either on the heap or in a temp file.
* The third option, to write chunks directly to the destination file,
* is unimplemented.
*
* This is the class passed from PeerCoordinator to PeerState so
* PeerState may start requests.
*
@@ -8,45 +24,81 @@ package org.klomp.snark;
* a piece is not completely downloaded, for example
* when the Peer disconnects or chokes.
*
* New objects for the same piece are created during the end game -
* this object should not be shared among multiple peers.
*
* @since 0.8.2
*/
class PartialPiece implements Comparable {
private final int piece;
// we store the piece so we can use it in compareTo()
private final Piece piece;
// null if using temp file
private final byte[] bs;
private final int off;
private final long createdTime;
private int off;
//private final long createdTime;
private File tempfile;
private RandomAccessFile raf;
private final int pclen;
private final File tempDir;
// Any bigger than this, use temp file instead of heap
private static final int MAX_IN_MEM = 128 * 1024;
// May be reduced on OOM
private static int _max_in_mem = MAX_IN_MEM;
/**
* Used by PeerCoordinator.
* Creates a new PartialPiece, with no chunks yet downloaded.
* Allocates the data.
* Allocates the data storage area, either on the heap or in the
* temp directory, depending on size.
*
* @param piece Piece number requested.
* @param len must be equal to the piece length
*/
public PartialPiece (int piece, int len) throws OutOfMemoryError {
public PartialPiece (Piece piece, int len, File tempDir) {
this.piece = piece;
this.bs = new byte[len];
this.off = 0;
this.createdTime = 0;
this.pclen = len;
//this.createdTime = 0;
this.tempDir = tempDir;
// temps for finals
byte[] tbs = null;
try {
if (len <= MAX_IN_MEM) {
try {
tbs = new byte[len];
return;
} catch (OutOfMemoryError oom) {
if (_max_in_mem > PeerState.PARTSIZE)
_max_in_mem /= 2;
Log log = I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class);
log.logAlways(Log.WARN, "OOM creating new partial piece");
// fall through to use temp file
}
}
// delay creating temp file until required in read()
} finally {
// finals
this.bs = tbs;
}
}
/**
* Used by PeerState.
* Creates a new PartialPiece, with chunks up to but not including
* firstOutstandingRequest already downloaded and stored in the Request byte array.
* Caller must synchronize
*
* Note that this cannot handle gaps; chunks after a missing chunk cannot be saved.
* That would be harder.
*
* @param firstOutstandingRequest the first request not fulfilled for the piece
* @since 0.9.1
*/
public PartialPiece (Request firstOutstandingRequest) {
this.piece = firstOutstandingRequest.piece;
this.bs = firstOutstandingRequest.bs;
this.off = firstOutstandingRequest.off;
this.createdTime = System.currentTimeMillis();
private void createTemp() throws IOException {
//tfile = SecureFile.createTempFile("piece", null, tempDir);
// debug
tempfile = SecureFile.createTempFile("piece_" + piece.getId() + '_', null, tempDir);
//I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Created " + tempfile);
// tfile.deleteOnExit() ???
raf = new RandomAccessFile(tempfile, "rw");
// Do not preallocate the file space.
// Not necessary to call setLength(), file is extended when written
//traf.setLength(len);
}
/**
@@ -55,33 +107,168 @@ class PartialPiece implements Comparable {
*/
public Request getRequest() {
return new Request(this.piece, this.bs, this.off, Math.min(this.bs.length - this.off, PeerState.PARTSIZE));
return new Request(this, this.off, Math.min(this.pclen - this.off, PeerState.PARTSIZE));
}
/** piece number */
public int getPiece() {
return this.piece;
return this.piece.getId();
}
/** how many bytes are good */
/**
* @since 0.9.1
*/
public int getLength() {
return this.pclen;
}
/**
* How many bytes are good - only valid by setDownloaded()
*/
public int getDownloaded() {
return this.off;
}
/**
* Call this before returning a PartialPiece to the PeerCoordinator
* @since 0.9.1
*/
public void setDownloaded(int offset) {
this.off = offset;
}
/****
public long getCreated() {
return this.createdTime;
}
****/
/**
* Highest downloaded first
* Piece must be complete.
* The SHA1 hash of the completely read data.
* @since 0.9.1
*/
public byte[] getHash() throws IOException {
MessageDigest sha1 = SHA1.getInstance();
if (bs != null) {
sha1.update(bs);
} else {
int read = 0;
byte[] buf = new byte[Math.min(pclen, 16384)];
synchronized (this) {
if (raf == null)
throw new IOException();
raf.seek(0);
while (read < pclen) {
int rd = raf.read(buf, 0, Math.min(buf.length, pclen - read));
if (rd < 0)
break;
read += rd;
sha1.update(buf, 0, rd);
}
}
if (read < pclen)
throw new IOException();
}
return sha1.digest();
}
/**
* Blocking.
* @since 0.9.1
*/
public void read(DataInputStream din, int off, int len) throws IOException {
if (bs != null) {
din.readFully(bs, off, len);
} else {
// read in fully before synching on raf
byte[] tmp = new byte[len];
din.readFully(tmp);
synchronized (this) {
if (raf == null)
createTemp();
raf.seek(off);
raf.write(tmp);
}
}
}
/**
* Piece must be complete.
* Caller must synchronize on out and seek to starting point.
* Caller must call release() when done with the whole piece.
*
* @param out stream to write to
* @param offset offset in the piece
* @param len length to write
* @since 0.9.1
*/
public void write(DataOutput out, int offset, int len) throws IOException {
if (bs != null) {
out.write(bs, offset, len);
} else {
int read = 0;
byte[] buf = new byte[Math.min(len, 16384)];
synchronized (this) {
if (raf == null)
throw new IOException();
raf.seek(offset);
while (read < len) {
int rd = Math.min(buf.length, len - read);
raf.readFully(buf, 0, rd);
read += rd;
out.write(buf, 0, rd);
}
}
}
}
/**
* Release all resources.
*
* @since 0.9.1
*/
public void release() {
if (bs == null) {
synchronized (this) {
if (raf != null)
locked_release();
}
//if (raf != null)
// I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Released " + tempfile);
}
}
/**
* Caller must synchronize
*
* @since 0.9.1
*/
private void locked_release() {
try {
raf.close();
} catch (IOException ioe) {
I2PAppContext.getGlobalContext().logManager().getLog(PartialPiece.class).warn("Error closing " + raf, ioe);
}
tempfile.delete();
}
/*
* Highest priority first,
* then rarest first,
* then highest downloaded first
*/
public int compareTo(Object o) throws ClassCastException {
return ((PartialPiece)o).off - this.off; // reverse
PartialPiece opp = (PartialPiece)o;
int d = this.piece.compareTo(opp.piece);
if (d != 0)
return d;
return opp.off - this.off; // reverse
}
@Override
public int hashCode() {
return piece * 7777;
return piece.getId() * 7777;
}
/**
@@ -92,13 +279,13 @@ class PartialPiece implements Comparable {
public boolean equals(Object o) {
if (o instanceof PartialPiece) {
PartialPiece pp = (PartialPiece)o;
return pp.piece == this.piece;
return pp.piece.getId() == this.piece.getId();
}
return false;
}
@Override
public String toString() {
return "Partial(" + piece + ',' + off + ',' + bs.length + ')';
return "Partial(" + piece.getId() + ',' + off + ',' + pclen + ')';
}
}

View File

@@ -460,7 +460,7 @@ public class Peer implements Comparable
if (this.deregister) {
PeerListener p = s.listener;
if (p != null) {
List<PartialPiece> pcs = s.returnPartialPieces();
List<Request> pcs = s.returnPartialPieces();
if (!pcs.isEmpty())
p.savePartialPieces(this, pcs);
// now covered by savePartialPieces

View File

@@ -46,6 +46,10 @@ public class PeerAcceptor
private final PeerCoordinator coordinator;
final PeerCoordinatorSet coordinators;
/** shorten timeout while reading handshake */
private static final long HASH_READ_TIMEOUT = 45*1000;
public PeerAcceptor(PeerCoordinator coordinator)
{
this.coordinator = coordinator;
@@ -69,11 +73,20 @@ public class PeerAcceptor
// talk about, and we can just look for that in our list of active torrents.
byte peerInfoHash[] = null;
if (in instanceof BufferedInputStream) {
// multitorrent
in.mark(LOOKAHEAD_SIZE);
peerInfoHash = readHash(in);
long timeout = socket.getReadTimeout();
socket.setReadTimeout(HASH_READ_TIMEOUT);
try {
peerInfoHash = readHash(in);
} catch (IOException ioe) {
// unique exception so ConnectionAcceptor can blame the peer
throw new ProtocolException(ioe.toString());
}
socket.setReadTimeout(timeout);
in.reset();
} else {
// is this working right?
// Single torrent - is this working right?
try {
peerInfoHash = readHash(in);
if (_log.shouldLog(Log.INFO))
@@ -130,22 +143,50 @@ public class PeerAcceptor
}
}
private static final String PROTO_STR = "BitTorrent protocol";
private static final int PROTO_STR_LEN = PROTO_STR.length();
private static final int PROTO_LEN = PROTO_STR_LEN + 1;
private static final int[] PROTO = new int[PROTO_LEN];
static {
PROTO[0] = PROTO_STR_LEN;
for (int i = 0; i < PROTO_STR_LEN; i++) {
PROTO[i+1] = PROTO_STR.charAt(i);
}
}
/** 48 */
private static final int LOOKAHEAD_SIZE = 1 + // chr(19)
"BitTorrent protocol".length() +
private static final int LOOKAHEAD_SIZE = PROTO_LEN +
8 + // blank, reserved
20; // infohash
/**
* Read ahead to the infohash, throwing an exception if there isn't enough data
* Read ahead to the infohash, throwing an exception if there isn't enough data.
* Also check the first 20 bytes for the correct protocol here and throw IOE if bad,
* so we don't hang waiting for 48 bytes if it's not a bittorrent client.
* The 20 bytes are checked again in Peer.handshake().
*/
private byte[] readHash(InputStream in) throws IOException {
byte buf[] = new byte[LOOKAHEAD_SIZE];
private static byte[] readHash(InputStream in) throws IOException {
for (int i = 0; i < PROTO_LEN; i++) {
int b = in.read();
if (b != PROTO[i])
throw new IOException("Bad protocol 0x" + Integer.toHexString(b) + " at byte " + i);
}
if (in.skip(8) != 8)
throw new IOException("EOF before hash");
byte buf[] = new byte[20];
int read = DataHelper.read(in, buf);
if (read != buf.length)
throw new IOException("Unable to read the hash (read " + read + ")");
byte rv[] = new byte[20];
System.arraycopy(buf, buf.length-rv.length, rv, 0, rv.length);
return rv;
return buf;
}
/**
* A unique exception so we can tell the ConnectionAcceptor about non-BT connections
* @since 0.9.1
*/
public static class ProtocolException extends IOException {
public ProtocolException(String s) {
super(s);
}
}
}

View File

@@ -25,6 +25,8 @@ import java.util.List;
import java.util.Random;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
/**
* TimerTask that checks for good/bad up/downloader. Works together
@@ -36,16 +38,18 @@ class PeerCheckerTask implements Runnable
private final PeerCoordinator coordinator;
private final I2PSnarkUtil _util;
private final Log _log;
private final Random random;
private int _runCount;
PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
{
_util = util;
_log = util.getContext().logManager().getLog(PeerCheckerTask.class);
random = util.getContext().random();
this.coordinator = coordinator;
}
private static final Random random = I2PAppContext.getGlobalContext().random();
public void run()
{
_runCount++;
@@ -82,6 +86,14 @@ class PeerCheckerTask implements Runnable
continue;
}
if (peer.getInactiveTime() > PeerCoordinator.MAX_INACTIVE) {
if (_log.shouldLog(Log.WARN))
_log.warn("Disconnecting peer idle " +
DataHelper.formatDuration(peer.getInactiveTime()) + ": " + peer);
peer.disconnect();
continue;
}
if (!peer.isChoking())
uploaders++;
@@ -92,14 +104,15 @@ class PeerCheckerTask implements Runnable
peer.setRateHistory(upload, download);
peer.resetCounters();
_util.debug(peer + ":", Snark.DEBUG);
_util.debug(" ul: " + upload*1024/KILOPERSECOND
if (_log.shouldLog(Log.DEBUG)) {
_log.debug(peer + ":"
+ " ul: " + upload*1024/KILOPERSECOND
+ " dl: " + download*1024/KILOPERSECOND
+ " i: " + peer.isInterested()
+ " I: " + peer.isInteresting()
+ " c: " + peer.isChoking()
+ " C: " + peer.isChoked(),
Snark.DEBUG);
+ " C: " + peer.isChoked());
}
// Choke a percentage of them rather than all so it isn't so drastic...
// unless this torrent is over the limit all by itself.
@@ -120,8 +133,8 @@ class PeerCheckerTask implements Runnable
// Check if it still wants pieces from us.
if (!peer.isInterested())
{
_util.debug("Choke uninterested peer: " + peer,
Snark.INFO);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Choke uninterested peer: " + peer);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -131,8 +144,8 @@ class PeerCheckerTask implements Runnable
}
else if (overBWLimitChoke)
{
_util.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
Snark.INFO);
if (_log.shouldLog(Log.DEBUG))
_log.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -144,7 +157,8 @@ class PeerCheckerTask implements Runnable
else if (peer.isInteresting() && peer.isChoked())
{
// If they are choking us make someone else a downloader
_util.debug("Choke choking peer: " + peer, Snark.DEBUG);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Choke choking peer: " + peer);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -156,7 +170,8 @@ class PeerCheckerTask implements Runnable
else if (!peer.isInteresting() && !coordinator.completed())
{
// If they aren't interesting make someone else a downloader
_util.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Choke uninteresting peer: " + peer);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -170,8 +185,8 @@ class PeerCheckerTask implements Runnable
&& download == 0)
{
// We are downloading but didn't receive anything...
_util.debug("Choke downloader that doesn't deliver:"
+ peer, Snark.DEBUG);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Choke downloader that doesn't deliver: " + peer);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -198,7 +213,10 @@ class PeerCheckerTask implements Runnable
// send PEX
if ((_runCount % 17) == 0 && !peer.isCompleted())
coordinator.sendPeers(peer);
peer.keepAlive();
// cheap failsafe for seeds connected to seeds, stop pinging and hopefully
// the inactive checker (above) will eventually disconnect it
if (coordinator.getNeededLength() > 0 || !peer.isCompleted())
peer.keepAlive();
// announce them to local tracker (TrackerClient does this too)
if (_util.getDHT() != null && (_runCount % 5) == 0) {
_util.getDHT().announce(coordinator.getInfoHash(), peer.getPeerID().getDestHash());
@@ -215,8 +233,8 @@ class PeerCheckerTask implements Runnable
|| uploaders > uploadLimit)
&& worstDownloader != null)
{
_util.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Choke worst downloader: " + worstDownloader);
worstDownloader.setChoking(true);
coordinator.uploaders--;

View File

@@ -148,11 +148,9 @@ class PeerConnectionIn implements Runnable
begin = din.readInt();
len = i-9;
Request req = ps.getOutstandingRequest(piece, begin, len);
byte[] piece_bytes;
if (req != null)
{
piece_bytes = req.bs;
din.readFully(piece_bytes, begin, len);
req.read(din);
ps.pieceMessage(req);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received data(" + piece + "," + begin + ") from " + peer);
@@ -160,8 +158,9 @@ class PeerConnectionIn implements Runnable
else
{
// XXX - Consume but throw away afterwards.
piece_bytes = new byte[len];
din.readFully(piece_bytes);
int rcvd = din.skipBytes(len);
if (rcvd != len)
throw new IOException("EOF reading unwanted data");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
}

View File

@@ -395,7 +395,7 @@ class PeerConnectionOut implements Runnable
while (it.hasNext())
{
Message m = (Message)it.next();
if (m.type == Message.REQUEST && m.piece == req.piece &&
if (m.type == Message.REQUEST && m.piece == req.getPiece() &&
m.begin == req.off && m.length == req.len)
{
if (_log.shouldLog(Log.DEBUG))
@@ -406,7 +406,7 @@ class PeerConnectionOut implements Runnable
}
Message m = new Message();
m.type = Message.REQUEST;
m.piece = req.piece;
m.piece = req.getPiece();
m.begin = req.off;
m.length = req.len;
addMessage(m);
@@ -492,7 +492,7 @@ class PeerConnectionOut implements Runnable
{
Message m = (Message)it.next();
if (m.type == Message.REQUEST
&& m.piece == req.piece
&& m.piece == req.getPiece()
&& m.begin == req.off
&& m.length == req.len)
it.remove();
@@ -502,7 +502,7 @@ class PeerConnectionOut implements Runnable
// Always send, just to be sure it it is really canceled.
Message m = new Message();
m.type = Message.CANCEL;
m.piece = req.piece;
m.piece = req.getPiece();
m.begin = req.off;
m.length = req.len;
addMessage(m);

View File

@@ -22,6 +22,7 @@ package org.klomp.snark;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@@ -48,7 +49,7 @@ import org.klomp.snark.dht.DHT;
/**
* Coordinates what peer does what.
*/
public class PeerCoordinator implements PeerListener
class PeerCoordinator implements PeerListener
{
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
@@ -68,6 +69,7 @@ public class PeerCoordinator implements PeerListener
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 40*1000; // 40 seconds
final static int MAX_UPLOADERS = 6;
public static final long MAX_INACTIVE = 8*60*1000;
/**
* Approximation of the number of current uploaders.
@@ -116,15 +118,21 @@ public class PeerCoordinator implements PeerListener
*/
private final List<Piece> wantedPieces;
/** partial pieces - lock by synching on wantedPieces */
/** The total number of bytes in wantedPieces, or -1 if not yet known.
* Sync on wantedPieces.
* @since 0.9.1
*/
private long wantedBytes;
/** partial pieces - lock by synching on wantedPieces - TODO store Requests, not PartialPieces */
private final List<PartialPiece> partialPieces;
private boolean halted = false;
private volatile boolean halted;
private final MagnetState magnetState;
private final CoordinatorListener listener;
private final I2PSnarkUtil _util;
private static final Random _random = I2PAppContext.getGlobalContext().random();
private final Random _random;
/**
* @param metainfo null if in magnet mode
@@ -134,6 +142,7 @@ public class PeerCoordinator implements PeerListener
CoordinatorListener listener, Snark torrent)
{
_util = util;
_random = util.getContext().random();
this.id = id;
this.infohash = infohash;
this.metainfo = metainfo;
@@ -171,16 +180,22 @@ public class PeerCoordinator implements PeerListener
}
}
// only called externally from Storage after the double-check fails
/**
* Only called externally from Storage after the double-check fails.
* Sets wantedBytes too.
*/
public void setWantedPieces()
{
if (metainfo == null || storage == null)
if (metainfo == null || storage == null) {
wantedBytes = -1;
return;
}
// Make a list of pieces
synchronized(wantedPieces) {
wantedPieces.clear();
BitField bitfield = storage.getBitField();
int[] pri = storage.getPiecePriorities();
long count = 0;
for (int i = 0; i < metainfo.getPieces(); i++) {
// only add if we don't have and the priority is >= 0
if ((!bitfield.get(i)) &&
@@ -189,8 +204,10 @@ public class PeerCoordinator implements PeerListener
if (pri != null)
p.setPriority(pri[i]);
wantedPieces.add(p);
count += metainfo.getPieceLength(i);
}
}
wantedBytes = count;
Collections.shuffle(wantedPieces, _random);
}
}
@@ -233,7 +250,9 @@ public class PeerCoordinator implements PeerListener
}
/**
* Returns how many bytes are still needed to get the complete file.
* Bytes not yet in storage. Does NOT account for skipped files.
* Not exact (does not adjust for last piece size).
* Returns how many bytes are still needed to get the complete torrent.
* @return -1 if in magnet mode
*/
public long getLeft()
@@ -244,6 +263,15 @@ public class PeerCoordinator implements PeerListener
return ((long) storage.needed()) * metainfo.getPieceLength(0);
}
/**
* Bytes still wanted. DOES account for skipped files.
* @return exact value. or -1 if no storage yet.
* @since 0.9.1
*/
public long getNeededLength() {
return wantedBytes;
}
/**
* Returns the total number of uploaded bytes of all peers.
*/
@@ -330,14 +358,32 @@ public class PeerCoordinator implements PeerListener
return infohash;
}
/**
* Inbound.
* Not halted, peers < max.
* @since 0.9.1
*/
public boolean needPeers()
{
return !halted && peers.size() < getMaxConnections();
}
/**
* Outbound.
* Not halted, peers < max, and need pieces.
* @since 0.9.1
*/
public boolean needOutboundPeers() {
//return wantedBytes != 0 && needPeers();
// minus one to make it a little easier for new peers to get in on large swarms
return wantedBytes != 0 && !halted && peers.size() < getMaxConnections() - 1;
}
/**
* Reduce max if huge pieces to keep from ooming when leeching
* @return 512K: 16; 1M: 11; 2M: 6
* Formerly used to
* reduce max if huge pieces to keep from ooming when leeching
* but now we don't
* @return usually 16
*/
private int getMaxConnections() {
if (metainfo == null)
@@ -347,13 +393,14 @@ public class PeerCoordinator implements PeerListener
return 4;
if (pieces <= 5)
return 6;
int size = metainfo.getPieceLength(0);
//int size = metainfo.getPieceLength(0);
int max = _util.getMaxConnections();
if (size <= 512*1024 || completed())
// Now that we use temp files, no memory concern
//if (size <= 512*1024 || completed())
return max;
if (size <= 1024*1024)
return (max + max + 2) / 3;
return (max + 2) / 3;
//if (size <= 1024*1024)
// return (max + max + 2) / 3;
//return (max + 2) / 3;
}
public boolean halted() { return halted; }
@@ -380,10 +427,27 @@ public class PeerCoordinator implements PeerListener
}
// delete any saved orphan partial piece
synchronized (partialPieces) {
for (PartialPiece pp : partialPieces) {
pp.release();
}
partialPieces.clear();
}
}
/**
* @since 0.9.1
*/
public void restart() {
halted = false;
synchronized (uploaded_old) {
Arrays.fill(uploaded_old, 0);
}
synchronized (downloaded_old) {
Arrays.fill(downloaded_old, 0);
}
timer.schedule((CHECK_PERIOD / 2) + _random.nextInt((int) CHECK_PERIOD));
}
public void connected(Peer peer)
{
if (halted)
@@ -396,7 +460,7 @@ public class PeerCoordinator implements PeerListener
synchronized(peers)
{
Peer old = peerIDInList(peer.getPeerID(), peers);
if ( (old != null) && (old.getInactiveTime() > 8*60*1000) ) {
if ( (old != null) && (old.getInactiveTime() > MAX_INACTIVE) ) {
// idle for 8 minutes, kill the old con (32KB/8min = 68B/sec minimum for one block)
if (_log.shouldLog(Log.WARN))
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
@@ -468,7 +532,10 @@ public class PeerCoordinator implements PeerListener
return null;
}
// returns true if actual attempt to add peer occurs
/**
* Add peer (inbound or outbound)
* @return true if actual attempt to add peer occurs
*/
public boolean addPeer(final Peer peer)
{
if (halted)
@@ -487,7 +554,7 @@ public class PeerCoordinator implements PeerListener
need_more = (!peer.isConnected()) && peersize < getMaxConnections();
// Check if we already have this peer before we build the connection
Peer old = peerIDInList(peer.getPeerID(), peers);
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
need_more = need_more && ((old == null) || (old.getInactiveTime() > MAX_INACTIVE));
}
if (need_more)
@@ -630,22 +697,23 @@ public class PeerCoordinator implements PeerListener
* -1 if none of the given pieces are wanted.
*/
public int wantPiece(Peer peer, BitField havePieces) {
return wantPiece(peer, havePieces, true);
Piece pc = wantPiece(peer, havePieces, true);
return pc != null ? pc.getId() : -1;
}
/**
* Returns one of pieces in the given BitField that is still wanted or
* -1 if none of the given pieces are wanted.
* null if none of the given pieces are wanted.
*
* @param record if true, actually record in our data structures that we gave the
* request to this peer. If false, do not update the data structures.
* @since 0.8.2
*/
private int wantPiece(Peer peer, BitField havePieces, boolean record) {
private Piece wantPiece(Peer peer, BitField havePieces, boolean record) {
if (halted) {
if (_log.shouldLog(Log.WARN))
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
return -1;
return null;
}
Piece piece = null;
@@ -680,7 +748,7 @@ public class PeerCoordinator implements PeerListener
// 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 (wantedSize > END_GAME_THRESHOLD)
return -1; // nothing to request and not in end game
return null; // nothing to request and not in end game
// let's not all get on the same piece
// Even better would be to sort by number of requests
if (record)
@@ -704,7 +772,7 @@ public class PeerCoordinator implements PeerListener
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
return -1; //If we still can't find a piece we want, so be it.
return null; //If we still can't find a piece we want, so be it.
} else {
// Should be a lot smarter here -
// share blocks rather than starting from 0 with each peer.
@@ -719,7 +787,7 @@ public class PeerCoordinator implements PeerListener
_log.info(peer + " is now requesting: piece " + piece + " priority " + piece.getPriority());
piece.setRequested(peer, true);
}
return piece.getId();
return piece;
} // synch
}
@@ -736,6 +804,7 @@ public class PeerCoordinator implements PeerListener
_log.debug("Updated piece priorities called but no priorities to set?");
return;
}
List<Piece> toCancel = new ArrayList();
synchronized(wantedPieces) {
// Add incomplete and previously unwanted pieces to the list
// Temp to avoid O(n**2)
@@ -749,6 +818,7 @@ public class PeerCoordinator implements PeerListener
if (!want.get(i)) {
Piece piece = new Piece(i);
wantedPieces.add(piece);
wantedBytes += metainfo.getPieceLength(i);
// As connections are already up, new Pieces will
// not have their PeerID list populated, so do that.
for (Peer p : peers) {
@@ -770,23 +840,32 @@ public class PeerCoordinator implements PeerListener
p.setPriority(priority);
} else {
iter.remove();
// cancel all peers
for (Peer peer : peers) {
peer.cancel(p.getId());
}
toCancel.add(p);
wantedBytes -= metainfo.getPieceLength(p.getId());
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Updated piece priorities, now wanted: " + wantedPieces);
// if we added pieces, they will be in-order unless we shuffle
Collections.shuffle(wantedPieces, _random);
}
// update request queues, in case we added wanted pieces
// and we were previously uninterested
for (Peer peer : peers) {
peer.request();
// cancel outside of wantedPieces lock to avoid deadlocks
if (!toCancel.isEmpty()) {
// cancel all peers
for (Peer peer : peers) {
for (Piece p : toCancel) {
peer.cancel(p.getId());
}
}
}
// ditto, avoid deadlocks
// update request queues, in case we added wanted pieces
// and we were previously uninterested
for (Peer peer : peers) {
peer.request();
}
}
/**
@@ -846,10 +925,11 @@ public class PeerCoordinator implements PeerListener
*
* @throws RuntimeException on IOE saving the piece
*/
public boolean gotPiece(Peer peer, int piece, byte[] bs)
public boolean gotPiece(Peer peer, PartialPiece pp)
{
if (metainfo == null || storage == null)
return true;
int piece = pp.getPiece();
if (halted) {
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
return true; // We don't actually care anymore.
@@ -872,7 +952,7 @@ public class PeerCoordinator implements PeerListener
try
{
if (storage.putPiece(piece, bs))
if (storage.putPiece(pp))
{
if (_log.shouldLog(Log.INFO))
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
@@ -895,33 +975,44 @@ public class PeerCoordinator implements PeerListener
throw new RuntimeException(msg, ioe);
}
wantedPieces.remove(p);
wantedBytes -= metainfo.getPieceLength(p.getId());
}
// just in case
removePartialPiece(piece);
boolean done = wantedBytes <= 0;
// Announce to the world we have it!
// Disconnect from other seeders when we get the last piece
List<Peer> toDisconnect = new ArrayList();
Iterator<Peer> it = peers.iterator();
while (it.hasNext())
{
Peer p = it.next();
List<Peer> toDisconnect = done ? new ArrayList() : null;
for (Peer p : peers) {
if (p.isConnected())
{
if (completed() && p.isCompleted())
if (done && p.isCompleted())
toDisconnect.add(p);
else
p.have(piece);
}
}
it = toDisconnect.iterator();
while (it.hasNext())
{
Peer p = it.next();
}
if (done) {
for (Peer p : toDisconnect) {
p.disconnect(true);
}
}
// put msg on the console if partial, since Storage won't do it
if (!completed())
snark.storageCompleted(storage);
synchronized (partialPieces) {
for (PartialPiece ppp : partialPieces) {
ppp.release();
}
partialPieces.clear();
}
}
return true;
}
@@ -998,17 +1089,24 @@ public class PeerCoordinator implements PeerListener
* Also mark the piece unrequested if this peer was the only one.
*
* @param peer partials, must include the zero-offset (empty) ones too
* No dup pieces, piece.setDownloaded() must be set
* @since 0.8.2
*/
public void savePartialPieces(Peer peer, List<PartialPiece> partials)
public void savePartialPieces(Peer peer, List<Request> partials)
{
if (halted)
return;
if (_log.shouldLog(Log.INFO))
_log.info("Partials received from " + peer + ": " + partials);
if (halted || completed()) {
for (Request req : partials) {
PartialPiece pp = req.getPartialPiece();
pp.release();
}
return;
}
synchronized(wantedPieces) {
for (PartialPiece pp : partials) {
if (pp.getDownloaded() > 0) {
for (Request req : partials) {
PartialPiece pp = req.getPartialPiece();
if (req.off > 0) {
// PartialPiece.equals() only compares piece number, which is what we want
int idx = partialPieces.indexOf(pp);
if (idx < 0) {
@@ -1017,10 +1115,12 @@ public class PeerCoordinator implements PeerListener
_log.info("Saving orphaned partial piece (new) " + pp);
} else if (idx >= 0 && pp.getDownloaded() > partialPieces.get(idx).getDownloaded()) {
// replace what's there now
partialPieces.get(idx).release();
partialPieces.set(idx, pp);
if (_log.shouldLog(Log.INFO))
_log.info("Saving orphaned partial piece (bigger) " + pp);
} else {
pp.release();
if (_log.shouldLog(Log.INFO))
_log.info("Discarding partial piece (not bigger)" + pp);
}
@@ -1029,10 +1129,14 @@ public class PeerCoordinator implements PeerListener
// sorts by remaining bytes, least first
Collections.sort(partialPieces);
PartialPiece gone = partialPieces.remove(max);
gone.release();
if (_log.shouldLog(Log.INFO))
_log.info("Discarding orphaned partial piece (list full)" + gone);
}
} // else drop the empty partial piece
} else {
// drop the empty partial piece
pp.release();
}
// synchs on wantedPieces...
markUnrequested(peer, pp.getPiece());
}
@@ -1058,10 +1162,18 @@ public class PeerCoordinator implements PeerListener
PartialPiece pp = iter.next();
int savedPiece = pp.getPiece();
if (havePieces.get(savedPiece)) {
iter.remove();
// this is just a double-check, it should be in there
boolean skipped = false;
for(Piece piece : wantedPieces) {
if (piece.getId() == savedPiece) {
if (peer.isCompleted() && piece.getPeerCount() > 1) {
// Try to preserve rarest-first when we have only one seeder
// by not preferring a partial piece that others have too
// from a seeder
skipped = true;
break;
}
iter.remove();
piece.setRequested(peer, true);
if (_log.shouldLog(Log.INFO)) {
_log.info("Restoring orphaned partial piece " + pp +
@@ -1070,8 +1182,12 @@ public class PeerCoordinator implements PeerListener
return pp;
}
}
if (_log.shouldLog(Log.WARN))
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
if (_log.shouldLog(Log.WARN)) {
if (skipped)
_log.warn("Partial piece " + pp + " with multiple peers skipped for seeder");
else
_log.warn("Partial piece " + pp + " NOT in wantedPieces??");
}
}
}
if (_log.shouldLog(Log.WARN) && !partialPieces.isEmpty())
@@ -1079,14 +1195,9 @@ public class PeerCoordinator implements PeerListener
}
// ...and this section turns this into the general move-requests-around code!
// Temporary? So PeerState never calls wantPiece() directly for now...
int piece = wantPiece(peer, havePieces);
if (piece >= 0) {
try {
return new PartialPiece(piece, metainfo.getPieceLength(piece));
} catch (OutOfMemoryError oom) {
if (_log.shouldLog(Log.WARN))
_log.warn("OOM creating new partial piece");
}
Piece piece = wantPiece(peer, havePieces, true);
if (piece != null) {
return new PartialPiece(piece, metainfo.getPieceLength(piece.getId()), _util.getTempDir());
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("We have no partial piece to return");
@@ -1121,7 +1232,7 @@ public class PeerCoordinator implements PeerListener
}
}
}
return wantPiece(peer, havePieces, false) >= 0;
return wantPiece(peer, havePieces, false) != null;
}
/**
@@ -1230,11 +1341,12 @@ public class PeerCoordinator implements PeerListener
}
/**
* Get peers from PEX -
* PeerListener callback
* @since 0.8.4
*/
public void gotPeers(Peer peer, List<PeerID> peers) {
if (completed() || !needPeers())
if (!needOutboundPeers())
return;
Destination myDest = _util.getMyDestination();
if (myDest == null)

View File

@@ -95,12 +95,11 @@ interface PeerListener
* will be closed.
*
* @param peer the Peer that got the piece.
* @param piece the piece number received.
* @param bs the byte array containing the piece.
* @param piece the piece received.
*
* @return true when the bytes represent the piece, false otherwise.
*/
boolean gotPiece(Peer peer, int piece, byte[] bs);
boolean gotPiece(Peer peer, PartialPiece piece);
/**
* Called when the peer wants (part of) a piece from us. Only called
@@ -167,7 +166,7 @@ interface PeerListener
* @param peer the peer
* @since 0.8.2
*/
void savePartialPieces(Peer peer, List<PartialPiece> pcs);
void savePartialPieces(Peer peer, List<Request> pcs);
/**
* Called when a peer has connected and there may be a partially

View File

@@ -39,13 +39,13 @@ class PeerState implements DataLoader
// Interesting and choking describes whether we are interested in or
// are choking the other side.
boolean interesting = false;
boolean choking = true;
volatile boolean interesting;
volatile boolean choking = true;
// Interested and choked describes whether the other side is
// interested in us or choked us.
boolean interested = false;
boolean choked = true;
volatile boolean interested;
volatile boolean choked = true;
/** the pieces the peer has */
BitField bitfield;
@@ -107,7 +107,7 @@ class PeerState implements DataLoader
// The only problem with returning the partials to the coordinator
// is that chunks above a missing request are lost.
// Future enhancements to PartialPiece could keep track of the holes.
List<PartialPiece> pcs = returnPartialPieces();
List<Request> pcs = returnPartialPieces();
if (!pcs.isEmpty()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " got choked, returning partial pieces to the PeerCoordinator: " + pcs);
@@ -231,8 +231,8 @@ class PeerState implements DataLoader
return;
}
if (_log.shouldLog(Log.INFO))
_log.info("Queueing (" + piece + ", " + begin + ", "
if (_log.shouldLog(Log.DEBUG))
_log.debug("Queueing (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
// don't load the data into mem now, let PeerConnectionOut do it
@@ -267,8 +267,8 @@ class PeerState implements DataLoader
return null;
}
if (_log.shouldLog(Log.INFO))
_log.info("Sending (" + piece + ", " + begin + ", "
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending (" + piece + ", " + begin + ", "
+ length + ")" + " to " + peer);
return pieceBytes;
}
@@ -304,22 +304,23 @@ class PeerState implements DataLoader
if (_log.shouldLog(Log.DEBUG))
_log.debug("got end of Chunk("
+ req.piece + "," + req.off + "," + req.len + ") from "
+ req.getPiece() + "," + req.off + "," + req.len + ") from "
+ peer);
// Last chunk needed for this piece?
if (getFirstOutstandingRequest(req.piece) == -1)
// FIXME if priority changed to skip, we will think we're done when we aren't
if (getFirstOutstandingRequest(req.getPiece()) == -1)
{
// warning - may block here for a while
if (listener.gotPiece(peer, req.piece, req.bs))
if (listener.gotPiece(peer, req.getPartialPiece()))
{
if (_log.shouldLog(Log.DEBUG))
_log.debug("Got " + req.piece + ": " + peer);
_log.debug("Got " + req.getPiece() + ": " + peer);
}
else
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.piece + " from " + peer);
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
}
}
@@ -335,7 +336,7 @@ class PeerState implements DataLoader
synchronized private int getFirstOutstandingRequest(int piece)
{
for (int i = 0; i < outstandingRequests.size(); i++)
if (outstandingRequests.get(i).piece == piece)
if (outstandingRequests.get(i).getPiece() == piece)
return i;
return -1;
}
@@ -371,7 +372,7 @@ class PeerState implements DataLoader
synchronized(this)
{
req = outstandingRequests.get(r);
while (req.piece == piece && req.off != begin
while (req.getPiece() == piece && req.off != begin
&& r < outstandingRequests.size() - 1)
{
r++;
@@ -379,7 +380,7 @@ class PeerState implements DataLoader
}
// Something wrong?
if (req.piece != piece || req.off != begin || req.len != length)
if (req.getPiece() != piece || req.off != begin || req.len != length)
{
if (_log.shouldLog(Log.INFO))
_log.info("Unrequested or unneeded 'piece: "
@@ -427,13 +428,13 @@ class PeerState implements DataLoader
Request rv = null;
int lowest = Integer.MAX_VALUE;
for (Request r : outstandingRequests) {
if (r.piece == piece && r.off < lowest) {
if (r.getPiece() == piece && r.off < lowest) {
lowest = r.off;
rv = r;
}
}
if (pendingRequest != null &&
pendingRequest.piece == piece && pendingRequest.off < lowest)
pendingRequest.getPiece() == piece && pendingRequest.off < lowest)
rv = pendingRequest;
if (_log.shouldLog(Log.DEBUG))
@@ -447,14 +448,16 @@ class PeerState implements DataLoader
* @return List of PartialPieces, even those with an offset == 0, or empty list
* @since 0.8.2
*/
synchronized List<PartialPiece> returnPartialPieces()
synchronized List<Request> returnPartialPieces()
{
Set<Integer> pcs = getRequestedPieces();
List<PartialPiece> rv = new ArrayList(pcs.size());
List<Request> rv = new ArrayList(pcs.size());
for (Integer p : pcs) {
Request req = getLowestOutstandingRequest(p.intValue());
if (req != null)
rv.add(new PartialPiece(req));
if (req != null) {
req.getPartialPiece().setDownloaded(req.off);
rv.add(req);
}
}
outstandingRequests.clear();
pendingRequest = null;
@@ -468,9 +471,9 @@ class PeerState implements DataLoader
synchronized private Set<Integer> getRequestedPieces() {
Set<Integer> rv = new HashSet(outstandingRequests.size() + 1);
for (Request req : outstandingRequests) {
rv.add(Integer.valueOf(req.piece));
rv.add(Integer.valueOf(req.getPiece()));
if (pendingRequest != null)
rv.add(Integer.valueOf(pendingRequest.piece));
rv.add(Integer.valueOf(pendingRequest.getPiece()));
}
return rv;
}
@@ -571,14 +574,14 @@ class PeerState implements DataLoader
* @since 0.8.1
*/
synchronized void cancelPiece(int piece) {
if (lastRequest != null && lastRequest.piece == piece)
if (lastRequest != null && lastRequest.getPiece() == piece)
lastRequest = null;
Iterator<Request> it = outstandingRequests.iterator();
while (it.hasNext())
{
Request req = it.next();
if (req.piece == piece)
if (req.getPiece() == piece)
{
it.remove();
// Send cancel even when we are choked to make sure that it is
@@ -594,10 +597,10 @@ class PeerState implements DataLoader
* @since 0.8.1
*/
synchronized boolean isRequesting(int piece) {
if (pendingRequest != null && pendingRequest.piece == piece)
if (pendingRequest != null && pendingRequest.getPiece() == piece)
return true;
for (Request req : outstandingRequests) {
if (req.piece == piece)
if (req.getPiece() == piece)
return true;
}
return false;
@@ -679,7 +682,7 @@ class PeerState implements DataLoader
{
int pieceLength;
boolean isLastChunk;
pieceLength = metainfo.getPieceLength(lastRequest.piece);
pieceLength = metainfo.getPieceLength(lastRequest.getPiece());
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
// Last part of a piece?
@@ -687,14 +690,13 @@ class PeerState implements DataLoader
more_pieces = requestNextPiece();
else
{
int nextPiece = lastRequest.piece;
PartialPiece nextPiece = lastRequest.getPartialPiece();
int nextBegin = lastRequest.off + PARTSIZE;
byte[] bs = lastRequest.bs;
int maxLength = pieceLength - nextBegin;
int nextLength = maxLength > PARTSIZE ? PARTSIZE
: maxLength;
Request req
= new Request(nextPiece, bs, nextBegin, nextLength);
= new Request(nextPiece,nextBegin, nextLength);
outstandingRequests.add(req);
if (!choked)
out.sendRequest(req);
@@ -740,7 +742,7 @@ class PeerState implements DataLoader
// what piece to give us next.
int nextPiece = listener.wantPiece(peer, bitfield);
if (nextPiece != -1
&& (lastRequest == null || lastRequest.piece != nextPiece)) {
&& (lastRequest == null || lastRequest.getPiece() != nextPiece)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " want piece " + nextPiece);
// Fail safe to make sure we are interested

View File

@@ -57,6 +57,15 @@ class Piece implements Comparable {
/** caller must synchronize */
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
/**
* How many peers have this piece?
* Caller must synchronize
* @since 0.9.1
*/
public int getPeerCount() {
return this.peers.size();
}
/** caller must synchronize */
public boolean isRequested() {
return this.requests != null && !this.requests.isEmpty();

View File

@@ -20,14 +20,16 @@
package org.klomp.snark;
import java.io.DataInputStream;
import java.io.IOException;
/**
* Holds all information needed for a partial piece request.
* This class should be used only by PeerState, PeerConnectionIn, and PeerConnectionOut.
*/
class Request
{
final int piece;
final byte[] bs;
private final PartialPiece piece;
final int off;
final int len;
long sendTime;
@@ -36,26 +38,49 @@ class Request
* Creates a new Request.
*
* @param piece Piece number requested.
* @param bs byte array where response should be stored.
* @param off the offset in the array.
* @param len the number of bytes requested.
*/
Request(int piece, byte[] bs, int off, int len)
Request(PartialPiece piece, int off, int len)
{
// Sanity check
if (off < 0 || len <= 0 || off + len > piece.getLength())
throw new IndexOutOfBoundsException("Illegal Request " + toString());
this.piece = piece;
this.bs = bs;
this.off = off;
this.len = len;
}
// Sanity check
if (piece < 0 || off < 0 || len <= 0 || off + len > bs.length)
throw new IndexOutOfBoundsException("Illegal Request " + toString());
/**
* @since 0.9.1
*/
public void read(DataInputStream din) throws IOException {
piece.read(din, off, len);
}
/**
* The piece number this Request is for
*
* @since 0.9.1
*/
public int getPiece() {
return piece.getPiece();
}
/**
* The PartialPiece this Request is for
*
* @since 0.9.1
*/
public PartialPiece getPartialPiece() {
return piece;
}
@Override
public int hashCode()
{
return piece ^ off ^ len;
return piece.getPiece() ^ off ^ len;
}
@Override
@@ -64,7 +89,7 @@ class Request
if (o instanceof Request)
{
Request req = (Request)o;
return req.piece == piece && req.off == off && req.len == len;
return req.piece.equals(piece) && req.off == off && req.len == len;
}
return false;
@@ -73,6 +98,6 @@ class Request
@Override
public String toString()
{
return "(" + piece + "," + off + "," + len + ")";
return "(" + piece.getPiece() + "," + off + "," + len + ")";
}
}

View File

@@ -249,7 +249,8 @@ public class Snark
private TrackerClient trackerclient;
private String rootDataDir = ".";
private final CompleteListener completeListener;
private boolean stopped;
private volatile boolean stopped;
private volatile boolean starting;
private byte[] id;
private byte[] infoHash;
private String additionalTrackerURL;
@@ -346,6 +347,8 @@ public class Snark
in = new FileInputStream(f);
else
{
/**** No, we don't ever fetch a torrent file this way
and we don't want to block in the constructor
activity = "Getting torrent";
File torrentFile = _util.get(torrent, 3);
if (torrentFile == null) {
@@ -355,6 +358,8 @@ public class Snark
torrentFile.deleteOnExit();
in = new FileInputStream(torrentFile);
}
*****/
throw new IOException("not found");
}
meta = new MetaInfo(in);
infoHash = meta.getInfoHash();
@@ -505,9 +510,19 @@ public class Snark
}
/**
* Start up contacting peers and querying the tracker
* Start up contacting peers and querying the tracker.
* Blocks if tunnel is not yet open.
*/
public void startTorrent() {
public synchronized void startTorrent() {
starting = true;
try {
x_startTorrent();
} finally {
starting = false;
}
}
private void x_startTorrent() {
boolean ok = _util.connect();
if (!ok) fatal("Unable to connect to I2P");
if (coordinator == null) {
@@ -538,21 +553,14 @@ public class Snark
}
stopped = false;
boolean coordinatorChanged = false;
if (coordinator.halted()) {
// ok, we have already started and stopped, but the coordinator seems a bit annoying to
// restart safely, so lets build a new one to replace the old
coordinator.restart();
if (_peerCoordinatorSet != null)
_peerCoordinatorSet.remove(coordinator);
PeerCoordinator newCoord = new PeerCoordinator(_util, id, infoHash, meta, storage, this, this);
if (_peerCoordinatorSet != null)
_peerCoordinatorSet.add(newCoord);
coordinator = newCoord;
coordinatorChanged = true;
_peerCoordinatorSet.add(coordinator);
}
if (!trackerclient.started() && !coordinatorChanged) {
if (!trackerclient.started()) {
trackerclient.start();
} else if (trackerclient.halted() || coordinatorChanged) {
} else if (trackerclient.halted()) {
if (storage != null) {
try {
storage.reopen(rootDataDir);
@@ -563,23 +571,29 @@ public class Snark
fatal("Could not reopen storage", ioe);
}
}
TrackerClient newClient = new TrackerClient(_util, meta, additionalTrackerURL, coordinator, this);
if (!trackerclient.halted())
trackerclient.halt();
trackerclient = newClient;
trackerclient.start();
} else {
debug("NOT starting TrackerClient???", NOTICE);
}
}
/**
* Stop contacting the tracker and talking with peers
*/
public void stopTorrent() {
stopTorrent(false);
}
/**
* Stop contacting the tracker and talking with peers
* @param fast if true, limit the life of the unannounce threads
* @since 0.9.1
*/
public synchronized void stopTorrent(boolean fast) {
stopped = true;
TrackerClient tc = trackerclient;
if (tc != null)
tc.halt();
tc.halt(fast);
PeerCoordinator pc = coordinator;
if (pc != null)
pc.halt();
@@ -670,6 +684,22 @@ public class Snark
return stopped;
}
/**
* Startup in progress.
* @since 0.9.1
*/
public boolean isStarting() {
return starting && stopped;
}
/**
* Set startup in progress.
* @since 0.9.1
*/
public void setStarting() {
starting = true;
}
/**
* @since 0.8.4
*/
@@ -782,6 +812,7 @@ public class Snark
}
/**
* Bytes not yet in storage. Does NOT account for skipped files.
* @return exact value. or -1 if no storage yet.
* getNeeded() * pieceLength(0) isn't accurate if last piece
* is still needed.
@@ -802,6 +833,20 @@ public class Snark
}
/**
* Bytes still wanted. DOES account for skipped files.
* FIXME -1 when not running.
* @return exact value. or -1 if no storage yet or when not running.
* @since 0.9.1
*/
public long getNeededLength() {
PeerCoordinator coord = coordinator;
if (coord != null)
return coord.getNeededLength();
return -1;
}
/**
* Does not account for skipped files.
* @return number of pieces still needed (magnet mode or not), or -1 if unknown
* @since 0.8.4
*/
@@ -1214,7 +1259,7 @@ public class Snark
total += c.getCurrentUploadRate();
}
long limit = 1024l * _util.getMaxUpBW();
debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
debug("Total up bw: " + total + " Limit: " + limit, Snark.NOTICE);
return total > limit;
}

View File

@@ -8,6 +8,8 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -18,8 +20,9 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
@@ -31,6 +34,8 @@ import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Manage multiple snarks
@@ -50,13 +55,14 @@ public class SnarkManager implements Snark.CompleteListener {
private Properties _config;
private final I2PAppContext _context;
private final Log _log;
private final List<String> _messages;
private final Queue<String> _messages;
private final I2PSnarkUtil _util;
private PeerCoordinatorSet _peerCoordinatorSet;
private ConnectionAcceptor _connectionAcceptor;
private Thread _monitor;
private volatile boolean _running;
private final Map<String, String> _trackerMap;
private volatile boolean _stopping;
private final Map<String, Tracker> _trackerMap;
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
@@ -83,6 +89,7 @@ public class SnarkManager implements Snark.CompleteListener {
public static final String DEFAULT_THEME = "ubergine";
private static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
public static final String PROP_PRIVATETRACKERS = "i2psnark.privatetrackers";
public static final int MIN_UP_BW = 2;
public static final int DEFAULT_MAX_UP_BW = 10;
@@ -93,7 +100,7 @@ public class SnarkManager implements Snark.CompleteListener {
* "name", "announceURL=websiteURL" pairs
* '=' in announceURL must be escaped as &#44;
*/
private static final String DEFAULT_TRACKERS[] = {
public static final String DEFAULT_TRACKERS[] = {
// "Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/"
// , "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/"
// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/"
@@ -123,12 +130,12 @@ public class SnarkManager implements Snark.CompleteListener {
_addSnarkLock = new Object();
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SnarkManager.class);
_messages = new ArrayList(16);
_messages = new LinkedBlockingQueue();
_util = new I2PSnarkUtil(_context);
_configFile = new File(CONFIG_FILE);
if (!_configFile.isAbsolute())
_configFile = new File(_context.getConfigDir(), CONFIG_FILE);
_trackerMap = Collections.synchronizedMap(new TreeMap(new IgnoreCaseComparator()));
_trackerMap = new ConcurrentHashMap(4);
loadConfig(null);
}
@@ -141,26 +148,35 @@ public class SnarkManager implements Snark.CompleteListener {
_connectionAcceptor = new ConnectionAcceptor(_util);
_monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor", true);
_monitor.start();
_context.addShutdownTask(new SnarkManagerShutdown());
// Not required, Jetty has a shutdown hook
//_context.addShutdownTask(new SnarkManagerShutdown());
}
/*
* Called by the webapp at Jetty shutdown.
* Stops all torrents. Does not close the tunnel, so the announces have a chance.
* Fix this so an individual webaapp stop will close the tunnel.
* Runs inline.
*/
public void stop() {
_running = false;
_monitor.interrupt();
_connectionAcceptor.halt();
(new SnarkManagerShutdown()).run();
stopAllTorrents(true);
}
/** @since 0.9.1 */
public boolean isStopping() { return _stopping; }
/** hook to I2PSnarkUtil for the servlet */
public I2PSnarkUtil util() { return _util; }
private static final int MAX_MESSAGES = 5;
private static final int MAX_MESSAGES = 100;
public void addMessage(String message) {
synchronized (_messages) {
_messages.add(message);
while (_messages.size() > MAX_MESSAGES)
_messages.remove(0);
_messages.offer(message);
while (_messages.size() > MAX_MESSAGES) {
_messages.poll();
}
if (_log.shouldLog(Log.INFO))
_log.info("MSG: " + message);
@@ -168,16 +184,14 @@ public class SnarkManager implements Snark.CompleteListener {
/** newest last */
public List<String> getMessages() {
synchronized (_messages) {
return new ArrayList(_messages);
}
if (_messages.isEmpty())
return Collections.EMPTY_LIST;
return new ArrayList(_messages);
}
/** @since 0.9 */
public void clearMessages() {
synchronized (_messages) {
_messages.clear();
}
}
/**
@@ -344,7 +358,7 @@ public class SnarkManager implements Snark.CompleteListener {
_util.setFilesPublic(areFilesPublic());
String ot = _config.getProperty(PROP_OPENTRACKERS);
if (ot != null)
_util.setOpenTrackerString(ot);
_util.setOpenTrackers(getOpenTrackers());
String useOT = _config.getProperty(PROP_USE_OPENTRACKERS);
boolean bOT = useOT == null || Boolean.valueOf(useOT).booleanValue();
_util.setUseOpenTrackers(bOT);
@@ -366,7 +380,7 @@ public class SnarkManager implements Snark.CompleteListener {
public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, String refreshDelay,
String startDelay, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers, String theme) {
String upLimit, String upBW, boolean useOpenTrackers, String theme) {
boolean changed = false;
//if (eepHost != null) {
// // unused, we use socket eepget
@@ -550,14 +564,6 @@ public class SnarkManager implements Snark.CompleteListener {
_util.setUseOpenTrackers(useOpenTrackers);
changed = true;
}
if (openTrackers != null) {
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) {
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
_util.setOpenTrackerString(openTrackers);
addMessage(_("Open Tracker list changed - torrent restart required to take effect."));
changed = true;
}
}
if (theme != null) {
if(!theme.equals(_config.getProperty(PROP_THEME))) {
_config.setProperty(PROP_THEME, theme);
@@ -572,6 +578,84 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
/**
* Others should use the version in I2PSnarkUtil
* @return non-null, empty if disabled
* @since 0.9.1
*/
private List<String> getOpenTrackers() {
if (!_util.shouldUseOpenTrackers())
return Collections.EMPTY_LIST;
return getListConfig(PROP_OPENTRACKERS, I2PSnarkUtil.DEFAULT_OPENTRACKERS);
}
/**
* @return non-null, fixed size, may be empty or unmodifiable
* @since 0.9.1
*/
public List<String> getPrivateTrackers() {
return getListConfig(PROP_PRIVATETRACKERS, null);
}
/**
* @param ot null to restore default
* @since 0.9.1
*/
public void saveOpenTrackers(List<String> ot) {
String val = setListConfig(PROP_OPENTRACKERS, ot);
if (ot == null)
ot = Collections.singletonList(I2PSnarkUtil.DEFAULT_OPENTRACKERS);
_util.setOpenTrackers(ot);
addMessage(_("Open Tracker list changed - torrent restart required to take effect."));
saveConfig();
}
/**
* @param pt null ok, default is none
* @since 0.9.1
*/
public void savePrivateTrackers(List<String> pt) {
setListConfig(PROP_PRIVATETRACKERS, pt);
addMessage(_("Private tracker list changed - affects newly created torrents only."));
saveConfig();
}
/**
* @param dflt default or null
* @return non-null, fixed size
* @since 0.9.1
*/
private List<String> getListConfig(String prop, String dflt) {
String val = _config.getProperty(prop);
if (val == null)
val = dflt;
if (val == null)
return Collections.EMPTY_LIST;
return Arrays.asList(val.split(","));
}
/**
* Sets the config, does NOT save it
* @param values may be null or empty
* @return the comma-separated config string, non-null
* @since 0.9.1
*/
private String setListConfig(String prop, List<String> values) {
if (values == null || values.isEmpty()) {
_config.remove(prop);
return "";
}
StringBuilder buf = new StringBuilder(64);
for (String s : values) {
if (buf.length() > 0)
buf.append(',');
buf.append(s);
}
String rv = buf.toString();
_config.setProperty(prop, rv);
return rv;
}
public void saveConfig() {
try {
synchronized (_configFile) {
@@ -787,7 +871,7 @@ public class SnarkManager implements Snark.CompleteListener {
torrent.startTorrent();
addMessage(_("Fetching {0}", name));
boolean haveSavedPeers = false;
if ((!util().connected()) && !haveSavedPeers) {
if ((_util.connected()) && !haveSavedPeers) {
addMessage(_("We have no saved peers and no other torrents are running. " +
"Fetch of {0} will not succeed until you start another torrent.", name));
}
@@ -810,6 +894,29 @@ public class SnarkManager implements Snark.CompleteListener {
_magnets.remove(snark.getName());
removeMagnetStatus(snark.getInfoHash());
}
/**
* Add and start a FetchAndAdd task.
* Remove it with deleteMagnet().
*
* @param torrent must be instanceof FetchAndAdd
* @throws RuntimeException via Snark.fatal()?
* @since 0.9.1
*/
public void addDownloader(Snark torrent) {
synchronized (_snarks) {
Snark snark = getTorrentByInfoHash(torrent.getInfoHash());
if (snark != null) {
addMessage(_("Download already running: {0}", snark.getBaseName()));
return;
}
String name = torrent.getName();
// Tell the dir monitor not to delete us
_magnets.add(name);
_snarks.put(name, torrent);
}
torrent.startTorrent();
}
/**
* Add a torrent from a MetaInfo. Save the MetaInfo data to filename.
@@ -1192,13 +1299,11 @@ public class SnarkManager implements Snark.CompleteListener {
// don't bother delaying if auto start is false
long delay = 60 * 1000 * getStartupDelayMinutes();
if (delay > 0 && shouldAutoStart()) {
_messages.add(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
addMessage(_("Adding torrents in {0}", DataHelper.formatDuration2(delay)));
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
// the first message was a "We are starting up in 1m"
synchronized (_messages) {
if (_messages.size() == 1)
_messages.remove(0);
}
// Remove that first message
if (_messages.size() == 1)
_messages.poll();
}
// here because we need to delay until I2CP is up
@@ -1330,7 +1435,7 @@ public class SnarkManager implements Snark.CompleteListener {
byte[] ih = Base64.decode(b64);
// ignore value - TODO put tracker URL in value
if (ih != null && ih.length == 20)
addMagnet("Magnet: " + I2PSnarkUtil.toHex(ih), ih, null, false);
addMagnet("* " + _("Magnet") + ' ' + I2PSnarkUtil.toHex(ih), ih, null, false);
// else remove from config?
}
}
@@ -1407,40 +1512,67 @@ public class SnarkManager implements Snark.CompleteListener {
}
/**
* Sorted map of name to announceURL=baseURL
* Unsorted map of name to Tracker object
* Modifiable, not a copy
* @since 0.9.1
*/
public Map<String, String> getTrackers() {
public Map<String, Tracker> getTrackerMap() {
return _trackerMap;
}
/**
* Unsorted, do not modify
*/
public Collection<Tracker> getTrackers() {
return _trackerMap.values();
}
/**
* Sorted copy
* @since 0.9.1
*/
public List<Tracker> getSortedTrackers() {
List<Tracker> rv = new ArrayList(_trackerMap.values());
Collections.sort(rv, new IgnoreCaseComparator());
return rv;
}
/** @since 0.9 */
private void initTrackerMap() {
String trackers = _config.getProperty(PROP_TRACKERS);
if ( (trackers == null) || (trackers.trim().length() <= 0) )
trackers = _context.getProperty(PROP_TRACKERS);
_trackerMap.clear();
if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2)
_trackerMap.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
setDefaultTrackerMap(true);
} else {
String[] toks = trackers.split(",");
for (int i = 0; i < toks.length; i += 2) {
String name = toks[i].trim().replace("&#44;", ",");
String url = toks[i+1].trim().replace("&#44;", ",");
if ( (name.length() > 0) && (url.length() > 0) )
_trackerMap.put(name, url);
if ( (name.length() > 0) && (url.length() > 0) ) {
String urls[] = url.split("=", 2);
String url2 = urls.length > 1 ? urls[1] : "";
_trackerMap.put(name, new Tracker(name, urls[0], url2));
}
}
}
}
/** @since 0.9 */
public void setDefaultTrackerMap() {
setDefaultTrackerMap(true);
}
/** @since 0.9.1 */
private void setDefaultTrackerMap(boolean save) {
_trackerMap.clear();
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2) {
_trackerMap.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
String name = DEFAULT_TRACKERS[i];
String urls[] = DEFAULT_TRACKERS[i+1].split("=", 2);
String url2 = urls.length > 1 ? urls[1] : null;
_trackerMap.put(name, new Tracker(name, urls[0], url2));
}
if (_config.remove(PROP_TRACKERS) != null) {
if (save && _config.remove(PROP_TRACKERS) != null) {
saveConfig();
}
}
@@ -1449,12 +1581,15 @@ public class SnarkManager implements Snark.CompleteListener {
public void saveTrackerMap() {
StringBuilder buf = new StringBuilder(2048);
boolean comma = false;
for (Map.Entry<String, String> e : _trackerMap.entrySet()) {
for (Map.Entry<String, Tracker> e : _trackerMap.entrySet()) {
if (comma)
buf.append(',');
else
comma = true;
buf.append(e.getKey().replace(",", "&#44;")).append(',').append(e.getValue().replace(",", "&#44;"));
Tracker t = e.getValue();
buf.append(e.getKey().replace(",", "&#44;")).append(',').append(t.announceURL.replace(",", "&#44;"));
if (t.baseURL != null)
buf.append('=').append(t.baseURL);
}
_config.setProperty(PROP_TRACKERS, buf.toString());
saveConfig();
@@ -1468,25 +1603,144 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
public class SnarkManagerShutdown extends I2PAppThread {
@Override
/**
* If not connected, thread it, otherwise inline
* @since 0.9.1
*/
public void startTorrent(byte[] infoHash) {
for (Snark snark : _snarks.values()) {
if (DataHelper.eq(infoHash, snark.getInfoHash())) {
if (snark.isStarting() || !snark.isStopped()) {
addMessage("Torrent already started");
return;
}
boolean connected = _util.connected();
if ((!connected) && !_util.isConnecting())
addMessage(_("Opening the I2P tunnel"));
addMessage(_("Starting up torrent {0}", snark.getBaseName()));
if (connected) {
snark.startTorrent();
} else {
// mark it for the UI
snark.setStarting();
(new I2PAppThread(new ThreadedStarter(snark), "TorrentStarter", true)).start();
try { Thread.sleep(200); } catch (InterruptedException ie) {}
}
return;
}
}
addMessage("Torrent not found?");
}
/**
* If not connected, thread it, otherwise inline
* @since 0.9.1
*/
public void startAllTorrents() {
if (_util.connected()) {
startAll();
} else {
addMessage(_("Opening the I2P tunnel and starting all torrents."));
for (Snark snark : _snarks.values()) {
// mark it for the UI
snark.setStarting();
}
(new I2PAppThread(new ThreadedStarter(null), "TorrentStarterAll", true)).start();
try { Thread.sleep(200); } catch (InterruptedException ie) {}
}
}
/**
* Use null constructor param for all
* @since 0.9.1
*/
private class ThreadedStarter implements Runnable {
private final Snark snark;
public ThreadedStarter(Snark s) { snark = s; }
public void run() {
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
Snark snark = getTorrent((String)iter.next());
if ( (snark != null) && (!snark.isStopped()) )
snark.stopTorrent();
if (snark != null) {
if (snark.isStopped())
snark.startTorrent();
} else {
startAll();
}
}
}
/**
* Inline
* @since 0.9.1
*/
private void startAll() {
for (Snark snark : _snarks.values()) {
if (snark.isStopped())
snark.startTorrent();
}
}
/**
* Stop all running torrents, and close the tunnel after a delay
* to allow for announces.
* If called at router shutdown via Jetty shutdown hook -> webapp destroy() -> stop(),
* the tunnel won't actually be closed as the SimpleScheduler is already shutdown
* or will be soon, so we delay a few seconds inline.
* @param finalShutdown if true, sleep at the end if any torrents were running
* @since 0.9.1
*/
public void stopAllTorrents(boolean finalShutdown) {
_stopping = true;
if (finalShutdown && _log.shouldLog(Log.WARN))
_log.warn("SnarkManager final shutdown");
int count = 0;
for (Snark snark : _snarks.values()) {
if (!snark.isStopped()) {
if (count == 0)
addMessage(_("Stopping all torrents and closing the I2P tunnel."));
count++;
if (finalShutdown)
snark.stopTorrent(true);
else
stopTorrent(snark, false);
// Throttle since every unannounce is now threaded.
// How to do this without creating a ton of threads?
try { Thread.sleep(20); } catch (InterruptedException ie) {}
}
}
if (_util.connected()) {
if (count > 0) {
// Schedule this even for final shutdown, as there's a chance
// that it's just this webapp that is stopping.
SimpleScheduler.getInstance().addEvent(new Disconnector(), 60*1000);
addMessage(_("Closing I2P tunnel after notifying trackers."));
if (finalShutdown) {
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
} else {
_util.disconnect();
_stopping = false;
addMessage(_("I2P tunnel closed."));
}
}
}
/** @since 0.9.1 */
private class Disconnector implements SimpleTimer.TimedEvent {
public void timeReached() {
if (_util.connected()) {
_util.disconnect();
_stopping = false;
addMessage(_("I2P tunnel closed."));
}
}
}
/**
* ignore case, current locale
* @since 0.9
*/
private static class IgnoreCaseComparator implements Comparator<String> {
public int compare(String l, String r) {
return l.toLowerCase().compareTo(r.toLowerCase());
private static class IgnoreCaseComparator implements Comparator<Tracker> {
public int compare(Tracker l, Tracker r) {
return l.name.toLowerCase().compareTo(r.name.toLowerCase());
}
}
}

View File

@@ -26,6 +26,7 @@ import net.i2p.util.I2PAppThread;
/**
* Makes sure everything ends correctly when shutting down.
* @deprecated unused
*/
public class SnarkShutdown extends I2PAppThread
{
@@ -61,7 +62,7 @@ public class SnarkShutdown extends I2PAppThread
//Snark.debug("Halting TrackerClient...", Snark.INFO);
if (trackerclient != null)
trackerclient.halt();
trackerclient.halt(true);
//Snark.debug("Halting PeerCoordinator...", Snark.INFO);
if (coordinator != null)

View File

@@ -50,6 +50,8 @@ public class Storage
private File[] RAFfile; // File to make it easier to reopen
/** priorities by file; default 0; may be null. @since 0.8.1 */
private int[] priorities;
/** is the file empty and sparse? */
private boolean[] isSparse;
private final StorageListener listener;
private final I2PSnarkUtil _util;
@@ -73,6 +75,8 @@ public class Storage
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap();
private static final boolean _isWindows = System.getProperty("os.name").startsWith("Win");
/**
* Creates a new storage based on the supplied MetaInfo. This will
* try to create and/or check all needed files in the MetaInfo.
@@ -202,7 +206,7 @@ public class Storage
RAFtime = new long[size];
RAFfile = new File[size];
priorities = new int[size];
isSparse = new boolean[size];
int i = 0;
Iterator it = files.iterator();
@@ -334,7 +338,8 @@ public class Storage
}
/**
* Must call setPiecePriorities() after calling this
* Must call Snark.updatePiecePriorities()
* (which calls getPiecePriorities()) after calling this.
* @param file canonical path (non-directory)
* @param pri default 0; <0 to disable
* @since 0.8.1
@@ -462,6 +467,7 @@ public class Storage
RAFlock = new Object[1];
RAFtime = new long[1];
RAFfile = new File[1];
isSparse = new boolean[1];
lengths[0] = metainfo.getTotalLength();
RAFlock[0] = new Object();
RAFfile[0] = base;
@@ -488,6 +494,7 @@ public class Storage
RAFlock = new Object[size];
RAFtime = new long[size];
RAFfile = new File[size];
isSparse = new boolean[size];
for (int i = 0; i < size; i++)
{
List<String> path = files.get(i);
@@ -701,6 +708,7 @@ public class Storage
}
// Make sure all files are available and of correct length
// The files should all exist as they have been created with zero length by createFilesFromNames()
for (int i = 0; i < rafs.length; i++)
{
long length = RAFfile[i].length();
@@ -725,6 +733,7 @@ public class Storage
SnarkManager.instance().addMessage(msg);
_util.debug(msg, Snark.ERROR);
changed = true;
resume = true;
_probablyComplete = false; // to force RW
synchronized(RAFlock[i]) {
checkRAF(i);
@@ -798,26 +807,54 @@ public class Storage
}
}
/** this calls openRAF(); caller must synnchronize and call closeRAF() */
/**
* This creates a (presumably) sparse file so that reads won't fail with IOE.
* Sets isSparse[nr] = true. balloonFile(nr) should be called later to
* defrag the file.
*
* This calls openRAF(); caller must synchronize and call closeRAF().
*/
private void allocateFile(int nr) throws IOException
{
// caller synchronized
openRAF(nr, false); // RW
// XXX - Is this the best way to make sure we have enough space for
// the whole file?
long remaining = lengths[nr];
if (listener != null)
listener.storageCreateFile(this, names[nr], remaining);
rafs[nr].setLength(remaining);
// don't bother ballooning later on Windows since there is no sparse file support
// until JDK7 using the JSR-203 interface.
// RAF seeks/writes do not create sparse files.
// Windows will zero-fill up to the point of the write, which
// will make the file fairly unfragmented, on average, at least until
// near the end where it will get exponentially more fragmented.
if (!_isWindows)
isSparse[nr] = true;
// caller will close rafs[nr]
if (listener != null)
listener.storageAllocated(this, lengths[nr]);
}
/**
* This "balloons" the file with zeros to eliminate disk fragmentation.,
* Overwrites the entire file with zeros. Sets isSparse[nr] = false.
*
* Caller must synchronize and call checkRAF() or openRAF().
* @since 0.9.1
*/
private void balloonFile(int nr) throws IOException
{
_util.debug("Ballooning " + nr + ": " + RAFfile[nr], Snark.INFO);
long remaining = lengths[nr];
final int ZEROBLOCKSIZE = (int) Math.min(remaining, 32*1024);
byte[] zeros = new byte[ZEROBLOCKSIZE];
rafs[nr].seek(0);
while (remaining > 0) {
int size = (int) Math.min(remaining, ZEROBLOCKSIZE);
rafs[nr].write(zeros, 0, size);
remaining -= size;
}
// caller will close rafs[nr]
if (listener != null)
listener.storageAllocated(this, lengths[nr]);
isSparse[nr] = false;
}
@@ -873,54 +910,66 @@ public class Storage
* matches), otherwise false.
* @exception IOException when some storage related error occurs.
*/
public boolean putPiece(int piece, byte[] ba) throws IOException
public boolean putPiece(PartialPiece pp) throws IOException
{
// First check if the piece is correct.
// Copy the array first to be paranoid.
byte[] bs = ba.clone();
int length = bs.length;
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
if (listener != null)
listener.storageChecked(this, piece, correctHash);
if (!correctHash)
return false;
int piece = pp.getPiece();
try {
synchronized(bitfield) {
if (bitfield.get(piece))
return true; // No need to store twice.
}
synchronized(bitfield)
{
if (bitfield.get(piece))
return true; // No need to store twice.
}
// TODO alternative - check hash on the fly as we write to the file,
// to save another I/O pass
boolean correctHash = metainfo.checkPiece(pp);
if (listener != null)
listener.storageChecked(this, piece, correctHash);
if (!correctHash) {
return false;
}
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) piece_size;
int i = 0;
long raflen = lengths[i];
while (start > raflen)
{
i++;
start -= raflen;
raflen = lengths[i];
}
// Early typecast, avoid possibly overflowing a temp integer
long start = (long) piece * (long) piece_size;
int i = 0;
long raflen = lengths[i];
while (start > raflen) {
i++;
start -= raflen;
raflen = lengths[i];
}
int written = 0;
int off = 0;
while (written < length)
{
int need = length - written;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(RAFlock[i])
{
checkRAF(i);
rafs[i].seek(start);
rafs[i].write(bs, off + written, len);
}
written += len;
if (need - len > 0)
{
i++;
raflen = lengths[i];
start = 0;
int written = 0;
int off = 0;
int length = metainfo.getPieceLength(piece);
while (written < length) {
int need = length - written;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(RAFlock[i]) {
checkRAF(i);
if (isSparse[i]) {
// If the file is a newly created sparse file,
// AND we aren't skipping it, balloon it with all
// zeros to un-sparse it by allocating the space.
// Obviously this could take a while.
// Once we have written to it, it isn't empty/sparse any more.
if (priorities == null || priorities[i] >= 0)
balloonFile(i);
else
isSparse[i] = false;
}
rafs[i].seek(start);
//rafs[i].write(bs, off + written, len);
pp.write(rafs[i], off + written, len);
}
written += len;
if (need - len > 0) {
i++;
raflen = lengths[i];
start = 0;
}
}
} finally {
pp.release();
}
changed = true;
@@ -957,7 +1006,7 @@ public class Storage
}
return true;
}
}
/**
* This is a dup of MetaInfo.getPieceLength() but we need it
@@ -1022,7 +1071,7 @@ public class Storage
/**
* Close unused RAFs - call periodically
*/
private static final long RAFCloseDelay = 7*60*1000;
private static final long RAFCloseDelay = 4*60*1000;
public void cleanRAFs() {
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
for (int i = 0; i < RAFlock.length; i++) {

View File

@@ -23,7 +23,7 @@ package org.klomp.snark;
/**
* Callback used when Storage changes.
*/
public interface StorageListener
interface StorageListener
{
/**
* Called when the storage creates a new file of a given length.

View File

@@ -0,0 +1,28 @@
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
package org.klomp.snark;
/**
* A structure for known trackers
*
* @since 0.9.1
*/
public class Tracker {
public final String name;
public final String announceURL;
public final String baseURL;
public final boolean supportsDetails;
/**
* @param baseURL The web site, may be null
*/
public Tracker(String name, String announceURL, String baseURL) {
this.name = name;
this.announceURL = announceURL;
this.baseURL = baseURL;
this.supportsDetails = name.contains("tracker2.postman.i2p");
}
}

View File

@@ -28,6 +28,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -35,18 +36,32 @@ import java.util.Random;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.util.Clock;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Informs metainfo tracker of events and gets new peers for peer
* coordinator.
*
* start() creates a thread and starts it.
* At the end of each run, a TimedEvent is queued on the SimpleTimer2 queue.
* The TimedEvent creates a new thread and starts it, so it does not
* clog SimpleTimer2.
*
* The thread runs one pass through the trackers, the PEX, and the DHT,
* then queues a new TimedEvent and exits.
*
* Thus there are only threads that are actively announcing, not one thread per torrent forever.
*
* start() may be called again after halt().
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class TrackerClient extends I2PAppThread
{
public class TrackerClient implements Runnable {
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(TrackerClient.class);
private static final String NO_EVENT = "";
private static final String STARTED_EVENT = "started";
@@ -56,25 +71,40 @@ public class TrackerClient extends I2PAppThread
private final static int SLEEP = 5; // 5 minutes.
private final static int DELAY_MIN = 2000; // 2 secs.
private final static int DELAY_MUL = 1500; // 1.5 secs.
private final static int DELAY_RAND = 6*1000;
private final static int MAX_REGISTER_FAILS = 10; // * INITIAL_SLEEP = 15m to register
private final static int INITIAL_SLEEP = 90*1000;
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
private I2PSnarkUtil _util;
private final I2PSnarkUtil _util;
private final MetaInfo meta;
private final String infoHash;
private final String peerID;
private final String additionalTrackerURL;
private final PeerCoordinator coordinator;
private final Snark snark;
private final int port;
private final String _threadName;
private boolean stop;
private boolean started;
private volatile boolean stop = true;
private volatile boolean started;
private volatile boolean _initialized;
private volatile int _runCount;
// running thread so it can be interrupted
private volatile Thread _thread;
// queued event so it can be cancelled
private volatile SimpleTimer2.TimedEvent _event;
// these 2 used in loop()
private volatile boolean runStarted;
private volatile int consecutiveFails;
private volatile boolean _fastUnannounce;
private List trackers;
private final List<Tracker> trackers;
/**
* Call start() to start it.
*
* @param meta null if in magnet mode
* @param additionalTrackerURL may be null, from the ?tr= param in magnet mode, otherwise ignored
*/
@@ -84,7 +114,7 @@ public class TrackerClient extends I2PAppThread
super();
// Set unique name.
String id = urlencode(snark.getID());
setName("TrackerClient " + id.substring(id.length() - 12));
_threadName = "TrackerClient " + id.substring(id.length() - 12);
_util = util;
this.meta = meta;
this.additionalTrackerURL = additionalTrackerURL;
@@ -92,12 +122,23 @@ public class TrackerClient extends I2PAppThread
this.snark = snark;
this.port = 6881; //(port == -1) ? 9 : port;
this.infoHash = urlencode(snark.getInfoHash());
this.peerID = urlencode(snark.getID());
this.trackers = new ArrayList(2);
}
@Override
public void start() {
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
super.start();
public synchronized void start() {
if (!stop) {
if (_log.shouldLog(Log.WARN))
_log.warn("Already started: " + _threadName);
return;
}
stop = false;
consecutiveFails = 0;
runStarted = false;
_fastUnannounce = false;
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
_thread.start();
started = true;
}
@@ -106,11 +147,50 @@ public class TrackerClient extends I2PAppThread
/**
* Interrupts this Thread to stop it.
* @param fast if true, limit the life of the unannounce threads
*/
public void halt()
{
stop = true;
this.interrupt();
public synchronized void halt(boolean fast) {
boolean wasStopped = stop;
if (wasStopped) {
if (_log.shouldLog(Log.WARN))
_log.warn("Already stopped: " + _threadName);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("Stopping: " + _threadName);
stop = true;
}
SimpleTimer2.TimedEvent e = _event;
if (e != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cancelling next announce " + _threadName);
e.cancel();
_event = null;
}
Thread t = _thread;
if (t != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Interrupting " + t.getName());
t.interrupt();
}
_fastUnannounce = true;
if (!wasStopped)
unannounce();
}
private void queueLoop(long delay) {
_event = new Runner(delay);
}
private class Runner extends SimpleTimer2.TimedEvent {
public Runner(long delay) {
super(SimpleTimer2.getInstance(), delay);
}
public void timeReached() {
_event = null;
_thread = new I2PAppThread(TrackerClient.this, _threadName + " #" + (++_runCount), true);
_thread.start();
}
}
private boolean verifyConnected() {
@@ -123,20 +203,51 @@ public class TrackerClient extends I2PAppThread
return !stop && _util.connected();
}
@Override
public void run()
{
String infoHash = urlencode(snark.getInfoHash());
String peerID = urlencode(snark.getID());
/**
* Setup the first time only,
* then one pass (usually) through the trackers, PEX, and DHT.
* This will take several seconds to several minutes.
*/
public void run() {
long begin = Clock.getInstance().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Start " + Thread.currentThread().getName());
try {
if (!_initialized) {
setup();
// FIXME dht
if (trackers.isEmpty()) {
stop = true;
return;
}
_initialized = true;
// FIXME only when starting everybody at once, not for a single torrent
long delay = I2PAppContext.getGlobalContext().random().nextInt(30*1000);
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {}
}
loop();
} finally {
// don't hold ref
_thread = null;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Finish " + Thread.currentThread().getName() +
" after " + DataHelper.formatDuration(Clock.getInstance().now() - begin));
}
}
/**
* Do this one time only (not every time it is started).
* @since 0.9.1
*/
private void setup() {
// Construct the list of trackers for this torrent,
// starting with the primary one listed in the metainfo,
// 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);
String primary = null;
if (meta != null)
primary = meta.getAnnounce();
@@ -192,58 +303,32 @@ public class TrackerClient extends I2PAppThread
this.snark.stopTorrent();
return;
}
}
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft();
boolean completed = (left == 0);
/**
* Announce to all the trackers, get peers from PEX and DHT, then queue up a SimpleTimer2 event.
* This will take several seconds to several minutes.
* @since 0.9.1
*/
private void loop() {
try
{
if (!verifyConnected()) return;
boolean runStarted = false;
boolean firstTime = true;
int consecutiveFails = 0;
Random r = I2PAppContext.getGlobalContext().random();
while(!stop)
{
if (!verifyConnected()) {
stop = true;
return;
}
// Local DHT tracker announce
if (_util.getDHT() != null)
_util.getDHT().announce(snark.getInfoHash());
try
{
// Sleep some minutes...
// Sleep the minimum interval for all the trackers, but 60s minimum
// except for the first time...
int delay;
int random = r.nextInt(120*1000);
if (firstTime) {
delay = r.nextInt(30*1000);
firstTime = false;
} else if (completed && runStarted)
delay = 3*SLEEP*60*1000 + random;
else if (snark.getTrackerProblems() != null && ++consecutiveFails < MAX_CONSEC_FAILS)
delay = INITIAL_SLEEP;
else
// sleep a while, when we wake up we will contact only the trackers whose intervals have passed
delay = SLEEP*60*1000 + random;
if (delay > 0)
Thread.sleep(delay);
}
catch(InterruptedException interrupt)
{
// ignore
}
if (stop)
break;
if (!verifyConnected()) return;
uploaded = coordinator.getUploaded();
downloaded = coordinator.getDownloaded();
left = coordinator.getLeft(); // -1 in magnet mode
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft(); // -1 in magnet mode
boolean completed = (left == 0);
// First time we got a complete download?
String event;
@@ -260,7 +345,7 @@ public class TrackerClient extends I2PAppThread
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
Tracker tr = (Tracker)iter.next();
if ((!stop) && (!tr.stop) &&
(completed || coordinator.needPeers()) &&
(completed || coordinator.needOutboundPeers() || !tr.started) &&
(event.equals(COMPLETED_EVENT) || System.currentTimeMillis() > tr.lastRequestTime + tr.interval))
{
try
@@ -292,18 +377,18 @@ public class TrackerClient extends I2PAppThread
}
}
if ( (left != 0) && (!completed) ) {
if (coordinator.needOutboundPeers()) {
// we only want to talk to new people if we need things
// from them (duh)
List<Peer> ordered = new ArrayList(peers);
Collections.shuffle(ordered, r);
Iterator<Peer> it = ordered.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
// FIXME if id == us || dest == us continue;
// only delay if we actually make an attempt to add peer
if(coordinator.addPeer(cur) && it.hasNext()) {
int delay = (DELAY_MUL * r.nextInt(10)) + DELAY_MIN;
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
@@ -335,13 +420,16 @@ public class TrackerClient extends I2PAppThread
tr.interval = LONG_SLEEP; // slow down
}
}
} else {
_util.debug("Not announcing to " + tr.announce + " last announce was " +
new Date(tr.lastRequestTime) + " interval is " + DataHelper.formatDuration(tr.interval), Snark.INFO);
}
if ((!tr.stop) && maxSeenPeers < tr.seenPeers)
maxSeenPeers = tr.seenPeers;
} // *** end of trackers loop here
// Get peers from PEX
if (left > 0 && coordinator.needPeers() && (meta == null || !meta.isPrivate()) && !stop) {
if (coordinator.needOutboundPeers() && (meta == null || !meta.isPrivate()) && !stop) {
Set<PeerID> pids = coordinator.getPEXPeers();
if (!pids.isEmpty()) {
_util.debug("Got " + pids.size() + " from PEX", Snark.INFO);
@@ -351,21 +439,23 @@ public class TrackerClient extends I2PAppThread
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = (DELAY_MUL * r.nextInt(10)) + DELAY_MIN;
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
} else {
_util.debug("Not getting PEX peers", Snark.INFO);
}
// Get peers from DHT
// FIXME this needs to be in its own thread
if (_util.getDHT() != null && (meta == null || !meta.isPrivate()) && !stop) {
int numwant;
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
if (event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
numwant = 1;
else
numwant = _util.getMaxConnections();
@@ -387,21 +477,51 @@ public class TrackerClient extends I2PAppThread
}
Collections.shuffle(peers, r);
Iterator<Peer> it = peers.iterator();
while ((!stop) && it.hasNext()) {
while ((!stop) && it.hasNext() && coordinator.needOutboundPeers()) {
Peer cur = it.next();
if (coordinator.addPeer(cur) && it.hasNext()) {
int delay = (DELAY_MUL * r.nextInt(10)) + DELAY_MIN;
int delay = r.nextInt(DELAY_RAND) + DELAY_MIN;
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
}
}
}
} else {
_util.debug("Not getting DHT peers", Snark.INFO);
}
// we could try and total the unique peers but that's too hard for now
snark.setTrackerSeenPeers(maxSeenPeers);
if (stop)
return;
if (!runStarted)
_util.debug(" Retrying in one minute...", Snark.DEBUG);
try {
// Sleep some minutes...
// Sleep the minimum interval for all the trackers, but 60s minimum
int delay;
int random = r.nextInt(120*1000);
if (completed && runStarted)
delay = 3*SLEEP*60*1000 + random;
else if (snark.getTrackerProblems() != null && ++consecutiveFails < MAX_CONSEC_FAILS)
delay = INITIAL_SLEEP;
else
// sleep a while, when we wake up we will contact only the trackers whose intervals have passed
delay = SLEEP*60*1000 + random;
if (delay > 20*1000) {
// put ourselves on SimpleTimer2
if (_log.shouldLog(Log.DEBUG))
_log.debug("Requeueing in " + DataHelper.formatDuration(delay) + ": " + Thread.currentThread().getName());
queueLoop(delay);
return;
} else if (delay > 0) {
Thread.sleep(delay);
}
} catch(InterruptedException interrupt) {}
} // *** end of while loop
} // try
catch (Throwable t)
@@ -410,26 +530,61 @@ public class TrackerClient extends I2PAppThread
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{
// Local DHT tracker unannounce
if (_util.getDHT() != null)
_util.getDHT().unannounce(snark.getInfoHash());
}
/**
* Creates a thread for each tracker in parallel if tunnel is still open
* @since 0.9.1
*/
private void unannounce() {
// Local DHT tracker unannounce
if (_util.getDHT() != null)
_util.getDHT().unannounce(snark.getInfoHash());
int i = 0;
for (Tracker tr : trackers) {
if (_util.connected() &&
tr.started && (!tr.stop) && tr.trackerProblems == null) {
try {
(new I2PAppThread(new Unannouncer(tr), _threadName + " U" + (++i), true)).start();
} catch (OutOfMemoryError oom) {
// probably ran out of threads, ignore
tr.reset();
}
} else {
tr.reset();
}
}
}
/**
* Send "stopped" to a single tracker
* @since 0.9.1
*/
private class Unannouncer implements Runnable {
private final Tracker tr;
public Unannouncer(Tracker tr) {
this.tr = tr;
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Running unannounce " + _threadName + " to " + tr.announce);
long uploaded = coordinator.getUploaded();
long downloaded = coordinator.getDownloaded();
long left = coordinator.getLeft();
try
{
// try to contact everybody we can
// Don't try to restart I2CP connection just to say goodbye
for (Iterator iter = trackers.iterator(); iter.hasNext(); ) {
if (!_util.connected()) return;
Tracker tr = (Tracker)iter.next();
if (tr.started && (!tr.stop) && tr.trackerProblems == null)
doRequest(tr, infoHash, peerID, uploaded,
if (_util.connected()) {
if (tr.started && (!tr.stop) && tr.trackerProblems == null)
doRequest(tr, infoHash, peerID, uploaded,
downloaded, left, STOPPED_EVENT);
}
}
}
catch(IOException ioe) { /* ignored */ }
}
tr.reset();
}
}
private TrackerInfo doRequest(Tracker tr, String infoHash,
@@ -459,7 +614,7 @@ public class TrackerClient extends I2PAppThread
if (! event.equals(NO_EVENT))
buf.append("&event=").append(event);
buf.append("&numwant=");
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needPeers())
if (left == 0 || event.equals(STOPPED_EVENT) || !coordinator.needOutboundPeers())
buf.append('0');
else
buf.append(_util.getMaxConnections());
@@ -467,7 +622,9 @@ public class TrackerClient extends I2PAppThread
_util.debug("Sending TrackerClient request: " + s, Snark.INFO);
tr.lastRequestTime = System.currentTimeMillis();
File fetched = _util.get(s);
// Don't wait for a response to stopped when shutting down
boolean fast = _fastUnannounce && event.equals(STOPPED_EVENT);
File fetched = _util.get(s, true, fast ? -1 : 0);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
@@ -526,7 +683,7 @@ public class TrackerClient extends I2PAppThread
* @return true for i2p hosts only
* @since 0.7.12
*/
static boolean isValidAnnounce(String ann) {
public static boolean isValidAnnounce(String ann) {
URL url;
try {
url = new URL(ann);
@@ -556,6 +713,13 @@ public class TrackerClient extends I2PAppThread
announce = a;
isPrimary = p;
interval = INITIAL_SLEEP;
}
/**
* Call before restarting
* @since 0.9.1
*/
public void reset() {
lastRequestTime = 0;
trackerProblems = null;
stop = false;

View File

@@ -0,0 +1,347 @@
package org.klomp.snark.web;
/*
* Released into the public domain
* with no warranty of any kind, either expressed or implied.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import net.i2p.I2PAppContext;
import net.i2p.client.streaming.I2PSocketEepGet;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.crypto.SHA1;
import net.i2p.util.EepGet;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SecureFile;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
import org.klomp.snark.SnarkManager;
import org.klomp.snark.Storage;
/**
* A cancellable torrent file downloader.
* We extend Snark so its status may be easily listed in the
* web table without adding a lot of code there.
*
* Upon successful download, this Snark will be deleted and
* a "real" Snark created.
*
* The methods return values similar to a Snark in magnet mode.
* A fake info hash, which is the SHA1 of the URL, is returned
* to prevent duplicates.
*
* This Snark may be stopped and restarted, although a partially
* downloaded file is discarded.
*
* @since 0.9.1 Moved from I2PSnarkUtil
*/
public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnable {
private final I2PAppContext _ctx;
private final Log _log;
private final SnarkManager _mgr;
private final String _url;
private final byte[] _fakeHash;
private final String _name;
private volatile long _remaining = -1;
private volatile long _total = -1;
private volatile long _transferred;
private volatile boolean _isRunning;
private volatile boolean _active;
private volatile long _started;
private String _failCause;
private Thread _thread;
private EepGet _eepGet;
private static final int RETRIES = 3;
/**
* Caller should call _mgr.addDownloader(this), which
* will start things off.
*/
public FetchAndAdd(I2PAppContext ctx, SnarkManager mgr, String url) {
// magnet constructor
super(mgr.util(), "Torrent download",
null, null, null, null, null, false, null);
_ctx = ctx;
_log = ctx.logManager().getLog(FetchAndAdd.class);
_mgr = mgr;
_url = url;
_name = "* " + _("Download torrent file from {0}", url);
byte[] fake = null;
try {
fake = SHA1.getInstance().digest(url.getBytes("ISO-8859-1"));
} catch (IOException ioe) {}
_fakeHash = fake;
}
/**
* Set off by startTorrent()
*/
public void run() {
_mgr.addMessage(_("Fetching {0}", urlify(_url)));
File file = get();
if (!_isRunning) // stopped?
return;
_isRunning = false;
if (file != null && file.exists() && file.length() > 0) {
// remove this in snarks
_mgr.deleteMagnet(this);
add(file);
} else {
_mgr.addMessage(_("Torrent was not retrieved from {0}", urlify(_url)) +
((_failCause != null) ? (": " + _failCause) : ""));
}
if (file != null)
file.delete();
}
/**
* Copied from I2PSnarkUtil so we may add ourselves as a status listener
* @return null on failure
*/
private File get() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetching [" + _url + "]");
File out = null;
try {
out = SecureFile.createTempFile("torrentFile", null, _mgr.util().getTempDir());
} catch (IOException ioe) {
_log.error("temp file error", ioe);
_mgr.addMessage("Temp file error: " + ioe);
if (out != null)
out.delete();
return null;
}
out.deleteOnExit();
if (!_mgr.util().connected()) {
_mgr.addMessage(_("Opening the I2P tunnel"));
if (!_mgr.util().connect())
return null;
}
I2PSocketManager manager = _mgr.util().getSocketManager();
if (manager == null)
return null;
_eepGet = new I2PSocketEepGet(_ctx, manager, RETRIES, out.getAbsolutePath(), _url);
_eepGet.addStatusListener(this);
if (_eepGet.fetch()) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch successful [" + _url + "]: size=" + out.length());
return out;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Fetch failed [" + _url + ']');
out.delete();
return null;
}
}
/**
* Tell SnarkManager to copy the torrent file over and add it to the Snarks list.
* This Snark may then be deleted.
*/
private void add(File file) {
_mgr.addMessage(_("Torrent fetched from {0}", urlify(_url)));
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] fileInfoHash = new byte[20];
String name = MetaInfo.getNameAndInfoHash(in, fileInfoHash);
try { in.close(); } catch (IOException ioe) {}
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
if (snark != null) {
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
}
name = Storage.filterName(name);
name = name + ".torrent";
File torrentFile = new File(_mgr.getDataDir(), name);
String canonical = torrentFile.getCanonicalPath();
if (torrentFile.exists()) {
if (_mgr.getTorrent(canonical) != null)
_mgr.addMessage(_("Torrent already running: {0}", name));
else
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
} else {
// This may take a LONG time to create the storage.
_mgr.copyAndAddTorrent(file, canonical);
}
} catch (IOException ioe) {
_mgr.addMessage(_("Torrent at {0} was not valid", urlify(_url)) + ": " + ioe.getMessage());
} catch (OutOfMemoryError oom) {
_mgr.addMessage(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + oom.getMessage());
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
}
// Snark overrides so all the buttons and stats on the web page work
@Override
public synchronized void startTorrent() {
if (_isRunning)
return;
// reset counters in case starting a second time
_remaining = -1;
// leave the total if we knew it before
//_total = -1;
_transferred = 0;
_failCause = null;
_started = _ctx.clock().now();
_isRunning = true;
_active = false;
_thread = new I2PAppThread(this, "Torrent File EepGet", true);
_thread.start();
}
@Override
public synchronized void stopTorrent() {
if (_thread != null && _isRunning) {
if (_eepGet != null)
_eepGet.stopFetching();
_thread.interrupt();
}
_isRunning = false;
_active = false;
}
@Override
public boolean isStopped() {
return !_isRunning;
}
@Override
public String getName() {
return _name;
}
@Override
public String getBaseName() {
return _name;
}
@Override
public byte[] getInfoHash() {
return _fakeHash;
}
/**
* @return torrent file size or -1
*/
@Override
public long getTotalLength() {
return _total;
}
/**
* @return -1 when done so the web will list us as "complete" instead of "seeding"
*/
@Override
public long getRemainingLength() {
long rv = _remaining;
return rv > 0 ? rv : -1;
}
/**
* @return torrent file bytes remaining or -1
*/
@Override
public long getNeededLength() {
return _remaining;
}
@Override
public long getDownloadRate() {
if (_isRunning && _active) {
long time = _ctx.clock().now() - _started;
if (time > 1000) {
long rv = (_transferred * 1000) / time;
if (rv >= 100)
return rv;
}
}
return 0;
}
@Override
public long getDownloaded() {
return _total - _remaining;
}
@Override
public int getPeerCount() {
return (_isRunning && _active && _transferred > 0) ? 1 : 0;
}
@Override
public int getTrackerSeenPeers() {
return (_transferred > 0) ? 1 : 0;
}
// End Snark overrides
// EepGet status listeners to maintain the state for the web page
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
}
_transferred = bytesTransferred;
if (cause != null)
_failCause = cause.toString();
_active = false;
}
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
_total = bytesRemaining + currentWrite + alreadyTransferred;
}
_transferred = bytesTransferred;
_active = true;
}
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
_total = bytesRemaining + alreadyTransferred;
}
_transferred = bytesTransferred;
_active = false;
}
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
if (bytesRemaining >= 0) {
_remaining = bytesRemaining;
}
_transferred = bytesTransferred;
_active = false;
}
public void headerReceived(String url, int attemptNum, String key, String val) {}
public void attempting(String url) {}
// End of EepGet status listeners
private String _(String s) {
return _mgr.util().getString(s);
}
private String _(String s, String o) {
return _mgr.util().getString(s, o);
}
private static String urlify(String s) {
return I2PSnarkServlet.urlify(s);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
@@ -192,7 +193,7 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
}
}
/** @return non-null */
/** @return A copy, non-null */
List<I2PSession> getSessions() {
return new ArrayList(_sessions);
}
@@ -207,6 +208,11 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
_sessions.remove(session);
}
/**
* Generic options used for clients and servers.
* NOT a copy, Do NOT modify for per-connection options, make a copy.
* @return NOT a copy, do NOT modify for per-connection options
*/
public Properties getClientOptions() { return _clientOptions; }
private void addtask(I2PTunnelTask tsk) {
@@ -326,10 +332,14 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
/**
* Configure the extra I2CP options to use in any subsequent I2CP sessions.
* Generic options used for clients and servers
* Usage: "clientoptions[ key=value]*" .
*
* Sets the event "clientoptions_onResult" = "ok" after completion.
*
* Deprecated To be made private, use setClientOptions().
* This does NOT update a running TunnelTask.
*
* @param args each args[i] is a key=value pair to add to the options
* @param l logger to receive events and output
*/
@@ -347,6 +357,27 @@ public class I2PTunnel extends EventDispatcherImpl implements Logging {
notifyEvent("clientoptions_onResult", "ok");
}
/**
* Generic options used for clients and servers.
* This DOES update a running TunnelTask, but NOT the session.
* A more efficient runClientOptions().
*
* @param opts non-null
* @since 0.9.1
*/
public void setClientOptions(Properties opts) {
for (Iterator iter = _clientOptions.keySet().iterator(); iter.hasNext();) {
Object key = iter.next();
if (!opts.containsKey(key))
iter.remove();
}
_clientOptions.putAll(opts);
for (I2PTunnelTask task : tasks) {
task.optionsUpdated(this);
}
notifyEvent("clientoptions_onResult", "ok");
}
/**
* Run the server pointing at the host and port specified using the private i2p
* destination loaded from the specified file. <p />

View File

@@ -471,8 +471,9 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
/**
* create the default options (using the default timeout, etc)
*
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
*/
protected I2PSocketOptions getDefaultOptions() {
Properties defaultOpts = getTunnel().getClientOptions();
@@ -483,8 +484,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
/**
* create the default options (using the default timeout, etc)
*
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
* Do not use overrides for per-socket options.
*/
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
Properties defaultOpts = getTunnel().getClientOptions();
@@ -495,6 +498,22 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
return opts;
}
/**
* Update the I2PSocketManager.
*
* @since 0.9.1
*/
@Override
public void optionsUpdated(I2PTunnel tunnel) {
if (getTunnel() != tunnel)
return;
I2PSocketManager sm = _ownDest ? sockMgr : socketManager;
if (sm == null)
return;
Properties props = tunnel.getClientOptions();
sm.setDefaultOptions(sm.buildOptions(props));
}
/**
* Create a new I2PSocket towards to the specified destination,
* adding it to the list of connections actually managed by this

View File

@@ -132,8 +132,9 @@ public class I2PTunnelConnectClient extends I2PTunnelHTTPClientBase implements R
}
/**
* create the default options (using the default timeout, etc)
*
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
*/
@Override
protected I2PSocketOptions getDefaultOptions() {

View File

@@ -225,7 +225,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
/**
* create the default options (using the default timeout, etc)
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
* unused?
*/
@Override
@@ -236,6 +238,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
//if (!defaultOpts.contains("i2p.streaming.inactivityTimeout"))
// defaultOpts.setProperty("i2p.streaming.inactivityTimeout", ""+DEFAULT_READ_TIMEOUT);
// delayed start
verifySocketManager();
I2PSocketOptions opts = sockMgr.buildOptions(defaultOpts);
if(!defaultOpts.containsKey(I2PSocketOptions.PROP_CONNECT_TIMEOUT)) {
opts.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
@@ -244,8 +248,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
/**
* create the default options (using the default timeout, etc)
*
* Create the default options (using the default timeout, etc).
* Warning, this does not make a copy of I2PTunnel's client options,
* it modifies them directly.
* Do not use overrides for per-socket options.
*/
@Override
protected I2PSocketOptions getDefaultOptions(Properties overrides) {
@@ -330,6 +336,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
String ahelperKey = null;
String userAgent = null;
String authorization = null;
int remotePort = 0;
while((line = reader.readLine(method)) != null) {
line = line.trim();
if(_log.shouldLog(Log.DEBUG)) {
@@ -486,10 +493,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
// Host becomes the destination's "{b32}.b32.i2p" string, or "i2p" on lookup failure
host = getHostName(destination);
if(requestURI.getPort() >= 0) {
// TODO support I2P ports someday
//if (port >= 0)
// host = host + ':' + port;
int rPort = requestURI.getPort();
if (rPort > 0) {
// Save it to put in the I2PSocketOptions,
remotePort = rPort;
/********
// but strip it from the URL
if(_log.shouldLog(Log.WARN)) {
_log.warn(getPrefix(requestId) + "Removing port from [" + request + "]");
}
@@ -500,6 +509,9 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
method = null;
break;
}
******/
} else {
remotePort = 80;
}
String query = requestURI.getRawQuery();
@@ -964,7 +976,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
// 1 == disconnect. see ConnectionOptions in the new streaming lib, which i
// dont want to hard link to here
//opts.setProperty("i2p.streaming.inactivityTimeoutAction", ""+1);
I2PSocket i2ps = createI2PSocket(clientDest, getDefaultOptions(opts));
I2PSocketOptions sktOpts = getDefaultOptions(opts);
if (remotePort > 0)
sktOpts.setPort(remotePort);
I2PSocket i2ps = createI2PSocket(clientDest, sktOpts);
byte[] data = newRequest.toString().getBytes("ISO-8859-1");
Runnable onTimeout = new OnTimeout(s, s.getOutputStream(), targetRequest, usingWWWProxy, currentProxy, requestId);
new I2PTunnelHTTPClientRunner(s, i2ps, sockLock, data, mySockets, onTimeout);
@@ -1174,8 +1189,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
}
public static final String DEFAULT_JUMP_SERVERS =
"http://i2host.i2p/cgi-bin/i2hostjump?," +
"http://stats.i2p/cgi-bin/jump.cgi?a=," +
"http://i2jump.i2p/";
"http://stats.i2p/cgi-bin/jump.cgi?a=";
//"http://i2jump.i2p/";
/**
* @param jumpServers comma- or space-separated list, or null
@@ -1207,15 +1222,21 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
StringTokenizer tok = new StringTokenizer(jumpServers, ", ");
while(tok.hasMoreTokens()) {
String jurl = tok.nextToken();
if(!jurl.startsWith("http://")) {
String jumphost;
try {
URI jURI = new URI(jurl);
String proto = jURI.getScheme();
jumphost = jURI.getHost();
if (proto == null || jumphost == null ||
!proto.toLowerCase(Locale.US).equals("http"))
continue;
jumphost = jumphost.toLowerCase(Locale.US);
if (!jumphost.endsWith(".i2p"))
continue;
} catch(URISyntaxException use) {
continue;
}
// Skip jump servers we don't know
String jumphost = jurl.substring(7); // "http://"
jumphost = jumphost.substring(0, jumphost.indexOf('/'));
if(!jumphost.endsWith(".i2p")) {
continue;
}
if(!jumphost.endsWith(".b32.i2p")) {
Destination dest = I2PAppContext.getGlobalContext().namingService().lookup(jumphost);
if(dest == null) {
@@ -1227,8 +1248,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn
out.write(jurl.getBytes());
out.write(uri.getBytes());
out.write("\">".getBytes());
out.write(jurl.substring(7).getBytes());
out.write(uri.getBytes());
// Translators: parameter is a host name
out.write(_("{0} jump service", jumphost).getBytes());
out.write("</a>\n".getBytes());
}
}

View File

@@ -77,7 +77,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
}
private void setupI2PTunnelHTTPServer(String spoofHost) {
_spoofHost = spoofHost;
_spoofHost = (spoofHost != null && spoofHost.trim().length() > 0) ? spoofHost.trim() : null;
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
}
@@ -96,6 +96,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
*/
@Override
protected void blockingHandle(I2PSocket socket) {
if (_log.shouldLog(Log.INFO))
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
long afterAccept = getTunnel().getContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many
@@ -115,8 +118,21 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
addEntry(headers, DEST32_HEADER, Base32.encode(socket.getPeerDestination().calculateHash().getData()) + ".b32.i2p");
addEntry(headers, DEST64_HEADER, socket.getPeerDestination().toBase64());
if ( (_spoofHost != null) && (_spoofHost.trim().length() > 0) )
setEntry(headers, "Host", _spoofHost);
// Port-specific spoofhost
Properties opts = getTunnel().getClientOptions();
String spoofHost;
int ourPort = socket.getLocalPort();
if (ourPort != 80 && ourPort > 0 && ourPort <= 65535 && opts != null) {
String portSpoof = opts.getProperty("spoofedHost." + ourPort);
if (portSpoof != null)
spoofHost = portSpoof.trim();
else
spoofHost = _spoofHost;
} else {
spoofHost = _spoofHost;
}
if (spoofHost != null)
setEntry(headers, "Host", spoofHost);
setEntry(headers, "Connection", "close");
// we keep the enc sent by the browser before clobbering it, since it may have
// been x-i2p-gzip
@@ -134,7 +150,6 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
// request from the socket, modifies the headers, sends the request to the
// server, reads the response headers, rewriting to include Content-encoding: x-i2p-gzip
// if it was one of the Accept-encoding: values, and gzip the payload
Properties opts = getTunnel().getClientOptions();
boolean allowGZIP = true;
if (opts != null) {
String val = opts.getProperty("i2ptunnel.gzip");

View File

@@ -111,6 +111,9 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable {
@Override
protected void blockingHandle(I2PSocket socket) {
if (_log.shouldLog(Log.INFO))
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
try {
String modifiedRegistration;
if(!this.method.equals("webirc")) {

View File

@@ -74,6 +74,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
protected int localPort = DEFAULT_LOCALPORT;
/**
* Warning, blocks in constructor while connecting to router and building tunnels;
* TODO move that to startRunning()
*
* @param privData Base64-encoded private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @throws IllegalArgumentException if the I2CP configuration is b0rked so
@@ -87,6 +90,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
/**
* Warning, blocks in constructor while connecting to router and building tunnels;
* TODO move that to startRunning()
*
* @param privkey file containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param privkeyname the name of the privKey file, not clear why we need this too
@@ -111,6 +117,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
/**
* Warning, blocks in constructor while connecting to router and building tunnels;
* TODO move that to startRunning()
*
* @param privData stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param privkeyname the name of the privKey file, not clear why we need this too
@@ -124,6 +133,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
/**
* Non-blocking
*
* @param sktMgr the existing socket manager
* @since 0.8.9
*/
@@ -142,6 +153,9 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
private static final int MAX_RETRIES = 4;
/**
* Warning, blocks while connecting to router and building tunnels;
* TODO move that to startRunning()
*
* @param privData stream containing the private key data,
* format is specified in {@link net.i2p.data.PrivateKeyFile PrivateKeyFile}
* @param privkeyname the name of the privKey file, not clear why we need this too
@@ -236,6 +250,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
/**
* Start running the I2PTunnelServer.
*
* TODO: Wait to connect to router until here.
*/
public void startRunning() {
// prevent JVM exit when running outside the router
@@ -295,6 +310,19 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
/**
* Update the I2PSocketManager.
*
* @since 0.9.1
*/
@Override
public void optionsUpdated(I2PTunnel tunnel) {
if (getTunnel() != tunnel || sockMgr == null)
return;
Properties props = tunnel.getClientOptions();
sockMgr.setDefaultOptions(sockMgr.buildOptions(props));
}
protected int getHandlerCount() {
int rv = DEFAULT_HANDLER_COUNT;
String cnt = getTunnel().getClientOptions().getProperty(PROP_HANDLER_COUNT);
@@ -408,7 +436,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());
_log.info("Incoming connection to '" + toString() + "' port " + socket.getLocalPort() +
" from: " + socket.getPeerDestination().calculateHash() + " port " + socket.getPort());
long afterAccept = I2PAppContext.getGlobalContext().clock().now();
long afterSocket = -1;
//local is fast, so synchronously. Does not need that many

View File

@@ -58,6 +58,15 @@ public abstract class I2PTunnelTask extends EventDispatcherImpl {
public abstract boolean close(boolean forced);
/**
* Notify the task that I2PTunnel's options have been updated.
* Extending classes should override and call I2PTunnel.getClientOptions(),
* then update the I2PSocketManager.
*
* @since 0.9.1
*/
public void optionsUpdated(I2PTunnel tunnel) {}
/**
* For tasks that don't call I2PTunnel.addSession() directly
* @since 0.8.13

View File

@@ -4,8 +4,10 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
@@ -29,6 +31,8 @@ import net.i2p.util.SecureFileOutputStream;
* These objects are bundled together under a TunnelControllerGroup where the
* entire group is stored / loaded from a single config file.
*
* This is the class used by several plugins to create tunnels, so
* take care to maintain the public methods as a stable API.
*/
public class TunnelController implements Logging {
private final Log _log;
@@ -152,8 +156,8 @@ public class TunnelController implements Logging {
}
String type = getType();
if ( (type == null) || (type.length() <= 0) ) {
if (_log.shouldLog(Log.WARN))
_log.warn("Cannot start the tunnel - no type specified");
if (_log.shouldLog(Log.ERROR))
_log.error("Cannot start the tunnel - no type specified");
return;
}
// Config options may have changed since instantiation, so do this again.
@@ -324,6 +328,7 @@ public class TunnelController implements Logging {
_log.info("Releasing session " + s);
TunnelControllerGroup.getInstance().release(this, s);
}
// _sessions.clear() ????
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No sessions to release? for " + getName());
@@ -382,20 +387,27 @@ public class TunnelController implements Logging {
}
}
private void setSessionOptions() {
List opts = new ArrayList();
for (Iterator iter = _config.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = _config.getProperty(key);
/**
* These are the ones stored with a prefix of "option."
*
* @return keys with the "option." prefix stripped
* @since 0.9.1 Much better than getClientOptions()
*/
public Properties getClientOptionProps() {
Properties opts = new Properties();
for (Map.Entry e : _config.entrySet()) {
String key = (String) e.getKey();
if (key.startsWith("option.")) {
key = key.substring("option.".length());
opts.add(key + "=" + val);
String val = (String) e.getValue();
opts.setProperty(key, val);
}
}
String args[] = new String[opts.size()];
for (int i = 0; i < opts.size(); i++)
args[i] = (String)opts.get(i);
_tunnel.runClientOptions(args, this);
return opts;
}
private void setSessionOptions() {
_tunnel.setClientOptions(getClientOptionProps());
}
private void setI2CPOptions() {
@@ -429,25 +441,59 @@ public class TunnelController implements Logging {
startTunnel();
}
/**
* As of 0.9.1, updates the options on an existing session
*/
public void setConfig(Properties config, String prefix) {
Properties props = new Properties();
for (Iterator iter = config.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = config.getProperty(key);
for (Map.Entry e : config.entrySet()) {
String key = (String) e.getKey();
if (key.startsWith(prefix)) {
key = key.substring(prefix.length());
String val = (String) e.getValue();
props.setProperty(key, val);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Set prop [" + key + "] to [" + val + "]");
}
}
_config = props;
// Set up some per-type defaults
// This really isn't the best spot to do this but for servers in particular,
// it's hard to override settings in the subclass since the session connect
// is done in the I2PTunnelServer constructor.
String type = getType();
if (type != null) {
if (type.equals("httpserver") || type.equals("streamrserver")) {
if (!_config.containsKey("option.shouldBundleReplyInfo"))
_config.setProperty("option.shouldBundleReplyInfo", "false");
} else if (type.contains("irc") || type.equals("streamrclient")) {
// maybe a bad idea for ircclient if DCC is enabled
if (!_config.containsKey("option.crypto.tagsToSend"))
_config.setProperty("option.crypto.tagsToSend", "20");
if (!_config.containsKey("option.crypto.lowTagThreshold"))
_config.setProperty("option.crypto.lowTagThreshold", "14");
}
}
// tell i2ptunnel, who will tell the TunnelTask, who will tell the SocketManager
setSessionOptions();
if (_running && _sessions != null) {
for (I2PSession s : _sessions) {
// tell the router via the session
if (!s.isClosed()) {
s.updateOptions(_tunnel.getClientOptions());
}
}
}
}
/**
* @return a copy
*/
public Properties getConfig(String prefix) {
Properties rv = new Properties();
for (Iterator iter = _config.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = _config.getProperty(key);
for (Map.Entry e : _config.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
rv.setProperty(prefix + key, val);
}
return rv;
@@ -458,19 +504,27 @@ public class TunnelController implements Logging {
public String getDescription() { return _config.getProperty("description"); }
public String getI2CPHost() { return _config.getProperty("i2cpHost"); }
public String getI2CPPort() { return _config.getProperty("i2cpPort"); }
/**
* These are the ones with a prefix of "option."
*
* @return one big string of "key=val key=val ..."
* @deprecated why would you want this? Use getClientOptionProps() instead
*/
public String getClientOptions() {
StringBuilder opts = new StringBuilder(64);
for (Iterator iter = _config.keySet().iterator(); iter.hasNext(); ) {
String key = (String)iter.next();
String val = _config.getProperty(key);
for (Map.Entry e : _config.entrySet()) {
String key = (String) e.getKey();
if (key.startsWith("option.")) {
key = key.substring("option.".length());
String val = (String) e.getValue();
if (opts.length() > 0) opts.append(' ');
opts.append(key).append('=').append(val);
}
}
return opts.toString();
}
public String getListenOnInterface() { return _config.getProperty("interface"); }
public String getTargetHost() { return _config.getProperty("targetHost"); }
public String getTargetPort() { return _config.getProperty("targetPort"); }
@@ -484,6 +538,7 @@ public class TunnelController implements Logging {
/** default true */
public boolean getStartOnLoad() { return Boolean.valueOf(_config.getProperty("startOnLoad", "true")).booleanValue(); }
public boolean getPersistentClientKey() { return Boolean.valueOf(_config.getProperty("option.persistentClientKey")).booleanValue(); }
public String getMyDestination() {
if (_tunnel != null) {
List<I2PSession> sessions = _tunnel.getSessions();
@@ -523,8 +578,14 @@ public class TunnelController implements Logging {
return true;
}
/**
* A text description of the tunnel.
* @deprecated unused
*/
public void getSummary(StringBuilder buf) {
String type = getType();
buf.append(type);
/****
if ("httpclient".equals(type))
getHttpClientSummary(buf);
else if ("client".equals(type))
@@ -535,8 +596,10 @@ public class TunnelController implements Logging {
getHttpServerSummary(buf);
else
buf.append("Unknown type ").append(type);
****/
}
/****
private void getHttpClientSummary(StringBuilder buf) {
String description = getDescription();
if ( (description != null) && (description.trim().length() > 0) )
@@ -628,7 +691,11 @@ public class TunnelController implements Logging {
}
}
}
****/
/**
*
*/
public void log(String s) {
synchronized (this) {
_messages.add(s);

View File

@@ -79,7 +79,8 @@ public class IrcInboundFilter implements Runnable {
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
// probably doesn't do much but can't hurt
output.flush();
if (!in.ready())
output.flush();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("inbound BLOCKED: "+inmsg);

View File

@@ -79,7 +79,9 @@ public class IrcOutboundFilter implements Runnable {
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
output.write(outmsg.getBytes("ISO-8859-1"));
// save 250 ms in streaming
output.flush();
// Check ready() so we don't split the initial handshake up into multiple streaming messages
if (!in.ready())
output.flush();
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");

View File

@@ -15,10 +15,12 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.data.DataFormatException;
import net.i2p.util.HexDump;
import net.i2p.util.Log;
@@ -203,7 +205,10 @@ public class SOCKS4aServer extends SOCKSServer {
// Let's not due a new Dest for every request, huh?
//I2PSocketManager sm = I2PSocketManagerFactory.createManager();
//destSock = sm.connect(I2PTunnel.destFromName(connHostName), null);
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName));
Properties overrides = new Properties();
I2PSocketOptions sktOpts = t.buildOptions(overrides);
sktOpts.setPort(connPort);
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName), sktOpts);
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
_log.error(err);

View File

@@ -366,7 +366,10 @@ public class SOCKS5Server extends SOCKSServer {
} catch (IOException ioe) {}
throw new SOCKSException("Host not found");
}
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName));
Properties overrides = new Properties();
I2PSocketOptions sktOpts = t.buildOptions(overrides);
sktOpts.setPort(connPort);
destSock = t.createI2PSocket(I2PAppContext.getGlobalContext().namingService().lookup(connHostName), sktOpts);
} else if ("localhost".equals(connHostName) || "127.0.0.1".equals(connHostName)) {
String err = "No localhost accesses allowed through the Socks Proxy";
_log.error(err);

View File

@@ -376,18 +376,6 @@ public class EditBean extends IndexBean {
*/
private static Properties getOptions(TunnelController controller) {
if (controller == null) return null;
String opts = controller.getClientOptions();
StringTokenizer tok = new StringTokenizer(opts);
Properties props = new Properties();
while (tok.hasMoreTokens()) {
String pair = tok.nextToken();
int eq = pair.indexOf('=');
if ( (eq <= 0) || (eq >= pair.length()) )
continue;
String key = pair.substring(0, eq);
String val = pair.substring(eq+1);
props.setProperty(key, val);
}
return props;
return controller.getClientOptionProps();
}
}

View File

@@ -1,6 +1,8 @@
<%
// NOTE: Do the header carefully so there is no whitespace before the <?xml... line
response.setHeader("X-Frame-Options", "SAMEORIGIN");
%><%@page pageEncoding="UTF-8"
%><%@page trimDirectiveWhitespaces="true"
%><%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"

View File

@@ -19,9 +19,10 @@
<title><%=intl._("I2P Tunnel Manager - Edit Client Tunnel")%></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<% if (editBean.allowCSS()) {
%><link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
%><link rel="icon" href="<%=editBean.getTheme()%>images/favicon.ico" />
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
@@ -497,8 +498,6 @@
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<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="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>

View File

@@ -19,9 +19,10 @@
<title><%=intl._("I2P Tunnel Manager - Edit Server Tunnel")%></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
<link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<% if (editBean.allowCSS()) {
%><link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
%><link rel="icon" href="<%=editBean.getTheme()%>images/favicon.ico" />
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
@@ -507,8 +508,6 @@
<div class="header"></div>
<div class="footer">
<div class="toolbox">
<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="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>

View File

@@ -5,6 +5,8 @@
if (request.getCharacterEncoding() == null)
request.setCharacterEncoding("UTF-8");
response.setHeader("X-Frame-Options", "SAMEORIGIN");
%><%@page pageEncoding="UTF-8"
%><%@page trimDirectiveWhitespaces="true"
%><%@page contentType="text/html" import="net.i2p.i2ptunnel.web.IndexBean"
@@ -22,7 +24,8 @@
<link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<% if (indexBean.allowCSS()) {
%><link href="<%=indexBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
%><link rel="icon" href="<%=indexBean.getTheme()%>images/favicon.ico" />
<link href="<%=indexBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=indexBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
%>
@@ -113,7 +116,7 @@
<%
if (("httpserver".equals(indexBean.getInternalType(curServer)) || ("httpbidirserver".equals(indexBean.getInternalType(curServer)))) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><label><%=intl._("Preview")%>:</label>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p"><%=intl._("Preview")%></a>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p" target="_parent"><%=intl._("Preview")%></a>
<%
} else if (indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><span class="text"><%=intl._("Base32 Address")%>:<br /><%=indexBean.getDestHashBase32(curServer)%>.b32.i2p</span>

View File

@@ -5,6 +5,8 @@
if (request.getCharacterEncoding() == null)
request.setCharacterEncoding("UTF-8");
response.setHeader("X-Frame-Options", "SAMEORIGIN");
%><%@page pageEncoding="UTF-8"
%><%@page contentType="text/html" import="net.i2p.i2ptunnel.web.EditBean"
%><?xml version="1.0" encoding="UTF-8"?>
@@ -55,7 +57,8 @@
<link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" />
<% if (editBean.allowCSS()) {
%><link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
%><link rel="icon" href="<%=editBean.getTheme()%>images/favicon.ico" />
<link href="<%=editBean.getTheme()%>default.css" rel="stylesheet" type="text/css" />
<link href="<%=editBean.getTheme()%>i2ptunnel.css" rel="stylesheet" type="text/css" />
<% }
%>
@@ -501,7 +504,8 @@ http://stats.i2p/cgi-bin/jump.cgi?a=
http://i2jump.i2p/" /><%
} /* httpclient */
} else { /* Server-only defaults */
%><input type="hidden" name="encrypt" value="" />
%><input type="hidden" name="privKeyFile" value="<%=editBean.getPrivateKeyFile(-1)%>" />
<input type="hidden" name="encrypt" value="" />
<input type="hidden" name="encryptKey" value="" />
<input type="hidden" name="accessMode" value="0" />
<input type="hidden" name="accessList" value="" />

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,19 @@ import java.net.ConnectException;
import java.nio.channels.SelectableChannel;
/**
* As this does not (yet) extend ServerSocketChannel it cannot be returned by StandardServerSocket.getChannel(),
* until we implement an I2P SocketAddress class.
*
* Warning, this interface and implementation is preliminary and subject to change without notice.
*
* @since 0.8.11
*/
public abstract class AcceptingChannel extends SelectableChannel {
abstract I2PSocket accept() throws I2PException, ConnectException;
I2PSocketManager _socketManager;
protected final I2PSocketManager _socketManager;
AcceptingChannel(I2PSocketManager manager) {
this._socketManager = manager;
}

View File

@@ -0,0 +1,107 @@
package net.i2p.client.streaming;
import java.net.SocketAddress;
import net.i2p.I2PAppContext;
import net.i2p.data.Destination;
import net.i2p.data.DataHelper;
/**
* A SocketAddress (Destination + port) so we can have SocketChannels.
* Ports are not widely used in I2P, in most cases the port will be zero.
* See InetSocketAddress for javadocs.
*
* Warning, this interface and implementation is preliminary and subject to change without notice.
*
* @since 0.9.1
*/
public class I2PSocketAddress extends SocketAddress {
private final int _port;
private final Destination _dest;
private final String _host;
// no constructor for port-only "wildcard" address
/**
* Does not do a reverse lookup. Host will be null.
*/
public I2PSocketAddress(Destination dest, int port) {
_port = port;
_dest = dest;
_host = null;
}
/**
* Does a naming service lookup to resolve the dest.
* May take several seconds for b32.
*/
public I2PSocketAddress(String host, int port) {
_port = port;
_dest = I2PAppContext.getGlobalContext().namingService().lookup(host);
_host = host;
}
public static I2PSocketAddress createUnresolved(String host, int port) {
return new I2PSocketAddress(port, host);
}
/** unresolved */
private I2PSocketAddress(int port, String host) {
_port = port;
_dest = null;
_host = host;
}
public int getPort() {
return _port;
}
public Destination getAddress() {
return _dest;
}
/**
* @return the host only if given in the constructor. Does not do a reverse lookup.
*/
public String getHostName() {
return _host;
}
public boolean isUnresolved() {
return _dest == null;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
if (_dest != null)
buf.append(_dest.calculateHash().toString());
else
buf.append(_host);
buf.append(':');
buf.append(_port);
return buf.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof I2PSocketAddress))
return false;
I2PSocketAddress o = (I2PSocketAddress) obj;
if (_port != o._port)
return false;
if (_dest != null)
return _dest.equals(o._dest);
if (o._dest != null)
return false;
if (_host != null)
return _host.equals(o._host);
return o._host == null;
}
@Override
public int hashCode() {
return DataHelper.hashCode(_dest) ^ DataHelper.hashCode(_host) ^ _port;
}
}

View File

@@ -40,7 +40,7 @@ import net.i2p.util.SocketTimeout;
* @author zzz
*/
public class I2PSocketEepGet extends EepGet {
private I2PSocketManager _socketManager;
private final I2PSocketManager _socketManager;
/** this replaces _proxy in the superclass. Sadly, I2PSocket does not extend Socket. */
private I2PSocket _socket;
@@ -111,8 +111,8 @@ public class I2PSocketEepGet extends EepGet {
if ("http".equals(url.getProtocol())) {
String host = url.getHost();
int port = url.getPort();
if (port != -1)
throw new IOException("Ports not supported in i2p: " + _actualURL);
if (port <= 0 || port > 65535)
port = 80;
// HTTP Proxy compatibility http://i2p/B64KEY/blah
// Rewrite the url to strip out the /i2p/,
@@ -143,6 +143,7 @@ public class I2PSocketEepGet extends EepGet {
// in the SYN packet, saving one RTT.
props.setProperty(PROP_CONNECT_DELAY, CONNECT_DELAY);
I2PSocketOptions opts = _socketManager.buildOptions(props);
opts.setPort(port);
_socket = _socketManager.connect(dest, opts);
} else {
throw new IOException("Unsupported protocol: " + _actualURL);

View File

@@ -38,11 +38,32 @@ public interface I2PSocketManager {
*/
public void setAcceptTimeout(long ms);
public long getAcceptTimeout();
/**
* Update the options on a running socket manager.
* Parameters in the I2PSocketOptions interface may be changed directly
* with the setters; no need to use this method for those.
* This does NOT update the underlying I2CP or tunnel options; use getSession().updateOptions() for that.
* @param options as created from a call to buildOptions(properties), non-null
*/
public void setDefaultOptions(I2PSocketOptions options);
/**
* Current options, not a copy, setters may be used to make changes.
*/
public I2PSocketOptions getDefaultOptions();
public I2PServerSocket getServerSocket();
/**
* Create a copy of the current options, to be used in a setDefaultOptions() call.
*/
public I2PSocketOptions buildOptions();
/**
* Create a modified copy of the current options, to be used in a setDefaultOptions() call.
* @param opts The new options, may be null
*/
public I2PSocketOptions buildOptions(Properties opts);
/**
@@ -102,6 +123,10 @@ public interface I2PSocketManager {
public String getName();
public void setName(String name);
/**
* Deprecated - Factory will initialize.
* @throws UnsupportedOperationException always
*/
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
public void addDisconnectListener(DisconnectListener lsnr);

View File

@@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.Properties;
@@ -161,40 +162,19 @@ public class I2PSocketManagerFactory {
}
private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) {
//if (false) {
//I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
//mgr.setSession(session);
//mgr.setDefaultOptions(new I2PSocketOptions());
//return mgr;
//} else {
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
if (classname != null) {
try {
Class cls = Class.forName(classname);
Object obj = cls.newInstance();
if (obj instanceof I2PSocketManager) {
I2PSocketManager mgr = (I2PSocketManager)obj;
I2PAppContext context = I2PAppContext.getGlobalContext();
mgr.init(context, session, opts, name);
return mgr;
} else {
throw new IllegalStateException("Invalid manager class [" + classname + "]");
}
} catch (ClassNotFoundException cnfe) {
_log.error("Error loading " + classname, cnfe);
throw new IllegalStateException("Invalid manager class [" + classname + "] - not found");
} catch (InstantiationException ie) {
_log.error("Error loading " + classname, ie);
throw new IllegalStateException("Invalid manager class [" + classname + "] - unable to instantiate");
} catch (IllegalAccessException iae) {
_log.error("Error loading " + classname, iae);
throw new IllegalStateException("Invalid manager class [" + classname + "] - illegal access");
}
} else {
throw new IllegalStateException("No manager class specified");
}
//}
I2PAppContext context = I2PAppContext.getGlobalContext();
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
try {
Class cls = Class.forName(classname);
Constructor<I2PSocketManager> con = (Constructor<I2PSocketManager>)
cls.getConstructor(new Class[] {I2PAppContext.class, I2PSession.class, Properties.class, String.class});
I2PSocketManager mgr = con.newInstance(new Object[] {context, session, opts, name});
return mgr;
} catch (Throwable t) {
_log.log(Log.CRIT, "Error loading " + classname, t);
throw new IllegalStateException(t);
}
}
private static String getHost() {

View File

@@ -105,6 +105,9 @@ public interface I2PSocketOptions {
/**
* The local port.
* Zero (default) means you will receive traffic on all ports.
* Nonzero means you will get traffic ONLY for that port, use with care,
* as most applications do not specify a remote port.
* @param port 0 - 65535
* @since 0.8.9
*/

View File

@@ -200,6 +200,9 @@ class I2PSocketOptionsImpl implements I2PSocketOptions {
/**
* The local port.
* Zero (default) means you will receive traffic on all ports.
* Nonzero means you will get traffic ONLY for that port, use with care,
* as most applications do not specify a remote port.
* @param port 0 - 65535
* @since 0.8.9
*/

View File

@@ -101,7 +101,8 @@
<manifest>
<!-- top level installer will rename to jrobin.jar -->
<!-- DTG added in 0.8.4, not in the classpath for very old installs, before we changed wrapper.config to specify * -->
<attribute name="Class-Path" value="i2p.jar router.jar jrobin.jar desktopgui.jar" />
<!-- very old installs don't have i2psnark,jstl,standard in the classpath... not added in WebAppConfiguration any more -->
<attribute name="Class-Path" value="i2p.jar router.jar jrobin.jar desktopgui.jar i2psnark.jar jstl.jar standard.jar" />
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
<attribute name="Build-Date" value="${build.timestamp}" />

View File

@@ -38,7 +38,7 @@ then
mkdir -p build
echo '// Automatically generated pseudo-java for xgettext - do not edit' > $JFILE
echo '// Translators may wish to translate a few of these, do not bother to translate all of them!!' >> $JFILE
sed 's/..,\(..*\)/_("\1");/' $CFILE >> $JFILE
sed -e '/^#/d' -e 's/..,\(..*\)/_("\1");/' $CFILE >> $JFILE
fi
# list specific files in core/ and router/ here, so we don't scan the whole tree

View File

@@ -19,6 +19,9 @@ public class CSSHelper extends HelperBase {
private static final String FORCE = "classic";
public static final String PROP_REFRESH = "routerconsole.summaryRefresh";
public static final String DEFAULT_REFRESH = "60";
public static final int MIN_REFRESH = 3;
public static final String PROP_DISABLE_REFRESH = "routerconsole.summaryDisableRefresh";
private static final String PROP_XFRAME = "routerconsole.disableXFrame";
public String getTheme(String userAgent) {
String url = BASE_THEME_PATH;
@@ -58,14 +61,53 @@ public class CSSHelper extends HelperBase {
NewsFetcher.getInstance(_context).showNews(val.equals("1"));
}
/**
* Should we send X_Frame_Options=SAMEORIGIN
* Default true
* @since 0.9.1
*/
public boolean shouldSendXFrame() {
return !_context.getBooleanProperty(PROP_XFRAME);
}
/** change refresh and save it */
public void setRefresh(String r) {
try {
if (Integer.parseInt(r) < MIN_REFRESH)
r = "" + MIN_REFRESH;
} catch (Exception e) {
}
_context.router().saveConfig(PROP_REFRESH, r);
}
/** @return refresh time in seconds, as a string */
public String getRefresh() {
return _context.getProperty(PROP_REFRESH, DEFAULT_REFRESH);
String r = _context.getProperty(PROP_REFRESH, DEFAULT_REFRESH);
try {
if (Integer.parseInt(r) < MIN_REFRESH)
r = "" + MIN_REFRESH;
} catch (Exception e) {
}
return r;
}
/**
* change disable refresh boolean and save it
* @since 0.9.1
*/
public void setDisableRefresh(String r) {
String disableRefresh = "false";
if ("0".equals(r))
disableRefresh = "true";
_context.router().saveConfig(PROP_DISABLE_REFRESH, disableRefresh);
}
/**
* @return true if refresh is disabled
* @since 0.9.1
*/
public boolean getDisableRefresh() {
return _context.getBooleanProperty(PROP_DISABLE_REFRESH);
}
/** translate the title and display consistently */

View File

@@ -72,7 +72,13 @@ public class ConfigHomeHandler extends FormHandler {
}
name = DataHelper.escapeHTML(name).replace(",", "&#44;"); // HomeHelper.S
url = DataHelper.escapeHTML(url).replace(",", "&#44;");
HomeHelper.App app = new HomeHelper.App(name, "", url, "/themes/console/images/itoopie_sm.png");
HomeHelper.App app = null;
if ("1".equals(group))
app = new HomeHelper.App(name, "", url, "/themes/console/images/eepsite.png");
else if ("2".equals(group))
app = new HomeHelper.App(name, "", url, "/themes/console/images/title_window.png");
else
app = new HomeHelper.App(name, "", url, "/themes/console/images/question.png");
apps.add(app);
addFormNotice(_("Added") + ": " + app.name);
} else {

View File

@@ -11,12 +11,12 @@ public class ConfigNavHelper extends HelperBase {
/** configX.jsp */
private static final String pages[] =
{"", "net", "ui", "home", "service", "update", "tunnels",
{"", "net", "ui", "sidebar", "home", "service", "update", "tunnels",
"clients", "peer", "keyring", "logging", "stats",
"reseed", "advanced" };
private static final String titles[] =
{_x("Bandwidth"), _x("Network"), _x("UI"), _x("Home Page"),
{_x("Bandwidth"), _x("Network"), _x("UI"), _x("Summary Bar"), _x("Home Page"),
_x("Service"), _x("Update"), _x("Tunnels"),
_x("Clients"), _x("Peers"), _x("Keyring"), _x("Logging"), _x("Stats"),
_x("Reseeding"), _x("Advanced") };

View File

@@ -61,7 +61,6 @@ public class ConfigNetHandler extends FormHandler {
}
public void setSave(String moo) { _saveRequested = true; }
public void setEnabletimesync(String moo) { }
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
@@ -289,10 +288,6 @@ public class ConfigNetHandler extends FormHandler {
removes.add(UDPTransport.PROP_FORCE_INTRODUCERS);
}
// Time sync enable, means NOT disabled
// Hmm router sets this at startup, not required here
//changes.put(Timestamper.PROP_DISABLED, "false");
// Hidden in the GUI
//LoadTestManager.setEnableLoadTesting(_context, _enableLoadTesting);
}

View File

@@ -9,7 +9,6 @@ import net.i2p.router.Router;
import net.i2p.router.transport.TransportManager;
import net.i2p.router.transport.udp.UDPAddress;
import net.i2p.router.transport.udp.UDPTransport;
import net.i2p.time.Timestamper;
import net.i2p.util.Addresses;
public class ConfigNetHelper extends HelperBase {
@@ -65,14 +64,6 @@ public class ConfigNetHelper extends HelperBase {
return "" + _context.getProperty(UDPTransport.PROP_INTERNAL_PORT, UDPTransport.DEFAULT_INTERNAL_PORT);
}
public String getEnableTimeSyncChecked() {
boolean disabled = _context.getBooleanProperty(Timestamper.PROP_DISABLED);
if (disabled)
return "";
else
return CHECKED;
}
/** @param prop must default to false */
public String getChecked(String prop) {
if (_context.getBooleanProperty(prop))

View File

@@ -1,5 +1,6 @@
package net.i2p.router.web;
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -208,7 +209,8 @@ public class ConfigServiceHandler extends FormHandler {
} catch (Throwable t) {
addFormError("Warning: unable to contact the service manager - " + t.getMessage());
}
addFormNotice("Threads dumped to wrapper.log");
File wlog = LogsHelper.wrapperLogFile(_context);
addFormNotice(_("Threads dumped to {0}", wlog.getAbsolutePath()));
} else if (_("View console on startup").equals(_action)) {
browseOnStartup(true);
addFormNotice(_("Console is to be shown on startup"));

View File

@@ -0,0 +1,168 @@
package net.i2p.router.web;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import net.i2p.data.DataHelper;
/**
* Simple summary bar configuration.
*
* @since 0.9.1
*/
public class ConfigSummaryHandler extends FormHandler {
private Map _settings;
@Override
protected void processForm() {
if (_action == null) return;
String group = getJettyString("group");
boolean deleting = _action.equals(_("Delete selected"));
boolean adding = _action.equals(_("Add item"));
boolean saving = _action.equals(_("Save order"));
boolean moving = _action.startsWith("move_");
if (_action.equals(_("Save")) && "0".equals(group)) {
try {
int refreshInterval = Integer.parseInt(getJettyString("refreshInterval"));
if (refreshInterval >= CSSHelper.MIN_REFRESH) {
_context.router().saveConfig(CSSHelper.PROP_REFRESH, "" + refreshInterval);
addFormNotice(_("Refresh interval changed"));
} else
addFormError(_("Refresh interval must be at least {0} seconds", CSSHelper.MIN_REFRESH));
} catch (java.lang.NumberFormatException e) {
addFormError(_("Refresh interval must be a number"));
return;
}
} else if (_action.equals(_("Restore full default"))) {
_context.router().saveConfig(SummaryHelper.PROP_SUMMARYBAR + "default", SummaryHelper.DEFAULT_FULL);
addFormNotice(_("Full summary bar default restored.") + " " +
_("Summary bar will refresh shortly."));
} else if (_action.equals(_("Restore minimal default"))) {
_context.router().saveConfig(SummaryHelper.PROP_SUMMARYBAR + "default", SummaryHelper.DEFAULT_MINIMAL);
addFormNotice(_("Minimal summary bar default restored.") + " " +
_("Summary bar will refresh shortly."));
} else if (adding || deleting || saving || moving) {
Map<Integer, String> sections = new TreeMap<Integer, String>();
for (Object o : _settings.keySet()) {
if (!(o instanceof String))
continue;
String k = (String) o;
if (!k.startsWith("order_"))
continue;
String v = getJettyString(k);
k = k.substring(6);
k = k.substring(k.indexOf('_') + 1);
try {
int order = Integer.parseInt(v);
sections.put(order, k);
} catch (java.lang.NumberFormatException e) {
addFormError(_("Order must be an integer"));
return;
}
}
if (adding) {
String name = getJettyString("name");
if (name == null || name.length() <= 0) {
addFormError(_("No section selected"));
return;
}
String order = getJettyString("order");
if (order == null || order.length() <= 0) {
addFormError(_("No order entered"));
return;
}
name = DataHelper.escapeHTML(name).replace(",", "&#44;");
order = DataHelper.escapeHTML(order).replace(",", "&#44;");
try {
int ki = Integer.parseInt(order);
sections.put(ki, name);
addFormNotice(_("Added") + ": " + name);
} catch (java.lang.NumberFormatException e) {
addFormError(_("Order must be an integer"));
return;
}
} else if (deleting) {
Set<Integer> toDelete = new HashSet();
for (Object o : _settings.keySet()) {
if (!(o instanceof String))
continue;
String k = (String) o;
if (!k.startsWith("delete_"))
continue;
k = k.substring(7);
try {
int ki = Integer.parseInt(k);
toDelete.add(ki);
} catch (java.lang.NumberFormatException e) {
continue;
}
}
for (Iterator<Integer> iter = sections.keySet().iterator(); iter.hasNext(); ) {
int i = iter.next();
if (toDelete.contains(i)) {
String removedName = sections.get(i);
iter.remove();
addFormNotice(_("Removed") + ": " + removedName);
}
}
} else if (moving) {
String parts[] = _action.split("_");
try {
int from = Integer.parseInt(parts[1]);
int to = 0;
if ("up".equals(parts[2]))
to = from - 1;
if ("down".equals(parts[2]))
to = from + 1;
if ("bottom".equals(parts[2]))
to = sections.size() - 1;
int n = -1;
if ("down".equals(parts[2]) || "bottom".equals(parts[2]))
n = 1;
for (int i = from; n * i < n * to; i += n) {
String temp = sections.get(i + n);
sections.put(i + n, sections.get(i));
sections.put(i, temp);
}
addFormNotice(_("Moved") + ": " + sections.get(to));
} catch (java.lang.NumberFormatException e) {
addFormError(_("Order must be an integer"));
return;
}
}
SummaryHelper.saveSummaryBarSections(_context, "default", sections);
addFormNotice(_("Saved order of sections.") + " " +
_("Summary bar will refresh shortly."));
} else {
addFormError(_("Unsupported"));
}
}
public void setSettings(Map settings) { _settings = new HashMap(settings); }
/** curses Jetty for returning arrays */
private String getJettyString(String key) {
String[] arr = (String[]) _settings.get(key);
if (arr == null)
return null;
return arr[0].trim();
}
public void setMovingAction() {
for (Object o : _settings.keySet()) {
if (!(o instanceof String))
continue;
String k = (String) o;
if (k.startsWith("move_") && k.endsWith(".x") && _settings.get(k) != null) {
_action = k.substring(0, k.length() - 2);
break;
}
}
}
}

View File

@@ -53,15 +53,15 @@ public class ConfigUIHelper extends HelperBase {
* Any language-specific flag added to the icon set must be
* added to the top-level build.xml for the updater.
*/
private static final String langs[] = {"ar", "cs", "da", "de", "ee", "en", "es", "fi",
"fr", "it", "nl", "pl", "pt", "ru",
private static final String langs[] = {"ar", "cs", "da", "de", "ee", "el", "en", "es", "fi",
"fr", "hu", "it", "nl", "pl", "pt", "ru",
"sv", "uk", "vi", "zh"};
private static final String flags[] = {"lang_ar", "cz", "dk", "de", "ee", "us", "es", "fi",
"fr", "it", "nl", "pl", "pt", "ru",
private static final String flags[] = {"lang_ar", "cz", "dk", "de", "ee", "gr", "us", "es", "fi",
"fr", "hu", "it", "nl", "pl", "pt", "ru",
"se", "ua", "vn", "cn"};
private static final String xlangs[] = {_x("Arabic"), _x("Czech"), _x("Danish"),
_x("German"), _x("Estonian"), _x("English"), _x("Spanish"), _x("Finnish"),
_x("French"), _x("Italian"), _x("Dutch"), _x("Polish"),
_x("German"), _x("Estonian"), _x("Greek"), _x("English"), _x("Spanish"), _x("Finnish"),
_x("French"), _x("Hungarian"), _x("Italian"), _x("Dutch"), _x("Polish"),
_x("Portuguese"), _x("Russian"), _x("Swedish"),
_x("Ukrainian"), _x("Vietnamese"), _x("Chinese")};

View File

@@ -191,7 +191,8 @@ public class GraphHelper extends FormHandler {
}
// FIXME jrobin doesn't support setting the timezone, will have to mod TimeAxis.java
_out.write("<p><i>" + _("All times are UTC.") + "</i></p>\n");
// 0.9.1 - all graphs currently state UTC on them, so this text blurb is unnecessary,
//_out.write("<p><i>" + _("All times are UTC.") + "</i></p>\n");
} catch (IOException ioe) {
ioe.printStackTrace();
}

View File

@@ -21,39 +21,43 @@ public class HomeHelper extends HelperBase {
static final String PROP_SERVICES = "routerconsole.services";
static final String PROP_FAVORITES = "routerconsole.favorites";
static final String PROP_OLDHOME = "routerconsole.oldHomePage";
private static final String PROP_SEARCH = "routerconsole.showSearch";
static final String DEFAULT_SERVICES =
_x("Addressbook") + S + _x("Manage your I2P hosts file here (I2P domain name resolution)") + S + "/susidns/index" + S + I + "book_addresses.png" + S +
_x("Configure Bandwidth") + S + _x("I2P Bandwidth Configuration") + S + "/config" + S + I + "wrench_orange.png" + S +
_x("Addressbook") + S + _x("Manage your I2P hosts file here (I2P domain name resolution)") + S + "/dns" + S + I + "book_addresses.png" + S +
_x("Configure Bandwidth") + S + _x("I2P Bandwidth Configuration") + S + "/config" + S + I + "action_log.png" + S +
_x("Configure Language") + S + _x("Console Language Selection") + S + "/configui" + S + I + "wrench_orange.png" + S +
_x("Customize Home Page") + S + _x("I2P Home Page Configuration") + S + "/confighome" + S + I + "wrench_orange.png" + S +
_x("Customize Home Page") + S + _x("I2P Home Page Configuration") + S + "/confighome" + S + I + "home_page.png" + S +
_x("Email") + S + _x("Anonymous webmail client") + S + "/susimail/susimail" + S + I + "email.png" + S +
_x("Help") + S + _x("I2P Router Help") + S + "/help" + S + I + "help.png" + S +
_x("Router Console") + S + _x("I2P Router Console") + S + "/console" + S + I + "wrench_orange.png" + S +
_x("Torrents") + S + _x("Built-in anonymous BitTorrent Client") + S + "/i2psnark/" + S + I + "film.png" + S +
_x("Website") + S + _x("Local web server") + S + "http://127.0.0.1:7658/" + S + I + "server.png" + S +
_x("Help") + S + _x("I2P Router Help") + S + "/help" + S + I + "support.png" + S +
_x("Router Console") + S + _x("I2P Router Console") + S + "/console" + S + I + "toolbox.png" + S +
_x("Torrents") + S + _x("Built-in anonymous BitTorrent Client") + S + "/i2psnark/" + S + I + "magnet.png" + S +
_x("Website") + S + _x("Local web server") + S + "http://127.0.0.1:7658/" + S + I + "server_32x32.png" + S +
"";
static final String DEFAULT_FAVORITES =
_x("Bug Reports") + S + _x("Bug tracker") + S + "http://trac.i2p2.i2p/report/1" + S + I + "bug.png" + S +
_x("Dev Forum") + S + _x("Development forum") + S + "http://zzz.i2p/" + S + I + "itoopie_sm.png" + S +
_x("diftracker") + S + _x("Bittorrent tracker") + S + "http://diftracker.i2p/" + S + I + "itoopie_sm.png" + S +
"echelon.i2p" + S + _x("I2P Applications") + S + "http://echelon.i2p/" + S + I + "itoopie_sm.png" + S +
_x("FAQ") + S + _x("Frequently Asked Questions") + S + "http://www.i2p2.i2p/faq" + S + I + "help.png" + S +
_x("Forum") + S + _x("Community forum") + S + "http://forum.i2p/" + S + I + "itoopie_sm.png" + S +
//"ident.i2p" + S + _x("Short message service") + S + "http://ident.i2p/" + S + I + "itoopie_sm.png" + S +
_x("Javadocs") + S + _x("Technical documentation") + S + "http://i2p-javadocs.i2p/" + S + I + "book.png" + S +
_x("Key Server") + S + _x("OpenPGP Keyserver") + S + "http://keys.i2p/" + S + I + "book.png" + S +
_x("Pastebin") + S + _x("I2P Pastebin") + S + "http://pastethis.i2p/" + S + I + "itoopie_sm.png" + S +
"Planet I2P" + S + _x("I2P News") + S + "http://planet.i2p/" + S + I + "itoopie_sm.png" + S +
"colombo-bt.i2p" + S + _x("The Italian Bittorrent Resource") + S + "http://colombo-bt.i2p/" + S + I + "colomboicon.png" + S +
_x("Dev Forum") + S + _x("Development forum") + S + "http://zzz.i2p/" + S + I + "eepsite.png" + S +
_x("diftracker") + S + _x("Bittorrent tracker") + S + "http://diftracker.i2p/" + S + I + "eepsite.png" + S +
"echelon.i2p" + S + _x("I2P Applications") + S + "http://echelon.i2p/" + S + I + "eepsite.png" + S +
_x("FAQ") + S + _x("Frequently Asked Questions") + S + "http://www.i2p2.i2p/faq" + S + I + "question.png" + S +
_x("Forum") + S + _x("Community forum") + S + "http://forum.i2p/" + S + I + "eepsite.png" + S +
_x("Anonymous Git Hosting") + S + _x("A public anonymous Git hosting site - supports pulling via Git and HTTP and pushing via SSH") + S + "http://git.repo.i2p/" + S + I + "git-logo.png" + S +
"Ident " + _x("Microblog") + S + _x("Your premier microblogging service on I2P") + S + "http://id3nt.i2p/" + S + I + "ident_icon_blue.png" + S +
_x("Javadocs") + S + _x("Technical documentation") + S + "http://i2p-javadocs.i2p/" + S + I + "education.png" + S +
//_x("Key Server") + S + _x("OpenPGP Keyserver") + S + "http://keys.i2p/" + S + I + "education.png" + S +
_x("killyourtv.i2p") + S + _x("Debian and Tahoe-LAFS repositories") + S + "http://killyourtv.i2p/" + S + I + "eepsite.png" + S +
_x("Pastebin") + S + _x("I2P Pastebin") + S + "http://pastethis.i2p/" + S + I + "eepsite.png" + S +
"Planet I2P" + S + _x("I2P News") + S + "http://planet.i2p/" + S + I + "eepsite.png" + S +
_x("Plugins") + S + _x("Add-on directory") + S + "http://plugins.i2p/" + S + I + "plugin.png" + S +
_x("Postman's Tracker") + S + _x("Bittorrent tracker") + S + "http://tracker2.postman.i2p/" + S + I + "itoopie_sm.png" + S +
_x("Project Website") + S + _x("I2P home page") + S + "http://www.i2p2.i2p/" + S + I + "help.png" + S +
"stats.i2p" + S + _x("I2P Netowrk Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "itoopie_sm.png" + S +
_x("Technical Docs") + S + _x("Technical documentation") + S + "http://www.i2p2.i2p/how" + S + I + "book.png" + S +
_x("Trac Wiki") + S + S + "http://trac.i2p2.i2p/" + S + I + "itoopie_sm.png" + S +
_x("Ugha's Wiki") + S + S + "http://ugha.i2p/" + S + I + "itoopie_sm.png" + S +
_x("Sponge's main site") + S + _x("Seedless and the Robert BitTorrent applications") + S + "http://sponge.i2p/" + S + I + "itoopie_sm.png" + S +
_x("Postman's Tracker") + S + _x("Bittorrent tracker") + S + "http://tracker2.postman.i2p/" + S + I + "eepsite.png" + S +
_x("Project Website") + S + _x("I2P home page") + S + "http://www.i2p2.i2p/" + S + I + "info_rhombus.png" + S +
"stats.i2p" + S + _x("I2P Netowrk Statistics") + S + "http://stats.i2p/cgi-bin/dashboard.cgi" + S + I + "eepsite.png" + S +
_x("Technical Docs") + S + _x("Technical documentation") + S + "http://www.i2p2.i2p/how" + S + I + "education.png" + S +
_x("Trac Wiki") + S + S + "http://trac.i2p2.i2p/" + S + I + "eepsite.png" + S +
_x("Ugha's Wiki") + S + S + "http://ugha.i2p/" + S + I + "eepsite.png" + S +
_x("Sponge's main site") + S + _x("Seedless and the Robert BitTorrent applications") + S + "http://sponge.i2p/" + S + I + "eepsite.png" + S +
"";
@@ -61,6 +65,10 @@ public class HomeHelper extends HelperBase {
return _context.getProperty(Messages.PROP_LANG) == null;
}
public boolean shouldShowSearch() {
return _context.getBooleanProperty(PROP_SEARCH);
}
public String getServices() {
List<App> plugins = NavHelper.getClientApps(_context);
return homeTable(PROP_SERVICES, DEFAULT_SERVICES, plugins);

View File

@@ -8,7 +8,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TreeMap;
import net.i2p.data.DataHelper;
import net.i2p.router.Job;
@@ -76,15 +75,17 @@ public class JobQueueHelper extends HelperBase {
out.flush();
buf.append("<hr><b>Scheduled jobs: ").append(timedJobs.size()).append("</b><ol>\n");
TreeMap<Long, Job> ordered = new TreeMap();
for (int i = 0; i < timedJobs.size(); i++) {
Job j = timedJobs.get(i);
ordered.put(Long.valueOf(j.getTiming().getStartAfter()), j);
}
for (Job j : ordered.values()) {
long prev = Long.MIN_VALUE;
for (Job j : timedJobs) {
long time = j.getTiming().getStartAfter() - now;
buf.append("<li>").append(j.getName()).append(" in ");
buf.append(DataHelper.formatDuration2(time)).append("</li>\n");
buf.append(DataHelper.formatDuration2(time));
if (time < 0)
buf.append(" <b>DELAYED</b>");
if (time < prev)
buf.append(" <b>** OUT OF ORDER **</b>");
prev = time;
buf.append("</li>\n");
}
buf.append("</ol></div>\n");

View File

@@ -3,6 +3,7 @@ package net.i2p.router.web;
import java.io.File;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.util.FileUtil;
import net.i2p.util.VersionComparator;
@@ -32,9 +33,13 @@ public class LogsHelper extends HelperBase {
return formatMessages(_context.logManager().getBuffer().getMostRecentCriticalMessages());
}
public String getServiceLogs() {
/**
* Does not necessarily exist.
* @since 0.9.1
*/
static File wrapperLogFile(I2PAppContext ctx) {
File f = null;
if (_context.hasWrapper()) {
if (ctx.hasWrapper()) {
String wv = System.getProperty("wrapper.version");
if (wv != null && (new VersionComparator()).compare(wv, LOCATION_AVAILABLE) >= 0) {
try {
@@ -51,9 +56,14 @@ public class LogsHelper extends HelperBase {
// look in new and old places
f = new File(System.getProperty("java.io.tmpdir"), "wrapper.log");
if (!f.exists())
f = new File(_context.getBaseDir(), "wrapper.log");
f = new File(ctx.getBaseDir(), "wrapper.log");
}
}
return f;
}
public String getServiceLogs() {
File f = wrapperLogFile(_context);
String str = FileUtil.readTextFile(f.getAbsolutePath(), 250, false);
if (str == null)
return _("File not found") + ": <b><code>" + f.getAbsolutePath() + "</code></b>";

View File

@@ -11,9 +11,28 @@ public class NetDbHelper extends HelperBase {
private int _full;
private boolean _lease;
private boolean _debug;
private boolean _graphical;
public NetDbHelper() {}
private static final String PROP_DEBUG = "routerconsole.debug";
private static final String titles[] =
{_x("Summary"), // 0
_x("Local Router"), // 1
_x("Router Lookup"), // 2
_x("All Routers"), // 3
_x("All Routers with Full Stats"), // 4
"LeaseSet Debug", // 5
_x("LeaseSets") }; // 6
private static final String links[] =
{"", // 0
"?r=.", // 1
"", // 2
"?f=2", // 3
"?f=1", // 4
"?l=2", // 5
"?l=1" }; // 6
public void setRouter(String r) {
if (r != null)
_routerPrefix = DataHelper.stripHTML(r); // XSS
@@ -30,30 +49,88 @@ public class NetDbHelper extends HelperBase {
_lease = _debug || "1".equals(l);
}
/**
* call for non-text-mode browsers
* @since 0.9.1
*/
public void allowGraphical() {
_graphical = true;
}
/**
* storeWriter() must be called previously
*/
public String getNetDbSummary() {
NetDbRenderer renderer = new NetDbRenderer(_context);
try {
if (_out != null) {
if (_routerPrefix != null)
renderer.renderRouterInfoHTML(_out, _routerPrefix);
else if (_lease)
renderer.renderLeaseSetHTML(_out, _debug);
else
renderer.renderStatusHTML(_out, _full);
return "";
} else {
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
if (_routerPrefix != null)
renderer.renderRouterInfoHTML(new OutputStreamWriter(baos), _routerPrefix);
else if (_lease)
renderer.renderLeaseSetHTML(new OutputStreamWriter(baos), _debug);
else
renderer.renderStatusHTML(new OutputStreamWriter(baos), _full);
return new String(baos.toByteArray());
}
renderNavBar();
if (_routerPrefix != null)
renderer.renderRouterInfoHTML(_out, _routerPrefix);
else if (_lease)
renderer.renderLeaseSetHTML(_out, _debug);
else
renderer.renderStatusHTML(_out, _full);
} catch (IOException ioe) {
ioe.printStackTrace();
return "";
}
return "";
}
/**
* @since 0.9.1
*/
private int getTab() {
if (_debug)
return 5;
if (_lease)
return 6;
if (".".equals(_routerPrefix))
return 1;
if (_routerPrefix != null)
return 2;
if (_full == 2)
return 3;
if (_full == 1)
return 4;
return 0;
}
/**
* @since 0.9.1
*/
private void renderNavBar() throws IOException {
StringBuilder buf = new StringBuilder(1024);
buf.append("<div class=\"confignav\" id=\"confignav\">");
// TODO fix up the non-light themes
String theme = _context.getProperty(CSSHelper.PROP_THEME_NAME);
boolean span = _graphical && (theme == null || theme.equals(CSSHelper.DEFAULT_THEME));
if (!span)
buf.append("<center>");
int tab = getTab();
for (int i = 0; i < titles.length; i++) {
if (i == 2 && tab != 2)
continue; // can't nav to lookup
if (i == 5 && !_context.getBooleanProperty(PROP_DEBUG))
continue;
if (i == tab) {
// we are there
if (span)
buf.append("<span class=\"tab2\">");
buf.append(_(titles[i]));
} else {
// we are not there, make a link
if (span)
buf.append("<span class=\"tab\">");
buf.append("<a href=\"netdb").append(links[i]).append("\">").append(_(titles[i])).append("</a>");
}
if (span)
buf.append(" </span>\n");
else if (i != titles.length - 1)
buf.append(" |\n");
}
if (!span)
buf.append("</center>");
buf.append("</div>");
_out.write(buf.toString());
}
}

View File

@@ -16,6 +16,7 @@ import java.text.DecimalFormat; // debug
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -74,7 +75,6 @@ public class NetDbRenderer {
public void renderRouterInfoHTML(Writer out, String routerPrefix) throws IOException {
StringBuilder buf = new StringBuilder(4*1024);
buf.append("<h2>" + _("Network Database RouterInfo Lookup") + "</h2>\n");
if (".".equals(routerPrefix)) {
renderRouterInfo(buf, _context.router().getRouterInfo(), true, true);
} else {
@@ -101,12 +101,8 @@ public class NetDbRenderer {
*/
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\">" + _("View RouterInfo") + "</a>");
buf.append("<h3>").append(_("LeaseSets"));
if (debug)
buf.append(" - Debug mode - Sorted by hash distance, closest first");
buf.append("</h3>\n");
buf.append("<p>Debug mode - Sorted by hash distance, closest first</p>\n");
Hash ourRKey;
Set<LeaseSet> leases;
DecimalFormat fmt;
@@ -187,7 +183,8 @@ public class NetDbRenderer {
FloodfillNetworkDatabaseFacade netdb = (FloodfillNetworkDatabaseFacade)_context.netDb();
buf.append("<p><b>Total Leasesets: ").append(leases.size());
buf.append("</b></p><p><b>Published (RAP) Leasesets: ").append(netdb.getKnownLeaseSets());
//buf.append("</b></p><p><b>Mod Data: " + HexDump.dump(_context.routingKeyGenerator().getModData()));
buf.append("</b></p><p><b>Mod Data: \"").append(DataHelper.getUTF8(_context.routingKeyGenerator().getModData()))
.append("\" Last Changed: ").append(new Date(_context.routingKeyGenerator().getLastChanged()));
int ff = _context.peerManager().getPeersByCapability(FloodfillNetworkDatabaseFacade.CAPABILITY_FLOODFILL).size();
buf.append("</b></p><p><b>Known Floodfills: ").append(ff);
buf.append("</b></p><p><b>Currently Floodfill? ");
@@ -201,6 +198,8 @@ public class NetDbRenderer {
int total = (int) Math.round(Math.pow(2, 3 + 256 - 1 - log2));
buf.append("</b></p><p><b>Estimated total floodfills: ").append(total);
buf.append("</b></p><p><b>Estimated total leasesets: ").append(total * rapCount / 8);
} else {
buf.append("</b></p><p><b>Not floodfill or no data");
}
buf.append("</b></p>");
}
@@ -226,10 +225,9 @@ public class NetDbRenderer {
}
/**
* @param mode 0: our info and charts only; 1: full routerinfos and charts; 2: abbreviated routerinfos and charts
* @param mode 0: charts only; 1: full routerinfos; 2: abbreviated routerinfos
*/
public void renderStatusHTML(Writer out, int mode) throws IOException {
out.write("<h2>" + _("Network Database Contents") + " (<a href=\"netdb?l=1\">" + _("View LeaseSets") + "</a>)</h2>\n");
if (!_context.netDb().isInitialized()) {
out.write(_("Not initialized"));
out.flush();
@@ -238,20 +236,16 @@ public class NetDbRenderer {
boolean full = mode == 1;
boolean shortStats = mode == 2;
boolean showStats = full || shortStats;
boolean showStats = full || shortStats; // this means show the router infos
Hash us = _context.routerHash();
out.write("<a name=\"routers\" ></a><h3>" + _("Routers") + " (<a href=\"netdb");
if (full || !showStats)
out.write("?f=2#routers\" >" + _("Show all routers"));
else
out.write("?f=1#routers\" >" + _("Show all routers with full stats"));
out.write("</a>)</h3>\n");
StringBuilder buf = new StringBuilder(8192);
RouterInfo ourInfo = _context.router().getRouterInfo();
renderRouterInfo(buf, ourInfo, true, true);
out.write(buf.toString());
buf.setLength(0);
if (showStats) {
RouterInfo ourInfo = _context.router().getRouterInfo();
renderRouterInfo(buf, ourInfo, true, true);
out.write(buf.toString());
buf.setLength(0);
}
ObjectCounter<String> versions = new ObjectCounter();
ObjectCounter<String> countries = new ObjectCounter();
@@ -279,6 +273,12 @@ public class NetDbRenderer {
}
}
//
// don't bother to reindent
//
if (!showStats) {
// the summary table
buf.append("<table border=\"0\" cellspacing=\"30\"><tr><th colspan=\"3\">")
.append(_("Network Database Router Statistics"))
.append("</th></tr><tr><td style=\"vertical-align: top;\">");
@@ -331,6 +331,12 @@ public class NetDbRenderer {
}
buf.append("</td></tr></table>");
//
// don't bother to reindent
//
} // if !showStats
out.write(buf.toString());
out.flush();
}
@@ -361,11 +367,10 @@ public class NetDbRenderer {
buf.append("<a name=\"our-info\" ></a><b>" + _("Our info") + ": ").append(hash).append("</b></th></tr><tr><td>\n");
} else {
buf.append("<b>" + _("Peer info for") + ":</b> ").append(hash).append("\n");
if (full) {
buf.append("[<a href=\"netdb\" >Back</a>]</th></tr><tr><td>\n");
} else {
buf.append("[<a href=\"netdb?r=").append(hash.substring(0, 6)).append("\" >").append(_("Full entry")).append("</a>]</th></tr><tr><td>\n");
if (!full) {
buf.append("[<a href=\"netdb?r=").append(hash.substring(0, 6)).append("\" >").append(_("Full entry")).append("</a>]");
}
buf.append("</th></tr><tr><td>\n");
}
long age = _context.clock().now() - info.getPublished();

View File

@@ -16,7 +16,7 @@ public class NewsHelper extends ContentHelper {
if (!news.exists())
_page = (new File(_context.getBaseDir(), "docs/initialNews/initialNews.xml")).getAbsolutePath();
return super.getContent();
}
}
/** @since 0.8.12 */
public boolean shouldShowNews() {

View File

@@ -158,7 +158,7 @@ public class PluginUpdateChecker extends UpdateHandler {
try {
_get = new PartialEepGet(_context, proxyHost, proxyPort, _baos, _xpi2pURL, TrustedUpdate.HEADER_BYTES);
_get.addStatusListener(PluginUpdateCheckerRunner.this);
_get.fetch();
_get.fetch(CONNECT_TIMEOUT);
} catch (Throwable t) {
_log.error("Error checking update for plugin", t);
}

View File

@@ -149,7 +149,7 @@ public class PluginUpdateHandler extends UpdateHandler {
else
_get = new EepGet(_context, 1, _updateFile, _xpi2pURL, false);
_get.addStatusListener(PluginUpdateRunner.this);
_get.fetch();
_get.fetch(CONNECT_TIMEOUT, -1, shouldProxy ? INACTIVITY_TIMEOUT : NOPROXY_INACTIVITY_TIMEOUT);
} catch (Throwable t) {
_log.error("Error downloading plugin", t);
}

View File

@@ -23,16 +23,21 @@ import net.i2p.stat.RateStat;
*
*/
class ProfileOrganizerRenderer {
private RouterContext _context;
private ProfileOrganizer _organizer;
private ProfileComparator _comparator;
private final RouterContext _context;
private final ProfileOrganizer _organizer;
private final ProfileComparator _comparator;
public ProfileOrganizerRenderer(ProfileOrganizer organizer, RouterContext context) {
_context = context;
_organizer = organizer;
_comparator = new ProfileComparator();
}
public void renderStatusHTML(Writer out, boolean full) throws IOException {
/**
* @param mode 0 = high cap; 1 = all; 2 = floodfill
*/
public void renderStatusHTML(Writer out, int mode) throws IOException {
boolean full = mode == 1;
Set<Hash> peers = _organizer.selectAllPeers();
long now = _context.clock().now();
@@ -68,7 +73,13 @@ class ProfileOrganizerRenderer {
int reliable = 0;
int integrated = 0;
StringBuilder buf = new StringBuilder(16*1024);
buf.append("<h2>").append(_("Peer Profiles")).append("</h2>\n<p>");
////
//// don't bother reindenting
////
if (mode < 2) {
//buf.append("<h2>").append(_("Peer Profiles")).append("</h2>\n<p>");
buf.append(ngettext("Showing 1 recent profile.", "Showing {0} recent profiles.", order.size())).append('\n');
if (older > 0)
buf.append(ngettext("Hiding 1 older profile.", "Hiding {0} older profiles.", older)).append('\n');
@@ -181,8 +192,13 @@ class ProfileOrganizerRenderer {
}
buf.append("</table>");
buf.append("<h2><a name=\"flood\"></a>").append(_("Floodfill and Integrated Peers"))
.append(" (").append(integratedPeers.size()).append(")</h2>\n");
////
//// don't bother reindenting
////
} else {
//buf.append("<h2><a name=\"flood\"></a>").append(_("Floodfill and Integrated Peers"))
// .append(" (").append(integratedPeers.size()).append(")</h2>\n");
buf.append("<table>");
buf.append("<tr>");
buf.append("<th class=\"smallhead\">").append(_("Peer")).append("</th>");
@@ -247,6 +263,12 @@ class ProfileOrganizerRenderer {
}
buf.append("</table>");
////
//// don't bother reindenting
////
}
if (mode < 2) {
buf.append("<h3>").append(_("Thresholds")).append("</h3>");
buf.append("<p><b>").append(_("Speed")).append(":</b> ").append(num(_organizer.getSpeedThreshold()))
.append(" (").append(fast).append(' ').append(_("fast peers")).append(")<br>");
@@ -262,6 +284,12 @@ class ProfileOrganizerRenderer {
buf.append("<li><b>").append(_("integration")).append("</b>: ").append(_("how many new peers have they told us about lately?")).append("</li>");
buf.append("<li><b>").append(_("status")).append("</b>: ").append(_("is the peer banned, or unreachable, or failing tunnel tests?")).append("</li>");
buf.append("</ul>");
////
//// don't bother reindenting
////
} // mode < 2
out.write(buf.toString());
out.flush();
}

View File

@@ -4,12 +4,52 @@ import java.io.IOException;
public class ProfilesHelper extends HelperBase {
private boolean _full;
private int _full;
private boolean _graphical;
private static final String titles[] =
{_x("High Capacity"), // 0
_x("Floodfill "), // 1
_x("Banned"), // 2
_x("All"), }; // 3
private static final String links[] =
{"", // 0
"?f=2", // 1
"?f=3", // 2
"?f=1" }; // 3
public ProfilesHelper() {}
public void setFull(String f) {
_full = f != null;
if (f != null) {
try {
_full = Integer.parseInt(f);
if (_full < 0 || _full > 3)
_full = 0;
} catch (NumberFormatException nfe) {}
}
}
/**
* call for non-text-mode browsers
* @since 0.9.1
*/
public void allowGraphical() {
_graphical = true;
}
/**
* @return empty string, writes directly to _out
* @since 0.9.1
*/
public String getSummary() {
try {
renderNavBar();
} catch (IOException ioe) {}
if (_full == 3)
getShitlistSummary();
else
getProfileSummary();
return "";
}
/** @return empty string, writes directly to _out */
@@ -33,4 +73,52 @@ public class ProfilesHelper extends HelperBase {
}
return "";
}
/**
* @since 0.9.1
*/
private int getTab() {
if (_full == 2)
return 1;
if (_full == 3)
return 2;
if (_full == 1)
return 3;
return 0;
}
/**
* @since 0.9.1
*/
private void renderNavBar() throws IOException {
StringBuilder buf = new StringBuilder(1024);
buf.append("<div class=\"confignav\" id=\"confignav\">");
// TODO fix up the non-light themes
String theme = _context.getProperty(CSSHelper.PROP_THEME_NAME);
boolean span = _graphical && (theme == null || theme.equals(CSSHelper.DEFAULT_THEME));
if (!span)
buf.append("<center>");
int tab = getTab();
for (int i = 0; i < titles.length; i++) {
if (i == tab) {
// we are there
if (span)
buf.append("<span class=\"tab2\">");
buf.append(_(titles[i]));
} else {
// we are not there, make a link
if (span)
buf.append("<span class=\"tab\">");
buf.append("<a href=\"profiles").append(links[i]).append("\">").append(_(titles[i])).append("</a>");
}
if (span)
buf.append(" </span>\n");
else if (i != titles.length - 1)
buf.append(" |\n");
}
if (!span)
buf.append("</center>");
buf.append("</div>");
_out.write(buf.toString());
}
}

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