forked from I2P_Developers/i2p.i2p
Compare commits
64 Commits
i2p_0_6_1_
...
i2p_0_6_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
784d465d17 | ||
|
|
327089c9d1 | ||
|
|
148dd99c86 | ||
|
|
98277d3b64 | ||
|
|
702e5a5eab | ||
|
|
54c91731c0 | ||
|
|
3bfc109476 | ||
|
|
3989638f2d | ||
|
|
4a65fd4f46 | ||
|
|
d525c49d45 | ||
|
|
c287bace0f | ||
|
|
ee0951b5b2 | ||
|
|
1eb3ae5e1b | ||
|
|
7d234b1978 | ||
|
|
6f424fa751 | ||
|
|
7726bd1a5c | ||
|
|
2a922098d6 | ||
|
|
3ec92c8b62 | ||
|
|
b37bb9372e | ||
|
|
369b6930e5 | ||
|
|
5033a22a9b | ||
|
|
6e5114a4c2 | ||
|
|
77c818a0b1 | ||
|
|
7ac673cea4 | ||
|
|
e5fa7e0ae4 | ||
|
|
ab4f3008cb | ||
|
|
f738a02760 | ||
|
|
7d64ecb628 | ||
|
|
7beacff028 | ||
|
|
952bcc696a | ||
|
|
19bba048f2 | ||
|
|
5966fcf5ff | ||
|
|
fbd7feee61 | ||
|
|
5faca98176 | ||
|
|
99951bf815 | ||
|
|
024b1a04e2 | ||
|
|
a4cc18df76 | ||
|
|
fef578973a | ||
|
|
35b75a7300 | ||
|
|
1c6c397913 | ||
|
|
c96965d364 | ||
|
|
12900ca709 | ||
|
|
f5b829a124 | ||
|
|
f62a6d3ce6 | ||
|
|
3d18bf870b | ||
|
|
d8071296eb | ||
|
|
c66e3256aa | ||
|
|
686742a67b | ||
|
|
cdf94295f3 | ||
|
|
fbf1705c4e | ||
|
|
453ecc4208 | ||
|
|
d1f2b447ac | ||
|
|
70c4560f02 | ||
|
|
9089fdd2d5 | ||
|
|
ef82cc4f20 | ||
|
|
f2c2a5b386 | ||
|
|
fc858bc950 | ||
|
|
dbb4b3d0c2 | ||
|
|
2b841ad667 | ||
|
|
5e094b43b3 | ||
|
|
33d57dd545 | ||
|
|
61f75b5f09 | ||
|
|
3f65e53592 | ||
|
|
99ae3ee459 |
45
Makefile.gcj
45
Makefile.gcj
@@ -24,10 +24,40 @@ JAR_BASE=i2p.jar mstreaming.jar streaming.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
|
||||
JAR_ROUTER=router.jar
|
||||
JAR_JBIGI=jbigi.jar
|
||||
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
|
||||
JAR_CONSOLE=\
|
||||
javax.servlet.jar \
|
||||
commons-el.jar \
|
||||
commons-logging.jar \
|
||||
jasper-runtime.jar \
|
||||
ant-apache-bcel.jar \
|
||||
ant.jar \
|
||||
jasper-compiler.jar \
|
||||
org.mortbay.jetty.jar \
|
||||
routerconsole.jar
|
||||
JAR_SUCKER=jdom.jar rome-0.7.jar sucker.jar
|
||||
LIBI2P_JARS=${JAR_BASE} ${JAR_CLIENTS} ${JAR_ROUTER} ${JAR_JBIGI}
|
||||
# unfortunately, its not quite ready for most end users, as the
|
||||
# ${JAR_CONSOLE} fails to compile with:
|
||||
# org/apache/commons/logging/impl/LogKitLogger.java: In class 'org.apache.commons.logging.impl.LogKitLogger':
|
||||
# .../LogKitLogger.java: In constructor '(java.lang.String)':
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:91: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: error: cannot find file for class org.apache.log.Hierarchy
|
||||
# .../LogKitLogger.java:104: confused by earlier errors, bailing out
|
||||
|
||||
SYSTEM_PROPS=
|
||||
#${JAR_CONSOLE}\
|
||||
#${JAR_XML} \
|
||||
#${JAR_SUCKER}
|
||||
#${JAR_CONSOLE}
|
||||
|
||||
SYSTEM_PROPS=-DloggerFilenameOverride=logs/log-router-@.txt \
|
||||
-Dorg.mortbay.http.Version.paranoid=true \
|
||||
-Dorg.mortbay.util.FileResource.checkAliases=false \
|
||||
-Dorg.mortbay.xml.XmlParser.NotValidating=true
|
||||
#SYSTEM_PROPS=-Di2p.weakPRNG=true
|
||||
OPTIMIZE=-O2
|
||||
#OPTIMIZE=-O3
|
||||
|
||||
LD_LIBRARY_PATH=${EXTRA_LD_PATH}:.
|
||||
|
||||
@@ -47,23 +77,24 @@ native_clean:
|
||||
@mkdir ${NATIVE_DIR}
|
||||
|
||||
native_shared: libi2p.so
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.Fortuna
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
|
||||
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
|
||||
@echo " and launching the appropriate proxies"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@echo "* i2psnark is an anonymous bittorrent client"
|
||||
@cd build ; ${GCJ} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@echo "* i2prouter is the main I2P router"
|
||||
@echo " it can be used, and while the router console won't load,"
|
||||
@echo " i2ptunnel will, so it will start all the proxies defined in i2ptunnel.config"
|
||||
|
||||
libi2p.so:
|
||||
@echo "* Building libi2p.so"
|
||||
@(cd build ; ${GCJ} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@(cd build ; ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@ls -l ${NATIVE_DIR}/libi2p.so
|
||||
@echo "* libi2p.so built"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="i2psnark">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="build" depends="builddep, jar, war" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
<ant dir="../../jetty/" target="build" />
|
||||
<ant dir="../../streaming/java/" target="build" />
|
||||
<!-- streaming will build ministreaming and core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
@@ -13,18 +14,75 @@
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/*Servlet.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="war" depends="jar">
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml">
|
||||
<classes dir="./build/obj" includes="**/*" />
|
||||
</war>
|
||||
</target>
|
||||
|
||||
<target name="standalone" depends="standalone_prep">
|
||||
<zip destfile="i2psnark-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="i2psnark/" />
|
||||
</zip>
|
||||
</target>
|
||||
<target name="standalone_prep" depends="war">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build" srcdir="src/" includes="org/klomp/snark/web/RunStandalone.java" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
|
||||
<jar destfile="./build/launch-i2psnark.jar" basedir="./build/" includes="org/klomp/snark/web/RunStandalone.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="org.klomp.snark.web.RunStandalone" />
|
||||
<attribute name="Class-Path" value="lib/i2p.jar lib/mstreaming.jar lib/streaming.jar lib/commons-el.jar lib/commons-logging.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/javax.servlet.jar lib/org.mortbay.jetty.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
|
||||
<delete dir="./dist" />
|
||||
<mkdir dir="./dist" />
|
||||
<copy file="./build/launch-i2psnark.jar" tofile="./dist/launch-i2psnark.jar" />
|
||||
<mkdir dir="./dist/webapps" />
|
||||
<copy file="../i2psnark.war" tofile="./dist/webapps/i2psnark.war" />
|
||||
<mkdir dir="./dist/lib" />
|
||||
<copy file="../../../core/java/build/i2p.jar" tofile="./dist/lib/i2p.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-el.jar" tofile="./dist/lib/commons-el.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
|
||||
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
|
||||
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
|
||||
<copy file="../../ministreaming/java/build/mstreaming.jar" tofile="./dist/lib/mstreaming.jar" />
|
||||
<copy file="../../streaming/java/build/streaming.jar" tofile="./dist/lib/streaming.jar" />
|
||||
<copy file="../jetty-i2psnark.xml" tofile="./dist/jetty-i2psnark.xml" />
|
||||
<copy file="../readme-standalone.txt" tofile="./dist/readme.txt" />
|
||||
<mkdir dir="./dist/work" />
|
||||
<mkdir dir="./dist/logs" />
|
||||
|
||||
<zip destfile="i2psnark-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="i2psnark/" />
|
||||
</zip>
|
||||
</target>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
<delete file="../i2psnark.war" />
|
||||
<delete file="./i2psnark-standalone.zip" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
|
||||
@@ -26,31 +26,57 @@ import java.net.*;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Accepts connections on a TCP port and routes them to sub-acceptors.
|
||||
*/
|
||||
public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
private final I2PServerSocket serverSocket;
|
||||
private final PeerAcceptor peeracceptor;
|
||||
private static final ConnectionAcceptor _instance = new ConnectionAcceptor();
|
||||
public static final ConnectionAcceptor instance() { return _instance; }
|
||||
private Log _log = new Log(ConnectionAcceptor.class);
|
||||
private I2PServerSocket serverSocket;
|
||||
private PeerAcceptor peeracceptor;
|
||||
private Thread thread;
|
||||
|
||||
private boolean stop;
|
||||
private boolean socketChanged;
|
||||
|
||||
private ConnectionAcceptor() {}
|
||||
|
||||
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
|
||||
if (serverSocket != socket) {
|
||||
if ( (peeracceptor == null) || (peeracceptor.coordinators != set) )
|
||||
peeracceptor = new PeerAcceptor(set);
|
||||
serverSocket = socket;
|
||||
stop = false;
|
||||
socketChanged = true;
|
||||
if (thread == null) {
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionAcceptor(I2PServerSocket serverSocket,
|
||||
PeerAcceptor peeracceptor)
|
||||
{
|
||||
this.serverSocket = serverSocket;
|
||||
this.peeracceptor = peeracceptor;
|
||||
|
||||
socketChanged = false;
|
||||
stop = false;
|
||||
thread = new Thread(this);
|
||||
thread = new I2PThread(this, "I2PSnark acceptor");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void halt()
|
||||
{
|
||||
if (true) throw new RuntimeException("wtf");
|
||||
stop = true;
|
||||
|
||||
I2PServerSocket ss = serverSocket;
|
||||
@@ -65,6 +91,14 @@ public class ConnectionAcceptor implements Runnable
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public void restart() {
|
||||
serverSocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
socketChanged = true;
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
}
|
||||
|
||||
public int getPort()
|
||||
{
|
||||
@@ -75,57 +109,35 @@ public class ConnectionAcceptor implements Runnable
|
||||
{
|
||||
while(!stop)
|
||||
{
|
||||
if (socketChanged) {
|
||||
// ok, already updated
|
||||
socketChanged = false;
|
||||
}
|
||||
if (serverSocket == null) {
|
||||
Snark.debug("Server socket went away.. boo hiss", Snark.ERROR);
|
||||
stop = true;
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
final I2PSocket socket = serverSocket.accept();
|
||||
Thread t = new Thread("Connection-" + socket)
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
BufferedInputStream bis = new BufferedInputStream(in);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(out);
|
||||
|
||||
// See what kind of connection it is.
|
||||
/*
|
||||
if (httpacceptor != null)
|
||||
{
|
||||
byte[] scratch = new byte[4];
|
||||
bis.mark(4);
|
||||
int len = bis.read(scratch);
|
||||
if (len != 4)
|
||||
throw new IOException("Need at least 4 bytes");
|
||||
bis.reset();
|
||||
if (scratch[0] == 19 && scratch[1] == 'B'
|
||||
&& scratch[2] == 'i' && scratch[3] == 't')
|
||||
peeracceptor.connection(socket, bis, bos);
|
||||
else if (scratch[0] == 'G' && scratch[1] == 'E'
|
||||
&& scratch[2] == 'T' && scratch[3] == ' ')
|
||||
httpacceptor.connection(socket, bis, bos);
|
||||
}
|
||||
else
|
||||
*/
|
||||
peeracceptor.connection(socket, bis, bos);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException ignored) { }
|
||||
}
|
||||
I2PSocket socket = serverSocket.accept();
|
||||
if (socket == null) {
|
||||
if (socketChanged) {
|
||||
continue;
|
||||
} else {
|
||||
Snark.debug("Null socket accepted, but socket wasn't changed?", Snark.ERROR);
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
} else {
|
||||
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
catch (I2PException ioe)
|
||||
{
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
if (!socketChanged) {
|
||||
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -139,5 +151,32 @@ public class ConnectionAcceptor implements Runnable
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
|
||||
throw new RuntimeException("wtf");
|
||||
}
|
||||
|
||||
private class Handler implements Runnable {
|
||||
private I2PSocket _socket;
|
||||
public Handler(I2PSocket socket) {
|
||||
_socket = socket;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = _socket.getInputStream();
|
||||
OutputStream out = _socket.getOutputStream();
|
||||
|
||||
if (true) {
|
||||
in = new BufferedInputStream(in);
|
||||
//out = new BufferedOutputStream(out);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handling socket from " + _socket.getPeerDestination().calculateHash().toBase64());
|
||||
peeracceptor.connection(_socket, in, out);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Error handling connection from " + _socket.getPeerDestination().calculateHash().toBase64(), ioe);
|
||||
try { _socket.close(); } catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ package org.klomp.snark;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.util.EepGet;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
@@ -13,7 +12,7 @@ import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Properties;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* I2P specific helpers for I2PSnark
|
||||
@@ -29,14 +28,17 @@ public class I2PSnarkUtil {
|
||||
private int _proxyPort;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
private Properties _opts;
|
||||
private Map _opts;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(Snark.class);
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_configured = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,25 +56,63 @@ public class I2PSnarkUtil {
|
||||
_proxyHost = null;
|
||||
_proxyPort = -1;
|
||||
}
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Properties opts) {
|
||||
public boolean configured() { return _configured; }
|
||||
|
||||
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
if (opts != null)
|
||||
_opts = opts;
|
||||
_opts.putAll(opts);
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
*/
|
||||
boolean connect() {
|
||||
public boolean connect() {
|
||||
if (_manager == null) {
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, _opts);
|
||||
Properties opts = new Properties();
|
||||
if (_opts != null) {
|
||||
for (Iterator iter = _opts.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
opts.setProperty(key, _opts.get(key).toString());
|
||||
}
|
||||
}
|
||||
if (opts.getProperty("inbound.nickname") == null)
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "1");
|
||||
if (opts.getProperty("i2p.streaming.writeTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.writeTimeout", "90000");
|
||||
if (opts.getProperty("i2p.streaming.readTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.readTimeout", "90000");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
}
|
||||
return (_manager != null);
|
||||
}
|
||||
|
||||
public boolean connected() { return _manager != null; }
|
||||
/**
|
||||
* Destroy the destination itself
|
||||
*/
|
||||
public void disconnect() {
|
||||
I2PSocketManager mgr = _manager;
|
||||
_manager = null;
|
||||
mgr.destroySocketManager();
|
||||
}
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
try {
|
||||
@@ -85,29 +125,44 @@ public class I2PSnarkUtil {
|
||||
/**
|
||||
* fetch the given URL, returning the file it is stored in, or null on error
|
||||
*/
|
||||
File get(String url) {
|
||||
public File get(String url) { return get(url, true); }
|
||||
public File get(String url, boolean rewrite) {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
out = File.createTempFile("i2psnark", "url");
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), url);
|
||||
String fetchURL = url;
|
||||
if (rewrite)
|
||||
fetchURL = rewriteAnnounce(url);
|
||||
//_log.debug("Rewritten url [" + fetchURL + "]");
|
||||
EepGet get = new EepGet(_context, _shouldProxy, _proxyHost, _proxyPort, 1, out.getAbsolutePath(), fetchURL);
|
||||
if (get.fetch()) {
|
||||
_log.debug("Fetch successful [" + url + "]: size=" + out.length());
|
||||
return out;
|
||||
} else {
|
||||
_log.warn("Fetch failed [" + url + "]");
|
||||
out.delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
I2PServerSocket getServerSocket() {
|
||||
public I2PServerSocket getServerSocket() {
|
||||
return _manager.getServerSocket();
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
return _manager.getSession().getMyDestination().toBase64();
|
||||
I2PSession sess = _manager.getSession();
|
||||
if (sess != null) {
|
||||
Destination dest = sess.getMyDestination();
|
||||
if (dest != null)
|
||||
return dest.toBase64();
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
Destination getDestination(String ip) {
|
||||
if (ip == null) return null;
|
||||
@@ -138,11 +193,22 @@ public class I2PSnarkUtil {
|
||||
int destStart = "http://".length();
|
||||
int destEnd = origAnnounce.indexOf(".i2p");
|
||||
int pathStart = origAnnounce.indexOf('/', destEnd);
|
||||
return "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||
String rv = "http://i2p/" + origAnnounce.substring(destStart, destEnd) + origAnnounce.substring(pathStart);
|
||||
//_log.debug("Rewriting [" + origAnnounce + "] as [" + rv + "]");
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** hook between snark's logger and an i2p log */
|
||||
void debug(String msg, int snarkDebugLevel, Throwable t) {
|
||||
if (t instanceof OutOfMemoryError) {
|
||||
try { Thread.sleep(100); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
t.printStackTrace();
|
||||
} catch (Throwable tt) {}
|
||||
try {
|
||||
System.out.println("OOM thread: " + Thread.currentThread().getName());
|
||||
} catch (Throwable tt) {}
|
||||
}
|
||||
switch (snarkDebugLevel) {
|
||||
case 0:
|
||||
case 1:
|
||||
|
||||
@@ -23,6 +23,8 @@ package org.klomp.snark;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
// Used to queue outgoing connections
|
||||
// sendMessage() should be used to translate them to wire format.
|
||||
class Message
|
||||
@@ -54,6 +56,8 @@ class Message
|
||||
int off;
|
||||
int len;
|
||||
|
||||
SimpleTimer.TimedEvent expireEvent;
|
||||
|
||||
/** Utility method for sending a message through a DataStream. */
|
||||
void sendMessage(DataOutputStream dos) throws IOException
|
||||
{
|
||||
|
||||
@@ -33,32 +33,48 @@ import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
/**
|
||||
* Note: this class is buggy, as it doesn't propogate custom meta fields into the bencoded
|
||||
* info data, and from there to the info_hash. At the moment, though, it seems to work with
|
||||
* torrents created by I2P-BT, I2PRufus and Azureus.
|
||||
*
|
||||
*/
|
||||
public class MetaInfo
|
||||
{
|
||||
{
|
||||
private static final Log _log = new Log(MetaInfo.class);
|
||||
private final String announce;
|
||||
private final byte[] info_hash;
|
||||
private final String name;
|
||||
private final String name_utf8;
|
||||
private final List files;
|
||||
private final List files_utf8;
|
||||
private final List lengths;
|
||||
private final int piece_length;
|
||||
private final byte[] piece_hashes;
|
||||
private final long length;
|
||||
private final Map infoMap;
|
||||
|
||||
private byte[] torrentdata;
|
||||
|
||||
MetaInfo(String announce, String name, List files, List lengths,
|
||||
MetaInfo(String announce, String name, String name_utf8, List files, List lengths,
|
||||
int piece_length, byte[] piece_hashes, long length)
|
||||
{
|
||||
this.announce = announce;
|
||||
this.name = name;
|
||||
this.name_utf8 = name_utf8;
|
||||
this.files = files;
|
||||
this.files_utf8 = null;
|
||||
this.lengths = lengths;
|
||||
this.piece_length = piece_length;
|
||||
this.piece_hashes = piece_hashes;
|
||||
this.length = length;
|
||||
|
||||
this.info_hash = calculateInfoHash();
|
||||
infoMap = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +106,7 @@ public class MetaInfo
|
||||
*/
|
||||
public MetaInfo(Map m) throws InvalidBEncodingException
|
||||
{
|
||||
_log.debug("Creating a metaInfo: " + m, new Exception("source"));
|
||||
BEValue val = (BEValue)m.get("announce");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing announce string");
|
||||
@@ -99,12 +116,19 @@ public class MetaInfo
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing info map");
|
||||
Map info = val.getMap();
|
||||
infoMap = info;
|
||||
|
||||
val = (BEValue)info.get("name");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing name string");
|
||||
name = val.getString();
|
||||
|
||||
val = (BEValue)info.get("name.utf-8");
|
||||
if (val != null)
|
||||
name_utf8 = val.getString();
|
||||
else
|
||||
name_utf8 = null;
|
||||
|
||||
val = (BEValue)info.get("piece length");
|
||||
if (val == null)
|
||||
throw new InvalidBEncodingException("Missing piece length number");
|
||||
@@ -121,6 +145,7 @@ public class MetaInfo
|
||||
// Single file case.
|
||||
length = val.getLong();
|
||||
files = null;
|
||||
files_utf8 = null;
|
||||
lengths = null;
|
||||
}
|
||||
else
|
||||
@@ -137,6 +162,7 @@ public class MetaInfo
|
||||
throw new InvalidBEncodingException("zero size files list");
|
||||
|
||||
files = new ArrayList(size);
|
||||
files_utf8 = new ArrayList(size);
|
||||
lengths = new ArrayList(size);
|
||||
long l = 0;
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
@@ -163,6 +189,19 @@ public class MetaInfo
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
|
||||
files.add(file);
|
||||
|
||||
val = (BEValue)desc.get("path.utf-8");
|
||||
if (val != null) {
|
||||
path_list = val.getList();
|
||||
path_length = path_list.size();
|
||||
if (path_length > 0) {
|
||||
file = new ArrayList(path_length);
|
||||
it = path_list.iterator();
|
||||
while (it.hasNext())
|
||||
file.add(((BEValue)it.next()).getString());
|
||||
files_utf8.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
length = l;
|
||||
}
|
||||
@@ -260,6 +299,12 @@ public class MetaInfo
|
||||
*/
|
||||
public boolean checkPiece(int piece, byte[] bs, int off, int length)
|
||||
{
|
||||
if (true)
|
||||
return fast_checkPiece(piece, bs, off, length);
|
||||
else
|
||||
return orig_checkPiece(piece, bs, off, length);
|
||||
}
|
||||
private boolean orig_checkPiece(int piece, byte[] bs, int off, int length) {
|
||||
// Check digest
|
||||
MessageDigest sha1;
|
||||
try
|
||||
@@ -278,6 +323,17 @@ public class MetaInfo
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean fast_checkPiece(int piece, byte[] bs, int off, int length) {
|
||||
SHA1 sha1 = new SHA1();
|
||||
|
||||
sha1.update(bs, off, length);
|
||||
byte[] hash = sha1.digest();
|
||||
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.
|
||||
@@ -322,7 +378,7 @@ public class MetaInfo
|
||||
*/
|
||||
public MetaInfo reannounce(String announce)
|
||||
{
|
||||
return new MetaInfo(announce, name, files,
|
||||
return new MetaInfo(announce, name, name_utf8, files,
|
||||
lengths, piece_length,
|
||||
piece_hashes, length);
|
||||
}
|
||||
@@ -343,7 +399,13 @@ public class MetaInfo
|
||||
private Map createInfoMap()
|
||||
{
|
||||
Map info = new HashMap();
|
||||
if (infoMap != null) {
|
||||
info.putAll(infoMap);
|
||||
return info;
|
||||
}
|
||||
info.put("name", name);
|
||||
if (name_utf8 != null)
|
||||
info.put("name.utf-8", name_utf8);
|
||||
info.put("piece length", new Integer(piece_length));
|
||||
info.put("pieces", piece_hashes);
|
||||
if (files == null)
|
||||
@@ -355,6 +417,8 @@ public class MetaInfo
|
||||
{
|
||||
Map file = new HashMap();
|
||||
file.put("path", files.get(i));
|
||||
if ( (files_utf8 != null) && (files_utf8.size() > i) )
|
||||
file.put("path.utf-8", files_utf8.get(i));
|
||||
file.put("length", lengths.get(i));
|
||||
l.add(file);
|
||||
}
|
||||
@@ -366,11 +430,26 @@ public class MetaInfo
|
||||
private byte[] calculateInfoHash()
|
||||
{
|
||||
Map info = createInfoMap();
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("info: ");
|
||||
for (Iterator iter = info.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
Object val = info.get(key);
|
||||
buf.append(key).append('=');
|
||||
if (val instanceof byte[])
|
||||
buf.append(Base64.encode((byte[])val, true));
|
||||
else
|
||||
buf.append(val.toString());
|
||||
}
|
||||
_log.debug(buf.toString());
|
||||
byte[] infoBytes = BEncoder.bencode(info);
|
||||
//_log.debug("info bencoded: [" + Base64.encode(infoBytes, true) + "]");
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA");
|
||||
return digest.digest(infoBytes);
|
||||
byte hash[] = digest.digest(infoBytes);
|
||||
_log.debug("info hash: [" + net.i2p.data.Base64.encode(hash) + "]");
|
||||
return hash;
|
||||
}
|
||||
catch(NoSuchAlgorithmException nsa)
|
||||
{
|
||||
|
||||
@@ -28,14 +28,17 @@ import java.util.Map;
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
public class Peer implements Comparable
|
||||
{
|
||||
private Log _log = new Log(Peer.class);
|
||||
// Identifying property, the peer id of the other side.
|
||||
private final PeerID peerID;
|
||||
|
||||
private final byte[] my_id;
|
||||
private final MetaInfo metainfo;
|
||||
final MetaInfo metainfo;
|
||||
|
||||
// The data in/output streams set during the handshake and used by
|
||||
// the actual connections.
|
||||
@@ -46,7 +49,11 @@ public class Peer implements Comparable
|
||||
// was successful, the connection setup and runs
|
||||
PeerState state;
|
||||
|
||||
private I2PSocket sock;
|
||||
|
||||
private boolean deregister = true;
|
||||
private static long __id;
|
||||
private long _id;
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
@@ -58,6 +65,8 @@ public class Peer implements Comparable
|
||||
this.peerID = peerID;
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
_id = ++__id;
|
||||
//_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,15 +78,17 @@ public class Peer implements Comparable
|
||||
*
|
||||
* @exception IOException when an error occurred during the handshake.
|
||||
*/
|
||||
public Peer(final I2PSocket sock, BufferedInputStream bis,
|
||||
BufferedOutputStream bos, byte[] my_id, MetaInfo metainfo)
|
||||
public Peer(final I2PSocket sock, InputStream in, OutputStream out, byte[] my_id, MetaInfo metainfo)
|
||||
throws IOException
|
||||
{
|
||||
this.my_id = my_id;
|
||||
this.metainfo = metainfo;
|
||||
this.sock = sock;
|
||||
|
||||
byte[] id = handshake(bis, bos);
|
||||
byte[] id = handshake(in, out);
|
||||
this.peerID = new PeerID(id, sock.getPeerDestination());
|
||||
_id = ++__id;
|
||||
_log.debug("Creating a new peer with " + peerID.getAddress().calculateHash().toBase64(), new Exception("creating " + _id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +104,10 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
return peerID.toString();
|
||||
if (peerID != null)
|
||||
return peerID.toString() + ' ' + _id;
|
||||
else
|
||||
return "[unknown id] " + _id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +115,7 @@ public class Peer implements Comparable
|
||||
*/
|
||||
public int hashCode()
|
||||
{
|
||||
return peerID.hashCode();
|
||||
return peerID.hashCode() ^ (2 << _id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +127,7 @@ public class Peer implements Comparable
|
||||
if (o instanceof Peer)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
return peerID.equals(p.peerID);
|
||||
return _id == p._id && peerID.equals(p.peerID);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
@@ -125,7 +139,14 @@ public class Peer implements Comparable
|
||||
public int compareTo(Object o)
|
||||
{
|
||||
Peer p = (Peer)o;
|
||||
return peerID.compareTo(p.peerID);
|
||||
int rv = peerID.compareTo(p.peerID);
|
||||
if (rv == 0) {
|
||||
if (_id > p._id) return 1;
|
||||
else if (_id < p._id) return -1;
|
||||
else return 0;
|
||||
} else {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,23 +166,40 @@ public class Peer implements Comparable
|
||||
if (state != null)
|
||||
throw new IllegalStateException("Peer already started");
|
||||
|
||||
_log.debug("Running connection to " + peerID.getAddress().calculateHash().toBase64(), new Exception("connecting"));
|
||||
try
|
||||
{
|
||||
// Do we need to handshake?
|
||||
if (din == null)
|
||||
{
|
||||
I2PSocket sock = I2PSnarkUtil.instance().connect(peerID);
|
||||
BufferedInputStream bis
|
||||
= new BufferedInputStream(sock.getInputStream());
|
||||
BufferedOutputStream bos
|
||||
= new BufferedOutputStream(sock.getOutputStream());
|
||||
byte [] id = handshake(bis, bos);
|
||||
sock = I2PSnarkUtil.instance().connect(peerID);
|
||||
_log.debug("Connected to " + peerID + ": " + sock);
|
||||
if ((sock == null) || (sock.isClosed())) {
|
||||
throw new IOException("Unable to reach " + peerID);
|
||||
}
|
||||
InputStream in = sock.getInputStream();
|
||||
OutputStream out = sock.getOutputStream(); //new BufferedOutputStream(sock.getOutputStream());
|
||||
if (true) {
|
||||
// buffered output streams are internally synchronized, so we can't get through to the underlying
|
||||
// I2PSocket's MessageOutputStream to close() it if we are blocking on a write(...). Oh, and the
|
||||
// buffer is unnecessary anyway, as unbuffered access lets the streaming lib do the 'right thing'.
|
||||
//out = new BufferedOutputStream(out);
|
||||
in = new BufferedInputStream(sock.getInputStream());
|
||||
}
|
||||
//BufferedInputStream bis
|
||||
// = new BufferedInputStream(sock.getInputStream());
|
||||
//BufferedOutputStream bos
|
||||
// = new BufferedOutputStream(sock.getOutputStream());
|
||||
byte [] id = handshake(in, out); //handshake(bis, bos);
|
||||
byte [] expected_id = peerID.getID();
|
||||
if (!Arrays.equals(expected_id, id))
|
||||
throw new IOException("Unexpected peerID '"
|
||||
+ PeerID.idencode(id)
|
||||
+ "' expected '"
|
||||
+ PeerID.idencode(expected_id) + "'");
|
||||
_log.debug("Handshake got matching IDs with " + toString());
|
||||
} else {
|
||||
_log.debug("Already have din [" + sock + "] with " + toString());
|
||||
}
|
||||
|
||||
PeerConnectionIn in = new PeerConnectionIn(this, din);
|
||||
@@ -176,23 +214,30 @@ public class Peer implements Comparable
|
||||
state = s;
|
||||
listener.connected(this);
|
||||
|
||||
_log.debug("Start running the reader with " + toString());
|
||||
// Use this thread for running the incomming connection.
|
||||
// The outgoing connection has created its own Thread.
|
||||
// The outgoing connection creates its own Thread.
|
||||
out.startup();
|
||||
Thread.currentThread().setName("Snark reader from " + peerID);
|
||||
s.in.run();
|
||||
}
|
||||
catch(IOException eofe)
|
||||
{
|
||||
// Ignore, probably just the other side closing the connection.
|
||||
// Or refusing the connection, timing out, etc.
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(this.toString(), eofe);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
Snark.debug(this + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
_log.error(this + ": " + t.getMessage(), t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (deregister) listener.disconnected(this);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,11 +245,11 @@ public class Peer implements Comparable
|
||||
* Sets DataIn/OutputStreams, does the handshake and returns the id
|
||||
* reported by the other side.
|
||||
*/
|
||||
private byte[] handshake(BufferedInputStream bis, BufferedOutputStream bos)
|
||||
private byte[] handshake(InputStream in, OutputStream out) //BufferedInputStream bis, BufferedOutputStream bos)
|
||||
throws IOException
|
||||
{
|
||||
din = new DataInputStream(bis);
|
||||
dout = new DataOutputStream(bos);
|
||||
din = new DataInputStream(in);
|
||||
dout = new DataOutputStream(out);
|
||||
|
||||
// Handshake write - header
|
||||
dout.write(19);
|
||||
@@ -219,11 +264,13 @@ public class Peer implements Comparable
|
||||
dout.write(my_id);
|
||||
dout.flush();
|
||||
|
||||
_log.debug("Wrote my shared hash and ID to " + toString());
|
||||
|
||||
// Handshake read - header
|
||||
byte b = din.readByte();
|
||||
if (b != 19)
|
||||
throw new IOException("Handshake failure, expected 19, got "
|
||||
+ (b & 0xff));
|
||||
+ (b & 0xff) + " on " + sock);
|
||||
|
||||
byte[] bs = new byte[19];
|
||||
din.readFully(bs);
|
||||
@@ -244,6 +291,7 @@ public class Peer implements Comparable
|
||||
|
||||
// Handshake read - peer id
|
||||
din.readFully(bs);
|
||||
_log.debug("Read the remote side's hash and peerID fully from " + toString());
|
||||
return bs;
|
||||
}
|
||||
|
||||
@@ -278,7 +326,19 @@ public class Peer implements Comparable
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
out.disconnect();
|
||||
PeerListener pl = s.listener;
|
||||
if (pl != null)
|
||||
pl.disconnected(this);
|
||||
}
|
||||
I2PSocket csock = sock;
|
||||
sock = null;
|
||||
if ( (csock != null) && (!csock.isClosed()) ) {
|
||||
try {
|
||||
csock.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error disconnecting " + toString(), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,4 +445,17 @@ public class Peer implements Comparable
|
||||
s.uploaded = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public long getInactiveTime() {
|
||||
PeerState s = state;
|
||||
if (s != null) {
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
return System.currentTimeMillis() - out.lastSent;
|
||||
else
|
||||
return -1; //"state, no out";
|
||||
} else {
|
||||
return -1; //"no state";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Accepts incomming connections from peers. The ConnectionAcceptor
|
||||
@@ -33,30 +37,109 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
*/
|
||||
public class PeerAcceptor
|
||||
{
|
||||
private static final Log _log = new Log(PeerAcceptor.class);
|
||||
private final PeerCoordinator coordinator;
|
||||
final PeerCoordinatorSet coordinators;
|
||||
|
||||
public PeerAcceptor(PeerCoordinator coordinator)
|
||||
{
|
||||
this.coordinator = coordinator;
|
||||
this.coordinators = null;
|
||||
}
|
||||
|
||||
public PeerAcceptor(PeerCoordinatorSet coordinators)
|
||||
{
|
||||
this.coordinators = coordinators;
|
||||
this.coordinator = null;
|
||||
}
|
||||
|
||||
public void connection(I2PSocket socket,
|
||||
BufferedInputStream bis, BufferedOutputStream bos)
|
||||
InputStream in, OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
if (coordinator.needPeers())
|
||||
{
|
||||
// XXX: inside this Peer constructor's handshake is where you'd deal with the other
|
||||
// side saying they want to communicate with another torrent - aka multitorrent
|
||||
// support. you'd then want to grab the meta info /they/ want, look that up in
|
||||
// our own list of active torrents, and put it on the right coordinator for it.
|
||||
// this currently, however, throws an IOException if the metainfo doesn't match
|
||||
// coodinator.getMetaInfo (Peer.java:242)
|
||||
Peer peer = new Peer(socket, bis, bos, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
coordinator.addPeer(peer);
|
||||
}
|
||||
else
|
||||
socket.close();
|
||||
// inside this Peer constructor's handshake is where you'd deal with the other
|
||||
// side saying they want to communicate with another torrent - aka multitorrent
|
||||
// support, but because of how the protocol works, we can get away with just reading
|
||||
// ahead the first $LOOKAHEAD_SIZE bytes to figure out which infohash they want to
|
||||
// talk about, and we can just look for that in our list of active torrents.
|
||||
byte peerInfoHash[] = null;
|
||||
if (in instanceof BufferedInputStream) {
|
||||
in.mark(LOOKAHEAD_SIZE);
|
||||
peerInfoHash = readHash(in);
|
||||
in.reset();
|
||||
} else {
|
||||
// is this working right?
|
||||
try {
|
||||
peerInfoHash = readHash(in);
|
||||
_log.info("infohash read from " + socket.getPeerDestination().calculateHash().toBase64()
|
||||
+ ": " + Base64.encode(peerInfoHash));
|
||||
} catch (IOException ioe) {
|
||||
_log.info("Unable to read the infohash from " + socket.getPeerDestination().calculateHash().toBase64());
|
||||
throw ioe;
|
||||
}
|
||||
in = new SequenceInputStream(new ByteArrayInputStream(peerInfoHash), in);
|
||||
}
|
||||
if (coordinator != null) {
|
||||
// single torrent capability
|
||||
MetaInfo meta = coordinator.getMetaInfo();
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
if (coordinator.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
coordinator.addPeer(peer);
|
||||
}
|
||||
else
|
||||
socket.close();
|
||||
} else {
|
||||
// its for another infohash, but we are only single torrent capable. b0rk.
|
||||
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
||||
+ ") while we only support (" + Base64.encode(meta.getInfoHash()) + ")");
|
||||
}
|
||||
} else {
|
||||
// multitorrent capable, so lets see what we can handle
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator cur = (PeerCoordinator)iter.next();
|
||||
MetaInfo meta = cur.getMetaInfo();
|
||||
|
||||
if (DataHelper.eq(meta.getInfoHash(), peerInfoHash)) {
|
||||
if (cur.needPeers())
|
||||
{
|
||||
Peer peer = new Peer(socket, in, out, cur.getID(),
|
||||
cur.getMetaInfo());
|
||||
cur.addPeer(peer);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Rejecting new peer for " + cur.snark.torrent);
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is only reached if none of the coordinators match the infohash
|
||||
throw new IOException("Peer wants another torrent (" + Base64.encode(peerInfoHash)
|
||||
+ ") while we don't support that hash");
|
||||
}
|
||||
}
|
||||
|
||||
private static final int LOOKAHEAD_SIZE = "19".length() +
|
||||
"BitTorrent protocol".length() +
|
||||
8 + // blank, reserved
|
||||
20; // infohash
|
||||
|
||||
/**
|
||||
* Read ahead to the infohash, throwing an exception if there isn't enough data
|
||||
*/
|
||||
private byte[] readHash(InputStream in) throws IOException {
|
||||
byte buf[] = new byte[LOOKAHEAD_SIZE];
|
||||
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-1, rv, 0, rv.length);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class PeerCheckerTask extends TimerTask
|
||||
{
|
||||
it.remove();
|
||||
coordinator.removePeerFromPieces(peer);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -185,6 +186,7 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
removed.add(worstDownloader);
|
||||
}
|
||||
|
||||
@@ -193,6 +195,10 @@ class PeerCheckerTask extends TimerTask
|
||||
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
}
|
||||
if (coordinator.halted()) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,11 @@ import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerConnectionIn implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionIn.class);
|
||||
private final Peer peer;
|
||||
private final DataInputStream din;
|
||||
|
||||
@@ -72,6 +75,8 @@ class PeerConnectionIn implements Runnable
|
||||
if (i == 0)
|
||||
{
|
||||
ps.keepAliveMessage();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received keepalive from " + peer + " on " + peer.metainfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -82,30 +87,44 @@ class PeerConnectionIn implements Runnable
|
||||
{
|
||||
case 0:
|
||||
ps.chokeMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received choke from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 1:
|
||||
ps.chokeMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unchoke from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 2:
|
||||
ps.interestedMessage(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received interested from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 3:
|
||||
ps.interestedMessage(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received not interested from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 4:
|
||||
piece = din.readInt();
|
||||
ps.haveMessage(piece);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received havePiece(" + piece + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 5:
|
||||
byte[] bitmap = new byte[i-1];
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.requestMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received request(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
case 7:
|
||||
piece = din.readInt();
|
||||
@@ -118,12 +137,16 @@ class PeerConnectionIn implements Runnable
|
||||
piece_bytes = req.bs;
|
||||
din.readFully(piece_bytes, begin, len);
|
||||
ps.pieceMessage(req);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
// XXX - Consume but throw away afterwards.
|
||||
piece_bytes = new byte[len];
|
||||
din.readFully(piece_bytes);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
@@ -131,22 +154,29 @@ class PeerConnectionIn implements Runnable
|
||||
begin = din.readInt();
|
||||
len = din.readInt();
|
||||
ps.cancelMessage(piece, begin, len);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer + " on " + peer.metainfo.getName());
|
||||
break;
|
||||
default:
|
||||
byte[] bs = new byte[i-1];
|
||||
din.readFully(bs);
|
||||
ps.unknownMessage(b, bs);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received unknown message from " + peer + " on " + peer.metainfo.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably the other side closed connection.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("IOError talking with " + peer, ioe);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
_log.error("Error talking with " + peer, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -24,8 +24,13 @@ import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
class PeerConnectionOut implements Runnable
|
||||
{
|
||||
private Log _log = new Log(PeerConnectionOut.class);
|
||||
private final Peer peer;
|
||||
private final DataOutputStream dout;
|
||||
|
||||
@@ -34,14 +39,24 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
// Contains Messages.
|
||||
private List sendQueue = new ArrayList();
|
||||
|
||||
private static long __id = 0;
|
||||
private long _id;
|
||||
|
||||
long lastSent;
|
||||
|
||||
public PeerConnectionOut(Peer peer, DataOutputStream dout)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.dout = dout;
|
||||
_id = ++__id;
|
||||
|
||||
lastSent = System.currentTimeMillis();
|
||||
quit = false;
|
||||
thread = new Thread(this);
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@@ -53,13 +68,13 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!quit)
|
||||
while (!quit && peer.isConnected())
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && sendQueue.isEmpty())
|
||||
while (!quit && peer.isConnected() && sendQueue.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -67,7 +82,7 @@ class PeerConnectionOut implements Runnable
|
||||
dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait();
|
||||
sendQueue.wait(60*1000);
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{
|
||||
@@ -75,7 +90,7 @@ class PeerConnectionOut implements Runnable
|
||||
}
|
||||
}
|
||||
state = peer.state;
|
||||
if (!quit && state != null)
|
||||
if (!quit && state != null && peer.isConnected())
|
||||
{
|
||||
// Piece messages are big. So if there are other
|
||||
// (control) messages make sure they are send first.
|
||||
@@ -84,37 +99,46 @@ class PeerConnectionOut implements Runnable
|
||||
// being send even if we get unchoked a little later.
|
||||
// (Since we will resent them anyway in that case.)
|
||||
// And remove piece messages if we are choking.
|
||||
|
||||
// this should get fixed for starvation
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (m == null && it.hasNext())
|
||||
{
|
||||
Message nm = (Message)it.next();
|
||||
if (nm.type == Message.PIECE)
|
||||
{
|
||||
if (state.choking)
|
||||
if (state.choking) {
|
||||
it.remove();
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
}
|
||||
nm = null;
|
||||
}
|
||||
else if (nm.type == Message.REQUEST && state.choked)
|
||||
{
|
||||
it.remove();
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
nm = null;
|
||||
}
|
||||
|
||||
if (m == null && nm != null)
|
||||
{
|
||||
m = nm;
|
||||
SimpleTimer.getInstance().removeEvent(nm.expireEvent);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (m == null && sendQueue.size() > 0)
|
||||
if (m == null && sendQueue.size() > 0) {
|
||||
m = (Message)sendQueue.remove(0);
|
||||
SimpleTimer.getInstance().removeEvent(m.expireEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.ALL)
|
||||
Snark.debug("Send " + peer + ": " + m, Snark.ALL);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send " + peer + ": " + m + " on " + peer.metainfo.getName());
|
||||
m.sendMessage(dout);
|
||||
lastSent = System.currentTimeMillis();
|
||||
|
||||
// Remove all piece messages after sending a choke message.
|
||||
if (m.type == Message.CHOKE)
|
||||
@@ -131,11 +155,14 @@ class PeerConnectionOut implements Runnable
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Ignore, probably other side closed connection.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("IOError sending to " + peer, ioe);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug(peer + ": " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
_log.error("Error sending to " + peer, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -148,15 +175,23 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if (quit == true)
|
||||
return;
|
||||
//if (quit == true)
|
||||
// return;
|
||||
|
||||
quit = true;
|
||||
thread.interrupt();
|
||||
if (thread != null)
|
||||
thread.interrupt();
|
||||
|
||||
sendQueue.clear();
|
||||
sendQueue.notify();
|
||||
}
|
||||
if (dout != null) {
|
||||
try {
|
||||
dout.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream to " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,10 +200,31 @@ class PeerConnectionOut implements Runnable
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
sendQueue.notify();
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/** remove messages not sent in 3m */
|
||||
private static final int SEND_TIMEOUT = 3*60*1000;
|
||||
private class RemoveTooSlow implements SimpleTimer.TimedEvent {
|
||||
private Message _m;
|
||||
public RemoveTooSlow(Message m) {
|
||||
_m = m;
|
||||
m.expireEvent = RemoveTooSlow.this;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
boolean removed = false;
|
||||
synchronized (sendQueue) {
|
||||
removed = sendQueue.remove(_m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
if (removed)
|
||||
_log.info("Took too long to send " + _m + " to " + peer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +250,7 @@ class PeerConnectionOut implements Runnable
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
@@ -23,13 +23,18 @@ package org.klomp.snark;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Coordinates what peer does what.
|
||||
*/
|
||||
public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
private final Log _log = new Log(PeerCoordinator.class);
|
||||
final MetaInfo metainfo;
|
||||
final Storage storage;
|
||||
final Snark snark;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
@@ -48,6 +53,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
/** estimate of the peers, without requiring any synchronization */
|
||||
volatile int peerCount;
|
||||
|
||||
/** Timer to handle all periodical tasks. */
|
||||
private final Timer timer = new Timer(true);
|
||||
@@ -60,14 +67,18 @@ public class PeerCoordinator implements PeerListener
|
||||
private boolean halted = false;
|
||||
|
||||
private final CoordinatorListener listener;
|
||||
|
||||
public String trackerProblems = null;
|
||||
public int trackerSeenPeers = 0;
|
||||
|
||||
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
|
||||
CoordinatorListener listener)
|
||||
CoordinatorListener listener, Snark torrent)
|
||||
{
|
||||
this.id = id;
|
||||
this.metainfo = metainfo;
|
||||
this.storage = storage;
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
@@ -80,6 +91,9 @@ public class PeerCoordinator implements PeerListener
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
@@ -91,12 +105,15 @@ public class PeerCoordinator implements PeerListener
|
||||
return storage.complete();
|
||||
}
|
||||
|
||||
public int getPeerCount() { return peerCount; }
|
||||
|
||||
public int getPeers()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return peers.size();
|
||||
int rv = peers.size();
|
||||
peerCount = rv;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,66 +154,85 @@ public class PeerCoordinator implements PeerListener
|
||||
return !halted && peers.size() < MAX_CONNECTIONS;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean halted() { return halted; }
|
||||
|
||||
public void halt()
|
||||
{
|
||||
halted = true;
|
||||
List removed = new ArrayList();
|
||||
synchronized(peers)
|
||||
{
|
||||
// Stop peer checker task.
|
||||
timer.cancel();
|
||||
|
||||
// Stop peers.
|
||||
Iterator it = peers.iterator();
|
||||
while(it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
peer.disconnect();
|
||||
it.remove();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
removed.addAll(peers);
|
||||
peers.clear();
|
||||
peerCount = 0;
|
||||
}
|
||||
|
||||
while (removed.size() > 0) {
|
||||
Peer peer = (Peer)removed.remove(0);
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
{
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Peer toDisconnect = null;
|
||||
synchronized(peers)
|
||||
{
|
||||
if (peerIDInList(peer.getPeerID(), peers))
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
|
||||
// idle for 2 minutes, kill the old con
|
||||
peers.remove(old);
|
||||
toDisconnect = old;
|
||||
old = null;
|
||||
}
|
||||
if (old != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Already connected to: " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("New connection to peer: " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New connection to peer: " + peer + " for " + metainfo.getName());
|
||||
|
||||
// Add it to the beginning of the list.
|
||||
// And try to optimistically make it a uploader.
|
||||
peers.add(0, peer);
|
||||
peerCount = peers.size();
|
||||
unchokePeer();
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
}
|
||||
}
|
||||
if (toDisconnect != null) {
|
||||
toDisconnect.disconnect(false);
|
||||
removePeerFromPieces(toDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean peerIDInList(PeerID pid, List peers)
|
||||
private static Peer peerIDInList(PeerID pid, List peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
if (pid.sameID(((Peer)it.next()).getPeerID()))
|
||||
return true;
|
||||
return false;
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
if (pid.sameID(cur.getPeerID()))
|
||||
return cur;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
@@ -215,6 +251,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
if (need_more)
|
||||
{
|
||||
_log.debug("Adding a peer " + peer.getPeerID().getAddress().calculateHash().toBase64() + " for " + metainfo.getName(), new Exception("add/run"));
|
||||
|
||||
// Run the peer with us as listener and the current bitfield.
|
||||
final PeerListener listener = this;
|
||||
final BitField bitfield = storage.getBitField();
|
||||
@@ -226,15 +264,16 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new Thread(r, threadName).start();
|
||||
new I2PThread(r, threadName).start();
|
||||
}
|
||||
else
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
if (peer.isConnected())
|
||||
Snark.debug("Add peer already connected: " + peer, Snark.INFO);
|
||||
_log.info("Add peer already connected: " + peer);
|
||||
else
|
||||
Snark.debug("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer, Snark.INFO);
|
||||
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,33 +284,36 @@ public class PeerCoordinator implements PeerListener
|
||||
// At the start are the peers that have us unchoked at the end the
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
synchronized (peers) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
}
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getBitMap()
|
||||
@@ -378,8 +420,9 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.fatal("Error reading storage", ioe);
|
||||
return null; // Never reached.
|
||||
snark.stopTorrent();
|
||||
_log.error("Error reading the storage for " + metainfo.getName(), ioe);
|
||||
throw new RuntimeException("B0rked");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,17 +455,17 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public boolean gotPiece(Peer peer, int piece, byte[] bs)
|
||||
{
|
||||
if (halted)
|
||||
if (halted) {
|
||||
_log.info("Got while-halted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
return true; // We don't actually care anymore.
|
||||
}
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Piece p = new Piece(piece);
|
||||
if (!wantedPieces.contains(p))
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug(peer + " piece " + piece + " no longer needed",
|
||||
Snark.INFO);
|
||||
_log.info("Got unwanted piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
|
||||
// No need to announce have piece to peers.
|
||||
// Assume we got a good piece, we don't really care anymore.
|
||||
@@ -433,22 +476,21 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
if (storage.putPiece(piece, bs))
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Recv p" + piece + " " + peer, Snark.INFO);
|
||||
_log.info("Got valid piece " + piece + "/" + metainfo.getPieces() +" from " + peer + " for " + metainfo.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Oops. We didn't actually download this then... :(
|
||||
downloaded -= metainfo.getPieceLength(piece);
|
||||
if (Snark.debug >= Snark.NOTICE)
|
||||
Snark.debug("Got BAD piece " + piece + " from " + peer,
|
||||
Snark.NOTICE);
|
||||
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
|
||||
return false; // No need to announce BAD piece to peers.
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
Snark.fatal("Error writing storage", ioe);
|
||||
snark.stopTorrent();
|
||||
_log.error("Error writing storage for " + metainfo.getName(), ioe);
|
||||
throw new RuntimeException("B0rked");
|
||||
}
|
||||
wantedPieces.remove(p);
|
||||
}
|
||||
@@ -470,8 +512,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
public void gotChoke(Peer peer, boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got choke(" + choke + "): " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Got choke(" + choke + "): " + peer);
|
||||
|
||||
if (listener != null)
|
||||
listener.peerChange(this, peer);
|
||||
@@ -489,8 +531,8 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
uploaders++;
|
||||
peer.setChoking(false);
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unchoke: " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unchoke: " + peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,8 +544,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
public void disconnected(Peer peer)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Disconnected " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Disconnected " + peer, new Exception("Disconnected by"));
|
||||
|
||||
synchronized(peers)
|
||||
{
|
||||
@@ -514,6 +556,7 @@ public class PeerCoordinator implements PeerListener
|
||||
unchokePeer();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
peerCount = peers.size();
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Hmm, any guesses as to what this is? Used by the multitorrent functionality
|
||||
* in the PeerAcceptor to pick the right PeerCoordinator to accept the con for.
|
||||
* Each PeerCoordinator is added to the set from within the Snark (and removed
|
||||
* from it there too)
|
||||
*/
|
||||
public class PeerCoordinatorSet {
|
||||
private static final PeerCoordinatorSet _instance = new PeerCoordinatorSet();
|
||||
public static final PeerCoordinatorSet instance() { return _instance; }
|
||||
private Set _coordinators;
|
||||
|
||||
private PeerCoordinatorSet() {
|
||||
_coordinators = new HashSet();
|
||||
}
|
||||
|
||||
public Iterator iterator() {
|
||||
synchronized (_coordinators) {
|
||||
return new ArrayList(_coordinators).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public void add(PeerCoordinator coordinator) {
|
||||
synchronized (_coordinators) {
|
||||
_coordinators.add(coordinator);
|
||||
}
|
||||
}
|
||||
public void remove(PeerCoordinator coordinator) {
|
||||
synchronized (_coordinators) {
|
||||
_coordinators.remove(coordinator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,11 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerState
|
||||
{
|
||||
private Log _log = new Log(PeerState.class);
|
||||
final Peer peer;
|
||||
final PeerListener listener;
|
||||
final MetaInfo metainfo;
|
||||
@@ -59,7 +62,7 @@ class PeerState
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 5;
|
||||
private final static int MAX_PIPELINE = 1;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
@@ -77,16 +80,15 @@ class PeerState
|
||||
|
||||
void keepAliveMessage()
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv alive", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv alive");
|
||||
/* XXX - ignored */
|
||||
}
|
||||
|
||||
void chokeMessage(boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv " + (choke ? "" : "un") + "choked",
|
||||
Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (choke ? "" : "un") + "choked");
|
||||
|
||||
choked = choke;
|
||||
if (choked)
|
||||
@@ -100,24 +102,23 @@ class PeerState
|
||||
|
||||
void interestedMessage(boolean interest)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv " + (interest ? "" : "un")
|
||||
+ "interested", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv " + (interest ? "" : "un")
|
||||
+ "interested");
|
||||
interested = interest;
|
||||
listener.gotInterest(peer, interest);
|
||||
}
|
||||
|
||||
void haveMessage(int piece)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv have(" + piece + ")", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv have(" + piece + ")");
|
||||
// Sanity check
|
||||
if (piece < 0 || piece >= metainfo.getPieces())
|
||||
{
|
||||
// XXX disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got strange 'have: " + piece + "' message from " + peer,
|
||||
+ Snark.INFO);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,14 +139,13 @@ class PeerState
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv bitfield", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv bitfield");
|
||||
if (bitfield != null)
|
||||
{
|
||||
// XXX - Be liberal in what you except?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got unexpected bitfield message from " + peer,
|
||||
Snark.INFO);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got unexpected bitfield message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,14 +157,13 @@ class PeerState
|
||||
|
||||
void requestMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " rcv request("
|
||||
+ piece + ", " + begin + ", " + length + ") ",
|
||||
Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " rcv request("
|
||||
+ piece + ", " + begin + ", " + length + ") ");
|
||||
if (choking)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Request received, but choking " + peer, Snark.INFO);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Request received, but choking " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -177,12 +176,11 @@ class PeerState
|
||||
|| length > 4*PARTSIZE)
|
||||
{
|
||||
// XXX - Protocol error -> disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got strange 'request: " + piece
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got strange 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer,
|
||||
Snark.INFO);
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -190,8 +188,8 @@ class PeerState
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got request for unknown piece: " + piece, Snark.INFO);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got request for unknown piece: " + piece);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -199,25 +197,18 @@ class PeerState
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Got out of range 'request: " + piece
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got out of range 'request: " + piece
|
||||
+ ", " + begin
|
||||
+ ", " + length
|
||||
+ "' message from " + peer,
|
||||
Snark.INFO);
|
||||
+ "' message from " + peer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Sending (" + piece + ", " + begin + ", "
|
||||
+ length + ")" + " to " + peer);
|
||||
out.sendPiece(piece, begin, length, pieceBytes);
|
||||
|
||||
// Tell about last subpiece delivery.
|
||||
if (begin + length == pieceBytes.length)
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Send p" + piece + " " + peer,
|
||||
Snark.DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,14 +236,13 @@ class PeerState
|
||||
{
|
||||
if (listener.gotPiece(peer, req.piece, req.bs))
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got " + req.piece + ": " + peer, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got " + req.piece + ": " + peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got BAD " + req.piece + " from " + peer,
|
||||
Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Got BAD " + req.piece + " from " + peer);
|
||||
// XXX ARGH What now !?!
|
||||
downloaded = 0;
|
||||
}
|
||||
@@ -275,21 +265,20 @@ class PeerState
|
||||
*/
|
||||
Request getOutstandingRequest(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("getChunk("
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("getChunk("
|
||||
+ piece + "," + begin + "," + length + ") "
|
||||
+ peer, Snark.DEBUG);
|
||||
+ peer);
|
||||
|
||||
int r = getFirstOutstandingRequest(piece);
|
||||
|
||||
// Unrequested piece number?
|
||||
if (r == -1)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unrequested 'piece: " + piece + ", "
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unrequested 'piece: " + piece + ", "
|
||||
+ begin + ", " + length + "' received from "
|
||||
+ peer,
|
||||
Snark.INFO);
|
||||
+ peer);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
@@ -309,13 +298,12 @@ class PeerState
|
||||
// Something wrong?
|
||||
if (req.piece != piece || req.off != begin || req.len != length)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Unrequested or unneeded 'piece: "
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Unrequested or unneeded 'piece: "
|
||||
+ piece + ", "
|
||||
+ begin + ", "
|
||||
+ length + "' received from "
|
||||
+ peer,
|
||||
Snark.INFO);
|
||||
+ peer);
|
||||
downloaded = 0; // XXX - punishment?
|
||||
return null;
|
||||
}
|
||||
@@ -323,9 +311,9 @@ class PeerState
|
||||
// Report missing requests.
|
||||
if (r != 0)
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.print("Some requests dropped, got " + req
|
||||
+ ", wanted:");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Some requests dropped, got " + req
|
||||
+ ", wanted for peer: " + peer);
|
||||
for (int i = 0; i < r; i++)
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
@@ -338,11 +326,9 @@ class PeerState
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.print(" " + dropReq);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("dropped " + dropReq + " with peer " + peer);
|
||||
}
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
System.err.println(" " + peer);
|
||||
}
|
||||
outstandingRequests.remove(0);
|
||||
}
|
||||
@@ -356,24 +342,23 @@ class PeerState
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Got cancel message ("
|
||||
+ piece + ", " + begin + ", " + length + ")",
|
||||
Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Got cancel message ("
|
||||
+ piece + ", " + begin + ", " + length + ")");
|
||||
out.cancelRequest(piece, begin, length);
|
||||
}
|
||||
|
||||
void unknownMessage(int type, byte[] bs)
|
||||
{
|
||||
if (Snark.debug >= Snark.WARNING)
|
||||
Snark.debug("Warning: Ignoring unknown message type: " + type
|
||||
+ " length: " + bs.length, Snark.WARNING);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Warning: Ignoring unknown message type: " + type
|
||||
+ " length: " + bs.length);
|
||||
}
|
||||
|
||||
void havePiece(int piece)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Tell " + peer + " havePiece(" + piece + ")", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tell " + peer + " havePiece(" + piece + ")");
|
||||
|
||||
synchronized(this)
|
||||
{
|
||||
@@ -474,8 +459,8 @@ class PeerState
|
||||
}
|
||||
}
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " requests " + outstandingRequests, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " requests " + outstandingRequests);
|
||||
}
|
||||
|
||||
// Starts requesting first chunk of next piece. Returns true if
|
||||
@@ -486,8 +471,8 @@ class PeerState
|
||||
if (bitfield != null)
|
||||
{
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " want piece " + nextPiece, Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
@@ -512,8 +497,8 @@ class PeerState
|
||||
|
||||
synchronized void setInteresting(boolean interest)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " setInteresting(" + interest + ")", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setInteresting(" + interest + ")");
|
||||
|
||||
if (interest != interesting)
|
||||
{
|
||||
@@ -527,8 +512,8 @@ class PeerState
|
||||
|
||||
synchronized void setChoking(boolean choke)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug(peer + " setChoking(" + choke + ")", Snark.DEBUG);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " setChoking(" + choke + ")");
|
||||
|
||||
if (choking != choke)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Main Snark program startup class.
|
||||
@@ -85,13 +86,41 @@ public class Snark
|
||||
"Commands: 'info', 'list', 'quit'.";
|
||||
|
||||
// String indicating main activity
|
||||
static String activity = "Not started";
|
||||
String activity = "Not started";
|
||||
|
||||
private static class OOMListener implements I2PThread.OOMEventListener {
|
||||
public void outOfMemory(OutOfMemoryError err) {
|
||||
try {
|
||||
err.printStackTrace();
|
||||
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
|
||||
} catch (Throwable t) {
|
||||
System.out.println("OOM in the OOM");
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
System.out.println(copyright);
|
||||
System.out.println();
|
||||
|
||||
if ( (args.length > 0) && ("--config".equals(args[0])) ) {
|
||||
I2PThread.addOOMEventListener(new OOMListener());
|
||||
SnarkManager sm = SnarkManager.instance();
|
||||
if (args.length > 1)
|
||||
sm.loadConfig(args[1]);
|
||||
System.out.println("Running in multitorrent mode");
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (sm) {
|
||||
sm.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse debug, share/ip and torrent file options.
|
||||
Snark snark = parseArguments(args);
|
||||
|
||||
@@ -101,7 +130,7 @@ public class Snark
|
||||
snark.acceptor,
|
||||
snark.trackerclient,
|
||||
snark);
|
||||
Runtime.getRuntime().addShutdownHook(snarkhook);
|
||||
//Runtime.getRuntime().addShutdownHook(snarkhook);
|
||||
|
||||
Timer timer = new Timer(true);
|
||||
TimerTask monitor = new PeerMonitorTask(snark.coordinator);
|
||||
@@ -130,15 +159,15 @@ public class Snark
|
||||
quit = true;
|
||||
else if ("list".equals(line))
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
synchronized(snark.coordinator.peers)
|
||||
{
|
||||
System.out.println(coordinator.peers.size()
|
||||
System.out.println(snark.coordinator.peers.size()
|
||||
+ " peers -"
|
||||
+ " (i)nterested,"
|
||||
+ " (I)nteresting,"
|
||||
+ " (c)hoking,"
|
||||
+ " (C)hoked:");
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
Iterator it = snark.coordinator.peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
@@ -152,18 +181,18 @@ public class Snark
|
||||
}
|
||||
else if ("info".equals(line))
|
||||
{
|
||||
System.out.println("Name: " + meta.getName());
|
||||
System.out.println("Torrent: " + torrent);
|
||||
System.out.println("Tracker: " + meta.getAnnounce());
|
||||
List files = meta.getFiles();
|
||||
System.out.println("Name: " + snark.meta.getName());
|
||||
System.out.println("Torrent: " + snark.torrent);
|
||||
System.out.println("Tracker: " + snark.meta.getAnnounce());
|
||||
List files = snark.meta.getFiles();
|
||||
System.out.println("Files: "
|
||||
+ ((files == null) ? 1 : files.size()));
|
||||
System.out.println("Pieces: " + meta.getPieces());
|
||||
System.out.println("Pieces: " + snark.meta.getPieces());
|
||||
System.out.println("Piece size: "
|
||||
+ meta.getPieceLength(0) / 1024
|
||||
+ snark.meta.getPieceLength(0) / 1024
|
||||
+ " KB");
|
||||
System.out.println("Total size: "
|
||||
+ meta.getTotalLength() / (1024 * 1024)
|
||||
+ snark.meta.getTotalLength() / (1024 * 1024)
|
||||
+ " MB");
|
||||
}
|
||||
else if ("".equals(line) || "help".equals(line))
|
||||
@@ -195,15 +224,22 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
static String torrent;
|
||||
static MetaInfo meta;
|
||||
static Storage storage;
|
||||
static PeerCoordinator coordinator;
|
||||
static ConnectionAcceptor acceptor;
|
||||
static TrackerClient trackerclient;
|
||||
public String torrent;
|
||||
public MetaInfo meta;
|
||||
public Storage storage;
|
||||
public PeerCoordinator coordinator;
|
||||
public ConnectionAcceptor acceptor;
|
||||
public TrackerClient trackerclient;
|
||||
public String rootDataDir = ".";
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
|
||||
private Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener)
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(torrent, ip, user_port, slistener, clistener, true, ".");
|
||||
}
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
|
||||
{
|
||||
if (slistener == null)
|
||||
slistener = this;
|
||||
@@ -212,7 +248,9 @@ public class Snark
|
||||
clistener = this;
|
||||
|
||||
this.torrent = torrent;
|
||||
this.rootDataDir = rootDir;
|
||||
|
||||
stopped = true;
|
||||
activity = "Network setup";
|
||||
|
||||
// "Taking Three as the subject to reason about--
|
||||
@@ -310,7 +348,7 @@ public class Snark
|
||||
}
|
||||
|
||||
debug(meta.toString(), INFO);
|
||||
|
||||
|
||||
// When the metainfo torrent was created from an existing file/dir
|
||||
// it already exists.
|
||||
if (storage == null)
|
||||
@@ -319,23 +357,69 @@ public class Snark
|
||||
{
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check();
|
||||
storage.check(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not create storage", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener);
|
||||
PeerAcceptor peeracceptor = new PeerAcceptor(coordinator);
|
||||
ConnectionAcceptor acceptor = new ConnectionAcceptor(serversocket,
|
||||
peeracceptor);
|
||||
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
trackerclient.start();
|
||||
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
/**
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
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
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.remove(coordinator);
|
||||
PeerCoordinator newCoord = new PeerCoordinator(coordinator.getID(), coordinator.getMetaInfo(),
|
||||
coordinator.getStorage(), coordinator.getListener(), this);
|
||||
set.add(newCoord);
|
||||
coordinator = newCoord;
|
||||
coordinatorChanged = true;
|
||||
}
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
trackerclient = newClient;
|
||||
trackerclient.start();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stop contacting the tracker and talking with peers
|
||||
*/
|
||||
public void stopTorrent() {
|
||||
stopped = true;
|
||||
trackerclient.halt();
|
||||
coordinator.halt();
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
PeerCoordinatorSet.instance().remove(coordinator);
|
||||
}
|
||||
|
||||
static Snark parseArguments(String[] args)
|
||||
@@ -357,6 +441,8 @@ public class Snark
|
||||
String ip = null;
|
||||
String torrent = null;
|
||||
|
||||
boolean configured = I2PSnarkUtil.instance().configured();
|
||||
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
@@ -403,7 +489,8 @@ public class Snark
|
||||
{
|
||||
String proxyHost = args[i+1];
|
||||
String proxyPort = args[i+2];
|
||||
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
|
||||
i += 3;
|
||||
}
|
||||
else if (args[i].equals("--i2cp"))
|
||||
@@ -424,7 +511,8 @@ public class Snark
|
||||
}
|
||||
}
|
||||
}
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
if (!configured)
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
|
||||
i += 3 + (opts != null ? 1 : 0);
|
||||
}
|
||||
else
|
||||
@@ -498,7 +586,7 @@ public class Snark
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public static void fatal(String s)
|
||||
public void fatal(String s)
|
||||
{
|
||||
fatal(s, null);
|
||||
}
|
||||
@@ -506,12 +594,13 @@ public class Snark
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
*/
|
||||
public static void fatal(String s, Throwable t)
|
||||
public void fatal(String s, Throwable t)
|
||||
{
|
||||
I2PSnarkUtil.instance().debug(s, ERROR, t);
|
||||
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
|
||||
//if (debug >= INFO && t != null)
|
||||
// t.printStackTrace();
|
||||
stopTorrent();
|
||||
throw new RuntimeException("die bart die");
|
||||
}
|
||||
|
||||
@@ -588,6 +677,15 @@ public class Snark
|
||||
allChecked = true;
|
||||
checking = false;
|
||||
}
|
||||
|
||||
public void storageCompleted(Storage storage)
|
||||
{
|
||||
Snark.debug("Completely received " + torrent, Snark.INFO);
|
||||
//storage.close();
|
||||
System.out.println("Completely received: " + torrent);
|
||||
if (completeListener != null)
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
@@ -595,4 +693,8 @@ public class Snark
|
||||
// have died. But in reality this does not always happen.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public interface CompleteListener {
|
||||
public void torrentComplete(Snark snark);
|
||||
}
|
||||
}
|
||||
|
||||
470
apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
Normal file
470
apps/i2psnark/java/src/org/klomp/snark/SnarkManager.java
Normal file
@@ -0,0 +1,470 @@
|
||||
package org.klomp.snark;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Manage multiple snarks
|
||||
*/
|
||||
public class SnarkManager implements Snark.CompleteListener {
|
||||
private static SnarkManager _instance = new SnarkManager();
|
||||
public static SnarkManager instance() { return _instance; }
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private String _configFile;
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _messages;
|
||||
|
||||
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
|
||||
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
|
||||
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
|
||||
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart";
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
|
||||
private SnarkManager() {
|
||||
_snarks = new HashMap();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
}
|
||||
|
||||
private static final int MAX_MESSAGES = 5;
|
||||
public void addMessage(String message) {
|
||||
synchronized (_messages) {
|
||||
_messages.add(message);
|
||||
while (_messages.size() > MAX_MESSAGES)
|
||||
_messages.remove(0);
|
||||
}
|
||||
_log.info("MSG: " + message);
|
||||
}
|
||||
|
||||
/** newest last */
|
||||
public List getMessages() {
|
||||
synchronized (_messages) {
|
||||
return new ArrayList(_messages);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldAutoStart() {
|
||||
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
|
||||
}
|
||||
private int getStartupDelayMinutes() { return 1; }
|
||||
public File getDataDir() {
|
||||
String dir = _config.getProperty(PROP_DIR);
|
||||
if ( (dir == null) || (dir.trim().length() <= 0) )
|
||||
dir = "i2psnark";
|
||||
return new File(dir);
|
||||
}
|
||||
|
||||
public void loadConfig(String filename) {
|
||||
_configFile = filename;
|
||||
if (_config == null)
|
||||
_config = new Properties();
|
||||
File cfg = new File(filename);
|
||||
if (cfg.exists()) {
|
||||
try {
|
||||
DataHelper.loadProps(_config, cfg);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading I2PSnark config '" + filename + "'", ioe);
|
||||
}
|
||||
}
|
||||
// now add sane defaults
|
||||
if (!_config.containsKey(PROP_I2CP_HOST))
|
||||
_config.setProperty(PROP_I2CP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_I2CP_PORT))
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
_config.setProperty(PROP_EEP_PORT, "4444");
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, "i2psnark");
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
_config.setProperty(PROP_AUTO_START, DEFAULT_AUTO_START);
|
||||
updateConfig();
|
||||
}
|
||||
|
||||
private void updateConfig() {
|
||||
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
|
||||
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
|
||||
String opts = _config.getProperty(PROP_I2CP_OPTS);
|
||||
Map i2cpOpts = new HashMap();
|
||||
if (opts != null) {
|
||||
StringTokenizer tok = new StringTokenizer(opts, " ");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
i2cpOpts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
|
||||
_log.debug("Configuring with I2CP options " + i2cpOpts);
|
||||
}
|
||||
//I2PSnarkUtil.instance().setI2CPConfig("66.111.51.110", 7654, new Properties());
|
||||
String eepHost = _config.getProperty(PROP_EEP_HOST);
|
||||
int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
if (eepHost != null)
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
private int getInt(String prop, int defaultVal) {
|
||||
String p = _config.getProperty(prop);
|
||||
try {
|
||||
if ( (p != null) && (p.trim().length() > 0) )
|
||||
return Integer.parseInt(p.trim());
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
int port = I2PSnarkUtil.instance().getEepProxyPort();
|
||||
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
|
||||
String host = I2PSnarkUtil.instance().getEepProxyHost();
|
||||
if ( (eepHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, port);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_EEP_HOST, eepHost);
|
||||
_config.setProperty(PROP_EEP_PORT, eepPort+"");
|
||||
addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
|
||||
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
|
||||
int port = oldI2CPPort;
|
||||
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
|
||||
String host = oldI2CPHost;
|
||||
Map opts = new HashMap();
|
||||
if (i2cpOpts == null) i2cpOpts = "";
|
||||
StringTokenizer tok = new StringTokenizer(i2cpOpts, " \t\n");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
opts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
Map oldOpts = new HashMap();
|
||||
String oldI2CPOpts = _config.getProperty(PROP_I2CP_OPTS);
|
||||
if (oldI2CPOpts == null) oldI2CPOpts = "";
|
||||
tok = new StringTokenizer(oldI2CPOpts, " \t\n");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split > 0)
|
||||
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
|
||||
}
|
||||
|
||||
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
|
||||
((!host.equals(i2cpHost) ||
|
||||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
|
||||
(!oldOpts.equals(opts)))) ) {
|
||||
boolean snarksActive = false;
|
||||
Set names = listTorrentFiles();
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
Snark snark = getTorrent((String)iter.next());
|
||||
if ( (snark != null) && (!snark.stopped) ) {
|
||||
snarksActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (snarksActive) {
|
||||
addMessage("Cannot change the I2CP settings while torrents are active");
|
||||
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
|
||||
+ "] oldOpts [" + oldOpts + "]");
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
addMessage("Disconnecting old I2CP destination");
|
||||
}
|
||||
Properties p = new Properties();
|
||||
p.putAll(opts);
|
||||
addMessage("I2CP settings changed to " + i2cpHost + ":" + port + " (" + i2cpOpts.trim() + ")");
|
||||
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, port, p);
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
addMessage("Unable to connect with the new settings, reverting to the old I2CP settings");
|
||||
I2PSnarkUtil.instance().setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
|
||||
ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok)
|
||||
addMessage("Unable to reconnect with the old settings!");
|
||||
} else {
|
||||
addMessage("Reconnected on the new I2CP destination");
|
||||
_config.setProperty(PROP_I2CP_HOST, i2cpHost.trim());
|
||||
_config.setProperty(PROP_I2CP_PORT, "" + port);
|
||||
_config.setProperty(PROP_I2CP_OPTS, i2cpOpts.trim());
|
||||
changed = true;
|
||||
// no PeerAcceptors/I2PServerSockets to deal with, since all snarks are inactive
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = getTorrent(name);
|
||||
if ( (snark != null) && (snark.acceptor != null) ) {
|
||||
snark.acceptor.restart();
|
||||
addMessage("I2CP listener restarted for " + snark.meta.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (shouldAutoStart() != autoStart) {
|
||||
_config.setProperty(PROP_AUTO_START, autoStart + "");
|
||||
addMessage("Adjusted autostart to " + autoStart);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
saveConfig();
|
||||
} else {
|
||||
addMessage("Configuration unchanged");
|
||||
}
|
||||
}
|
||||
|
||||
public void saveConfig() {
|
||||
try {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Unable to save the config to '" + _configFile + "'");
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getConfig() { return _config; }
|
||||
|
||||
/** hardcoded for sanity. perhaps this should be customizable, for people who increase their ulimit, etc. */
|
||||
private static final int MAX_FILES_PER_TORRENT = 128;
|
||||
|
||||
/** set of filenames that we are dealing with */
|
||||
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
|
||||
/**
|
||||
* Grab the torrent given the (canonical) filename
|
||||
*/
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
addMessage("Error connecting to I2P - check your I2CP settings");
|
||||
return;
|
||||
}
|
||||
}
|
||||
File sfile = new File(filename);
|
||||
try {
|
||||
filename = sfile.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to add the torrent " + filename, ioe);
|
||||
addMessage("ERR: Could not add the torrent '" + filename + "': " + ioe.getMessage());
|
||||
return;
|
||||
}
|
||||
File dataDir = getDataDir();
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
if (torrent == null) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
MetaInfo info = new MetaInfo(fis);
|
||||
fis.close();
|
||||
fis = null;
|
||||
|
||||
String rejectMessage = locked_validateTorrent(info);
|
||||
if (rejectMessage != null) {
|
||||
sfile.delete();
|
||||
addMessage(rejectMessage);
|
||||
return;
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
_snarks.put(filename, torrent);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
|
||||
if (sfile.exists())
|
||||
sfile.delete();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
File f = new File(filename);
|
||||
if (shouldAutoStart()) {
|
||||
torrent.startTorrent();
|
||||
addMessage("Torrent added and started: '" + f.getName() + "'");
|
||||
} else {
|
||||
addMessage("Torrent added: '" + f.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private String locked_validateTorrent(MetaInfo info) throws IOException {
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
|
||||
} else if (info.getPieces() <= 0) {
|
||||
return "No pieces in " + info.getName() + "? deleting it";
|
||||
} else if (info.getPieceLength(0) > 10*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
|
||||
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
List lengths = info.getLengths();
|
||||
if (lengths != null)
|
||||
for (int i = 0; i < lengths.size(); i++)
|
||||
System.out.println("File " + i + " is " + lengths.get(i) + " long");
|
||||
|
||||
return "Torrents larger than 10GB are not supported yet (because we're paranoid): " + info.getName() + ", deleting it";
|
||||
} else {
|
||||
// ok
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the torrent, leaving it on the list of torrents unless told to remove it
|
||||
*/
|
||||
public Snark stopTorrent(String filename, boolean shouldRemove) {
|
||||
File sfile = new File(filename);
|
||||
try {
|
||||
filename = sfile.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to remove the torrent " + filename, ioe);
|
||||
addMessage("ERR: Could not remove the torrent '" + filename + "': " + ioe.getMessage());
|
||||
return null;
|
||||
}
|
||||
int remaining = 0;
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
if (shouldRemove)
|
||||
torrent = (Snark)_snarks.remove(filename);
|
||||
else
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
remaining = _snarks.size();
|
||||
}
|
||||
if (torrent != null) {
|
||||
boolean wasStopped = torrent.stopped;
|
||||
torrent.stopTorrent();
|
||||
if (remaining == 0) {
|
||||
// should we disconnect/reconnect here (taking care to deal with the other thread's
|
||||
// I2PServerSocket.accept() call properly?)
|
||||
////I2PSnarkUtil.instance().
|
||||
}
|
||||
if (!wasStopped)
|
||||
addMessage("Torrent stopped: '" + sfile.getName() + "'");
|
||||
}
|
||||
return torrent;
|
||||
}
|
||||
/**
|
||||
* Stop the torrent and delete the torrent file itself, but leaving the data
|
||||
* behind.
|
||||
*/
|
||||
public void removeTorrent(String filename) {
|
||||
Snark torrent = stopTorrent(filename, true);
|
||||
if (torrent != null) {
|
||||
File torrentFile = new File(filename);
|
||||
torrentFile.delete();
|
||||
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private class DirMonitor implements Runnable {
|
||||
public void run() {
|
||||
try { Thread.sleep(60*1000*getStartupDelayMinutes()); } catch (InterruptedException ie) {}
|
||||
// the first message was a "We are starting up in 1m"
|
||||
synchronized (_messages) {
|
||||
if (_messages.size() == 1)
|
||||
_messages.remove(0);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
File dir = getDataDir();
|
||||
_log.debug("Directory Monitor loop over " + dir.getAbsolutePath());
|
||||
try {
|
||||
monitorTorrents(dir);
|
||||
} catch (Exception e) {
|
||||
_log.error("Error in the DirectoryMonitor", e);
|
||||
}
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void torrentComplete(Snark snark) {
|
||||
File f = new File(snark.torrent);
|
||||
long len = snark.meta.getTotalLength();
|
||||
addMessage("Download complete of " + f.getName()
|
||||
+ (len < 5*1024*1024 ? " (size: " + (len/1024) + "KB)" : " (size: " + (len/(1024*1024l)) + "MB)"));
|
||||
}
|
||||
|
||||
private void monitorTorrents(File dir) {
|
||||
String fileNames[] = dir.list(TorrentFilenameFilter.instance());
|
||||
List foundNames = new ArrayList(0);
|
||||
if (fileNames != null) {
|
||||
for (int i = 0; i < fileNames.length; i++) {
|
||||
try {
|
||||
foundNames.add(new File(dir, fileNames[i]).getCanonicalPath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error resolving '" + fileNames[i] + "' in '" + dir, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set existingNames = listTorrentFiles();
|
||||
// lets find new ones first...
|
||||
for (int i = 0; i < foundNames.size(); i++) {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connect())
|
||||
addTorrent((String)foundNames.get(i));
|
||||
else
|
||||
addMessage("Unable to connect to I2P");
|
||||
}
|
||||
}
|
||||
// now lets see which ones have been removed...
|
||||
for (Iterator iter = existingNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (foundNames.contains(name)) {
|
||||
// known and still there. noop
|
||||
} else {
|
||||
// known, but removed. drop it
|
||||
stopTorrent(name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class TorrentFilenameFilter implements FilenameFilter {
|
||||
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
|
||||
public static TorrentFilenameFilter instance() { return _filter; }
|
||||
public boolean accept(File dir, String name) {
|
||||
return (name != null) && (name.endsWith(".torrent"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,12 @@ package org.klomp.snark;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Makes sure everything ends correctly when shutting down.
|
||||
*/
|
||||
public class SnarkShutdown extends Thread
|
||||
public class SnarkShutdown extends I2PThread
|
||||
{
|
||||
private final Storage storage;
|
||||
private final PeerCoordinator coordinator;
|
||||
@@ -72,7 +74,8 @@ public class SnarkShutdown extends Thread
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
Snark.fatal("Couldn't properly close storage", ioe);
|
||||
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
|
||||
throw new RuntimeException("b0rking");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.util.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import net.i2p.crypto.SHA1;
|
||||
|
||||
/**
|
||||
* Maintains pieces on disk. Can be used to store and retrieve pieces.
|
||||
*/
|
||||
@@ -121,7 +123,7 @@ public class Storage
|
||||
}
|
||||
|
||||
// Note that the piece_hashes are not correctly setup yet.
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), files,
|
||||
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
|
||||
lengthsList, piece_size, piece_hashes, total);
|
||||
|
||||
}
|
||||
@@ -129,6 +131,14 @@ public class Storage
|
||||
// Creates piece hases for a new storage.
|
||||
public void create() throws IOException
|
||||
{
|
||||
if (true) {
|
||||
fast_digestCreate();
|
||||
} else {
|
||||
orig_digestCreate();
|
||||
}
|
||||
}
|
||||
|
||||
private void orig_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
MessageDigest digest = null;
|
||||
try
|
||||
@@ -164,6 +174,34 @@ public class Storage
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
|
||||
private void fast_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
SHA1 digest = new SHA1();
|
||||
|
||||
byte[] piece_hashes = metainfo.getPieceHashes();
|
||||
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
piece_hashes[20 * i + j] = hash[j];
|
||||
|
||||
bitfield.set(i);
|
||||
|
||||
if (listener != null)
|
||||
listener.storageChecked(this, i, true);
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
listener.storageAllChecked(this);
|
||||
|
||||
// Reannounce to force recalculating the info_hash.
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
|
||||
private void getFiles(File base) throws IOException
|
||||
{
|
||||
ArrayList files = new ArrayList();
|
||||
@@ -240,9 +278,9 @@ public class Storage
|
||||
/**
|
||||
* Creates (and/or checks) all files from the metainfo file list.
|
||||
*/
|
||||
public void check() throws IOException
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(filterName(metainfo.getName()));
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
@@ -256,7 +294,10 @@ public class Storage
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
names[0] = base.getName();
|
||||
}
|
||||
else
|
||||
@@ -277,7 +318,10 @@ public class Storage
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
total += lengths[i];
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
if (f.exists() && !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
names[i] = f.getName();
|
||||
}
|
||||
|
||||
@@ -368,8 +412,11 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
if (listener != null)
|
||||
if (listener != null) {
|
||||
listener.storageAllChecked(this);
|
||||
if (needed <= 0)
|
||||
listener.storageCompleted(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void allocateFile(int nr) throws IOException
|
||||
@@ -399,12 +446,18 @@ public class Storage
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
if (rafs == null) return;
|
||||
for (int i = 0; i < rafs.length; i++)
|
||||
{
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].close();
|
||||
}
|
||||
try {
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].close();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,6 +536,12 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,4 +49,10 @@ public interface StorageListener
|
||||
* storage is known.
|
||||
*/
|
||||
void storageAllChecked(Storage storage);
|
||||
|
||||
/**
|
||||
* Called the one time when the data is completely received and checked.
|
||||
*
|
||||
*/
|
||||
void storageCompleted(Storage storage);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Informs metainfo tracker of events and gets new peers for peer
|
||||
@@ -32,8 +34,9 @@ import org.klomp.snark.bencode.*;
|
||||
*
|
||||
* @author Mark Wielaard (mark@klomp.org)
|
||||
*/
|
||||
public class TrackerClient extends Thread
|
||||
public class TrackerClient extends I2PThread
|
||||
{
|
||||
private static final Log _log = new Log(TrackerClient.class);
|
||||
private static final String NO_EVENT = "";
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
@@ -46,6 +49,7 @@ public class TrackerClient extends Thread
|
||||
private final int port;
|
||||
|
||||
private boolean stop;
|
||||
private boolean started;
|
||||
|
||||
private long interval;
|
||||
private long lastRequestTime;
|
||||
@@ -60,8 +64,18 @@ public class TrackerClient extends Thread
|
||||
this.port = 6881; //(port == -1) ? 9 : port;
|
||||
|
||||
stop = false;
|
||||
started = false;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (stop) throw new RuntimeException("Dont rerun me, create a copy");
|
||||
super.start();
|
||||
started = true;
|
||||
}
|
||||
|
||||
public boolean halted() { return stop; }
|
||||
public boolean started() { return started; }
|
||||
|
||||
/**
|
||||
* Interrupts this Thread to stop it.
|
||||
*/
|
||||
@@ -71,13 +85,26 @@ public class TrackerClient extends Thread
|
||||
this.interrupt();
|
||||
}
|
||||
|
||||
private boolean verifyConnected() {
|
||||
while (!stop && !I2PSnarkUtil.instance().connected()) {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
return !stop && I2PSnarkUtil.instance().connected();
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// XXX - Support other IPs
|
||||
String announce = I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||
String announce = meta.getAnnounce(); //I2PSnarkUtil.instance().rewriteAnnounce(meta.getAnnounce());
|
||||
String infoHash = urlencode(meta.getInfoHash());
|
||||
String peerID = urlencode(coordinator.getID());
|
||||
|
||||
_log.debug("Announce: [" + meta.getAnnounce() + "] infoHash: " + infoHash
|
||||
+ " xmitAnnounce: [" + announce + "]");
|
||||
|
||||
long uploaded = coordinator.getUploaded();
|
||||
long downloaded = coordinator.getDownloaded();
|
||||
long left = coordinator.getLeft();
|
||||
@@ -86,6 +113,7 @@ public class TrackerClient extends Thread
|
||||
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
@@ -95,10 +123,20 @@ public class TrackerClient extends Thread
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID,
|
||||
uploaded, downloaded, left,
|
||||
STARTED_EVENT);
|
||||
Iterator it = info.getPeers().iterator();
|
||||
while (it.hasNext())
|
||||
coordinator.addPeer((Peer)it.next());
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if (!completed) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
started = true;
|
||||
coordinator.trackerProblems = null;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -106,6 +144,7 @@ public class TrackerClient extends Thread
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
@@ -123,12 +162,14 @@ public class TrackerClient extends Thread
|
||||
}
|
||||
}
|
||||
|
||||
Random r = new Random();
|
||||
while(!stop)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
Thread.sleep(SLEEP*60*1000);
|
||||
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
@@ -137,6 +178,8 @@ public class TrackerClient extends Thread
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
if (!verifyConnected()) return;
|
||||
|
||||
uploaded = coordinator.getUploaded();
|
||||
downloaded = coordinator.getDownloaded();
|
||||
@@ -163,9 +206,22 @@ public class TrackerClient extends Thread
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
Iterator it = info.getPeers().iterator();
|
||||
while (it.hasNext())
|
||||
coordinator.addPeer((Peer)it.next());
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
// we only want to talk to new people if we need things
|
||||
// from them (duh)
|
||||
List ordered = new ArrayList(peers);
|
||||
Collections.shuffle(ordered);
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -179,13 +235,15 @@ public class TrackerClient extends Thread
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
Snark.debug("TrackerClient: " + t, Snark.ERROR);
|
||||
t.printStackTrace();
|
||||
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
|
||||
if (t instanceof OutOfMemoryError)
|
||||
throw (OutOfMemoryError)t;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!verifyConnected()) return;
|
||||
TrackerInfo info = doRequest(announce, infoHash, peerID, uploaded,
|
||||
downloaded, left, STOPPED_EVENT);
|
||||
}
|
||||
@@ -203,7 +261,7 @@ public class TrackerClient extends Thread
|
||||
+ "?info_hash=" + infoHash
|
||||
+ "&peer_id=" + peerID
|
||||
+ "&port=" + port
|
||||
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString()
|
||||
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString() + ".i2p"
|
||||
+ "&uploaded=" + uploaded
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
@@ -216,21 +274,26 @@ public class TrackerClient extends Thread
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
|
||||
fetched.deleteOnExit();
|
||||
InputStream in = new FileInputStream(fetched);
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(fetched);
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
throw new IOException(failure);
|
||||
|
||||
interval = info.getInterval() * 1000;
|
||||
return info;
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
if (failure != null)
|
||||
throw new IOException(failure);
|
||||
|
||||
interval = info.getInterval() * 1000;
|
||||
return info;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
fetched.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -170,6 +170,9 @@ public class BEValue
|
||||
}
|
||||
}
|
||||
|
||||
/** return the untyped value */
|
||||
public Object getValue() { return value; }
|
||||
|
||||
public String toString()
|
||||
{
|
||||
String valueString;
|
||||
|
||||
@@ -62,6 +62,8 @@ public class BEncoder
|
||||
bencode((List)o, out);
|
||||
else if (o instanceof Map)
|
||||
bencode((Map)o, out);
|
||||
else if (o instanceof BEValue)
|
||||
bencode(((BEValue)o).getValue(), out);
|
||||
else
|
||||
throw new IllegalArgumentException("Cannot bencode: " + o.getClass());
|
||||
}
|
||||
|
||||
620
apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
Normal file
620
apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
Normal file
@@ -0,0 +1,620 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
import org.klomp.snark.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class I2PSnarkServlet extends HttpServlet {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private SnarkManager _manager;
|
||||
private static long _nonce;
|
||||
|
||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||
|
||||
public void init(ServletConfig cfg) throws ServletException {
|
||||
super.init(cfg);
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSnarkServlet.class);
|
||||
_nonce = _context.random().nextLong();
|
||||
_manager = SnarkManager.instance();
|
||||
String configFile = _context.getProperty(PROP_CONFIG_FILE);
|
||||
if ( (configFile == null) || (configFile.trim().length() <= 0) )
|
||||
configFile = "i2psnark.config";
|
||||
_manager.loadConfig(configFile);
|
||||
}
|
||||
|
||||
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
processRequest(req);
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.write(HEADER_BEGIN);
|
||||
// we want it to go to the base URI so we don't refresh with some funky action= value
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
|
||||
out.write(HEADER);
|
||||
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("I2PSnark<br />\n");
|
||||
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
List msgs = _manager.getMessages();
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = (String)msgs.get(i);
|
||||
out.write(msg + "\n");
|
||||
}
|
||||
out.write("</pre></td></tr></table>\n");
|
||||
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
displaySnark(out, snark, uri, i);
|
||||
}
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
}
|
||||
|
||||
out.write(TABLE_FOOTER);
|
||||
writeAddForm(out, req);
|
||||
if (false) // seeding needs to register the torrent first (boo, hiss)
|
||||
writeSeedForm(out, req);
|
||||
writeConfigForm(out, req);
|
||||
out.write(FOOTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do what they ask, adding messages to _manager.addMessage as necessary
|
||||
*/
|
||||
private void processRequest(HttpServletRequest req) {
|
||||
String action = req.getParameter("action");
|
||||
if (action == null) {
|
||||
// noop
|
||||
} else if ("Add torrent".equals(action)) {
|
||||
String newFile = req.getParameter("newFile");
|
||||
String newURL = req.getParameter("newURL");
|
||||
File f = null;
|
||||
if ( (newFile != null) && (newFile.trim().length() > 0) )
|
||||
f = new File(newFile.trim());
|
||||
if ( (f != null) && (!f.exists()) ) {
|
||||
_manager.addMessage("Torrent file " + newFile +" does not exist");
|
||||
}
|
||||
if ( (f != null) && (f.exists()) ) {
|
||||
File local = new File(_manager.getDataDir(), f.getName());
|
||||
String canonical = null;
|
||||
try {
|
||||
canonical = local.getCanonicalPath();
|
||||
|
||||
if (local.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + newFile);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + newFile);
|
||||
} else {
|
||||
boolean ok = FileUtil.copy(f.getAbsolutePath(), local.getAbsolutePath(), true);
|
||||
if (ok) {
|
||||
_manager.addMessage("Copying torrent to " + local.getAbsolutePath());
|
||||
_manager.addTorrent(canonical);
|
||||
} else {
|
||||
_manager.addMessage("Unable to copy the torrent to " + local.getAbsolutePath() + " from " + f.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("hrm: " + local, ioe);
|
||||
}
|
||||
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
|
||||
_manager.addMessage("Fetching " + newURL);
|
||||
I2PThread fetch = new I2PThread(new FetchAndAdd(newURL), "Fetch and add");
|
||||
fetch.start();
|
||||
} else {
|
||||
// no file or URL specified
|
||||
}
|
||||
} else if ("Stop".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Start".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
snark.startTorrent();
|
||||
_manager.addMessage("Starting up torrent " + name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Remove".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, true);
|
||||
// should we delete the torrent file?
|
||||
// yeah, need to, otherwise it'll get autoadded again (at the moment
|
||||
File f = new File(name);
|
||||
f.delete();
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Delete".equals(action)) {
|
||||
String torrent = req.getParameter("torrent");
|
||||
if (torrent != null) {
|
||||
byte infoHash[] = Base64.decode(torrent);
|
||||
if ( (infoHash != null) && (infoHash.length == 20) ) { // valid sha1
|
||||
for (Iterator iter = _manager.listTorrentFiles().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if ( (snark != null) && (DataHelper.eq(infoHash, snark.meta.getInfoHash())) ) {
|
||||
_manager.stopTorrent(name, true);
|
||||
File f = new File(name);
|
||||
f.delete();
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
List files = snark.meta.getFiles();
|
||||
String dataFile = snark.meta.getName();
|
||||
for (int i = 0; files != null && i < files.size(); i++) {
|
||||
File df = new File(_manager.getDataDir(), (String)files.get(i));
|
||||
boolean deleted = FileUtil.rmdir(df, false);
|
||||
if (deleted)
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
if (dataFile != null) {
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
boolean deleted = f.delete();
|
||||
if (deleted)
|
||||
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("Save configuration".equals(action)) {
|
||||
String dataDir = req.getParameter("dataDir");
|
||||
boolean autoStart = req.getParameter("autoStart") != null;
|
||||
String seedPct = req.getParameter("seedPct");
|
||||
String eepHost = req.getParameter("eepHost");
|
||||
String eepPort = req.getParameter("eepPort");
|
||||
String i2cpHost = req.getParameter("i2cpHost");
|
||||
String i2cpPort = req.getParameter("i2cpPort");
|
||||
String i2cpOpts = req.getParameter("i2cpOpts");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
|
||||
} else if ("Create torrent".equals(action)) {
|
||||
String baseData = req.getParameter("baseFile");
|
||||
if (baseData != null) {
|
||||
File baseFile = new File(_manager.getDataDir(), baseData);
|
||||
String announceURL = req.getParameter("announceURL");
|
||||
String announceURLOther = req.getParameter("announceURLOther");
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
s.create();
|
||||
MetaInfo info = s.getMetaInfo();
|
||||
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
|
||||
if (torrentFile.exists())
|
||||
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
|
||||
FileOutputStream out = new FileOutputStream(torrentFile);
|
||||
out.write(info.getTorrentData());
|
||||
out.close();
|
||||
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
|
||||
// now fire it up and seed away!
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath());
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchAndAdd implements Runnable {
|
||||
private String _url;
|
||||
public FetchAndAdd(String url) {
|
||||
_url = url;
|
||||
}
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
File file = I2PSnarkUtil.instance().get(_url, false);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
MetaInfo info = new MetaInfo(in);
|
||||
String name = info.getName();
|
||||
name = name.replace('/', '_');
|
||||
name = name.replace('\\', '_');
|
||||
name = name.replace('&', '+');
|
||||
name = name.replace('\'', '_');
|
||||
name = name.replace('"', '_');
|
||||
name = name.replace('`', '_');
|
||||
name = name + ".torrent";
|
||||
File torrentFile = new File(_manager.getDataDir(), name);
|
||||
|
||||
String canonical = torrentFile.getCanonicalPath();
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + name);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + name);
|
||||
} else {
|
||||
FileUtil.copy(file.getAbsolutePath(), canonical, true);
|
||||
_manager.addTorrent(canonical);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Torrent was not retrieved from " + _url);
|
||||
}
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List getSortedSnarks(HttpServletRequest req) {
|
||||
Set files = _manager.listTorrentFiles();
|
||||
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
|
||||
ArrayList rv = new ArrayList(fileNames.size());
|
||||
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
Snark snark = _manager.getTorrent(name);
|
||||
if (snark != null)
|
||||
rv.add(snark);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
|
||||
String filename = snark.torrent;
|
||||
File f = new File(filename);
|
||||
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
|
||||
long total = snark.meta.getTotalLength();
|
||||
long remaining = snark.storage.needed() * snark.meta.getPieceLength(0);
|
||||
if (remaining > total)
|
||||
remaining = total;
|
||||
int totalBps = 4096; // should probably grab this from the snark...
|
||||
long remainingSeconds = remaining / totalBps;
|
||||
long uploaded = snark.coordinator.getUploaded();
|
||||
|
||||
boolean isRunning = !snark.stopped;
|
||||
boolean isValid = snark.meta != null;
|
||||
boolean singleFile = snark.meta.getFiles() == null;
|
||||
|
||||
String err = snark.coordinator.trackerProblems;
|
||||
int curPeers = snark.coordinator.getPeerCount();
|
||||
int knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
|
||||
String statusString = "Unknown";
|
||||
if (err != null) {
|
||||
if (isRunning)
|
||||
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "TrackerErr (" + err + ")";
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning)
|
||||
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Complete";
|
||||
} else {
|
||||
if (isRunning)
|
||||
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Stopped";
|
||||
}
|
||||
|
||||
String rowClass = (row % 2 == 0 ? "snarkTorrentEven" : "snarkTorrentOdd");
|
||||
out.write("<tr class=\"" + rowClass + "\">");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write(statusString + "</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentName " + rowClass + "\">");
|
||||
|
||||
if (remaining == 0)
|
||||
out.write("<a href=\"file:///" + _manager.getDataDir().getAbsolutePath() + File.separatorChar + snark.meta.getName()
|
||||
+ "\" title=\"Download the completed file\">");
|
||||
out.write(filename);
|
||||
if (remaining == 0)
|
||||
out.write("</a>");
|
||||
out.write("</td>\n\t");
|
||||
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
// lets hold off on the ETA until we have rates sorted...
|
||||
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
|
||||
} else {
|
||||
out.write(formatSize(total)); // 3GB
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
|
||||
+ "\">" + formatSize(uploaded) + "</td>\n\t");
|
||||
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
|
||||
//out.write("n/a"); //2KBps/12KBps/4KBps
|
||||
//out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Stop the torrent\">Stop</a>");
|
||||
} else {
|
||||
if (isValid)
|
||||
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Start the torrent\">Start</a> ");
|
||||
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
|
||||
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
|
||||
}
|
||||
out.write("</td>\n</tr>\n");
|
||||
}
|
||||
|
||||
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String newURL = req.getParameter("newURL");
|
||||
if ( (newURL == null) || (newURL.trim().length() <= 0) ) newURL = "http://";
|
||||
String newFile = req.getParameter("newFile");
|
||||
if ( (newFile == null) || (newFile.trim().length() <= 0) ) newFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
|
||||
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
|
||||
out.write("<span class=\"snarkAddInfo\">Alternately, you can copy .torrent files to " + _manager.getDataDir().getAbsolutePath() + "<br />\n");
|
||||
out.write("Removing that .torrent file will cause the torrent to stop.<br /></span>\n");
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman's tracker", "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",
|
||||
"Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt",
|
||||
"The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
};
|
||||
private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String baseFile = req.getParameter("baseFile");
|
||||
if (baseFile == null)
|
||||
baseFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("Data to seed: <input type=\"text\" name=\"baseFile\" size=\"50\" value=\"" + baseFile
|
||||
+ "\" title=\"File within " + _manager.getDataDir().getAbsolutePath() + " to seed\" /><br />\n");
|
||||
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
|
||||
for (int i = 0; i + 1 < DEFAULT_TRACKERS.length; i += 2)
|
||||
out.write("\t<option value=\"" + DEFAULT_TRACKERS[i+1] + "\">" + DEFAULT_TRACKERS[i] + "</option>\n");
|
||||
out.write("</select><br />\n");
|
||||
out.write(" or: ");
|
||||
out.write("<input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
|
||||
"title=\"Custom tracker URL\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Create torrent\" name=\"action\" />\n");
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String dataDir = _manager.getDataDir().getAbsolutePath();
|
||||
boolean autoStart = _manager.shouldAutoStart();
|
||||
int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<span class=\"snarkConfig\"><hr />\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Configuration:</span><br />\n");
|
||||
out.write("Data directory: <input type=\"text\" size=\"40\" name=\"dataDir\" value=\"" + dataDir + "\" ");
|
||||
out.write("title=\"Directory to store torrents and data\" disabled=\"true\" /><br />\n");
|
||||
out.write("Auto start: <input type=\"checkbox\" name=\"autoStart\" value=\"true\" "
|
||||
+ (autoStart ? "checked " : "")
|
||||
+ "title=\"If true, automatically start torrents that are added\" />");
|
||||
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
|
||||
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
|
||||
//out.write("<br />\n");
|
||||
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
|
||||
if (seedPct <= 0)
|
||||
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"0\">Unlimited</option>\n\t");
|
||||
if (seedPct == 100)
|
||||
out.write("<option value=\"100\" selected=\"true\">100%</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"100\">100%</option>\n\t");
|
||||
if (seedPct == 150)
|
||||
out.write("<option value=\"150\" selected=\"true\">150%</option>\n\t");
|
||||
else
|
||||
out.write("<option value=\"150\">150%</option>\n\t");
|
||||
out.write("</select><br />\n");
|
||||
|
||||
//out.write("<hr />\n");
|
||||
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
|
||||
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" /><br />\n");
|
||||
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
|
||||
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
|
||||
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
|
||||
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" /> <br />\n");
|
||||
StringBuffer opts = new StringBuffer(64);
|
||||
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
|
||||
for (Iterator iter = options.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = (String)options.get(key);
|
||||
opts.append(key).append('=').append(val).append(' ');
|
||||
}
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
|
||||
+ opts.toString() + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
|
||||
out.write("</span>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
private String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + "B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return (bytes/1024) + "KB";
|
||||
else if (bytes < 5*1024*1024*1024l)
|
||||
return (bytes/(1024*1024)) + "MB";
|
||||
else
|
||||
return (bytes/(1024*1024*1024)) + "GB";
|
||||
}
|
||||
|
||||
private static final String HEADER_BEGIN = "<html>\n" +
|
||||
"<head>\n" +
|
||||
"<title>I2PSnark - anonymous bittorrent</title>\n";
|
||||
|
||||
private static final String HEADER = "<style>\n" +
|
||||
"body {\n" +
|
||||
" background-color: #C7CFB4;\n" +
|
||||
"}\n" +
|
||||
".snarkTitle {\n" +
|
||||
" text-align: left;\n" +
|
||||
" float: left;\n" +
|
||||
" margin: 0px 0px 5px 5px;\n" +
|
||||
" display: inline;\n" +
|
||||
" font-size: 16pt;\n" +
|
||||
" font-weight: bold;\n" +
|
||||
"}\n" +
|
||||
".snarkRefresh {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
".snarkMessages {\n" +
|
||||
" border: none;\n" +
|
||||
" background-color: #CECFC6;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
" font-weight: 100;\n" +
|
||||
" width: 100%;\n" +
|
||||
" text-align: left;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" border: 0px;\n" +
|
||||
" padding: 5px;\n" +
|
||||
" border-width: 0px;\n" +
|
||||
" border-spacing: 0px;\n" +
|
||||
"}\n" +
|
||||
"table {\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" border: 0px;\n" +
|
||||
" padding: 0px;\n" +
|
||||
" border-width: 0px;\n" +
|
||||
" border-spacing: 0px;\n" +
|
||||
"}\n" +
|
||||
"th {\n" +
|
||||
" background-color: #C7D5D5;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentEven {\n" +
|
||||
" background-color: #E7E7E7;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentOdd {\n" +
|
||||
" background-color: #DDDDCC;\n" +
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 12pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
".snarkConfigTitle {\n" +
|
||||
" font-size: 12pt;\n" +
|
||||
" font-weight: bold;\n" +
|
||||
"}\n" +
|
||||
".snarkConfig {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
"</style>\n" +
|
||||
"</head>\n" +
|
||||
"<body>\n";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
|
||||
"<thead>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
|
||||
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</thead>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
|
||||
|
||||
private static final String TABLE_FOOTER = "</table>\n";
|
||||
|
||||
private static final String FOOTER = "</body></html>";
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.util.FileUtil;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
public class RunStandalone {
|
||||
private Server _server;
|
||||
|
||||
static {
|
||||
System.setProperty("org.mortbay.http.Version.paranoid", "true");
|
||||
System.setProperty("org.mortbay.xml.XmlParser.NotValidating", "true");
|
||||
}
|
||||
|
||||
private RunStandalone(String args[]) {}
|
||||
|
||||
public static void main(String args[]) {
|
||||
RunStandalone runner = new RunStandalone(args);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
File workDir = new File("work");
|
||||
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
|
||||
if (!workDirRemoved)
|
||||
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
|
||||
boolean workDirCreated = workDir.mkdirs();
|
||||
if (!workDirCreated)
|
||||
System.err.println("ERROR: Unable to create Jetty temporary work directory");
|
||||
|
||||
try {
|
||||
_server = new Server("jetty-i2psnark.xml");
|
||||
_server.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
_server.stop();
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
114
apps/i2psnark/jetty-i2psnark.xml
Normal file
114
apps/i2psnark/jetty-i2psnark.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Jetty Server -->
|
||||
<!-- =============================================================== -->
|
||||
<Configure class="org.mortbay.jetty.Server">
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Listeners -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add and configure a HTTP listener to port 8080 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SocketListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.util.InetAddrPort">
|
||||
<Set name="host">127.0.0.1</Set>
|
||||
<Set name="port">8002</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">10</Set>
|
||||
<Set name="MaxIdleTimeMs">30000</Set>
|
||||
<Set name="LowResourcePersistTimeMs">1000</Set>
|
||||
<Set name="ConfidentialPort">8443</Set>
|
||||
<Set name="IntegralPort">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a HTTPS SSL listener on port 8443 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SunJsseListener">
|
||||
<Set name="Port">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
<Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set>
|
||||
<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
|
||||
<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
|
||||
<Set name="NonPersistentUserAgent">MSIE 5</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a AJP13 listener on port 8009 -->
|
||||
<!-- This protocol can be used with mod_jk in apache, IIS etc. -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!--
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.ajp.AJP13Listener">
|
||||
<Set name="PoolName">ajp</Set>
|
||||
<Set name="Port">8009</Set>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">20</Set>
|
||||
<Set name="MaxIdleTimeMs">0</Set>
|
||||
<Set name="confidentialPort">443</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Contexts -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a all web application within the webapps directory. -->
|
||||
<!-- + No virtual host specified -->
|
||||
<!-- + Look in the webapps directory relative to jetty.home or . -->
|
||||
<!-- + Use the default webdefault.xml in jetty's install -->
|
||||
<!-- + Upack the war file -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Set name="rootWebApp">i2psnark</Set>
|
||||
<Call name="addWebApplication">
|
||||
<Arg>/</Arg>
|
||||
<Arg>webapps/i2psnark.war</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Log -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="RequestLog">
|
||||
<New class="org.mortbay.http.NCSARequestLog">
|
||||
<Arg>./logs/yyyy_mm_dd.i2psnark-request.log</Arg>
|
||||
<Set name="retainDays">90</Set>
|
||||
<Set name="append">true</Set>
|
||||
<Set name="extended">false</Set>
|
||||
<Set name="buffered">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Other Server Options -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="requestsPerGC">2000</Set>
|
||||
<Set name="statsOn">false</Set>
|
||||
|
||||
</Configure>
|
||||
6
apps/i2psnark/readme-standalone.txt
Normal file
6
apps/i2psnark/readme-standalone.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
To run I2PSnark from the command line, run "java -jar lib/i2psnark.jar", but
|
||||
to run it with the web UI, run "java -jar launch-i2psnark.jar". I2PSnark is
|
||||
GPL'ed software, based on Snark (http://www.klomp.org/) to run on top of I2P
|
||||
(http://www.i2p.net/) within a webserver (such as the bundled Jetty from
|
||||
http://jetty.mortbay.org/). For more information about I2PSnark, get in touch
|
||||
with the folks at http://forum.i2p.net/
|
||||
25
apps/i2psnark/web.xml
Normal file
25
apps/i2psnark/web.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
|
||||
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
|
||||
|
||||
<web-app>
|
||||
<servlet>
|
||||
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
|
||||
<servlet-class>org.klomp.snark.web.I2PSnarkServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<!-- precompiled servlets -->
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>org.klomp.snark.web.I2PSnarkServlet</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<session-config>
|
||||
<session-timeout>
|
||||
30
|
||||
</session-timeout>
|
||||
</session-config>
|
||||
</web-app>
|
||||
@@ -386,6 +386,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
if (pos < 0) {
|
||||
l.log("Invalid request url [" + request + "]");
|
||||
if (out != null) {
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
destination = request.substring(0, pos);
|
||||
line = method + " " + request.substring(pos);
|
||||
}
|
||||
@@ -444,8 +456,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
boolean gzip = DEFAULT_GZIP;
|
||||
if (ok != null)
|
||||
gzip = Boolean.valueOf(ok).booleanValue();
|
||||
if (gzip)
|
||||
newRequest.append("Accept-Encoding: x-i2p-gzip\r\n");
|
||||
if (gzip) {
|
||||
// according to rfc2616 s14.3, this *should* force identity, even if
|
||||
// an explicit q=0 for gzip doesn't. tested against orion.i2p, and it
|
||||
// seems to work.
|
||||
newRequest.append("Accept-Encoding: \r\n");
|
||||
newRequest.append("X-Accept-Encoding: x-i2p-gzip;q=1.0, identity;q=0.5, deflate;q=0, gzip;q=0, *;q=0\r\n");
|
||||
}
|
||||
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
@@ -485,17 +502,20 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
_log.warn("Unable to resolve " + destination + " (proxy? " + usingWWWProxy + ", request: " + targetRequest);
|
||||
String str;
|
||||
byte[] header;
|
||||
boolean showAddrHelper = false;
|
||||
if (usingWWWProxy)
|
||||
str = FileUtil.readTextFile("docs/dnfp-header.ht", 100, true);
|
||||
else if(ahelper != 0)
|
||||
str = FileUtil.readTextFile("docs/dnfb-header.ht", 100, true);
|
||||
else
|
||||
else {
|
||||
str = FileUtil.readTextFile("docs/dnfh-header.ht", 100, true);
|
||||
showAddrHelper = true;
|
||||
}
|
||||
if (str != null)
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination);
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, destination, showAddrHelper);
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
@@ -564,7 +584,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy) throws IOException {
|
||||
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
@@ -576,6 +596,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
if (showAddrHelper) {
|
||||
out.write("<br><br>Click below to try an address helper link:<br><br><a href=\"http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
}
|
||||
}
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
@@ -601,7 +628,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
header = str.getBytes();
|
||||
else
|
||||
header = ERR_DESTINATION_UNKNOWN;
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy);
|
||||
writeErrorMessage(header, out, targetRequest, usingWWWProxy, wwwProxy, false);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn(getPrefix(requestId) + "Error writing out the 'destination was unknown' " + "message", ioe);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,11 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
// we keep the enc sent by the browser before clobbering it, since it may have
|
||||
// been x-i2p-gzip
|
||||
String enc = headers.getProperty("Accept-encoding");
|
||||
headers.setProperty("Accept-encoding", "identity;q=1, *;q=0");
|
||||
String altEnc = headers.getProperty("X-Accept-encoding");
|
||||
|
||||
// according to rfc2616 s14.3, this *should* force identity, even if
|
||||
// "identity;q=1, *;q=0" didn't.
|
||||
headers.setProperty("Accept-encoding", "");
|
||||
String modifiedHeader = formatHeaders(headers, command);
|
||||
|
||||
//String modifiedHeader = getModifiedHeader(socket);
|
||||
@@ -97,8 +101,12 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
allowGZIP = false;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("HTTP server encoding header: " + enc);
|
||||
if ( allowGZIP && (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) ) {
|
||||
_log.info("HTTP server encoding header: " + enc + "/" + altEnc);
|
||||
boolean useGZIP = ( (enc != null) && (enc.indexOf("x-i2p-gzip") >= 0) );
|
||||
if ( (!useGZIP) && (altEnc != null) && (altEnc.indexOf("x-i2p-gzip") >= 0) )
|
||||
useGZIP = true;
|
||||
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
req.start();
|
||||
} else {
|
||||
@@ -297,6 +305,8 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
String value = buf.substring(split+2); // ": "
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
else if ("X-Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "X-Accept-encoding";
|
||||
headers.setProperty(name, value);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the header [" + name + "] = [" + value + "]");
|
||||
|
||||
@@ -139,9 +139,11 @@ public class IndexBean {
|
||||
return stop();
|
||||
else if ("start".equals(_action))
|
||||
return start();
|
||||
else if ("Save changes".equals(_action))
|
||||
else if ("Save changes".equals(_action) || // IE workaround:
|
||||
(_action.toLowerCase().indexOf("s</span>ave") >= 0))
|
||||
return saveChanges();
|
||||
else if ("Delete this proxy".equals(_action))
|
||||
else if ("Delete this proxy".equals(_action) || // IE workaround:
|
||||
(_action.toLowerCase().indexOf("d</span>elete") >= 0))
|
||||
return deleteTunnel();
|
||||
else
|
||||
return "Action " + _action + " unknown";
|
||||
|
||||
@@ -3,30 +3,29 @@
|
||||
|
||||
<target name="all" depends="build" />
|
||||
<target name="fetchJettylib" >
|
||||
<available property="jetty.available" file="jetty-5.1.2.zip" />
|
||||
<available property="jetty.available" file="jetty-5.1.6.zip" />
|
||||
<ant target="doFetchJettylib" />
|
||||
</target>
|
||||
<target name="doFetchJettylib" unless="jetty.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.2" />
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
|
||||
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
|
||||
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
|
||||
<echo message="such as the routerconsole." />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.2.zip" verbose="true" dest="jetty-5.1.2.zip" />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
|
||||
<ant target="doExtract" />
|
||||
</target>
|
||||
<target name="doExtract">
|
||||
<unzip src="jetty-5.1.2.zip" dest="." />
|
||||
<unzip src="jetty-5.1.6.zip" dest="." />
|
||||
<mkdir dir="jettylib" />
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/lib">
|
||||
<fileset dir="jetty-5.1.6/lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.2/ext">
|
||||
<fileset dir="jetty-5.1.6/ext">
|
||||
<include name="ant.jar" />
|
||||
<include name="commons-el.jar" />
|
||||
<include name="commons-logging.jar" />
|
||||
<include name="jasper-compiler.jar" />
|
||||
<include name="jasper-runtime.jar" />
|
||||
<include name="javax.servlet.jar" />
|
||||
@@ -34,7 +33,9 @@
|
||||
<include name="xercesImpl.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<delete dir="jetty-5.1.2" />
|
||||
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
|
||||
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
|
||||
<delete dir="jetty-5.1.6" />
|
||||
</target>
|
||||
<target name="build" depends="fetchJettylib" />
|
||||
<target name="builddep" />
|
||||
|
||||
@@ -59,6 +59,8 @@ public class ConfigLoggingHelper {
|
||||
buf.append(prefix).append('=').append(level).append('\n');
|
||||
}
|
||||
buf.append("</textarea><br />\n");
|
||||
buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>");
|
||||
buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>");
|
||||
buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.time.Timestamper;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask;
|
||||
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerAndRekeyTask;
|
||||
|
||||
/**
|
||||
* Handler to deal with form submissions from the main config form and act
|
||||
@@ -31,6 +35,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _recheckReachabilityRequested;
|
||||
private boolean _timeSyncEnabled;
|
||||
private boolean _requireIntroductions;
|
||||
private boolean _hiddenMode;
|
||||
private boolean _dynamicKeys;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
private String _inboundRate;
|
||||
@@ -62,6 +68,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
public void setRequireIntroductions(String moo) { _requireIntroductions = true; }
|
||||
public void setHiddenMode(String moo) { _hiddenMode = true; }
|
||||
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
_hostname = (hostname != null ? hostname.trim() : null);
|
||||
@@ -263,6 +271,28 @@ public class ConfigNetHandler extends FormHandler {
|
||||
addFormNotice("Updating bandwidth share percentage");
|
||||
}
|
||||
}
|
||||
|
||||
// If hidden mode value changes, restart is required
|
||||
if (_hiddenMode && "false".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
|
||||
_context.router().setConfigSetting(Router.PROP_HIDDEN, "true");
|
||||
_context.router().getRouterInfo().addCapability(RouterInfo.CAPABILITY_HIDDEN);
|
||||
addFormNotice("Gracefully restarting into Hidden Router Mode. Make sure you have no 0-1 length "
|
||||
+ "<a href=\"configtunnels.jsp\">tunnels!</a>");
|
||||
hiddenSwitch();
|
||||
}
|
||||
|
||||
if (!_hiddenMode && "true".equalsIgnoreCase(_context.getProperty(Router.PROP_HIDDEN, "false"))) {
|
||||
_context.router().removeConfigSetting(Router.PROP_HIDDEN);
|
||||
_context.router().getRouterInfo().delCapability(RouterInfo.CAPABILITY_HIDDEN);
|
||||
addFormNotice("Gracefully restarting to exit Hidden Router Mode");
|
||||
hiddenSwitch();
|
||||
}
|
||||
|
||||
if (_dynamicKeys) {
|
||||
_context.router().setConfigSetting(Router.PROP_DYNAMIC_KEYS, "true");
|
||||
} else {
|
||||
_context.router().removeConfigSetting(Router.PROP_DYNAMIC_KEYS);
|
||||
}
|
||||
|
||||
if (_requireIntroductions) {
|
||||
_context.router().setConfigSetting(UDPTransport.PROP_FORCE_INTRODUCERS, "true");
|
||||
@@ -290,6 +320,12 @@ public class ConfigNetHandler extends FormHandler {
|
||||
addFormNotice("Soft restart complete");
|
||||
}
|
||||
}
|
||||
|
||||
private void hiddenSwitch() {
|
||||
// Full restart required to generate new keys
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
private void updateRates() {
|
||||
boolean updated = false;
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.router.transport.udp.UDPAddress;
|
||||
import net.i2p.router.transport.udp.UDPTransport;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
public class ConfigNetHelper {
|
||||
private RouterContext _context;
|
||||
@@ -63,6 +64,22 @@ public class ConfigNetHelper {
|
||||
return " checked ";
|
||||
}
|
||||
|
||||
public String getHiddenModeChecked() {
|
||||
String enabled = _context.getProperty(Router.PROP_HIDDEN, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getDynamicKeysChecked() {
|
||||
String enabled = _context.getProperty(Router.PROP_DYNAMIC_KEYS, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getRequireIntroductionsChecked() {
|
||||
short status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
|
||||
@@ -33,6 +33,21 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateWrapperManagerAndRekeyTask implements Runnable {
|
||||
private int _exitCode;
|
||||
public UpdateWrapperManagerAndRekeyTask(int exitCode) {
|
||||
_exitCode = exitCode;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
Router.killKeys();
|
||||
WrapperManager.signalStopped(_exitCode);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processForm() {
|
||||
if (_action == null) return;
|
||||
@@ -56,6 +71,14 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerTask(Router.EXIT_HARD_RESTART));
|
||||
_context.router().shutdown(Router.EXIT_HARD_RESTART);
|
||||
addFormNotice("Hard restart requested");
|
||||
} else if ("Rekey and Restart".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful restart");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
} else if ("Rekey and Shutdown".equals(_action)) {
|
||||
addFormNotice("Rekeying after graceful shutdown");
|
||||
_context.router().addShutdownTask(new UpdateWrapperManagerAndRekeyTask(Router.EXIT_GRACEFUL));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL);
|
||||
} else if ("Run I2P on startup".equals(_action)) {
|
||||
installService();
|
||||
} else if ("Don't run I2P on startup".equals(_action)) {
|
||||
@@ -195,4 +218,4 @@ public class ConfigServiceHandler extends FormHandler {
|
||||
addFormError("Error updating the client config");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ public class ConfigStatsHandler extends FormHandler {
|
||||
|
||||
if (_explicitFilter) {
|
||||
_stats.clear();
|
||||
if (_explicitFilterValue == null)
|
||||
_explicitFilterValue = "";
|
||||
|
||||
if (_explicitFilterValue.indexOf(',') != -1) {
|
||||
StringTokenizer tok = new StringTokenizer(_explicitFilterValue, ",");
|
||||
|
||||
@@ -135,7 +135,7 @@ public class ConfigTunnelsHandler extends FormHandler {
|
||||
if (saveRequired) {
|
||||
boolean saved = _context.router().saveConfig();
|
||||
if (saved)
|
||||
addFormNotice("Configuration saved successfully");
|
||||
addFormNotice("Exploratory tunnel configuration saved successfully");
|
||||
else
|
||||
addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class JobQueueHelper {
|
||||
private RouterContext _context;
|
||||
private Writer _out;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
* @param contextId begging few characters of the routerHash, or null to pick
|
||||
* the first one we come across.
|
||||
*/
|
||||
public void setContextId(String contextId) {
|
||||
try {
|
||||
_context = ContextHelper.getContext(contextId);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public JobQueueHelper() {}
|
||||
|
||||
public void setWriter(Writer writer) { _out = writer; }
|
||||
|
||||
public String getJobQueueSummary() {
|
||||
try {
|
||||
if (_out != null) {
|
||||
_context.jobQueue().renderStatusHTML(_out);
|
||||
return "";
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
_context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,14 +83,23 @@ public class SummaryHelper {
|
||||
|
||||
long ms = _context.clock().getOffset();
|
||||
|
||||
if (ms < 60 * 1000) {
|
||||
return now + " (" + (ms / 1000) + "s)";
|
||||
} else if (ms < 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 1000)) + "m)";
|
||||
} else if (ms < 24 * 60 * 60 * 1000) {
|
||||
return now + " (" + (ms / (60 * 60 * 1000)) + "h)";
|
||||
long diff = ms;
|
||||
if (diff < 0)
|
||||
diff = 0 - diff;
|
||||
if (diff == 0) {
|
||||
return now + " (no skew)";
|
||||
} else if (diff < 1000) {
|
||||
return now + " (" + ms + "ms skew)";
|
||||
} else if (diff < 5 * 1000) {
|
||||
return now + " (" + (ms / 1000) + "s skew)";
|
||||
} else if (diff < 60 * 1000) {
|
||||
return now + " <b>(" + (ms / 1000) + "s skew)</b>";
|
||||
} else if (diff < 60 * 60 * 1000) {
|
||||
return now + " <b>(" + (ms / (60 * 1000)) + "m skew)</b>";
|
||||
} else if (diff < 24 * 60 * 60 * 1000) {
|
||||
return now + " <b>(" + (ms / (60 * 60 * 1000)) + "h skew)</b>";
|
||||
} else {
|
||||
return now + " (" + (ms / (24 * 60 * 60 * 1000)) + "d)";
|
||||
return now + " <b>(" + (ms / (24 * 60 * 60 * 1000)) + "d skew)</b>";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +417,28 @@ public class SummaryHelper {
|
||||
return _context.tunnelManager().getOutboundTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many inbound client tunnels we have.
|
||||
*
|
||||
*/
|
||||
public int getInboundClientTunnels() {
|
||||
if (_context == null)
|
||||
return 0;
|
||||
else
|
||||
return _context.tunnelManager().getInboundClientTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many active outbound client tunnels we have.
|
||||
*
|
||||
*/
|
||||
public int getOutboundClientTunnels() {
|
||||
if (_context == null)
|
||||
return 0;
|
||||
else
|
||||
return _context.tunnelManager().getOutboundClientTunnelCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* How many tunnels we are participating in.
|
||||
*
|
||||
@@ -459,4 +490,4 @@ public class SummaryHelper {
|
||||
public boolean updateAvailable() {
|
||||
return NewsFetcher.getInstance(_context).updateAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,23 @@
|
||||
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
|
||||
Sharing a higher percentage will improve your anonymity and help the network
|
||||
<hr />
|
||||
<b>Dynamic Router Keys: </b>
|
||||
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
|
||||
<p>
|
||||
This setting causes your router identity to be regenerated every time your IP address
|
||||
changes. If you have a dynamic IP this option can speed up your reintegration into
|
||||
the network (since people will have shitlisted your old router identity), and, for
|
||||
very weak adversaries, help frustrate trivial
|
||||
<a href="http://www.i2p.net/how_threatmodel#intersection">intersection
|
||||
attacks</a> against the NetDB. Your different router identities would only be
|
||||
'hidden' among other I2P users at your ISP, and further analysis would link
|
||||
the router identities further.</p>
|
||||
<p>Note that when I2P detects an IP address change, it will automatically
|
||||
initiate a restart in order to rekey and to disconnect from peers before they
|
||||
update their profiles - any long lasting client connections will be disconnected,
|
||||
though such would likely already be the case anyway, since the IP address changed.
|
||||
</p>
|
||||
<hr />
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
<jsp:getProperty name="tunnelshelper" property="form" />
|
||||
<hr />
|
||||
<i>Note - Exploratory tunnel setting changes are stored in the router.config file.</i></br>
|
||||
<i>Client tunnel changes are temporary and are not saved.</i><br>
|
||||
<i>To make permanent client tunnel changes see the </i><a href="i2ptunnel/index.jsp">i2ptunnel page</a>.<br>
|
||||
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,10 @@ div.logo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.toolbar {
|
||||
font-weight: bold
|
||||
}
|
||||
|
||||
div.routersummary {
|
||||
/* width: 8em; */
|
||||
/* height: 5em; */
|
||||
|
||||
21
apps/routerconsole/jsp/jobs.jsp
Normal file
21
apps/routerconsole/jsp/jobs.jsp
Normal file
@@ -0,0 +1,21 @@
|
||||
<%@page contentType="text/html"%>
|
||||
<%@page pageEncoding="UTF-8"%>
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html><head>
|
||||
<title>I2P Router Console - job queue</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
</head><body>
|
||||
|
||||
<%@include file="nav.jsp" %>
|
||||
<%@include file="summary.jsp" %>
|
||||
|
||||
<div class="main" id="main">
|
||||
<jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" />
|
||||
<jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" />
|
||||
<jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" />
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,4 @@
|
||||
<%@page import="java.io.File" %>
|
||||
<% response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Cache-Control","no-cache");
|
||||
response.setDateHeader("Expires", 0);
|
||||
@@ -13,22 +14,32 @@
|
||||
<a href="index.jsp"><img src="i2plogo.png" alt="Router Console" width="187" height="35" /></a><br />
|
||||
[<a href="config.jsp">configuration</a> | <a href="help.jsp">help</a>]
|
||||
</div>
|
||||
|
||||
<h4>
|
||||
<div class="toolbar">
|
||||
<% if (new File("docs/toolbar.html").exists()) { %>
|
||||
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="toolbarhelper" scope="request" />
|
||||
<jsp:setProperty name="toolbarhelper" property="page" value="docs/toolbar.html" />
|
||||
<jsp:setProperty name="toolbarhelper" property="maxLines" value="300" />
|
||||
<jsp:getProperty name="toolbarhelper" property="content" />
|
||||
<% } else { %>
|
||||
<!-- Could not find docs/toolbar.html! -->
|
||||
<a href="susimail/susimail">Susimail</a> |
|
||||
<a href="susidns/index.jsp">SusiDNS</a> |
|
||||
<a href="syndie/">Syndie</a> |
|
||||
<a href="i2psnark/">I2PSnark</a> |
|
||||
<a href="http://localhost:7658/">My Eepsite</a> <br>
|
||||
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |
|
||||
<a href="tunnels.jsp">Tunnels</a> |
|
||||
<a href="profiles.jsp">Profiles</a> |
|
||||
<a href="netdb.jsp">NetDB</a> |
|
||||
<a href="logs.jsp">Logs</a> |
|
||||
<a href="jobs.jsp">Jobs</a> |
|
||||
<a href="oldstats.jsp">Stats</a> |
|
||||
<a href="oldconsole.jsp">Internals</a>
|
||||
<% } %>
|
||||
<jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" />
|
||||
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:getProperty name="navhelper" property="clientAppLinks" />
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.NoticeHelper" id="noticehelper" scope="request" />
|
||||
<jsp:setProperty name="noticehelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
|
||||
@@ -72,9 +72,9 @@
|
||||
|
||||
<jsp:getProperty name="helper" property="destinations" />
|
||||
|
||||
<u><b>Tunnels</b></u><br />
|
||||
<b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br />
|
||||
<b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br />
|
||||
<u><b>Tunnels in/out</b></u><br />
|
||||
<b>Exploratory:</b> <jsp:getProperty name="helper" property="inboundTunnels" />/<jsp:getProperty name="helper" property="outboundTunnels" /><br />
|
||||
<b>Client:</b> <jsp:getProperty name="helper" property="inboundClientTunnels" />/<jsp:getProperty name="helper" property="outboundClientTunnels" /><br />
|
||||
<b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br />
|
||||
<hr />
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ void string_copy(string_t src,string_t dest) {
|
||||
void string_copy_raw(string_t src, void* dest,size_t size) {
|
||||
size = min(src->size,size);
|
||||
memcpy(dest,src->data,size);
|
||||
((char*)dest)[size] = '\0';
|
||||
}
|
||||
|
||||
const char* string_data(string_t self) {
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
## Copyright 2004 Brian Ristuccia. This program is Free Software;
|
||||
## You can redistribute it and/or modify it under the same terms as
|
||||
## Perl itself.
|
||||
|
||||
package Net::SAM;
|
||||
|
||||
@ISA = ( "IO::Socket::INET" );
|
||||
|
||||
use strict;
|
||||
|
||||
use POSIX;
|
||||
|
||||
use Switch;
|
||||
|
||||
use IO::Socket;
|
||||
use IO::Select;
|
||||
|
||||
#use Net::SAM::StreamSession;
|
||||
#use Net::SAM::DatagramSession;
|
||||
#use Net::SAM::RawSession;
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my $type = ref($class) || $class;
|
||||
my $self = $type->SUPER::new("127.0.0.1:7656");
|
||||
|
||||
|
||||
${*$self}->{incomingraw} = [];
|
||||
|
||||
# Connect us to the local SAM proxy.
|
||||
# my $samsock = IO::Socket::INET->new('127.0.0.1:7657');
|
||||
#$self->{samsock}=$samsock;
|
||||
|
||||
# Say hello, read response.
|
||||
$self->SUPER::send("HELLO VERSION MIN=1.0 MAX=1.0\n");
|
||||
|
||||
while (! ${*$self}->{greeted}) {
|
||||
$self->readprocess();
|
||||
}
|
||||
print "Created SAM object\n";
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub lookup {
|
||||
my $self = shift;
|
||||
my $name= shift;
|
||||
|
||||
$self->SUPER::send("NAMING LOOKUP NAME=$name\n");
|
||||
undef ${*$self}->{RESULT};
|
||||
while (! ${*$self}->{RESULT}) {
|
||||
$self->readprocess();
|
||||
}
|
||||
if ( ${*$self}->{RESULT} == "OK" ) {
|
||||
return ${*$self}->{VALUE};
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
#sub createsession {
|
||||
# my ($self) = shift;
|
||||
# my ($sesstype) = shift;
|
||||
# print $self->{samsock} "SESSION CREATE STYLE=$SESSTYPE DESTINATION=$DEST, DIRECTION=
|
||||
#}
|
||||
|
||||
#sub waitfor {
|
||||
# my ($self) = shift;
|
||||
# my ($prefix) = shift;
|
||||
# my ($response) = <$samsock>;#
|
||||
|
||||
# if $response =~
|
||||
|
||||
|
||||
#}
|
||||
|
||||
|
||||
sub readprocesswrite {
|
||||
my $self = shift;
|
||||
$self->readprocess();
|
||||
$self->dowrite();
|
||||
}
|
||||
|
||||
sub doread {
|
||||
my $self = shift;
|
||||
my $rv;
|
||||
my $data;
|
||||
|
||||
$rv = $self->recv($data, $POSIX::BUFSIZE, 0);
|
||||
|
||||
if ( defined($rv) && ( length($data) >= 1 ) ) {
|
||||
# We received some data. Put it in our buffer.
|
||||
${*$self}->{inbuffer} += $data;
|
||||
} else {
|
||||
# No data. Either we're on a non-blocking socket, or there
|
||||
# was an error or EOF
|
||||
if ( $!{EAGAIN} ) {
|
||||
return 1;
|
||||
} else {
|
||||
# I suppose caller can look at $! for details
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub dowrite {
|
||||
my $self = shift;
|
||||
my $rv;
|
||||
my $data;
|
||||
|
||||
$rv = $self->send(${*$self}->{outbuffer}, 0);
|
||||
|
||||
if ( ! defined($rv) ) {
|
||||
warn "SAM::dowrite - Couldn't write for no apparent reason.\n";
|
||||
return undef;
|
||||
}
|
||||
|
||||
if ( $rv == length(${*$self}->{outbuffer}) || $!{EWOULDBLOCK} ) {
|
||||
substr(${*$self}->{outbuffer},0, $rv) = ''; # Remove from buffer
|
||||
|
||||
# Nuke buffer if empty
|
||||
delete ${*$self}->{outbuffer} unless length(${*$self}->{outbuffer});
|
||||
} else {
|
||||
# Socket closed on us or something?
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub messages {
|
||||
my $self = shift;
|
||||
|
||||
return @{ ${*$self}->{messages} };
|
||||
}
|
||||
|
||||
sub queuemessage {
|
||||
|
||||
my $self = shift;
|
||||
my $message = shift;
|
||||
|
||||
push @{ ${*$self}->{messages} } , $message;
|
||||
}
|
||||
|
||||
sub unqueuemessage {
|
||||
my $self = shift;
|
||||
|
||||
return unshift(@{ ${*$self}->{messages} } );
|
||||
|
||||
}
|
||||
|
||||
sub readprocess {
|
||||
my $self = shift;
|
||||
|
||||
$self->doread();
|
||||
$self->process();
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my %tvhash;
|
||||
my $payload;
|
||||
|
||||
|
||||
# Before we can read any new messages, if an existing message has payload
|
||||
# we must read it in. Otherwise we'll create garbage messages containing
|
||||
# the payload of previous messages.
|
||||
|
||||
if ( ${*$self}->{payloadrequired} >= 1 ) {
|
||||
|
||||
if ( length( ${*$self}->{inbuffer} ) >= ${*$self}->{payloadrequired} ) {
|
||||
# Scarf payload from inbuffer into $payload
|
||||
$payload = substr(${*$self}->{inbuffer}, 0,
|
||||
${*$self}->{payloadrequired});
|
||||
|
||||
# Nuke payload from inbuffer
|
||||
substr(${*$self}->{inbuffer}, 0,
|
||||
${*$self}->{payloadrequired} ) = '';
|
||||
|
||||
# Put message with payload into spool
|
||||
push @{ ${*$self}->{messages} } ,
|
||||
${*$self}->{messagerequiringpayload}.$payload;
|
||||
|
||||
# Delete the saved message requiring payload
|
||||
delete ${*$self}->{messagerequiringpayload};
|
||||
} else {
|
||||
# Insufficient payload in inbuffer. Try again later.
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( ${*$self}->{inbuffer} =~ s/(.*\n)// ) {
|
||||
%tvhash = $self->_hashtv($1); # Returns a tag/value hash
|
||||
if ( $tvhash{SIZE} ) {
|
||||
# We've got a message with payload on our hands. :(
|
||||
${*$self}->{payloadrequired} = $tvhash{SIZE};
|
||||
${*$self}->{messagerequiringpayload} = $1;
|
||||
return 1; # Could call ourself here, but we'll get called again.
|
||||
} else {
|
||||
push @{ ${*$self}->{messages} } , $1;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
# sub junk {
|
||||
|
||||
|
||||
# print "readprocess: " . $self->connected() . "\n";
|
||||
|
||||
# # May block if the SAM bridge gets hosed
|
||||
# my $response = <$self>;
|
||||
|
||||
# print "readprocess: $!" . $self->connected() . "\n";
|
||||
|
||||
# chomp $response;
|
||||
# my ($primative, $more, $extra) = split (' ', $response, 3);
|
||||
|
||||
# $primative = uc($primative);
|
||||
|
||||
# print "readprocess: " . $self->connected() . " -- $primative -- $more -- $extra\n";
|
||||
|
||||
# switch ($primative) {
|
||||
|
||||
# case "HELLO" {
|
||||
# if ($more !~ m/REPLY/ ) { die ("Bogus HELLO response") }
|
||||
# if ($extra =~ m/NOVERSION/ ) {
|
||||
# die("SAM Bridge Doesn't support my version") ;
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# ${*$self}->{greeted} = 1;
|
||||
# };
|
||||
# case "SESSION" {
|
||||
# if ( $more !~ m/STATUS/ ) {
|
||||
# die("Bogus SESSION response");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# }
|
||||
# case "STREAM" {};
|
||||
# case "DATAGRAM" {
|
||||
# if ( $more !~ m/RECEIVE/ ) {
|
||||
# die("Bogus DATAGRAM response.");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# push @{ ${*$self}->{incomingdatagram } },
|
||||
# [ ${*$self}->{DESTINATION},
|
||||
# $self->_readblock(${*$self}->{SIZE}) ];
|
||||
|
||||
# };
|
||||
# case "RAW" {
|
||||
# if ( $more !~ m/RECEIVE/ ) {
|
||||
# die("Bogus RAW response.");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
|
||||
# push @{ $self->{incomingraw} }, $self->_readblock($self->{SIZE});
|
||||
# };
|
||||
# case "NAMING" {
|
||||
# if ( $more !~ m/REPLY/ ) {
|
||||
# die("Bogus NAMING response");
|
||||
# }
|
||||
# $self->_hashtv($extra);
|
||||
# };
|
||||
# case "DEST" {};
|
||||
# }
|
||||
# return 1;
|
||||
# }
|
||||
|
||||
sub getfh {
|
||||
# Return the FH of the SAM socket so apps can select() or poll() on it
|
||||
my $self = shift;
|
||||
return $self->{samsock};
|
||||
}
|
||||
|
||||
sub _readblock {
|
||||
my $self = shift;
|
||||
my $size = shift;
|
||||
my $chunk;
|
||||
my $payload;
|
||||
|
||||
while ( $size > 1 ) {
|
||||
# XXX: May block. No error checking.
|
||||
print "readblock: $size\n";
|
||||
$size -= $self->SUPER::recv($chunk, $size);
|
||||
$payload .= $chunk;
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
|
||||
sub _hashtv {
|
||||
my $self = shift;
|
||||
my $tvstring = shift;
|
||||
my $tvhash;
|
||||
|
||||
while ( $tvstring =~ m/(\S+)=(\S+)/sg ) {
|
||||
$tvhash->{$1}=$2;
|
||||
print "hashtv: $1=$2\n"
|
||||
}
|
||||
return $tvhash;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
# Do nothing yet.
|
||||
}
|
||||
|
||||
#sub StreamSession {
|
||||
# my $self = shift;
|
||||
# return Net::SAM::StreamSession->new($self);
|
||||
#}
|
||||
|
||||
#sub DatagramSession {
|
||||
# return Net::SAM::DatagramSession->new($self);
|
||||
#}
|
||||
|
||||
#sub RawSession {
|
||||
# return Net::SAM::RawSession->new($self);
|
||||
#}
|
||||
|
||||
1;
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package Net::SAM::DatagramSession;
|
||||
|
||||
use Net::SAM;
|
||||
|
||||
@ISA = ("Net::SAM");
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my ($dest , $direction, $options) = shift;
|
||||
|
||||
my $self = $class->SUPER::new(@_);
|
||||
|
||||
$self->SUPER::send("SESSION CREATE STYLE=DATAGRAM DESTINATION=$dest DIRECTION=$direction $options\n");
|
||||
|
||||
undef ${*$self}->{RESULT};
|
||||
|
||||
while ( ! ${*$self}->{RESULT} ) {
|
||||
$self->readprocess() || return undef;
|
||||
|
||||
}
|
||||
|
||||
if ( ${*$self}->{RESULT} == "OK" ) {
|
||||
return $self;
|
||||
} else {
|
||||
return undef; # sorry.
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
my $self = shift;
|
||||
my $destination = shift;
|
||||
my $message = shift;
|
||||
my $size = length($message);
|
||||
$self->SUPER::send("DATAGRAM SEND DESTINATION=$destination SIZE=$size\n$message");
|
||||
}
|
||||
|
||||
sub receive {
|
||||
my $self = shift;
|
||||
|
||||
# Shift one off the fifo array. Returns undef if none wait.
|
||||
return shift @{ $self->{incomingdatagram} };
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
package Net::SAM::RawSession;
|
||||
|
||||
use Net::SAM;
|
||||
|
||||
@ISA = ("Net::SAM");
|
||||
|
||||
sub new {
|
||||
my ($class) = shift;
|
||||
my ($dest , $direction, $options) = shift;
|
||||
|
||||
my $self = $class->SUPER::new(@_);
|
||||
|
||||
$self->send("SESSION CREATE STYLE=RAW DESTINATION=$dest DIRECTION=$direction $options\n");
|
||||
|
||||
undef $self->{result};
|
||||
while ( ! $self->{RESULT} ) {
|
||||
$self->readprocess();
|
||||
}
|
||||
|
||||
if ( $self->{RESULT} == "OK" ) {
|
||||
return $self;
|
||||
} else {
|
||||
return 0; # sorry.
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
my $self = shift;
|
||||
my $destination = shift;
|
||||
my $message = shift;
|
||||
my $samsock = $self->{samsock};
|
||||
my $size = length($message);
|
||||
print $samsock "RAW SEND DESTINATION=$destination SIZE=$size\n$message";
|
||||
}
|
||||
|
||||
sub receive {
|
||||
my $self = shift;
|
||||
|
||||
# Shift one off the fifo array. Returns undef if none wait.
|
||||
return shift @{ $self->{incomingraw} };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
1;
|
||||
108
apps/sam/perl/README
Normal file
108
apps/sam/perl/README
Normal file
@@ -0,0 +1,108 @@
|
||||
# BASIC Perl SAM Module
|
||||
# created 2005 by postman (postman@i2pmail.org)
|
||||
|
||||
|
||||
1. What does it do?
|
||||
|
||||
The SAM module is a little Perl add-on that - on one side -
|
||||
establishes communication with a I2P router's (http://www.i2p.net) SAM bridge
|
||||
(Simple anonymous messaging ( http://www.i2p.net/sam)). On the
|
||||
other side it exposes a simple socket like interface to the user.
|
||||
Over this interface the user can send or receive datastreams from I2P
|
||||
destinations as if he would communicate with a normal IP socket.
|
||||
|
||||
The SAM module can be integrated into perl scripts that
|
||||
want to communicate with I2P services.
|
||||
|
||||
|
||||
2. Is this code usable?
|
||||
|
||||
This perl module should be considered as proof-of-concept
|
||||
quality. It did surely work for me and my test setups, but
|
||||
it might not work at all on your system. If you run into problems
|
||||
you can contact me.
|
||||
|
||||
|
||||
3. Does ist support DATAGRAM and RAW sessions?
|
||||
|
||||
No, at the moment the module only supports STREAM sessions.
|
||||
Support for other session types might be added in the future.
|
||||
|
||||
|
||||
4. How to install it?
|
||||
Create a Subfolder called I2P in your Perl Installation's Net Module
|
||||
folder (i.e. /usr/lib/perl5/5.8.4./Net/I2P ) and copy the module there.
|
||||
You can now use it with use Net::I2P::SAM.
|
||||
The module only depends on Net::IO::Socket for operations. This
|
||||
should be already installed.
|
||||
|
||||
|
||||
5. How to debug?
|
||||
You can switch on debugging with the constructor ( see below ).
|
||||
|
||||
|
||||
6. How to use it?
|
||||
|
||||
|
||||
$sam = new Net::I2P::SAM('127.0.0.1','7656',1);
|
||||
|
||||
# you can omit host/port - then the defult is assumed
|
||||
# the 3rd argument is the debugging switch. If you switched it on
|
||||
# there'll be a default debug in /tmp/sam-debug
|
||||
# $sam will now either contain a object reference or 0
|
||||
# if it's 0 then we coudl not talk to the SAM bridge at all ( connect failed)
|
||||
# or we could not agree to a version
|
||||
|
||||
# next we can tune the tunnel settings we want for this session:
|
||||
# The syntax is just like the one used on www.i2p.net/sam
|
||||
|
||||
$sam->change_settings("inbound.length=1,inbound.lenghVariance=0,outbound.length=1,outbound.lengthVariance=0,inbound.nickname=fun,outbound.nickname=fun");
|
||||
|
||||
# next we open a new session.
|
||||
# only stream is supported
|
||||
# most of the time we use a transient destination
|
||||
# otherwise state the hosts.txt name you want to use as in your session
|
||||
# direction is most of the times both :)
|
||||
# this cab return 1 for success or 0 for failure
|
||||
$sam->create_session("STREAM","TRANSIENT","BOTH");
|
||||
|
||||
|
||||
# now connect to our service
|
||||
$sam->connect($sam->lookup("myservice.i2p"));
|
||||
|
||||
# or
|
||||
|
||||
$sam->connect("I2PDESTINATIONKEY.....AAAA");
|
||||
|
||||
# if this returns 1 - we got a stream session and can now receive and send data
|
||||
# otherwise consult the debug.
|
||||
|
||||
|
||||
|
||||
# Send data is just like the the raw perl send
|
||||
# we send the data as scalar var and optional flags (most of the times 0)
|
||||
$sam->send($data,0);
|
||||
|
||||
|
||||
# receiving data is similar to the perl recv
|
||||
# we define the mac number of bytes and optional flags
|
||||
|
||||
$indata = $sam->receive(512,0);
|
||||
|
||||
# close the session
|
||||
|
||||
$sam->close();
|
||||
|
||||
# that's the most important things to know.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
922
apps/sam/perl/SAM.pm
Normal file
922
apps/sam/perl/SAM.pm
Normal file
@@ -0,0 +1,922 @@
|
||||
# Perl Basic SAM module
|
||||
# created 2005 by postman (postman@i2pmail.org)
|
||||
# This code is under GPL.
|
||||
# This code is proof of concept
|
||||
|
||||
|
||||
package Net::I2P::SAM;
|
||||
require 5.001;
|
||||
use strict;
|
||||
use POSIX;
|
||||
|
||||
|
||||
# we do not extend the IO::Socket:INET Package
|
||||
# we just use it, so we keep our stuff sorted
|
||||
# This is a testsetup - the next release but
|
||||
# be an extension of IO::Socket::INET;
|
||||
|
||||
use IO::Socket::INET;
|
||||
|
||||
|
||||
use vars qw($VERSION @ISA @EXPORT);
|
||||
|
||||
|
||||
sub new {
|
||||
my ($this,$host,$port,$debug) = @_;
|
||||
my $classname=ref($this) || $this;
|
||||
my $self = {};
|
||||
bless($self,$classname);
|
||||
|
||||
|
||||
#%{$self->{conf}} = The hash where we store tunnel / SAM Settings
|
||||
#$self->{debug} = Whether we should output debugging lines ( this is very helpful when having problems)
|
||||
#$self->{debugfile} = Where to log
|
||||
#%{$self->{samreply}} = This hash stores the key=VALUE pairs of sam reply
|
||||
#$self->{sock} = The INET Socket over which we talk to the SAM bridge
|
||||
#$self->{inbuffer} = a simple layouted inbuffer
|
||||
#$self->{outbuffer} = a simple layouted outbuffer
|
||||
# ( the buffers are for dealing with differences between user wanted in/outputsizes
|
||||
# and what we're able to deliver on a machine side)
|
||||
#$self->{sessiontype} = unused ( will be used when we support different sessiontypes )
|
||||
#$self->{lookupresult}= contains the result of a SAM host/userhost naming lookup;
|
||||
#$self->{samerror} = The human readable SAM error message ( if wanted )
|
||||
#$self->{streamid} = The virtual streamid. It will be created upon connect;
|
||||
#$self->{bytestoread} = contains the reported size of a packet from sam
|
||||
#$self->{bytestosend} = contains the wanted size of a packet to sam
|
||||
#$self->{hasbanner} = is set to 1 when we receive a greeting string upon connect
|
||||
|
||||
#
|
||||
|
||||
%{$self->{conf}}=();
|
||||
if($debug==1) {
|
||||
$self->{debug}=1;
|
||||
}
|
||||
$self->{debugfile}="/tmp/sam-debug";
|
||||
$self->{debughandle}=undef;
|
||||
%{$self->{samreply}}=undef;
|
||||
$self->{sock}=undef;
|
||||
$self->{inbuffer}=undef;
|
||||
$self->{outbuffer}=undef;
|
||||
$self->{sessiontype}=undef;
|
||||
$self->{lookupresult}=undef;
|
||||
$self->{samerror}=undef;
|
||||
$self->{streamid}=undef;
|
||||
$self->{bytestoread}=undef;
|
||||
$self->{bytestosend}=undef;
|
||||
$self->{hasbanner}=1;
|
||||
|
||||
# state == -1 (no socket exists)
|
||||
# state == 0 (socket exists, but we're not helloed)
|
||||
# state == 1 (socket exists, and we're helloed)
|
||||
# state == 200 ( we bound a session)
|
||||
# state == 250 ( we have a virtual stream)
|
||||
|
||||
$self->{state}=-1;
|
||||
|
||||
|
||||
# if the user has specified a host/port for contacting the SAM
|
||||
# Bridge, we'll override the defaults. Otherwise we just use
|
||||
# defaults.
|
||||
#
|
||||
if($host) {
|
||||
${$self->{conf}}{host}=$host;
|
||||
} else {
|
||||
${$self->{conf}}{host}="127.0.0.1";
|
||||
}
|
||||
|
||||
if($port) {
|
||||
${$self->{conf}}{port}=$port;
|
||||
} else {
|
||||
${$self->{conf}}{port}=7656;
|
||||
}
|
||||
# defaults for the tunnelparameters
|
||||
# see www.i2p.net/sam
|
||||
${$self->{conf}}{iblength}=2;
|
||||
${$self->{conf}}{oblength}=2;
|
||||
${$self->{conf}}{ibquant}=2;
|
||||
${$self->{conf}}{obquant}=2;
|
||||
${$self->{conf}}{ibbackup}=0;
|
||||
${$self->{conf}}{obbackup}=0;
|
||||
${$self->{conf}}{ibvariance}=0;
|
||||
${$self->{conf}}{obvariance}=0;
|
||||
${$self->{conf}}{iballowzero}="true";
|
||||
${$self->{conf}}{oballowzero}="true";
|
||||
${$self->{conf}}{ibduration}=600000;
|
||||
${$self->{conf}}{obduration}=600000;
|
||||
${$self->{conf}}{ibnickname}="SAM-Perl-Destination";
|
||||
${$self->{conf}}{obnickname}="SAM-Perl-Destination";
|
||||
|
||||
|
||||
|
||||
# ok, let's open a simple debug file
|
||||
# if the user wants us
|
||||
if($self->{debug} == 1) {
|
||||
if ( ! open (LOGFILE,">" .$self->{debugfile})) {
|
||||
print "constructor: Cannot open debugging file, switching debugging off.";
|
||||
$self->{debug}=0;
|
||||
}
|
||||
|
||||
# switch off the nerveracking buffer for the debugfile
|
||||
select((select(LOGFILE), $| = 1)[0]);
|
||||
}
|
||||
|
||||
# ok now, lets move to the manager
|
||||
# he manages connecting and hello
|
||||
# if the proc_mgr returns 1 the user will get our
|
||||
# object reference. if not, he'll just get a 0.
|
||||
if($self->proc_mgr()) {
|
||||
return $self;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub proc_mgr {
|
||||
my ($self) = @_;
|
||||
my $return=undef;
|
||||
|
||||
if ($self->{state} == -1) {
|
||||
$self->log("Debug: SAM::proc_mgr(): Opening Socket Connection to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
$return=$self->sam_connect();
|
||||
if($return==0) {
|
||||
return 0;
|
||||
}
|
||||
if($return == 1) {
|
||||
$self->log("Debug: SAM::proc_mgr(): State Transition -1 => 0");
|
||||
$self->{state}=0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if($self->{state}==0) {
|
||||
if(!$self->hello()) {
|
||||
$self->log("Debug: SAM::proc_mgr(): Closing Socket");
|
||||
$self->{sock}->close();
|
||||
$self->log("State SAM::proc_mgr(): Transition 0 => -1");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub change_settings {
|
||||
my ($self,$list)=@_;
|
||||
my (@tochange,$id,$v,$k);
|
||||
|
||||
# we cannot change the settings if we have a session already
|
||||
# so our state must be 1
|
||||
|
||||
if($self->{state} >1) {
|
||||
$self->log("Debug: SAM::change_settings(): Cannot change tunnel settings after establishing a session.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@tochange=split(",",$list);
|
||||
foreach $id (@tochange) {
|
||||
($k,$v)=split("=",$id);
|
||||
lc($v);
|
||||
lc($k);
|
||||
|
||||
|
||||
$self->log("Debug: SAM::change_settings(): Parsed Setting: Key: $k - Value: $v");
|
||||
if($k eq "inbound.length" || $k eq "outbound.length") {
|
||||
# make v an int
|
||||
$v*1;
|
||||
if ($v >3 || $v < 0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{iballowzero} eq "false" && $k eq "inbound.length" && $v==0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{oballowzero} eq "false" && $k eq "outbound.length" && $v==0) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if($k eq "inbound.length") {
|
||||
${$self->{conf}}{iblength}=$v;
|
||||
}
|
||||
if($k eq "outbound.length") {
|
||||
${$self->{conf}}{oblength}=$v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
if($k eq "inbound.quantity" || $k eq "outbound.quantity") {
|
||||
$v*1;
|
||||
if($v < 0 || $v >3 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.quantity") {
|
||||
${$self->{conf}}{ibquant}=$v;
|
||||
}
|
||||
if($k eq "outbound.quantity") {
|
||||
${$self->{conf}}{obquant}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.backupquantity" || $k eq "outbound.backupquantity") {
|
||||
$v*1;
|
||||
if($v < 0 || $v >2 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.backupquantity") {
|
||||
${$self->{conf}}{ibbackup}=$v;
|
||||
}
|
||||
if($k eq "outbound.backupquantity") {
|
||||
${$self->{conf}}{obbackup}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.lengthvariance" || $k eq "outbound.lengthvariance") {
|
||||
$v*1;
|
||||
if($v < -2 || $v >2 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.lengthvariance") {
|
||||
${$self->{conf}}{ibvariance}=$v;
|
||||
}
|
||||
if($k eq "outbound.lengthvariance") {
|
||||
${$self->{conf}}{ibvariance}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.duration" || $k eq "outbound.duration") {
|
||||
$v*1;
|
||||
if($v < 300000 || $v >1200000 ) {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (out of range)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($k eq "inbound.duration") {
|
||||
${$self->{conf}}{ibduration}=$v;
|
||||
}
|
||||
if($k eq "outbound.duration") {
|
||||
${$self->{conf}}{obduration}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.nickname" || $k eq "outbound.nickname") {
|
||||
$v=substr($v,0,20);
|
||||
|
||||
if ($k eq "inbound.nickname") {
|
||||
${$self->{conf}}{ibnickname}=$v;
|
||||
}
|
||||
if($k eq "outbound.nickname") {
|
||||
${$self->{conf}}{obnickname}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
if($k eq "inbound.allowzerohop" || $k eq "outbound.allowzerohop") {
|
||||
if($v ne "true" && $v ne "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (must be boolean)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{iblength} ==0 && $k eq "inbound.allowzerohop" && $v eq "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{conf}}{oblength} == 0 && $k eq "outbound.allowzerohop" && $v eq "false") {
|
||||
$self->log("Error: SAM::change_settings(): Wrong value $v is not valid for $k (length forbidden by allowzero=false)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if ($k eq "inbound.allowzerohop") {
|
||||
${$self->{conf}}{iballowzero}=$v;
|
||||
}
|
||||
if($k eq "outbound.allowzerohop") {
|
||||
${$self->{conf}}{oballowzero}=$v;
|
||||
}
|
||||
}
|
||||
|
||||
$self->log("Debug: SAM::change_settings(): Setting $k to $v");
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub hello {
|
||||
my ($self) = @_;
|
||||
my $greeting="HELLO VERSION MIN=1.0 MAX=1.0\n";
|
||||
my $return=undef;
|
||||
my $return2=undef;
|
||||
|
||||
$self->{outbuffer} .= $greeting;
|
||||
$return=$self->raw_send();
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("HELLO")) {
|
||||
$self->{state}=1;
|
||||
$self->log("Debug: SAM::hello(): State Transition 0 => 1");
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::hello(): HELLO Failed. Cannot read HELLO response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::hello(): HELLO Failed. Cannot send HELLO String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub create_session {
|
||||
my ($self,$type,$destination,$direction) = @_;
|
||||
my $line="SESSION CREATE ";
|
||||
my $return;
|
||||
|
||||
uc($type);
|
||||
# WE ARE ONLY DOING STREAMS ATM
|
||||
if ($type ne "STREAM") {
|
||||
$self->log("Error: SAM::create_session(): SESSION failed. Only session of STREAM type are allowed");
|
||||
return 0;
|
||||
}
|
||||
if(length($destination)==0) {
|
||||
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on destination to TRANSIENT.");
|
||||
$destination="TRANSIENT";
|
||||
}
|
||||
|
||||
$line.="STYLE=$type DESTINATION=$destination";
|
||||
|
||||
uc($direction);
|
||||
if($direction ne "BOTH" && $direction ne "CREATE" && $direction ne "RECEIVE") {
|
||||
$self->log("Warn: SAM::create_session(): SESSION: fallback setting on direction to BOTH.");
|
||||
$direction="BOTH";
|
||||
}
|
||||
|
||||
$line .= " DIRECTION=$direction";
|
||||
$line .= " inbound.length=".${$self->{conf}}{iblength}." outbound.length=".${$self->{conf}}{oblength};
|
||||
$line .= " inbound.quantity=".${$self->{conf}}{ibquant}." outbound.quantity=".${$self->{conf}}{obquant};
|
||||
$line .= " inbound.backupQuantity=".${$self->{conf}}{ibbackup}." outbound.backupQuantity=".${$self->{conf}}{obbackup};
|
||||
$line .= " inbound.lengthVariance=".${$self->{conf}}{ibvariance}." outbound.lengthVariance=".${$self->{conf}}{obvariance};
|
||||
$line .= " inbound.duration=".${$self->{conf}}{ibduration}." outbound.duration=".${$self->{conf}}{obduration};
|
||||
$line .= " inbound.nickname=".${$self->{conf}}{ibnickname}." outbound.nickname=".${$self->{conf}}{obnickname};
|
||||
$line .= " inbound.allowZeroHop=".${$self->{conf}}{iballowzero}." outbound.allowZeroHop=".${$self->{conf}}{oballowzero};
|
||||
$line .="\n";
|
||||
|
||||
$self->{outbuffer}.=$line;
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("SESSION ")) {
|
||||
$self->{state}=200;
|
||||
$self->log("Debug: SAM::create_session(): State Transition 1 => 200");
|
||||
# flush the whole inbuffer;
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::create_session(): SESSION Failed. Cannot send SESSION String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
sub parse_sam {
|
||||
|
||||
# this is the main function that parses all SAM replies
|
||||
# depending on wanted action and state we'll set different
|
||||
# properties like $self->{bytestoread} etc.
|
||||
# parse_sam does not CUT OUT SAM messages from the payloads
|
||||
# (look at $self->recv for that)
|
||||
#
|
||||
my ($self,$state) = @_;
|
||||
my (@data,$id,$k,$v);
|
||||
%{$self->{samreply}}=();
|
||||
|
||||
|
||||
uc($state);
|
||||
|
||||
if( $self->{inbuffer} =~ /^(.[^ ]*) (.[^ ]*) (.*)$/m ) {
|
||||
${$self->{samreply}}{COMMAND}=$1;
|
||||
${$self->{samreply}}{REPLY}=$2;
|
||||
|
||||
@data=split(" ",$3);
|
||||
|
||||
foreach $id (@data) {
|
||||
($k,$v)=split("=",$id);
|
||||
|
||||
#print "k: $k - v: $v\n";
|
||||
${$self->{samreply}}{$k}=$v;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Could not parse the SAM Reply. Has the specs changed?");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if($state eq "HELLO") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "HELLO") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state HELLO but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "REPLY") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for HELLO");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error :SAM::parse_sam(): Got no OK Result for HELLO");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for HELLO dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "SESSION") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "SESSION") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state SESSION but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for SESSION");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Got no OK Result for SESSION: ".${$self->{samreply}}{RESULT});
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for SESSION dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "NAMING") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "NAMING") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state NAMING but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "REPLY") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): Got a OK result for NAMING");
|
||||
$self->{lookupresult}=${$self->{samreply}}{VALUE};
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Got no OK Result for NAMING: ".${$self->{samreply}}{RESULT});
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Unknown Reply type for NAMING dialogue");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($state eq "STREAM") {
|
||||
if (${$self->{samreply}}{COMMAND} ne "STREAM") {
|
||||
$self->log("Error: SAM::parse_sam(): We're in state STREAM but got no proper response from SAM");
|
||||
return 0;
|
||||
}
|
||||
|
||||
# CREATING STREAMS
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
if(${$self->{samreply}}{RESULT} eq "OK") {
|
||||
$self->log("Debug: SAM::parse_sam(): STREAM STATUS OK - Next action is awaited");
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): STREAM STATUS NOT OK.: ".${$self->{samreply}}{RESULT});
|
||||
|
||||
if(length(${$self->{samreply}}{MESSAGE}) == 0) {
|
||||
$self->{samerror}=${$self->{samreply}}{RESULT};
|
||||
} else {
|
||||
$self->{samerror}=${$self->{samreply}}{MESSAGE};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# SEND/RECEIVING STREAMS
|
||||
# this can happen directly after a connect
|
||||
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
if(${$self->{samreply}}{SIZE} > 0) {
|
||||
$self->log("Debug: SAM::parse_sam(): SAM notify: RECEIVED Data. SIZE=".${$self->{samreply}}{SIZE});
|
||||
$self->{bytestoread}=${$self->{samreply}}{SIZE};
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::parse_sam(): Received empty payload");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
# STREAMS are closed - bad thing
|
||||
# this can happen directly after a connect
|
||||
if(${$self->{samreply}}{REPLY} eq "CLOSED") {
|
||||
$self->log("Error: SAM::parse_sam(): Stream is closed: We need to interupt the session");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub raw_send {
|
||||
|
||||
# this function sends a crafted SAM request + payload to the
|
||||
# SAM socket
|
||||
my ($self) = @_;
|
||||
my $return;
|
||||
|
||||
$self->log("Debug: SAM::raw_send(): >>> ".$self->{outbuffer});
|
||||
$return = $self->{sock}->send($self->{outbuffer},0);
|
||||
if(! defined($return)) {
|
||||
$self->log("Error: SAM::raw_send(): Cannot send to Socket");
|
||||
$self->close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $return == length($self->{outbuffer}) || $!{EWOULDBLOCK} ) {
|
||||
substr($self->{outbuffer},0, $return) = '';
|
||||
delete $self->{outbuffer} unless length($self->{outbuffer});
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error :SAM::raw_send(): Could send, but length does not match. Closing");
|
||||
$self->close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub raw_read {
|
||||
my ($self,$size) = @_;
|
||||
my $input;
|
||||
my $data;
|
||||
|
||||
# this reads SAM replies from the SAM socket and fills the
|
||||
# inbuffer
|
||||
|
||||
if(!$size || $size > POSIX::BUFSIZ) {
|
||||
$size=POSIX::BUFSIZ;
|
||||
}
|
||||
|
||||
|
||||
$input = $self->{sock}->recv($data, $size, 0);
|
||||
|
||||
if(defined($input) && length($data) >= 1) {
|
||||
|
||||
|
||||
$self->log("Debug: SAM::raw_read(): <<< $data");
|
||||
if(length($self->{inbuffer}) == 0 ) {
|
||||
$self->{inbuffer} = $data;
|
||||
} else {
|
||||
$self->{inbuffer} .= $data;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
if ( $!{EAGAIN} ) {
|
||||
$self->{bytestoread}=0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
sub sam_connect {
|
||||
# bsic connect to the sam bridge socket itself
|
||||
my ($self)=@_;
|
||||
my $return=undef;
|
||||
|
||||
|
||||
|
||||
$return=$self->{sock}=IO::Socket::INET->new(${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
|
||||
if($return==0) {
|
||||
$self->log("Debug: SAM::sam_connect(): Connection failed to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
return 0;
|
||||
} else {
|
||||
$self->log("Debug: SAM::sam_connect(): Connection established to ".${$self->{conf}}{host}.":".${$self->{conf}}{port});
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
sub send {
|
||||
|
||||
# the public fumction to send data to sam
|
||||
# the user gives his payload and we create
|
||||
# valid SAM requests + payload from it ( as much as needed)
|
||||
my ($self,$content,$flags)=@_;
|
||||
my $return;
|
||||
my $maxsize=(POSIX::BUFSIZ-100);
|
||||
|
||||
if($self->{state}!=250) {
|
||||
$self->log("Error: SAM::send(): wrong state for send command: Needed:250 Recent:".$self->{state});
|
||||
return 0;
|
||||
}
|
||||
# ok, what can happen?
|
||||
# it could be that $content is far bigger than
|
||||
# POSIX::BUFSIZ; so we need to do a little loop
|
||||
# apart from that sending is in our hand
|
||||
|
||||
if(length($content) > $maxsize) {
|
||||
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=$maxsize\n".substr($content,0,$maxsize);
|
||||
$content=substr($content,$maxsize,length($content));
|
||||
} else {
|
||||
$self->{outbuffer}.="STREAM SEND ID=".$self->{streamid}." SIZE=".length($content)."\n".$content;
|
||||
}
|
||||
|
||||
if( $self->raw_send()) {
|
||||
return 1;
|
||||
} else {
|
||||
$self->log("Error: SAM::send(): Could not send. Closing Link");
|
||||
}
|
||||
}
|
||||
|
||||
sub recv {
|
||||
my($self,$varname,$size,$flags)=@_;
|
||||
my $return;
|
||||
my $tunebuffer;
|
||||
my $counter;
|
||||
my $chunk;
|
||||
|
||||
|
||||
# main recv wrapper. We need to invest a few thoughts
|
||||
# for this. To the user it will be like a $socket->recv
|
||||
# (hopefully)
|
||||
# at first some sanity checks
|
||||
if(!$flags) {
|
||||
$flags=0;
|
||||
}
|
||||
|
||||
$self->{bytestoread}=0;
|
||||
# size must not exceed The posix limit;
|
||||
|
||||
if(!$size || $size > POSIX::BUFSIZ) {
|
||||
$self->log("Warn: SAM::recv(): Setting buffersize to POSIX::BUFSIZ");
|
||||
$size=POSIX::BUFSIZ;
|
||||
}
|
||||
|
||||
|
||||
# nobody should call is prior to state 250
|
||||
if($self->{state}!=250) {
|
||||
$self->log("Error: SAM::recv(): wrong state for rcv command: Needed:250 Recent:".$self->{state});
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
# we have a greeting banner left from connect
|
||||
# flush it to the user. This can happen on several services
|
||||
# like smtp/pop3/nntp but not on HTTP and other stuff
|
||||
|
||||
if($self->{hasbanner}) {
|
||||
|
||||
#print "D: ".$self->{inbuffer};
|
||||
|
||||
if(length($self->{inbuffer}) >0 ) {
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
$self->{hasbanner}=0;
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): Should have a banner but i have empty inbuffer?");
|
||||
return 0;
|
||||
}
|
||||
# should never reach here
|
||||
return 1;
|
||||
}
|
||||
# when there's something in the inbuffer left
|
||||
# flush it to the user. If the amount of data is bigger than
|
||||
# the userspecified limit, only transfer $size Bytes to the
|
||||
# client
|
||||
if(length($self->{inbuffer}) > 0) {
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
# OK, we got noting in the inbuffer
|
||||
# we'll fetch a new chunk of data and then add the data to the inbuffer
|
||||
# if bytestoread is bigger than POSIX::BUFSIZ we'll internally use
|
||||
# a loop of reads and fill the buffer til bytestoread is 0
|
||||
|
||||
if(length($self->{inbuffer}) == 0) {
|
||||
# read the first packet
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("STREAM") && ${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
# it's possible that the packet does not contain any payload at all!!
|
||||
# if this is the case we need
|
||||
if($self->{inbuffer}=~/^.*$/) {
|
||||
$self->log("Warn: SAM::recv(): Got only only one line from SAM - but i expected some payload too. Calling raw_read again ");
|
||||
$self->raw_read();
|
||||
}
|
||||
# ok, cut the SAM HEADER from the payload
|
||||
$self->{inbuffer}=substr($self->{inbuffer},(length($self->{inbuffer})-$self->{bytestoread}), length($self->{inbuffer}));
|
||||
$self->log("Debug: SAM::recv(): Recived a Stream Packet and cut the SAM header out");
|
||||
# ok, check if bytestoread is still bigger than our buffersize
|
||||
# this means we can load more. Dangerous loop but well...
|
||||
|
||||
while(length($self->{inbuffer}) < $self->{bytestoread}) {
|
||||
# this should definately end some day
|
||||
$counter++;
|
||||
if($counter > 10000) {
|
||||
$self->log("Error: SAM::recv(): WTF, could not fill inbuffer as predicted by SAM header");
|
||||
last;
|
||||
}
|
||||
# read as long til we have read all of the payload provided by SAM
|
||||
|
||||
$self->log("Debug: SAM::recv(): Load another chunk. Buffersize:".length($self->{inbuffer})." / Bytestoread:".$self->{bytestoread} );
|
||||
$self->raw_read();
|
||||
}
|
||||
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): parse_sam() did not succeed. Interupting");
|
||||
delete $self->{inbuffer};
|
||||
return 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
$self->log("Error: SAM::recv(): Could not read from the socket");
|
||||
delete $self->{inbuffer};
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
$chunk=substr($self->{inbuffer},0, $size);
|
||||
substr($self->{inbuffer},0, $size)='';
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
sub lookup {
|
||||
my($self,$name)=@_;
|
||||
my $return;
|
||||
|
||||
$self->{outbuffer}="NAMING LOOKUP NAME=$name\n";
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("NAMING")) {
|
||||
$self->log("Debug: SAM::lookup(): Naming Lookup successful");
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return $self->{lookupresult};
|
||||
}
|
||||
} else {
|
||||
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error :SAM::lookup(): NAMING Failed. Cannot send NAMING String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub sam_close {
|
||||
my ($self) =@_;
|
||||
$self->log("Debug: SAM::sam_close(): Closing Socket to SAM");
|
||||
$self->{sock}->close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub close {
|
||||
my ($self)=@_;
|
||||
my $return;
|
||||
$self->{outbuffer}.="STREAM CLOSE ID=".$self->{streamid}."\n";
|
||||
$self->log("Debug: SAM::close(): CLosing Stream with id: ".$self->{streamid});
|
||||
$return=$self->raw_send();
|
||||
# well, we do not care wether this worked or not
|
||||
$self->sam_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub connect {
|
||||
my ($self,$destination)=@_;
|
||||
my $return;
|
||||
|
||||
$self->{streamid}= int(rand(200000000));
|
||||
if(length($destination) == 0) {
|
||||
$self->log("Error: SAM::connect(): need I2P destination to connect to with the SAM Bridge");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$self->{outbuffer}.="STREAM CONNECT ID=".$self->{streamid}." DESTINATION=$destination\n";
|
||||
$return=$self->raw_send();
|
||||
|
||||
if($return == 1) {
|
||||
if($self->raw_read()) {
|
||||
if($self->parse_sam("STREAM")) {
|
||||
if(${$self->{samreply}}{REPLY} eq "STATUS") {
|
||||
$self->{state}=250;
|
||||
$self->log("Debug: SAM::connect(): State Transition 200 => 250");
|
||||
# flush the whole inbuffer;
|
||||
delete $self->{inbuffer};
|
||||
delete $self->{outbuffer};
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(${$self->{samreply}}{REPLY} eq "RECEIVED") {
|
||||
$self->{state}=250;
|
||||
$self->log("Debug: SAM::connect(): State Transition 200 => 250. Got a banner");
|
||||
delete $self->{inbuffer};
|
||||
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
$self->raw_read();
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
$self->{inbuffer}=substr($self->{inbuffer}, 0, $self->{bytestoread});
|
||||
$self->{hasbanner}=1;
|
||||
|
||||
#print "D: toread:".$self->{bytestoread};
|
||||
#print "D: buffer:".$self->{inbuffer}."\n";
|
||||
#print "D: buffersize:".length($self->{inbuffer})."\n";
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
$self->log("Error: SAM::connect(): STREAM Failed. Cannot read SAM Response");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($return == 0) {
|
||||
$self->log("Error: SAM::connect(): STREAM Failed. Cannot send SESSION String");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub log {
|
||||
my($self,$line)=@_;
|
||||
if($line=~/.*\n$/) {
|
||||
chomp $line;
|
||||
}
|
||||
if ( $self->{debug} ) {
|
||||
print LOGFILE "$line\n";
|
||||
}
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use Net::SAM::RawSession;
|
||||
use Net::SAM::DatagramSession;
|
||||
|
||||
$sam=Net::SAM::DatagramSession->new($ARGV[0], "BOTH", "tunnels.depthInbound=0");
|
||||
print "Connected? " . $sam->connected() . "\n";
|
||||
|
||||
$me = $sam->lookup("ME");
|
||||
print "Sending to $me.\n";
|
||||
$sam->send($me,"fooquux");
|
||||
|
||||
$sam->readprocess();
|
||||
($source, $message) = @{ $sam->receive() };
|
||||
print "$source -- $message";
|
||||
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 15*1000;
|
||||
public static final long MAX_RESEND_DELAY = 10*1000;
|
||||
public static final long MIN_RESEND_DELAY = 2*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
@@ -298,7 +298,7 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
||||
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet, timeout + _context.clock().now()), timeout);
|
||||
RetransmissionTimer.getInstance().addEvent(new ResendPacketEvent(packet, timeout + _context.clock().now()), timeout);
|
||||
}
|
||||
|
||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||
@@ -758,8 +758,8 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resetting the inactivity timer to " + howLong, new Exception("Reset by"));
|
||||
// this will get rescheduled, and rescheduled, and rescheduled...
|
||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
||||
SimpleTimer.getInstance().addEvent(_activityTimer, howLong);
|
||||
RetransmissionTimer.getInstance().removeEvent(_activityTimer);
|
||||
RetransmissionTimer.getInstance().addEvent(_activityTimer, howLong);
|
||||
}
|
||||
|
||||
private class ActivityTimer implements SimpleTimer.TimedEvent {
|
||||
@@ -773,7 +773,7 @@ public class Connection {
|
||||
long left = getTimeLeft();
|
||||
if (left > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
|
||||
SimpleTimer.getInstance().addEvent(ActivityTimer.this, left);
|
||||
RetransmissionTimer.getInstance().addEvent(ActivityTimer.this, left);
|
||||
return;
|
||||
}
|
||||
// these are either going to time out or cause further rescheduling
|
||||
@@ -963,7 +963,7 @@ public class Connection {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Delaying resend of " + _packet + " as there are "
|
||||
+ _activeResends + " active resends already in play");
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
||||
_nextSendTime = 1000 + _context.clock().now();
|
||||
return false;
|
||||
}
|
||||
@@ -992,7 +992,7 @@ public class Connection {
|
||||
newWindowSize = 1;
|
||||
|
||||
// setRTT has its own ceiling
|
||||
getOptions().setRTT(getOptions().getRTT() + 10*1000);
|
||||
//getOptions().setRTT(getOptions().getRTT() + 10*1000);
|
||||
getOptions().setWindowSize(newWindowSize);
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -1055,7 +1055,7 @@ public class Connection {
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
_nextSendTime = timeout + _context.clock().now();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -50,7 +50,7 @@ class ConnectionHandler {
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
|
||||
SimpleTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
|
||||
RetransmissionTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
|
||||
synchronized (_synQueue) {
|
||||
_synQueue.add(packet);
|
||||
_synQueue.notifyAll();
|
||||
|
||||
@@ -89,6 +89,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setInboundBufferSize(opts.getInboundBufferSize());
|
||||
setCongestionAvoidanceGrowthRateFactor(opts.getCongestionAvoidanceGrowthRateFactor());
|
||||
setSlowStartGrowthRateFactor(opts.getSlowStartGrowthRateFactor());
|
||||
setWriteTimeout(opts.getWriteTimeout());
|
||||
setReadTimeout(opts.getReadTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public class ConnectionPacketHandler {
|
||||
if (packet.getOptionalDelay() > 60000) {
|
||||
// requested choke
|
||||
choke = true;
|
||||
con.getOptions().setRTT(con.getOptions().getRTT() + 10*1000);
|
||||
//con.getOptions().setRTT(con.getOptions().getRTT() + 10*1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public class ConnectionPacketHandler {
|
||||
// take note of congestion
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("congestion.. dup " + packet);
|
||||
SimpleTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
|
||||
RetransmissionTimer.getInstance().addEvent(new AckDup(con), con.getOptions().getSendAckDelay());
|
||||
//con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
//fastAck = true;
|
||||
} else {
|
||||
@@ -272,7 +272,7 @@ public class ConnectionPacketHandler {
|
||||
oldSize = 1;
|
||||
|
||||
// setRTT has its own ceiling
|
||||
con.getOptions().setRTT(con.getOptions().getRTT() + 10*1000);
|
||||
//con.getOptions().setRTT(con.getOptions().getRTT() + 10*1000);
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
|
||||
@@ -109,4 +109,11 @@ public class I2PSocketFull implements I2PSocket {
|
||||
_connection = null;
|
||||
_listener = null;
|
||||
}
|
||||
public String toString() {
|
||||
Connection c = _connection;
|
||||
if (c == null)
|
||||
return super.toString();
|
||||
else
|
||||
return c.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
// no need to be overly worried about duplicates - it would just
|
||||
// push it further out
|
||||
if (!_enqueued) {
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
RetransmissionTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
|
||||
} else {
|
||||
@@ -256,8 +256,12 @@ public class MessageOutputStream extends OutputStream {
|
||||
long begin = _context.clock().now();
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
if (_buf == null) throw new IOException("closed (buffer went away)");
|
||||
if (_buf == null) {
|
||||
_dataLock.notifyAll();
|
||||
throw new IOException("closed (buffer went away)");
|
||||
}
|
||||
if (_dataReceiver == null) {
|
||||
_dataLock.notifyAll();
|
||||
throwAnyError();
|
||||
return;
|
||||
}
|
||||
@@ -293,7 +297,10 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (_closed) return;
|
||||
if (_closed) {
|
||||
synchronized (_dataLock) { _dataLock.notifyAll(); }
|
||||
return;
|
||||
}
|
||||
_closed = true;
|
||||
flush();
|
||||
_log.debug("Output stream closed after writing " + _written);
|
||||
@@ -305,6 +312,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
_valid = 0;
|
||||
locked_updateBufferSize();
|
||||
}
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
if (ba != null) {
|
||||
_dataCache.release(ba);
|
||||
|
||||
@@ -236,14 +236,14 @@ public class PacketHandler {
|
||||
}
|
||||
packet.releasePayload();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG) && !packet.isFlagSet(Packet.FLAG_SYNCHRONIZE))
|
||||
_log.debug("Packet received on an unknown stream (and not an ECHO or SYN): " + packet);
|
||||
//if (_log.shouldLog(Log.DEBUG) && !packet.isFlagSet(Packet.FLAG_SYNCHRONIZE))
|
||||
// _log.debug("Packet received on an unknown stream (and not an ECHO or SYN): " + packet);
|
||||
if (sendId <= 0) {
|
||||
Connection con = _manager.getConnectionByOutboundId(packet.getReceiveStreamId());
|
||||
if (con != null) {
|
||||
if ( (con.getHighestAckedThrough() <= 5) && (packet.getSequenceNum() <= 5) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Received additional packets before the syn on " + con + ": " + packet);
|
||||
receiveKnownCon(con, packet);
|
||||
return;
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class RetransmissionTimer extends SimpleTimer {
|
||||
private static final RetransmissionTimer _instance = new RetransmissionTimer();
|
||||
public static final SimpleTimer getInstance() { return _instance; }
|
||||
protected RetransmissionTimer() { super("StreamingTimer"); }
|
||||
}
|
||||
3
apps/syndie/doc/readme-standalone.txt
Normal file
3
apps/syndie/doc/readme-standalone.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
To run Syndie, fire it up with "java -jar launch-syndie.jar" (or, on windows,
|
||||
to run without the dos box, "javaw -jar launch-syndie.jar"). For further
|
||||
information, swing to http://localhost:8001/
|
||||
@@ -11,7 +11,7 @@
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/org.mortbay.jetty.jar:../../jetty/jettylib/javax.servlet.jar:../../jdom/jdom.jar:../../rome/rome-0.7.jar" />
|
||||
@@ -43,6 +43,55 @@
|
||||
</war>
|
||||
<delete dir="./tmpwar" />
|
||||
</target>
|
||||
<target name="standalone" depends="standalone_prep">
|
||||
<zip destfile="syndie-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="syndie/" />
|
||||
</zip>
|
||||
</target>
|
||||
<target name="standalone_prep" depends="war">
|
||||
<javac debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build" srcdir="src/" includes="net/i2p/syndie/web/RunStandalone.java" >
|
||||
<classpath>
|
||||
<pathelement location="../../jetty/jettylib/commons-logging.jar" />
|
||||
<pathelement location="../../jetty/jettylib/commons-el.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
</classpath>
|
||||
</javac>
|
||||
|
||||
<jar destfile="./build/launch-syndie.jar" basedir="./build/" includes="net/i2p/syndie/web/RunStandalone.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.syndie.web.RunStandalone" />
|
||||
<attribute name="Class-Path" value="lib/i2p.jar lib/commons-el.jar lib/commons-logging.jar lib/jasper-compiler.jar lib/jasper-runtime.jar lib/javax.servlet.jar lib/org.mortbay.jetty.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
|
||||
<delete dir="./dist" />
|
||||
<mkdir dir="./dist" />
|
||||
<copy file="./build/launch-syndie.jar" tofile="./dist/launch-syndie.jar" />
|
||||
<copy file="../syndie.war" tofile="./dist/syndie.war" />
|
||||
<mkdir dir="./dist/lib" />
|
||||
<copy file="../../../core/java/build/i2p.jar" tofile="./dist/lib/i2p.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-el.jar" tofile="./dist/lib/commons-el.jar" />
|
||||
<copy file="../../jetty/jettylib/commons-logging.jar" tofile="./dist/lib/commons-logging.jar" />
|
||||
<copy file="../../jetty/jettylib/javax.servlet.jar" tofile="./dist/lib/javax.servlet.jar" />
|
||||
<copy file="../../jetty/jettylib/org.mortbay.jetty.jar" tofile="./dist/lib/org.mortbay.jetty.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-compiler.jar" tofile="./dist/lib/jasper-compiler.jar" />
|
||||
<copy file="../../jetty/jettylib/jasper-runtime.jar" tofile="./dist/lib/jasper-runtime.jar" />
|
||||
<copy file="../jetty-syndie.xml" tofile="./dist/jetty-syndie.xml" />
|
||||
<copy file="../doc/readme-standalone.txt" tofile="./dist/readme.txt" />
|
||||
<mkdir dir="./dist/work" />
|
||||
<mkdir dir="./dist/logs" />
|
||||
<mkdir dir="./dist/archive" />
|
||||
|
||||
<copy file="../../../installer/resources/blogMeta.snm" tofile="dist/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
|
||||
<copy file="../../../installer/resources/blogPost.snd" tofile="dist/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1132012800001.snd" />
|
||||
|
||||
<zip destfile="syndie-standalone.zip">
|
||||
<zipfileset dir="./dist/" prefix="syndie/" />
|
||||
</zip>
|
||||
</target>
|
||||
<target name="precompilejsp">
|
||||
<delete dir="../jsp/WEB-INF/" />
|
||||
<delete file="../jsp/web-fragment.xml" />
|
||||
@@ -107,6 +156,8 @@
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
<delete dir="./dist" />
|
||||
<delete file="./syndie-standalone.zip" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
|
||||
@@ -67,8 +67,10 @@ public class Archive {
|
||||
File meta = new File(f[i], METADATA_FILE);
|
||||
if (meta.exists()) {
|
||||
BlogInfo bi = new BlogInfo();
|
||||
FileInputStream fi = null;
|
||||
try {
|
||||
bi.load(new FileInputStream(meta));
|
||||
fi = new FileInputStream(meta);
|
||||
bi.load(fi);
|
||||
if (bi.verify(_context)) {
|
||||
info.add(bi);
|
||||
} else {
|
||||
@@ -77,6 +79,8 @@ public class Archive {
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading the blog", ioe);
|
||||
} finally {
|
||||
if (fi != null) try { fi.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +280,13 @@ public class Archive {
|
||||
} else {
|
||||
// we have an explicit key - no caching
|
||||
entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entries[i]));
|
||||
FileInputStream fi = null;
|
||||
try {
|
||||
fi = new FileInputStream(entries[i]);
|
||||
entry.load(fi);
|
||||
} finally {
|
||||
if (fi != null) try { fi.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
_log.error("Keyed entry " + entries[i].getPath() + " is not valid");
|
||||
|
||||
@@ -26,8 +26,9 @@ class ArchiveIndexer {
|
||||
|
||||
File headerFile = new File(rootDir, Archive.HEADER_FILE);
|
||||
if (headerFile.exists()) {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile), "UTF-8"));
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(headerFile), "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
StringTokenizer tok = new StringTokenizer(line, ":");
|
||||
@@ -36,6 +37,8 @@ class ArchiveIndexer {
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
log.error("Error reading header file", ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +84,7 @@ class ArchiveIndexer {
|
||||
totalSize += entry.getCompleteSize();
|
||||
String entryTags[] = entry.getTags();
|
||||
threads.addEntry(entry.getURI(), entryTags);
|
||||
log.debug("Adding entry " + entry.getURI() + " to the threads, with tag count " + (entryTags != null ? entryTags.length : 0));
|
||||
for (int t = 0; t < entryTags.length; t++) {
|
||||
if (!tags.containsKey(entryTags[t])) {
|
||||
tags.put(entryTags[t], new TreeMap());
|
||||
@@ -103,6 +107,7 @@ class ArchiveIndexer {
|
||||
String forceNewThread = rec.getHeader(HTMLRenderer.HEADER_FORCE_NEW_THREAD);
|
||||
if ( (forceNewThread != null) && (Boolean.valueOf(forceNewThread).booleanValue()) ) {
|
||||
// ignore the parent
|
||||
log.warn("Ignore the parent of " + entry.getURI() + ": " + reply);
|
||||
} else {
|
||||
BlogURI parent = new BlogURI(reply.trim());
|
||||
if ( (parent.getKeyHash() != null) && (parent.getEntryId() >= 0) ) {
|
||||
@@ -178,7 +183,7 @@ class ArchiveIndexer {
|
||||
|
||||
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {}
|
||||
public void receiveArchive(String name, String description, String locationSchema, String location, String postingKey, String anchorText) {}
|
||||
public void receiveAttachment(int id, String anchorText) {}
|
||||
public void receiveAttachment(int id, int thumbnail, String anchorText) {}
|
||||
public void receiveBegin() {}
|
||||
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId, List blogArchiveLocations, String anchorText) {}
|
||||
public void receiveBold(String text) {}
|
||||
|
||||
@@ -193,9 +193,11 @@ public class BlogManager {
|
||||
List rv = new ArrayList();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if (files[i].isFile() && !files[i].isHidden()) {
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(new FileInputStream(files[i]));
|
||||
in = new FileInputStream(files[i]);
|
||||
pub.readBytes(in);
|
||||
BlogInfo info = _archive.getBlogInfo(pub.calculateHash());
|
||||
if (info != null)
|
||||
rv.add(info);
|
||||
@@ -203,6 +205,8 @@ public class BlogManager {
|
||||
_log.error("Error listing the blog", ioe);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error listing the blog", dfe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,8 +216,9 @@ public class BlogManager {
|
||||
public SigningPrivateKey getMyPrivateKey(BlogInfo blog) {
|
||||
if (blog == null) return null;
|
||||
File keyFile = new File(_privKeyDir, Base64.encode(blog.getKey().calculateHash().getData()) + ".priv");
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(keyFile);
|
||||
in = new FileInputStream(keyFile);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
pub.readBytes(in);
|
||||
SigningPrivateKey priv = new SigningPrivateKey();
|
||||
@@ -225,6 +230,8 @@ public class BlogManager {
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error reading the blog key", dfe);
|
||||
return null;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,10 +271,11 @@ public class BlogManager {
|
||||
}
|
||||
|
||||
private Properties loadUserProps(File userFile) {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fin = new FileInputStream(userFile);
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
|
||||
in = new BufferedReader(new InputStreamReader(fin, "UTF-8"));
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
int split = line.indexOf('=');
|
||||
@@ -281,6 +289,8 @@ public class BlogManager {
|
||||
return props;
|
||||
} catch (IOException ioe) {
|
||||
return null;
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,9 +524,22 @@ public class BlogManager {
|
||||
String ok = register(user, getDefaultLogin(), getDefaultPass(), "", "default", "Default Syndie blog", "");
|
||||
if (User.LOGIN_OK.equals(ok)) {
|
||||
_log.info("Default user created: " + user);
|
||||
for (int i = 0; i < DEFAULT_SINGLE_USER_ARCHIVES.length; i++)
|
||||
user.getPetNameDB().add(new PetName("DefaultArchive" + i, "syndie", "syndiearchive", DEFAULT_SINGLE_USER_ARCHIVES[i]));
|
||||
scheduleSyndication(DEFAULT_SINGLE_USER_ARCHIVES);
|
||||
String altArchives = _context.getProperty("syndie.defaultSingleUserArchives");
|
||||
String archives[] = DEFAULT_SINGLE_USER_ARCHIVES;
|
||||
if ( (altArchives != null) && (altArchives.trim().length() > 0) ) {
|
||||
ArrayList list = new ArrayList();
|
||||
StringTokenizer tok = new StringTokenizer(altArchives, ",\t ");
|
||||
while (tok.hasMoreTokens())
|
||||
list.add(tok.nextToken());
|
||||
if (list.size() > 0) {
|
||||
archives = new String[list.size()];
|
||||
for (int i = 0; i < list.size(); i++)
|
||||
archives[i] = (String)list.get(i);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < archives.length; i++)
|
||||
user.getPetNameDB().add(new PetName("DefaultArchive" + i, "syndie", "syndiearchive", archives[i]));
|
||||
scheduleSyndication(archives);
|
||||
saveUser(user);
|
||||
return;
|
||||
} else {
|
||||
@@ -540,11 +563,17 @@ public class BlogManager {
|
||||
}
|
||||
public boolean authorizeRemote(String pass) {
|
||||
if (isSingleUser()) return true;
|
||||
String rem = getRemotePasswordHash();
|
||||
if ( (rem == null) || (rem.trim().length() <= 0) )
|
||||
return false;
|
||||
String hash = Base64.encode(_context.sha().calculateHash(DataHelper.getUTF8(pass.trim())).getData());
|
||||
return (hash.equals(rem));
|
||||
String rem = getRemotePasswordHash();
|
||||
boolean ok = false;
|
||||
if ( (rem != null) && (rem.trim().length() > 0) )
|
||||
ok = hash.equals(rem);
|
||||
if (!ok) {
|
||||
rem = getAdminPasswordHash();
|
||||
if ( (rem != null) && (rem.trim().length() > 0) )
|
||||
ok = hash.equals(rem);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
public boolean authorizeRemote(User user) {
|
||||
if (isSingleUser()) return true;
|
||||
@@ -737,6 +766,8 @@ public class BlogManager {
|
||||
|
||||
long entryId = getNextBlogEntry(user);
|
||||
|
||||
_log.debug("Next blog entry ID = " + entryId + " for user " + user.getUsername());
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(tags, " ,\n\t");
|
||||
String tagList[] = new String[tok.countTokens()];
|
||||
for (int i = 0; i < tagList.length; i++)
|
||||
@@ -803,11 +834,14 @@ public class BlogManager {
|
||||
boolean ok = getArchive().storeEntry(c);
|
||||
if (ok) {
|
||||
getArchive().regenerateIndex();
|
||||
long prevEntryId = user.getMostRecentEntry();
|
||||
user.setMostRecentEntry(entryId);
|
||||
if(shouldAuthenticate)
|
||||
if(shouldAuthenticate) {
|
||||
saveUser(user);
|
||||
else
|
||||
} else {
|
||||
storeUser(user);
|
||||
}
|
||||
_log.debug("New entry posted, entryId=" + entryId + " prev=" + prevEntryId);
|
||||
return uri;
|
||||
} else {
|
||||
return null;
|
||||
@@ -887,7 +921,13 @@ public class BlogManager {
|
||||
private Properties getKnownHosts(File filename) throws IOException {
|
||||
Properties rv = new Properties();
|
||||
if (filename.exists()) {
|
||||
rv.load(new FileInputStream(filename));
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(filename);
|
||||
rv.load(in);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -928,7 +968,8 @@ public class BlogManager {
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.UK);
|
||||
private final long getDayBegin(long now) {
|
||||
public final long getDayBegin() { return getDayBegin(_context.clock().now()); }
|
||||
public final long getDayBegin(long now) {
|
||||
synchronized (_dateFormat) {
|
||||
try {
|
||||
String str = _dateFormat.format(new Date(now));
|
||||
@@ -952,6 +993,7 @@ public class BlogManager {
|
||||
if ( (location != null) && (location.trim().length() > 0) )
|
||||
buf.append(location.trim());
|
||||
System.setProperty("syndie.updateArchives", buf.toString());
|
||||
writeConfig();
|
||||
Updater.wakeup();
|
||||
}
|
||||
public void scheduleSyndication(String locations[]) {
|
||||
@@ -966,6 +1008,7 @@ public class BlogManager {
|
||||
for (Iterator iter = locs.iterator(); iter.hasNext(); )
|
||||
buf.append(iter.next().toString().trim()).append(',');
|
||||
System.setProperty("syndie.updateArchives", buf.toString());
|
||||
writeConfig();
|
||||
Updater.wakeup();
|
||||
}
|
||||
public void unscheduleSyndication(String location) {
|
||||
@@ -977,6 +1020,7 @@ public class BlogManager {
|
||||
buf.append(archives[i]).append(",");
|
||||
System.setProperty("syndie.updateArchives", buf.toString());
|
||||
}
|
||||
writeConfig();
|
||||
}
|
||||
public boolean syndicationScheduled(String location) {
|
||||
String archives[] = getUpdateArchives();
|
||||
|
||||
@@ -37,7 +37,13 @@ public class EntryExtractor {
|
||||
|
||||
public boolean extract(File entryFile, File entryDir, SessionKey entryKey, BlogInfo info) throws IOException {
|
||||
EntryContainer entry = new EntryContainer();
|
||||
entry.load(new FileInputStream(entryFile));
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(entryFile);
|
||||
entry.load(in);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
boolean ok = entry.verifySignature(_context, info);
|
||||
if (!ok) {
|
||||
return false;
|
||||
|
||||
@@ -14,7 +14,7 @@ public class HeaderReceiver implements SMLParser.EventReceiver {
|
||||
|
||||
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText) {}
|
||||
public void receiveArchive(String name, String description, String locationSchema, String location, String postingKey, String anchorText) {}
|
||||
public void receiveAttachment(int id, String anchorText) {}
|
||||
public void receiveAttachment(int id, int thumbnail, String anchorText) {}
|
||||
public void receiveBegin() {}
|
||||
public void receiveBlog(String name, String blogKeyHash, String blogPath, long blogEntryId, List blogArchiveLocations, String anchorText) {}
|
||||
public void receiveBold(String text) {}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
|
||||
/** sort BlogURI instances with the highest entryId first */
|
||||
public class NewestEntryFirstComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
BlogURI left = (BlogURI)lhs;
|
||||
BlogURI right = (BlogURI)rhs;
|
||||
if (left.getEntryId() > right.getEntryId()) {
|
||||
return -1;
|
||||
} else if (left.getEntryId() == right.getEntryId()) {
|
||||
return DataHelper.compareTo(left.getKeyHash().getData(), right.getKeyHash().getData());
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.i2p.syndie;
|
||||
|
||||
import java.util.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
/** sort ThreadNodeImpl instances with the highest entryId first */
|
||||
public class NewestNodeFirstComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
ThreadNodeImpl left = (ThreadNodeImpl)lhs;
|
||||
ThreadNodeImpl right = (ThreadNodeImpl)rhs;
|
||||
long l = left.getMostRecentPostDate();
|
||||
long r = right.getMostRecentPostDate();
|
||||
if (l > r) {
|
||||
return -1;
|
||||
} else if (l == r) {
|
||||
// ok, the newest responses match, so lets fall back and compare the roots themselves
|
||||
l = left.getEntry().getEntryId();
|
||||
r = right.getEntry().getEntryId();
|
||||
if (l > r) {
|
||||
return -1;
|
||||
} else if (l == r) {
|
||||
return DataHelper.compareTo(left.getEntry().getKeyHash().getData(), right.getEntry().getKeyHash().getData());
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,12 +158,15 @@ public class Sucker {
|
||||
if (!lastIdFile.exists())
|
||||
lastIdFile.createNewFile();
|
||||
|
||||
FileInputStream fis = new FileInputStream(lastIdFile);
|
||||
String number = readLine(fis);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(lastIdFile);
|
||||
String number = readLine(fis);
|
||||
messageNumber = Integer.parseInt(number);
|
||||
} catch (NumberFormatException e) {
|
||||
messageNumber = 0;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
// Create outputDir if missing
|
||||
@@ -226,28 +229,39 @@ public class Sucker {
|
||||
|
||||
_log.debug("entries: " + entries.size());
|
||||
|
||||
FileOutputStream hos = new FileOutputStream(historyFile, true);
|
||||
FileOutputStream hos = null;
|
||||
|
||||
// Process list backwards to get syndie to display the
|
||||
// entries in the right order. (most recent at top)
|
||||
for (int i = entries.size()-1; i >= 0; i--) {
|
||||
SyndEntry e = (SyndEntry) entries.get(i);
|
||||
try {
|
||||
hos = new FileOutputStream(historyFile, true);
|
||||
|
||||
attachmentCounter=0;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Syndicate entry: " + e.getLink());
|
||||
|
||||
String messageId = convertToSml(e);
|
||||
if (messageId!=null) {
|
||||
hos.write(messageId.getBytes());
|
||||
hos.write("\n".getBytes());
|
||||
// Process list backwards to get syndie to display the
|
||||
// entries in the right order. (most recent at top)
|
||||
for (int i = entries.size()-1; i >= 0; i--) {
|
||||
SyndEntry e = (SyndEntry) entries.get(i);
|
||||
|
||||
attachmentCounter=0;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Syndicate entry: " + e.getLink());
|
||||
|
||||
String messageId = convertToSml(e);
|
||||
if (messageId!=null) {
|
||||
hos.write(messageId.getBytes());
|
||||
hos.write("\n".getBytes());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (hos != null) try { hos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
if(!pushToSyndie) {
|
||||
FileOutputStream fos = new FileOutputStream(lastIdFile);
|
||||
fos.write(("" + messageNumber).getBytes());
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(lastIdFile);
|
||||
fos.write(("" + messageNumber).getBytes());
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug("done fetching");
|
||||
@@ -734,8 +748,9 @@ public class Sucker {
|
||||
String lineToCompare = messageId.substring(0, idx-1);
|
||||
idx = lineToCompare.lastIndexOf(":");
|
||||
lineToCompare = lineToCompare.substring(0, idx-1);
|
||||
FileInputStream his = null;
|
||||
try {
|
||||
FileInputStream his = new FileInputStream(historyFile);
|
||||
his = new FileInputStream(historyFile);
|
||||
String line;
|
||||
while ((line = readLine(his)) != null) {
|
||||
idx = line.lastIndexOf(":");
|
||||
@@ -751,6 +766,8 @@ public class Sucker {
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (his != null) try { his.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,15 +53,19 @@ class ThreadNodeImpl implements ThreadNode {
|
||||
void summarizeThread() {
|
||||
_recursiveAuthors.add(_entry.getKeyHash());
|
||||
_recursiveEntries.add(_entry);
|
||||
_mostRecentPostDate = _entry.getEntryId();
|
||||
_mostRecentPostAuthor = _entry.getKeyHash();
|
||||
// children are always 'newer' than parents, even if their dates are older
|
||||
// (e.g. post #1 for a child on tuesday is 'newer' than post #5 for the parent on tuesday)
|
||||
_mostRecentPostDate = -1;
|
||||
_mostRecentPostAuthor = null;
|
||||
|
||||
// we need to go through all children (recursively), in case the
|
||||
// tree is out of order (which it shouldn't be, if its built carefully...)
|
||||
for (int i = 0; i < _children.size(); i++) {
|
||||
ThreadNodeImpl node = (ThreadNodeImpl)_children.get(i);
|
||||
node.summarizeThread();
|
||||
if (node.getMostRecentPostDate() > _mostRecentPostDate) {
|
||||
// >= so we can give reasonable order when a child is a reply to a parent
|
||||
// (since the child must have been posted after the parent)
|
||||
if (node.getMostRecentPostDate() >= _mostRecentPostDate) {
|
||||
_mostRecentPostDate = node.getMostRecentPostDate();
|
||||
_mostRecentPostAuthor = node.getMostRecentPostAuthor();
|
||||
}
|
||||
@@ -69,6 +73,22 @@ class ThreadNodeImpl implements ThreadNode {
|
||||
_recursiveAuthors.addAll(node.getRecursiveAuthors());
|
||||
_recursiveEntries.addAll(node.getRecursiveEntries());
|
||||
}
|
||||
|
||||
if (_mostRecentPostDate < 0) {
|
||||
_mostRecentPostDate = _entry.getEntryId();
|
||||
_mostRecentPostAuthor = _entry.getKeyHash();
|
||||
}
|
||||
|
||||
// now reorder the children
|
||||
TreeSet ordered = new TreeSet(new NewestNodeFirstComparator());
|
||||
for (int i = 0; i < _children.size(); i++) {
|
||||
ThreadNodeImpl kid = (ThreadNodeImpl)_children.get(i);
|
||||
ordered.add(kid);
|
||||
}
|
||||
List kids = new ArrayList(ordered.size());
|
||||
for (Iterator iter = ordered.iterator(); iter.hasNext(); )
|
||||
kids.add(iter.next());
|
||||
_children = kids;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -5,7 +5,9 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.PetNameDB;
|
||||
import net.i2p.client.naming.PetName;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.web.AddressesServlet;
|
||||
|
||||
/**
|
||||
* User session state and preferences.
|
||||
@@ -44,6 +46,15 @@ public class User {
|
||||
private boolean _dataImported;
|
||||
|
||||
static final String PROP_USERHASH = "__userHash";
|
||||
|
||||
private static final String DEFAULT_FAVORITE_TAGS[] = {
|
||||
"syndie", "syndie.tech", "syndie.intro", "syndie.bugs", "syndie.featurerequest", "syndie.announce",
|
||||
"i2p", "i2p.tech", "i2p.bugs", "i2p.i2phex", "i2p.susimail", "i2p.irc",
|
||||
"bt.i2psnark", "bt.i2prufus", "bt.i2p-bt", "bt.azureus", "bt.misc",
|
||||
"security.misc",
|
||||
"chat",
|
||||
"test"
|
||||
};
|
||||
|
||||
/**
|
||||
* Ugly hack to fetch the default User instance - this is the default
|
||||
@@ -99,6 +110,24 @@ public class User {
|
||||
public long getMostRecentEntry() { return _mostRecentEntry; }
|
||||
public Map getBlogGroups() { return _blogGroups; }
|
||||
public List getShitlistedBlogs() { return _shitlistedBlogs; }
|
||||
public List getFavoriteTags() {
|
||||
List rv = new ArrayList();
|
||||
for (Iterator iter = _petnames.getNames().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
PetName pn = _petnames.getByName(name);
|
||||
if (AddressesServlet.PROTO_TAG.equals(pn.getProtocol()))
|
||||
rv.add(pn.getLocation());
|
||||
}
|
||||
if (rv.size() <= 0) {
|
||||
for (int i = 0; i < DEFAULT_FAVORITE_TAGS.length; i++) {
|
||||
if (!_petnames.containsName(DEFAULT_FAVORITE_TAGS[i])) {
|
||||
_petnames.add(new PetName(DEFAULT_FAVORITE_TAGS[i], AddressesServlet.NET_SYNDIE,
|
||||
AddressesServlet.PROTO_TAG, DEFAULT_FAVORITE_TAGS[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
public String getAddressbookLocation() { return _addressbookLocation; }
|
||||
public boolean getShowImages() { return _showImagesByDefault; }
|
||||
public boolean getShowExpanded() { return _showExpandedByDefault; }
|
||||
@@ -282,7 +311,7 @@ public class User {
|
||||
// shitlist=hash,hash,hash
|
||||
List shitlistedBlogs = getShitlistedBlogs();
|
||||
if (shitlistedBlogs.size() > 0) {
|
||||
buf.setLength(0);
|
||||
//buf.setLength(0);
|
||||
buf.append("shitlistedblogs=");
|
||||
for (int i = 0; i < shitlistedBlogs.size(); i++) {
|
||||
Hash blog = (Hash)shitlistedBlogs.get(i);
|
||||
@@ -292,6 +321,13 @@ public class User {
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
List favoriteTags = getFavoriteTags();
|
||||
if (favoriteTags.size() > 0) {
|
||||
buf.append("favoritetags=");
|
||||
for (int i = 0; i < favoriteTags.size(); i++)
|
||||
buf.append(((String)favoriteTags.get(i)).trim()).append(" ");
|
||||
buf.append('\n');
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,12 @@ class WritableThreadIndex extends ThreadIndex {
|
||||
void addParent(BlogURI parent, BlogURI child) { _parents.put(child, parent); }
|
||||
void addEntry(BlogURI entry, String tags[]) {
|
||||
if (tags == null) tags = NO_TAGS;
|
||||
String oldTags[] = (String[])_tags.put(entry, tags);
|
||||
Object old = _tags.get(entry);
|
||||
if (old != null) {
|
||||
System.err.println("Old value: " + old + " new tags: " + tags + " entry: " + entry);
|
||||
} else {
|
||||
_tags.put(entry, tags);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +113,9 @@ class WritableThreadIndex extends ThreadIndex {
|
||||
while (node.getParent() != null)
|
||||
node = node.getParent();
|
||||
|
||||
roots.add(node);
|
||||
if (!roots.contains(node)) {
|
||||
roots.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
// store them, sorted by most recently updated thread first
|
||||
@@ -129,35 +136,4 @@ class WritableThreadIndex extends ThreadIndex {
|
||||
buf.append("</threadIndex>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** sort BlogURI instances with the highest entryId first */
|
||||
private class NewestEntryFirstComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
BlogURI left = (BlogURI)lhs;
|
||||
BlogURI right = (BlogURI)rhs;
|
||||
if (left.getEntryId() > right.getEntryId()) {
|
||||
return -1;
|
||||
} else if (left.getEntryId() == right.getEntryId()) {
|
||||
return DataHelper.compareTo(left.getKeyHash().getData(), right.getKeyHash().getData());
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
/** sort ThreadNodeImpl instances with the highest entryId first */
|
||||
private class NewestNodeFirstComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
ThreadNodeImpl left = (ThreadNodeImpl)lhs;
|
||||
ThreadNodeImpl right = (ThreadNodeImpl)rhs;
|
||||
long l = left.getMostRecentPostDate();
|
||||
long r = right.getMostRecentPostDate();
|
||||
if (l > r) {
|
||||
return -1;
|
||||
} else if (l == r) {
|
||||
return DataHelper.compareTo(left.getEntry().getKeyHash().getData(), right.getEntry().getKeyHash().getData());
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +281,9 @@ public class ArchiveIndex {
|
||||
*
|
||||
*/
|
||||
public void selectMatchesOrderByEntryId(List out, Hash blog, String tag) {
|
||||
selectMatchesOrderByEntryId(out, blog, tag, 0);
|
||||
}
|
||||
public void selectMatchesOrderByEntryId(List out, Hash blog, String tag, long lowestEntryId) {
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _blogs.size(); i++) {
|
||||
BlogSummary summary = (BlogSummary)_blogs.get(i);
|
||||
@@ -288,7 +291,8 @@ public class ArchiveIndex {
|
||||
if (!blog.equals(summary.blog))
|
||||
continue;
|
||||
}
|
||||
if (tag != null) {
|
||||
|
||||
if ( (tag != null) && (tag.trim().length() > 0) ) {
|
||||
if (!tag.equals(summary.tag)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Tag [" + summary.tag + "] does not match the requested [" + tag + "] in " + summary.blog.toBase64());
|
||||
@@ -312,13 +316,21 @@ public class ArchiveIndex {
|
||||
|
||||
for (int j = 0; j < summary.entries.size(); j++) {
|
||||
EntrySummary entry = (EntrySummary)summary.entries.get(j);
|
||||
String k = (Long.MAX_VALUE-entry.entry.getEntryId()) + "-" + entry.entry.getKeyHash().toBase64();
|
||||
ordered.put(k, entry.entry);
|
||||
//System.err.println("Including match: " + k);
|
||||
if (entry.entry.getEntryId() < lowestEntryId) {
|
||||
long daysAgo1 = entry.entry.getEntryId() / (24*60*60*1000l);
|
||||
long daysAgo2 = lowestEntryId / (24*60*60*1000l);
|
||||
continue;
|
||||
} else {
|
||||
String k = (Long.MAX_VALUE-entry.entry.getEntryId()) + "-" + entry.entry.getKeyHash().toBase64();
|
||||
ordered.put(k, entry.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) {
|
||||
BlogURI entry = (BlogURI)iter.next();
|
||||
if (entry.getEntryId() < lowestEntryId) {
|
||||
continue;
|
||||
}
|
||||
if (!out.contains(entry))
|
||||
out.add(entry);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ public class BlogURI {
|
||||
private Hash _blogHash;
|
||||
private long _entryId;
|
||||
|
||||
public static final Comparator COMPARATOR = new NewestFirstComparator();
|
||||
|
||||
public BlogURI() {
|
||||
this(null, -1);
|
||||
}
|
||||
@@ -95,4 +97,20 @@ public class BlogURI {
|
||||
if (!u.toString().equals(uri))
|
||||
System.err.println("Not a match: [" + uri + "] != [" + u.toString() + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Order the BlogURIs by entryId, with the highest entryId first
|
||||
*/
|
||||
private static class NewestFirstComparator implements Comparator {
|
||||
public int compare(Object lhs, Object rhs) {
|
||||
BlogURI l = (BlogURI)lhs;
|
||||
BlogURI r = (BlogURI)rhs;
|
||||
if (l.getEntryId() > r.getEntryId())
|
||||
return -1;
|
||||
else if (l.getEntryId() < r.getEntryId())
|
||||
return 1;
|
||||
else // same date, compare by blog hash (aka randomly)
|
||||
return DataHelper.compareTo(l.getKeyHash().getData(), r.getKeyHash().getData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,12 @@ public class FilteredThreadIndex extends ThreadIndex {
|
||||
private List _roots;
|
||||
private List _ignoredAuthors;
|
||||
private Collection _filteredAuthors;
|
||||
private boolean _filterAuthorsByRoot;
|
||||
|
||||
public static final String GROUP_FAVORITE = "Favorite";
|
||||
public static final String GROUP_IGNORE = "Ignore";
|
||||
|
||||
public FilteredThreadIndex(User user, Archive archive, Collection tags, Collection authors) {
|
||||
public FilteredThreadIndex(User user, Archive archive, Collection tags, Collection authors, boolean filterAuthorsByRoot) {
|
||||
super();
|
||||
_user = user;
|
||||
_archive = archive;
|
||||
@@ -31,6 +32,7 @@ public class FilteredThreadIndex extends ThreadIndex {
|
||||
_filteredAuthors = authors;
|
||||
if (_filteredAuthors == null)
|
||||
_filteredAuthors = Collections.EMPTY_SET;
|
||||
_filterAuthorsByRoot = filterAuthorsByRoot;
|
||||
|
||||
_ignoredAuthors = new ArrayList();
|
||||
for (Iterator iter = user.getPetNameDB().iterator(); iter.hasNext(); ) {
|
||||
@@ -53,12 +55,12 @@ public class FilteredThreadIndex extends ThreadIndex {
|
||||
_roots = new ArrayList(_baseIndex.getRootCount());
|
||||
for (int i = 0; i < _baseIndex.getRootCount(); i++) {
|
||||
ThreadNode node = _baseIndex.getRoot(i);
|
||||
if (!isIgnored(node, _ignoredAuthors, _filteredTags, _filteredAuthors))
|
||||
if (!isIgnored(node, _ignoredAuthors, _filteredTags, _filteredAuthors, _filterAuthorsByRoot))
|
||||
_roots.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIgnored(ThreadNode node, List ignoredAuthors, Collection requestedTags, Collection filteredAuthors) {
|
||||
private boolean isIgnored(ThreadNode node, List ignoredAuthors, Collection requestedTags, Collection filteredAuthors, boolean filterAuthorsByRoot) {
|
||||
if (filteredAuthors.size() <= 0) {
|
||||
boolean allAuthorsIgnored = true;
|
||||
for (Iterator iter = node.getRecursiveAuthorIterator(); iter.hasNext(); ) {
|
||||
@@ -75,9 +77,16 @@ public class FilteredThreadIndex extends ThreadIndex {
|
||||
boolean filteredAuthorMatches = false;
|
||||
for (Iterator iter = filteredAuthors.iterator(); iter.hasNext(); ) {
|
||||
Hash author = (Hash)iter.next();
|
||||
if (node.containsAuthor(author)) {
|
||||
filteredAuthorMatches = true;
|
||||
break;
|
||||
if (filterAuthorsByRoot) {
|
||||
if (node.getEntry().getKeyHash().equals(author)) {
|
||||
filteredAuthorMatches = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (node.containsAuthor(author)) {
|
||||
filteredAuthorMatches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!filteredAuthorMatches)
|
||||
@@ -107,4 +116,5 @@ public class FilteredThreadIndex extends ThreadIndex {
|
||||
public ThreadNode getNode(BlogURI uri) { return _baseIndex.getNode(uri); }
|
||||
public Collection getFilteredTags() { return _filteredTags; }
|
||||
public Collection getFilteredAuthors() { return _filteredAuthors; }
|
||||
public boolean getFilterAuthorsByRoot() { return _filterAuthorsByRoot; }
|
||||
}
|
||||
|
||||
@@ -111,5 +111,5 @@ public class EventReceiverImpl implements SMLParser.EventReceiver {
|
||||
public void receiveH5(String text) {}
|
||||
public void receivePre(String text) {}
|
||||
public void receiveHR() {}
|
||||
public void receiveAttachment(int id, String anchorText) {}
|
||||
public void receiveAttachment(int id, int thumbnail, String anchorText) {}
|
||||
}
|
||||
|
||||
@@ -31,16 +31,27 @@ public class HTMLPreviewRenderer extends HTMLRenderer {
|
||||
ArchiveViewerBean.PARAM_ATTACHMENT + "=" + id;
|
||||
}
|
||||
|
||||
public void receiveAttachment(int id, String anchorText) {
|
||||
public void receiveAttachment(int id, int thumb, String anchorText) {
|
||||
anchorText = sanitizeString(anchorText);
|
||||
if (!continueBody()) { return; }
|
||||
if ( (id < 0) || (_files == null) || (id >= _files.size()) ) {
|
||||
_bodyBuffer.append(sanitizeString(anchorText));
|
||||
_bodyBuffer.append(anchorText);
|
||||
} else {
|
||||
File f = (File)_files.get(id);
|
||||
String name = (String)_filenames.get(id);
|
||||
String type = (String)_fileTypes.get(id);
|
||||
_bodyBuffer.append("<a ").append(getClass("attachmentView")).append(" href=\"").append(getAttachmentURL(id)).append("\">");
|
||||
_bodyBuffer.append(sanitizeString(anchorText)).append("</a>");
|
||||
if(thumb >= 0) {
|
||||
_bodyBuffer.append("<img src=\"").
|
||||
append(getAttachmentURL(thumb)).
|
||||
append("\" alt=\"").append(anchorText).
|
||||
append("\" title=\"").append(anchorText).
|
||||
append("\" />");
|
||||
} else {
|
||||
_bodyBuffer.append(anchorText);
|
||||
}
|
||||
|
||||
_bodyBuffer.append("</a>");
|
||||
_bodyBuffer.append(getSpan("attachmentSummary")).append(" (");
|
||||
_bodyBuffer.append(getSpan("attachmentSummarySize")).append(f.length()/1024).append("KB</span>, ");
|
||||
_bodyBuffer.append(getSpan("attachmentSummaryName")).append(" \"").append(sanitizeString(name)).append("\"</span>, ");
|
||||
@@ -87,11 +98,13 @@ public class HTMLPreviewRenderer extends HTMLRenderer {
|
||||
_postBodyBuffer.append(getSpan("summDetailExternal")).append("External links:</span> ");
|
||||
for (int i = 0; i < _links.size(); i++) {
|
||||
Link l = (Link)_links.get(i);
|
||||
_postBodyBuffer.append("<a ").append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
String schema = l.schema;
|
||||
_postBodyBuffer.append("<a ");
|
||||
_postBodyBuffer.append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
if (l.schema != null)
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
if (l.location != null)
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location));
|
||||
_postBodyBuffer.append(getSpan("summDetailExternalNet")).append(" (").append(sanitizeString(l.schema)).append(")</span></a> ");
|
||||
}
|
||||
|
||||
@@ -341,14 +341,16 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
}
|
||||
|
||||
|
||||
String url = getPageURL(blog, null, -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false));
|
||||
//String url = getPageURL(blog, null, -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false));
|
||||
String url = getMetadataURL(blog);
|
||||
_bodyBuffer.append(getSpan("blogEntrySummary")).append(" [<a ").append(getClass("blogLink")).append(" href=\"").append(url);
|
||||
_bodyBuffer.append("\">");
|
||||
if ( (name != null) && (name.trim().length() > 0) )
|
||||
_bodyBuffer.append(sanitizeString(name));
|
||||
else
|
||||
_bodyBuffer.append("view");
|
||||
_bodyBuffer.append("</a> (<a ").append(getClass("blogMeta")).append(" href=\"").append(getMetadataURL(blog)).append("\">meta</a>)");
|
||||
_bodyBuffer.append("</a> ");
|
||||
//_bodyBuffer.append("</a> (<a ").append(getClass("blogMeta")).append(" href=\"").append(getMetadataURL(blog)).append("\">meta</a>)");
|
||||
if ( (tag != null) && (tag.trim().length() > 0) ) {
|
||||
url = getPageURL(blog, tag, -1, -1, -1, false, false);
|
||||
_bodyBuffer.append(" <a ").append(getClass("blogTagLink")).append(" href=\"").append(url);
|
||||
@@ -434,10 +436,14 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
_links.add(l);
|
||||
if (!continueBody()) { return; }
|
||||
if ( (schema == null) || (location == null) ) return;
|
||||
_bodyBuffer.append("<a ").append(getClass("externalLink")).append(" href=\"externallink.jsp?schema=");
|
||||
_bodyBuffer.append("<a ");
|
||||
_bodyBuffer.append(getClass("externalLink")).append(" href=\"externallink.jsp?schema=");
|
||||
_bodyBuffer.append(sanitizeURL(schema)).append("&location=");
|
||||
_bodyBuffer.append(sanitizeURL(location)).append("&description=");
|
||||
_bodyBuffer.append(sanitizeURL(text)).append("\">").append(sanitizeString(text)).append("</a>");
|
||||
_bodyBuffer.append(sanitizeURL(text));
|
||||
_bodyBuffer.append("\">").
|
||||
append(sanitizeString(text)).
|
||||
append("</a>");
|
||||
}
|
||||
|
||||
protected static class Address {
|
||||
@@ -507,14 +513,23 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveAttachment(int id, String anchorText) {
|
||||
public void receiveAttachment(int id, int thumb, String anchorText) {
|
||||
if (!continueBody()) { return; }
|
||||
Attachment attachments[] = _entry.getAttachments();
|
||||
if ( (id < 0) || (id >= attachments.length)) {
|
||||
_bodyBuffer.append(getSpan("attachmentUnknown")).append(sanitizeString(anchorText)).append("</span>");
|
||||
} else {
|
||||
_bodyBuffer.append("<a ").append(getClass("attachmentView")).append(" href=\"").append(getAttachmentURL(id)).append("\">");
|
||||
_bodyBuffer.append(sanitizeString(anchorText)).append("</a>");
|
||||
_bodyBuffer.append("<a ").append(getClass("attachmentView")).append(" href=\"").append(getAttachmentURL(id)).append("\">");
|
||||
if(thumb >= 0) {
|
||||
_bodyBuffer.append("<img src=\"").
|
||||
append(getAttachmentURL(thumb)).
|
||||
append("\" alt=\"").append(anchorText).
|
||||
append("\" title=\"").append(anchorText).
|
||||
append("\" />");
|
||||
} else {
|
||||
_bodyBuffer.append(anchorText);
|
||||
}
|
||||
_bodyBuffer.append("</a>");
|
||||
_bodyBuffer.append(getSpan("attachmentSummary")).append(" (");
|
||||
_bodyBuffer.append(getSpan("attachmentSummarySize")).append(attachments[id].getDataLength()/1024).append("KB</span>, ");
|
||||
_bodyBuffer.append(getSpan("attachmentSummaryName")).append(" \"").append(sanitizeString(attachments[id].getName())).append("\"</span>, ");
|
||||
@@ -637,12 +652,14 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
_postBodyBuffer.append(getSpan("summDetailExternal")).append("External links:</span> ");
|
||||
for (int i = 0; i < _links.size(); i++) {
|
||||
Link l = (Link)_links.get(i);
|
||||
_postBodyBuffer.append("<a ").append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
String schema = l.schema;
|
||||
_postBodyBuffer.append("<a ");
|
||||
_postBodyBuffer.append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
if (l.schema != null)
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
if (l.location != null)
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location));
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location, 30));
|
||||
_postBodyBuffer.append(getSpan("summDetailExternalNet")).append(" (").append(sanitizeString(l.schema)).append(")</span></a> ");
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
@@ -973,6 +990,13 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
else
|
||||
return orig.toString();
|
||||
}
|
||||
public static final String sanitizeStrippedXML(String orig) {
|
||||
if (orig == null) return "";
|
||||
orig = orig.replaceAll("&", "&");
|
||||
orig = orig.replaceAll("<", "<");
|
||||
orig = orig.replaceAll(">", ">");
|
||||
return orig;
|
||||
}
|
||||
|
||||
private static final String STYLE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
public static String sanitizeStyle(String style) {
|
||||
@@ -1062,8 +1086,9 @@ public class HTMLRenderer extends EventReceiverImpl {
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_AUTHOR).append('=').append(blog.toBase64()).append('&');
|
||||
if (tag != null)
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_TAGS).append('=').append(sanitizeTagParam(tag)).append('&');
|
||||
String entry = null;
|
||||
if (entryId >= 0) {
|
||||
String entry = blog.toBase64() + '/' + entryId;
|
||||
entry = blog.toBase64() + '/' + entryId;
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_VIEW_POST).append('=').append(entry).append('&');
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_VISIBLE).append('=').append(entry).append('&');
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class RSSRenderer extends HTMLRenderer {
|
||||
_bodyBuffer.append(sanitizeString(anchorText));
|
||||
}
|
||||
}
|
||||
public void receiveAttachment(int id, String anchorText) {
|
||||
public void receiveAttachment(int id, int thumb, String anchorText) {
|
||||
if (!continueBody()) { return; }
|
||||
_bodyBuffer.append(sanitizeString(anchorText));
|
||||
}
|
||||
@@ -314,4 +314,4 @@ public class RSSRenderer extends HTMLRenderer {
|
||||
String sanitized = sanitizeXML(t);
|
||||
System.out.println("[" + str + "] --> [" + sanitized + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ public class SMLParser {
|
||||
private static final String T_ATTACHMENT = "attachment";
|
||||
private static final String T_ARCHIVE = "archive";
|
||||
|
||||
private static final String P_THUMBNAIL = "thumbnail";
|
||||
private static final String P_ATTACHMENT = "attachment";
|
||||
private static final String P_WHO_QUOTED = "author";
|
||||
private static final String P_QUOTE_LOCATION = "location";
|
||||
@@ -284,7 +285,10 @@ public class SMLParser {
|
||||
} else if (T_PRE.equals(tagName)) {
|
||||
receiver.receivePre(body);
|
||||
} else if (T_ATTACHMENT.equals(tagName)) {
|
||||
receiver.receiveAttachment((int)getLong(P_ATTACHMENT_ID, attr), body);
|
||||
receiver.receiveAttachment(
|
||||
(int)getLong(P_ATTACHMENT_ID, attr),
|
||||
(int)getLong(P_THUMBNAIL, attr),
|
||||
body);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("need to learn how to parse the tag [" + tagName + "]");
|
||||
@@ -402,7 +406,7 @@ public class SMLParser {
|
||||
String postingKey, String anchorText);
|
||||
public void receiveImage(String alternateText, int attachmentId);
|
||||
public void receiveAddress(String name, String schema, String protocol, String location, String anchorText);
|
||||
public void receiveAttachment(int id, String anchorText);
|
||||
public void receiveAttachment(int id, int thumb, String anchorText);
|
||||
public void receiveBold(String text);
|
||||
public void receiveItalic(String text);
|
||||
public void receiveUnderline(String text);
|
||||
|
||||
@@ -36,10 +36,17 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
public static final String PARAM_REMOVE_FROM_GROUP_NAME = "removeName";
|
||||
/** group to remove from the bookmarked entry, or if blank, remove the entry itself */
|
||||
public static final String PARAM_REMOVE_FROM_GROUP = "removeGroup";
|
||||
/** add the specified tag to the favorites list */
|
||||
public static final String PARAM_ADD_TAG = "addTag";
|
||||
/** index into the nav tree to start displaying */
|
||||
public static final String PARAM_OFFSET = "offset";
|
||||
public static final String PARAM_TAGS = "tags";
|
||||
/** only show threads that the given author participates in */
|
||||
public static final String PARAM_AUTHOR = "author";
|
||||
/** only show threads started by the given author */
|
||||
public static final String PARAM_THREAD_AUTHOR = "threadAuthorOnly";
|
||||
/** search back through the blog for entries this many days */
|
||||
public static final String PARAM_DAYS_BACK = "daysBack";
|
||||
// parameters for editing one's profile
|
||||
public static final String PARAM_PROFILE_NAME = "profileName";
|
||||
public static final String PARAM_PROFILE_DESC = "profileDesc";
|
||||
@@ -64,7 +71,33 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String getNavLink(String uri, String viewPost, String viewThread, String tags, String author, int offset) {
|
||||
public static String getAddTagToFavoritesLink(String uri, String tag, String author, String visible, String viewPost,
|
||||
String viewThread, String offset) {
|
||||
//protected String getAddToGroupLink(User user, Hash author, String group, String uri, String visible,
|
||||
// String viewPost, String viewThread, String offset, String tags, String filteredAuthor) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(uri);
|
||||
buf.append('?');
|
||||
if (!empty(visible))
|
||||
buf.append(PARAM_VISIBLE).append('=').append(visible).append('&');
|
||||
buf.append(PARAM_ADD_TAG).append('=').append(sanitizeTagParam(tag)).append('&');
|
||||
|
||||
if (!empty(viewPost))
|
||||
buf.append(PARAM_VIEW_POST).append('=').append(viewPost).append('&');
|
||||
else if (!empty(viewThread))
|
||||
buf.append(PARAM_VIEW_THREAD).append('=').append(viewThread).append('&');
|
||||
|
||||
if (!empty(offset))
|
||||
buf.append(PARAM_OFFSET).append('=').append(offset).append('&');
|
||||
|
||||
if (!empty(author))
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
|
||||
BaseServlet.addAuthActionParams(buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String getNavLink(String uri, String viewPost, String viewThread, String tags, String author, boolean authorOnly, int offset) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(uri);
|
||||
buf.append('?');
|
||||
@@ -76,8 +109,11 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
if (!empty(tags))
|
||||
buf.append(PARAM_TAGS).append('=').append(tags).append('&');
|
||||
|
||||
if (!empty(author))
|
||||
if (!empty(author)) {
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
if (authorOnly)
|
||||
buf.append(PARAM_THREAD_AUTHOR).append("=true&");
|
||||
}
|
||||
|
||||
buf.append(PARAM_OFFSET).append('=').append(offset).append('&');
|
||||
|
||||
@@ -85,7 +121,7 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
}
|
||||
|
||||
public static String getViewPostLink(String uri, ThreadNode node, User user, boolean isPermalink,
|
||||
String offset, String tags, String author) {
|
||||
String offset, String tags, String author, boolean authorOnly) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(uri);
|
||||
if (node.getChildCount() > 0) {
|
||||
@@ -107,13 +143,42 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
buf.append(PARAM_OFFSET).append('=').append(offset).append('&');
|
||||
if (!empty(tags))
|
||||
buf.append(PARAM_TAGS).append('=').append(tags).append('&');
|
||||
if (!empty(author))
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
}
|
||||
|
||||
if (authorOnly && !empty(author)) {
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
buf.append(PARAM_THREAD_AUTHOR).append("=true&");
|
||||
} else if (!isPermalink && !empty(author))
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String getViewPostLink(String uri, BlogURI post, User user, boolean isPermalink,
|
||||
String offset, String tags, String author, boolean authorOnly) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(uri);
|
||||
buf.append('?').append(PARAM_VISIBLE).append('=');
|
||||
buf.append(post.getKeyHash().toBase64()).append('/');
|
||||
buf.append(post.getEntryId()).append('&');
|
||||
buf.append(PARAM_VIEW_POST).append('=');
|
||||
buf.append(post.getKeyHash().toBase64()).append('/');
|
||||
buf.append(post.getEntryId()).append('&');
|
||||
|
||||
if (!isPermalink) {
|
||||
if (!empty(offset))
|
||||
buf.append(PARAM_OFFSET).append('=').append(offset).append('&');
|
||||
if (!empty(tags))
|
||||
buf.append(PARAM_TAGS).append('=').append(tags).append('&');
|
||||
if (!empty(author)) {
|
||||
buf.append(PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
if (authorOnly)
|
||||
buf.append(PARAM_THREAD_AUTHOR).append("=true&");
|
||||
}
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final boolean empty(String val) { return (val == null) || (val.trim().length() <= 0); }
|
||||
|
||||
@@ -122,9 +187,14 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
*/
|
||||
public void render(User user, Writer out, Archive archive, BlogURI post,
|
||||
boolean inlineReply, ThreadIndex index, String baseURI, String replyHiddenFields,
|
||||
String offset, String requestTags, String filteredAuthor) throws IOException {
|
||||
String offset, String requestTags, String filteredAuthor, boolean authorOnly) throws IOException {
|
||||
EntryContainer entry = archive.getEntry(post);
|
||||
if (entry == null) return;
|
||||
ThreadNode node = index.getNode(post);
|
||||
if (node == null) {
|
||||
_log.error("Post is not in the index: " + post.toString());
|
||||
return;
|
||||
}
|
||||
_entry = entry;
|
||||
|
||||
_baseURI = baseURI;
|
||||
@@ -171,8 +241,6 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
if ( (author == null) || (author.trim().length() <= 0) )
|
||||
author = post.getKeyHash().toBase64().substring(0,6);
|
||||
|
||||
ThreadNode node = index.getNode(post);
|
||||
|
||||
out.write(author);
|
||||
out.write("</a> @ ");
|
||||
out.write(getEntryDate(post.getEntryId()));
|
||||
@@ -189,15 +257,25 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
out.write("'\">");
|
||||
out.write(" " + tag);
|
||||
out.write("</a>\n");
|
||||
if (user.getAuthenticated() && (!user.getFavoriteTags().contains(tag)) && (!"[none]".equals(tag)) ) {
|
||||
out.write("<a href=\"");
|
||||
String cur = node.getEntry().getKeyHash().toBase64() + '/' + node.getEntry().getEntryId();
|
||||
out.write(getAddTagToFavoritesLink(baseURI, tag, filteredAuthor, cur, null, cur, offset));
|
||||
out.write("\" title=\"Add the tag '");
|
||||
out.write(tag);
|
||||
out.write("' to your favorites list\">");
|
||||
out.write("<img src=\"images/addToFavorites.png\" alt=\":)\" border=\"0\" />");
|
||||
out.write("</a>\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.write("\n<a href=\"");
|
||||
out.write(getViewPostLink(baseURI, node, user, true, offset, requestTags, filteredAuthor));
|
||||
out.write(getViewPostLink(baseURI, node, user, true, offset, requestTags, filteredAuthor, authorOnly));
|
||||
out.write("\" title=\"Select a shareable link directly to this post\">permalink</a>\n");
|
||||
|
||||
|
||||
if (!inlineReply) {
|
||||
if (true || (!inlineReply) ) {
|
||||
String refuseReply = (String)_headers.get(HEADER_REFUSE_REPLIES);
|
||||
boolean allowReply = false;
|
||||
if ( (refuseReply != null) && (Boolean.valueOf(refuseReply).booleanValue()) ) {
|
||||
@@ -273,9 +351,11 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
out.write("<tr class=\"postReplyOptions\">\n");
|
||||
out.write(" <td colspan=\"3\">\n");
|
||||
out.write(" <input type=\"submit\" value=\"Preview...\" name=\"Post\" />\n");
|
||||
out.write(" Tags: <input type=\"text\" size=\"10\" name=\"" + PostServlet.PARAM_TAGS + "\" />\n");
|
||||
out.write(" in a new thread? <input type=\"checkbox\" name=\"" + PostServlet.PARAM_IN_NEW_THREAD + "\" value=\"true\" />\n");
|
||||
out.write(" refuse replies? <input type=\"checkbox\" name=\"" + PostServlet.PARAM_REFUSE_REPLIES + "\" value=\"true\" />\n");
|
||||
out.write(" Tags: ");
|
||||
BaseServlet.writeTagField(_user, "", out, "Optional tags to categorize your response", "No tags", false);
|
||||
// <input type=\"text\" size=\"10\" name=\"" + PostServlet.PARAM_TAGS + "\" title=\"Optional tags to categorize your response\" />\n");
|
||||
out.write(" in a new thread? <input type=\"checkbox\" name=\"" + PostServlet.PARAM_IN_NEW_THREAD + "\" value=\"true\" title=\"If true, this will fork a new top level thread\" />\n");
|
||||
out.write(" refuse replies? <input type=\"checkbox\" name=\"" + PostServlet.PARAM_REFUSE_REPLIES + "\" value=\"true\" title=\"If true, only you will be able to reply to the post\" />\n");
|
||||
out.write(" attachment: <input type=\"file\" name=\"entryfile0\" />\n");
|
||||
out.write(" </td>\n</tr>\n</form>\n");
|
||||
out.write("<!-- body reply end -->\n");
|
||||
@@ -312,10 +392,10 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
for (int i = 0; i < _entry.getAttachments().length; i++) {
|
||||
_postBodyBuffer.append("<option value=\"").append(i).append("\">");
|
||||
Attachment a = _entry.getAttachments()[i];
|
||||
_postBodyBuffer.append(sanitizeString(a.getName()));
|
||||
_postBodyBuffer.append(sanitizeString(a.getName(), 30));
|
||||
if ( (a.getDescription() != null) && (a.getDescription().trim().length() > 0) ) {
|
||||
_postBodyBuffer.append(": ");
|
||||
_postBodyBuffer.append(sanitizeString(a.getDescription()));
|
||||
_postBodyBuffer.append(sanitizeString(a.getDescription(), 30));
|
||||
}
|
||||
_postBodyBuffer.append(" (").append(a.getDataLength()/1024).append("KB");
|
||||
_postBodyBuffer.append(", type ").append(sanitizeString(a.getMimeType())).append(")</option>\n");
|
||||
@@ -332,7 +412,7 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
boolean expanded = (_user != null ? _user.getShowExpanded() : false);
|
||||
boolean images = (_user != null ? _user.getShowImages() : false);
|
||||
_postBodyBuffer.append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, expanded, images));
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> ");
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(b.name, 30)).append("</a> ");
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
}
|
||||
@@ -341,12 +421,14 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
_postBodyBuffer.append(getSpan("summDetailExternal")).append("External links:</span> ");
|
||||
for (int i = 0; i < _links.size(); i++) {
|
||||
Link l = (Link)_links.get(i);
|
||||
_postBodyBuffer.append("<a ").append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
String schema = l.schema;
|
||||
_postBodyBuffer.append("<a ");
|
||||
_postBodyBuffer.append(getClass("summDetailExternalLink")).append(" href=\"externallink.jsp?");
|
||||
if (l.schema != null)
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
_postBodyBuffer.append("schema=").append(sanitizeURL(l.schema)).append('&');
|
||||
if (l.location != null)
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location, 60));
|
||||
_postBodyBuffer.append("location=").append(sanitizeURL(l.location)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(l.location, 30));
|
||||
_postBodyBuffer.append(getSpan("summDetailExternalNet")).append(" (").append(sanitizeString(l.schema)).append(")</span></a> ");
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
@@ -373,7 +455,7 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
_postBodyBuffer.append(AddressesServlet.PARAM_NAME).append("=").append(sanitizeTagParam(a.name)).append('&');
|
||||
if (a.protocol != null)
|
||||
_postBodyBuffer.append(AddressesServlet.PARAM_PROTO).append("=").append(sanitizeTagParam(a.protocol)).append('&');
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(a.name)).append("</a>");
|
||||
_postBodyBuffer.append("\">").append(sanitizeString(a.name, 30)).append("</a>");
|
||||
}
|
||||
}
|
||||
_postBodyBuffer.append("<br />\n");
|
||||
@@ -424,8 +506,12 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
String inReplyTo = (String)_headers.get(HEADER_IN_REPLY_TO);
|
||||
if ( (inReplyTo != null) && (inReplyTo.trim().length() > 0) ) {
|
||||
BlogURI replyURI = new BlogURI(inReplyTo);
|
||||
if (replyURI.getEntryId() > 0)
|
||||
_postBodyBuffer.append(" <a ").append(getClass("summDetailParent")).append(" href=\"").append(getPageURL(replyURI.getKeyHash(), null, replyURI.getEntryId(), 0, 0, true, true)).append("\">(view parent)</a><br />\n");
|
||||
if (replyURI.getEntryId() > 0) {
|
||||
_postBodyBuffer.append(" <a ").append(getClass("summDetailParent"));
|
||||
_postBodyBuffer.append(" href=\"");
|
||||
_postBodyBuffer.append(getPageURL(replyURI.getKeyHash(), null, replyURI.getEntryId(), 0, 0, true, true));
|
||||
_postBodyBuffer.append("\">(view parent)</a><br />\n");
|
||||
}
|
||||
}
|
||||
|
||||
_postBodyBuffer.append(" </td>\n");
|
||||
@@ -465,12 +551,18 @@ public class ThreadedHTMLRenderer extends HTMLRenderer {
|
||||
public String getPageURL(Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum, boolean expandEntries, boolean showImages) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append(_baseURI).append('?');
|
||||
String entry = null;
|
||||
if ( (blog != null) && (entryId > 0) ) {
|
||||
buf.append(PARAM_VIEW_POST).append('=').append(Base64.encode(blog.getData())).append('/').append(entryId).append('&');
|
||||
entry = blog.toBase64() + '/' + entryId;
|
||||
buf.append(PARAM_VIEW_THREAD).append('=').append(entry).append('&');
|
||||
buf.append(PARAM_VISIBLE).append('=').append(Base64.encode(blog.getData())).append('/').append(entryId).append('&');
|
||||
} else if (blog != null) {
|
||||
buf.append(PARAM_AUTHOR).append('=').append(blog.toBase64()).append('&');
|
||||
}
|
||||
if (tag != null)
|
||||
buf.append(PARAM_TAGS).append('=').append(sanitizeTagParam(tag)).append('&');
|
||||
if ( (blog != null) && (entryId > 0) )
|
||||
buf.append("#blog://").append(entry);
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,14 @@ public class AddressesServlet extends BaseServlet {
|
||||
public static final String PARAM_NET = "addrNet";
|
||||
public static final String PARAM_PROTO = "addrProto";
|
||||
public static final String PARAM_SYNDICATE = "addrSyndicate";
|
||||
public static final String PARAM_TAG = "addrTag";
|
||||
public static final String PARAM_ACTION = "action";
|
||||
|
||||
public static final String PROTO_BLOG = "syndieblog";
|
||||
public static final String PROTO_ARCHIVE = "syndiearchive";
|
||||
public static final String PROTO_I2PHEX = "i2phex";
|
||||
public static final String PROTO_EEPSITE = "eep";
|
||||
public static final String PROTO_TAG = "syndietag";
|
||||
|
||||
public static final String NET_SYNDIE = "syndie";
|
||||
public static final String NET_I2P = "i2p";
|
||||
@@ -57,6 +59,10 @@ public class AddressesServlet extends BaseServlet {
|
||||
public static final String ACTION_UPDATE_EEPSITE = "Update eepsite";
|
||||
public static final String ACTION_ADD_EEPSITE = "Add eepsite";
|
||||
|
||||
public static final String ACTION_DELETE_TAG = "Delete tag";
|
||||
public static final String ACTION_UPDATE_TAG = "Update tag";
|
||||
public static final String ACTION_ADD_TAG = "Add tag";
|
||||
|
||||
public static final String ACTION_DELETE_OTHER = "Delete address";
|
||||
public static final String ACTION_UPDATE_OTHER = "Update address";
|
||||
public static final String ACTION_ADD_OTHER = "Add other address";
|
||||
@@ -75,6 +81,9 @@ public class AddressesServlet extends BaseServlet {
|
||||
pn = buildNewName(req, PROTO_ARCHIVE);
|
||||
_log.debug("pn for protoArchive [" + req.getParameter(PARAM_PROTO) + "]: " + pn);
|
||||
renderArchives(user, db, uri, pn, out);
|
||||
pn = buildNewName(req, PROTO_TAG);
|
||||
_log.debug("pn for protoTag [" + req.getParameter(PARAM_TAG) + "]: " + pn);
|
||||
renderTags(user, db, uri, pn, out);
|
||||
pn = buildNewName(req, PROTO_I2PHEX);
|
||||
_log.debug("pn for protoPhex [" + req.getParameter(PARAM_PROTO) + "]: " + pn);
|
||||
renderI2Phex(user, db, uri, pn, out);
|
||||
@@ -103,18 +112,25 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName() + "\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the author\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation()
|
||||
+ "\" title=\"Blog hash for the author\" /> ");
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_FAVORITE))
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE + "\" checked=\"true\" value=\"true\" /> ");
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, their posts are highlighted\" /> ");
|
||||
else
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE + "\" value=\"true\" /> ");
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE
|
||||
+ "\" value=\"true\" title=\"If true, their posts are highlighted\" /> ");
|
||||
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_IGNORE)) {
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE + "\" checked=\"true\" value=\"true\" /> ");
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, their threads are hidden\" /> ");
|
||||
} else {
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE + "\" value=\"true\" /> ");
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE
|
||||
+ "\" value=\"true\" title=\"If true, their threads are hidden\" /> ");
|
||||
out.write("<a href=\"" + getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + '='
|
||||
+ pn.getLocation() + "\" title=\"View threads by the given author\">View posts</a> ");
|
||||
}
|
||||
@@ -130,18 +146,25 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_BLOG + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName() + "\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the author\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation()
|
||||
+ "\" title=\"Blog hash for the author\" /> ");
|
||||
if (newName.isMember(FilteredThreadIndex.GROUP_FAVORITE))
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE + "\" checked=\"true\" value=\"true\" /> ");
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, their posts are highlighted\" /> ");
|
||||
else
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE + "\" value=\"true\" /> ");
|
||||
out.write("Favorite? <input type=\"checkbox\" name=\"" + PARAM_FAVORITE
|
||||
+ "\" value=\"true\" title=\"If true, their posts are highlighted\" /> ");
|
||||
|
||||
if (newName.isMember(FilteredThreadIndex.GROUP_IGNORE)) {
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE + "\" checked=\"true\" value=\"true\" /> ");
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, their threads are hidden\" /> ");
|
||||
} else {
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE + "\" value=\"true\" /> ");
|
||||
out.write("Ignored? <input type=\"checkbox\" name=\"" + PARAM_IGNORE
|
||||
+ "\" value=\"true\" title=\"If true, their threads are hidden\" /> ");
|
||||
}
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_BLOG + "\" /> ");
|
||||
@@ -167,18 +190,24 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_ARCHIVE + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + pn.getName() + "\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"20\" value=\"" + pn.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + pn.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the remote archive\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"20\" value=\"" + pn.getLocation()
|
||||
+ "\" title=\"URL to the remote archive's archive/archive.txt\" /> ");
|
||||
if (BlogManager.instance().authorizeRemote(user)) {
|
||||
|
||||
if (BlogManager.instance().syndicationScheduled(pn.getLocation()))
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE + "\" checked=\"true\" value=\"true\" />");
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, periodically pull down posts they have\" />");
|
||||
else
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE + "\" value=\"true\" />");
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE
|
||||
+ "\" value=\"true\" title=\"If true, periodically pull down posts they have\" />");
|
||||
|
||||
out.write("<a href=\"" + getSyndicateLink(user, pn.getLocation())
|
||||
+ "\" title=\"Synchronize manually with the peer\">Sync manually</a> ");
|
||||
} else {
|
||||
out.write("You are not <a href=\"admin.jsp\">authorized</a> to syndicate with the archive ");
|
||||
}
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_DELETE_ARCHIVE + "\" /> ");
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_UPDATE_ARCHIVE + "\" /> ");
|
||||
@@ -191,14 +220,19 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_ARCHIVE + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName() + "\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"20\" value=\"" + newName.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the remote archive\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"20\" value=\"" + newName.getLocation()
|
||||
+ "\" title=\"URL to the remote archive's archive/archive.txt\" /> ");
|
||||
if (BlogManager.instance().authorizeRemote(user)) {
|
||||
if (BlogManager.instance().syndicationScheduled(newName.getLocation()))
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE + "\" checked=\"true\" value=\"true\" />");
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE
|
||||
+ "\" checked=\"true\" value=\"true\" title=\"If true, periodically pull down posts they have\" />");
|
||||
else
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE + "\" value=\"true\" />");
|
||||
out.write("Syndicate? <input type=\"checkbox\" name=\"" + PARAM_SYNDICATE
|
||||
+ "\" value=\"true\" title=\"If true, periodically pull down posts they have\" />");
|
||||
}
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_ARCHIVE + "\" /> ");
|
||||
@@ -225,9 +259,12 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_I2PHEX + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_I2P + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName() + "\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the I2Phex peer\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation()
|
||||
+ "\" title=\"I2P destination of the I2Phex peer\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_DELETE_PEER + "\" /> ");
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_UPDATE_PEER + "\" /> ");
|
||||
@@ -240,9 +277,12 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_I2PHEX + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_I2P + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName() + "\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the I2Phex peer\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation()
|
||||
+ "\" title=\"I2P destination of the I2Phex peer\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_PEER + "\" /> ");
|
||||
out.write("</td></tr>\n");
|
||||
@@ -267,9 +307,12 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_EEPSITE + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_I2P + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName() + "\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the eepsite\" />" + pn.getName() + " ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation()
|
||||
+ "\" title=\"I2P destination of the eepsite\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_DELETE_EEPSITE + "\" /> ");
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_UPDATE_EEPSITE + "\" /> ");
|
||||
@@ -282,9 +325,12 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_EEPSITE + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_I2P + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName() + "\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the eepsite\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation()
|
||||
+ "\" title=\"I2P destination of the eepsite\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_EEPSITE + "\" /> ");
|
||||
out.write("</td></tr>\n");
|
||||
@@ -292,6 +338,53 @@ public class AddressesServlet extends BaseServlet {
|
||||
|
||||
out.write("<tr><td colspan=\"3\"><hr /></td></tr>\n");
|
||||
}
|
||||
|
||||
private void renderTags(User user, PetNameDB db, String baseURI, PetName newName, PrintWriter out) throws IOException {
|
||||
TreeSet names = new TreeSet();
|
||||
for (Iterator iter = db.getNames().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
PetName pn = db.getByName(name);
|
||||
if (PROTO_TAG.equals(pn.getProtocol()))
|
||||
names.add(name);
|
||||
}
|
||||
out.write("<tr><td colspan=\"3\"><b>Favorite tags</b></td></tr>\n");
|
||||
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
PetName pn = db.getByName((String)iter.next());
|
||||
out.write("<form action=\"" + baseURI + "\" method=\"POST\">");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_TAG + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName()
|
||||
+ "\" />" + pn.getName() + " ");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_LOC + "\" value=\"" + pn.getLocation()
|
||||
+ "\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_DELETE_TAG + "\" /> ");
|
||||
out.write("</td></tr>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
out.write("<form action=\"" + baseURI + "\" method=\"POST\">");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PROTO + "\" value=\"" + PROTO_TAG + "\" />");
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_NET + "\" value=\"" + NET_SYNDIE + "\" />");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Tag (or group of tags)\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_TAG + "\" /> ");
|
||||
out.write("</td></tr>\n");
|
||||
out.write("</form>\n");
|
||||
|
||||
out.write("<tr><td colspan=\"3\"><hr /></td></tr>\n");
|
||||
}
|
||||
|
||||
private void renderOther(User user, PetNameDB db, String baseURI, PetName newName, PrintWriter out) throws IOException {
|
||||
TreeSet names = new TreeSet();
|
||||
for (Iterator iter = db.getNames().iterator(); iter.hasNext(); ) {
|
||||
@@ -307,11 +400,16 @@ public class AddressesServlet extends BaseServlet {
|
||||
out.write("<form action=\"" + baseURI + "\" method=\"POST\">");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Network: <input type=\"text\" name=\"" + PARAM_NET + "\" value=\"" + pn.getNetwork() + "\" /> ");
|
||||
out.write("Protocol: <input type=\"text\" name=\"" + PARAM_PROTO + "\" value=\"" + pn.getProtocol() + "\" /> ");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName() + "\" />" + pn.getName() +" ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (pn.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Network: <input type=\"text\" name=\"" + PARAM_NET + "\" value=\"" + pn.getNetwork()
|
||||
+ "\" title=\"What network is this on - i2p, tor, internet, freenet, etc\" /> ");
|
||||
out.write("Protocol: <input type=\"text\" name=\"" + PARAM_PROTO + "\" value=\"" + pn.getProtocol()
|
||||
+ "\" title=\"How do we access/interact with this resource\" /> ");
|
||||
out.write("Name: <input type=\"hidden\" name=\"" + PARAM_NAME + "\" value=\"" + pn.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the location\" />" + pn.getName() +" ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + pn.getLocation()
|
||||
+ "\" title=\"URL\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_DELETE_OTHER + "\" /> ");
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_UPDATE_OTHER + "\" /> ");
|
||||
@@ -323,11 +421,16 @@ public class AddressesServlet extends BaseServlet {
|
||||
writeAuthActionFields(out);
|
||||
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "") + " />\n");
|
||||
out.write("Network: <input type=\"text\" name=\"" + PARAM_NET + "\" value=\"" + newName.getNetwork() + "\" /> ");
|
||||
out.write("Protocol: <input type=\"text\" name=\"" + PARAM_PROTO + "\" value=\"" + newName.getProtocol() + "\" /> ");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName() + "\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation() + "\" /> ");
|
||||
out.write("<input type=\"checkbox\" name=\"" + PARAM_IS_PUBLIC + "\" value=\"true\" " + (newName.getIsPublic() ? " checked=\"true\" " : "")
|
||||
+ " title=\"If checked, this name can be shared with one click when posting\" />\n");
|
||||
out.write("Network: <input type=\"text\" name=\"" + PARAM_NET + "\" value=\"" + newName.getNetwork()
|
||||
+ "\" title=\"What network is this on - i2p, tor, internet, freenet, etc\" /> ");
|
||||
out.write("Protocol: <input type=\"text\" name=\"" + PARAM_PROTO + "\" value=\"" + newName.getProtocol()
|
||||
+ "\" title=\"How do we access/interact with this resource\" /> ");
|
||||
out.write("Name: <input type=\"text\" name=\"" + PARAM_NAME + "\" size=\"10\" value=\"" + newName.getName()
|
||||
+ "\" title=\"Short, locally unique 'pet name' for the location\" /> ");
|
||||
out.write("Location: <input type=\"text\" name=\"" + PARAM_LOC + "\" size=\"3\" value=\"" + newName.getLocation()
|
||||
+ "\" title=\"URL\" /> ");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"" + PARAM_ACTION + "\" value=\"" + ACTION_ADD_OTHER + "\" /> ");
|
||||
out.write("</td></tr>\n");
|
||||
@@ -382,6 +485,7 @@ public class AddressesServlet extends BaseServlet {
|
||||
if (PROTO_ARCHIVE.equals(reqProto) ||
|
||||
PROTO_BLOG.equals(reqProto) ||
|
||||
PROTO_EEPSITE.equals(reqProto) ||
|
||||
PROTO_TAG.equals(reqProto) ||
|
||||
PROTO_I2PHEX.equals(reqProto))
|
||||
return false;
|
||||
else // its something other than the four default types
|
||||
|
||||
@@ -196,13 +196,20 @@ public class ArchiveServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
private void dump(File source, HttpServletResponse resp) throws ServletException, IOException {
|
||||
FileInputStream in = new FileInputStream(source);
|
||||
OutputStream out = resp.getOutputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.close();
|
||||
in.close();
|
||||
FileInputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new FileInputStream(source);
|
||||
out = resp.getOutputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.close();
|
||||
in.close();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import net.i2p.data.*;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
import net.i2p.util.FileUtil;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
@@ -63,19 +64,21 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
* key=value& of params that need to be tacked onto an http request that updates data, to
|
||||
* prevent spoofing
|
||||
*/
|
||||
protected String getAuthActionParams() { return PARAM_AUTH_ACTION + '=' + _authNonce + '&'; }
|
||||
protected static String getAuthActionParams() { return PARAM_AUTH_ACTION + '=' + _authNonce + '&'; }
|
||||
/**
|
||||
* key=value& of params that need to be tacked onto an http request that updates data, to
|
||||
* prevent spoofing
|
||||
*/
|
||||
protected void addAuthActionParams(StringBuffer buf) {
|
||||
public static void addAuthActionParams(StringBuffer buf) {
|
||||
buf.append(PARAM_AUTH_ACTION).append('=').append(_authNonce).append('&');
|
||||
}
|
||||
|
||||
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html");
|
||||
resp.setContentType("text/html;charset=UTF-8");
|
||||
resp.setHeader("cache-control", "no-cache");
|
||||
resp.setHeader("pragma", "no-cache");
|
||||
|
||||
User user = (User)req.getSession().getAttribute("user");
|
||||
String login = req.getParameter("login");
|
||||
@@ -136,6 +139,7 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
|
||||
forceNewIndex = handleAddressbook(user, req) || forceNewIndex;
|
||||
forceNewIndex = handleBookmarking(user, req) || forceNewIndex;
|
||||
forceNewIndex = handleManageTags(user, req) || forceNewIndex;
|
||||
handleUpdateProfile(user, req);
|
||||
}
|
||||
|
||||
@@ -148,17 +152,28 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
|
||||
FilteredThreadIndex index = (FilteredThreadIndex)req.getSession().getAttribute("threadIndex");
|
||||
|
||||
boolean authorOnly = Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue();
|
||||
if ( (index != null) && (authorOnly != index.getFilterAuthorsByRoot()) )
|
||||
forceNewIndex = true;
|
||||
|
||||
Collection tags = getFilteredTags(req);
|
||||
Collection filteredAuthors = getFilteredAuthors(req);
|
||||
boolean tagsChanged = ( (index != null) && (!index.getFilteredTags().equals(tags)) );
|
||||
boolean authorsChanged = ( (index != null) && (!index.getFilteredAuthors().equals(filteredAuthors)) );
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("authorOnly=" + authorOnly + " forceNewIndex? " + forceNewIndex + " authors=" + filteredAuthors);
|
||||
|
||||
if (forceNewIndex || (index == null) || (tagsChanged) || (authorsChanged) ) {
|
||||
index = new FilteredThreadIndex(user, BlogManager.instance().getArchive(), getFilteredTags(req), filteredAuthors);
|
||||
index = new FilteredThreadIndex(user, BlogManager.instance().getArchive(), getFilteredTags(req), filteredAuthors, authorOnly);
|
||||
req.getSession().setAttribute("threadIndex", index);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New filtered index created (forced? " + forceNewIndex + ", tagsChanged? " + tagsChanged + ", authorsChanged? " + authorsChanged + ")");
|
||||
}
|
||||
|
||||
render(user, req, resp, index);
|
||||
}
|
||||
protected void render(User user, HttpServletRequest req, HttpServletResponse resp, ThreadIndex index) throws IOException, ServletException {
|
||||
render(user, req, resp.getWriter(), index);
|
||||
}
|
||||
|
||||
@@ -237,6 +252,35 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
return rv;
|
||||
}
|
||||
|
||||
private boolean handleManageTags(User user, HttpServletRequest req) {
|
||||
if (!user.getAuthenticated())
|
||||
return false;
|
||||
|
||||
boolean rv = false;
|
||||
|
||||
String tag = req.getParameter(ThreadedHTMLRenderer.PARAM_ADD_TAG);
|
||||
if ( (tag != null) && (tag.trim().length() > 0) ) {
|
||||
tag = HTMLRenderer.sanitizeString(tag, false);
|
||||
String name = tag;
|
||||
PetNameDB db = user.getPetNameDB();
|
||||
PetName pn = db.getByLocation(tag);
|
||||
if (pn == null) {
|
||||
if (db.containsName(name)) {
|
||||
int i = 0;
|
||||
while (db.containsName(name + i))
|
||||
i++;
|
||||
name = tag + i;
|
||||
}
|
||||
|
||||
pn = new PetName(name, AddressesServlet.NET_SYNDIE, AddressesServlet.PROTO_TAG, tag);
|
||||
db.add(pn);
|
||||
BlogManager.instance().saveUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleAddressbook(User user, HttpServletRequest req) {
|
||||
if ( (!user.getAuthenticated()) || (empty(AddressesServlet.PARAM_ACTION)) ) {
|
||||
return false;
|
||||
@@ -244,7 +288,15 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
|
||||
String action = req.getParameter(AddressesServlet.PARAM_ACTION);
|
||||
|
||||
if ( (AddressesServlet.ACTION_ADD_ARCHIVE.equals(action)) ||
|
||||
if (AddressesServlet.ACTION_ADD_TAG.equals(action)) {
|
||||
String name = req.getParameter(AddressesServlet.PARAM_NAME);
|
||||
if (!user.getPetNameDB().containsName(name)) {
|
||||
PetName pn = new PetName(name, AddressesServlet.NET_SYNDIE, AddressesServlet.PROTO_TAG, name);
|
||||
user.getPetNameDB().add(pn);
|
||||
BlogManager.instance().saveUser(user);
|
||||
}
|
||||
return false;
|
||||
} else if ( (AddressesServlet.ACTION_ADD_ARCHIVE.equals(action)) ||
|
||||
(AddressesServlet.ACTION_ADD_BLOG.equals(action)) ||
|
||||
(AddressesServlet.ACTION_ADD_EEPSITE.equals(action)) ||
|
||||
(AddressesServlet.ACTION_ADD_OTHER.equals(action)) ||
|
||||
@@ -276,8 +328,10 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
(AddressesServlet.ACTION_DELETE_BLOG.equals(action)) ||
|
||||
(AddressesServlet.ACTION_DELETE_EEPSITE.equals(action)) ||
|
||||
(AddressesServlet.ACTION_DELETE_OTHER.equals(action)) ||
|
||||
(AddressesServlet.ACTION_DELETE_TAG.equals(action)) ||
|
||||
(AddressesServlet.ACTION_DELETE_PEER.equals(action)) ) {
|
||||
PetName pn = user.getPetNameDB().getByName(req.getParameter(AddressesServlet.PARAM_NAME));
|
||||
String name = req.getParameter(AddressesServlet.PARAM_NAME);
|
||||
PetName pn = user.getPetNameDB().getByName(name);
|
||||
if (pn != null) {
|
||||
user.getPetNameDB().remove(pn);
|
||||
BlogManager.instance().saveUser(user);
|
||||
@@ -414,7 +468,18 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
if ( (pass0 != null) && (pass1 != null) && (pass0.equals(pass1)) ) {
|
||||
BlogManager.instance().changePasswrd(user, oldPass, pass0, pass1);
|
||||
}
|
||||
|
||||
|
||||
if (user.getAuthenticated() && !BlogManager.instance().authorizeRemote(user)) {
|
||||
String adminPass = req.getParameter("adminPass");
|
||||
if (adminPass != null) {
|
||||
boolean authorized = BlogManager.instance().authorizeRemote(adminPass);
|
||||
if (authorized) {
|
||||
user.setAllowAccessRemote(authorized);
|
||||
BlogManager.instance().saveUser(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean updated = BlogManager.instance().updateMetadata(user, user.getBlog(), opts);
|
||||
}
|
||||
|
||||
@@ -488,24 +553,47 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
protected void renderBegin(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index) throws IOException {
|
||||
out.write("<html>\n<head><title>" + getTitle() + "</title>\n" + BEGIN_HTML);
|
||||
out.write("<html>\n<head><title>" + getTitle() + "</title>\n");
|
||||
out.write("<meta http-equiv=\"cache-control\" content=\"no-cache\" />");
|
||||
out.write("<meta http-equiv=\"pragma\" content=\"no-cache\" />");
|
||||
out.write("<style>");
|
||||
out.write(STYLE_HTML);
|
||||
Reader css = null;
|
||||
try {
|
||||
InputStream in = req.getSession().getServletContext().getResourceAsStream("/syndie.css");
|
||||
if (in != null) {
|
||||
css = new InputStreamReader(in, "UTF-8");
|
||||
char buf[] = new char[1024];
|
||||
int read = 0;
|
||||
while ( (read = css.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
if (css != null)
|
||||
css.close();
|
||||
}
|
||||
String content = FileUtil.readTextFile("./docs/syndie_standard.css", -1, true);
|
||||
if (content != null) out.write(content);
|
||||
out.write("</style>");
|
||||
out.write(BEGIN_HTML);
|
||||
}
|
||||
protected void renderNavBar(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index) throws IOException {
|
||||
//out.write("<tr class=\"topNav\"><td class=\"topNav_user\" colspan=\"2\" nowrap=\"true\">\n");
|
||||
out.write("<tr class=\"topNav\"><td colspan=\"3\" nowrap=\"true\"><span class=\"topNav_user\">\n");
|
||||
out.write("<!-- nav bar begin -->\n");
|
||||
out.write("<a href=\"threads.jsp\" title=\"Syndie home\">Threads</a> <a href=\"blogs.jsp\" title=\"Blog summary\">Blogs</a> ");
|
||||
if (user.getAuthenticated() && (user.getBlog() != null) ) {
|
||||
out.write("Logged in as <a href=\"" + getProfileLink(req, user.getBlog()) + "\" title=\"Edit your profile\">");
|
||||
out.write(user.getUsername());
|
||||
out.write("</a>\n");
|
||||
out.write("(<a href=\"switchuser.jsp\" title=\"Log in as another user\">switch</a>)\n");
|
||||
out.write("<a href=\"" + getPostURI() + "\" title=\"Post a new thread\">Post a new thread</a>\n");
|
||||
out.write("<a href=\"" + getPostURI() + "\" title=\"Post a new thread\">Post</a>\n");
|
||||
out.write("<a href=\"addresses.jsp\" title=\"View your addressbook\">Addressbook</a>\n");
|
||||
} else {
|
||||
out.write("<form action=\"" + req.getRequestURI() + "\" method=\"POST\">\n");
|
||||
writeAuthActionFields(out);
|
||||
out.write("Login: <input type=\"text\" name=\"login\" />\n");
|
||||
out.write("Password: <input type=\"password\" name=\"password\" />\n");
|
||||
out.write("Login: <input type=\"text\" name=\"login\" title=\"Login name for your Syndie account\" />\n");
|
||||
out.write("Password: <input type=\"password\" name=\"password\" title=\"Password to get into your Syndie account\" />\n");
|
||||
out.write("<input type=\"submit\" name=\"action\" value=\"Login\" /></form>\n");
|
||||
}
|
||||
//out.write("</td><td class=\"topNav_admin\">\n");
|
||||
@@ -531,10 +619,11 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
SKIP_TAGS.add("filter");
|
||||
// post and visible are skipped since we aren't good at filtering by tag when the offset will
|
||||
// skip around randomly. at least, not yet.
|
||||
SKIP_TAGS.add("visible");
|
||||
SKIP_TAGS.add(ThreadedHTMLRenderer.PARAM_VISIBLE);
|
||||
//SKIP_TAGS.add("post");
|
||||
//SKIP_TAGS.add("thread");
|
||||
SKIP_TAGS.add("offset"); // if we are adjusting the filter, ignore the previous offset
|
||||
SKIP_TAGS.add(ThreadedHTMLRenderer.PARAM_OFFSET); // if we are adjusting the filter, ignore the previous offset
|
||||
SKIP_TAGS.add(ThreadedHTMLRenderer.PARAM_DAYS_BACK);
|
||||
SKIP_TAGS.add("addLocation");
|
||||
SKIP_TAGS.add("addGroup");
|
||||
SKIP_TAGS.add("login");
|
||||
@@ -575,6 +664,12 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
PetNameDB db = user.getPetNameDB();
|
||||
TreeSet names = new TreeSet(db.getNames());
|
||||
out.write("<option value=\"\">Any authors</option>\n");
|
||||
if (user.getAuthenticated()) {
|
||||
if ("favorites".equals(author))
|
||||
out.write("<option selected=\"true\" value=\"favorites\">All recent posts by favorite authors</option>\n");
|
||||
else
|
||||
out.write("<option value=\"favorites\">All recent posts by favorite authors</option>\n");
|
||||
}
|
||||
if (user.getBlog() != null) {
|
||||
if ( (author != null) && (author.equals(user.getBlog().toBase64())) )
|
||||
out.write("<option value=\"" + user.getBlog().toBase64() + "\" selected=\"true\">Threads you posted in</option>\n");
|
||||
@@ -585,7 +680,7 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
String name = (String) iter.next();
|
||||
PetName pn = db.getByName(name);
|
||||
if ("syndieblog".equals(pn.getProtocol())) {
|
||||
if ("syndieblog".equals(pn.getProtocol()) && pn.isMember(FilteredThreadIndex.GROUP_FAVORITE)) {
|
||||
if ( (author != null) && (author.equals(pn.getLocation())) )
|
||||
out.write("<option value=\"" + pn.getLocation() + "\" selected=\"true\">Threads " + name + " posted in</option>\n");
|
||||
else
|
||||
@@ -594,14 +689,74 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
}
|
||||
out.write("</select>\n");
|
||||
|
||||
out.write("Tags: <input type=\"text\" name=\"" + ThreadedHTMLRenderer.PARAM_TAGS + "\" size=\"10\" value=\"" + tags + "\" />\n");
|
||||
out.write("Tags: ");
|
||||
writeTagField(user, tags, out);
|
||||
|
||||
String days = req.getParameter(ThreadedHTMLRenderer.PARAM_DAYS_BACK);
|
||||
if (days == null)
|
||||
days = "";
|
||||
out.write("Age: <input type=\"text\" name=\"" + ThreadedHTMLRenderer.PARAM_DAYS_BACK + "\" size=\"2\" value=\"" + days
|
||||
+ "\" title=\"Posts are filtered to include only ones which were made within this many days ago\" /> days\n");
|
||||
|
||||
out.write("<input type=\"submit\" name=\"action\" value=\"Go\" />\n");
|
||||
out.write("</td><td class=\"controlBarRight\" width=\"1%\"><a href=\"#threads\" title=\"Jump to the thread navigation\">Threads</a></td>\n");
|
||||
out.write("</td><td class=\"controlBarRight\" width=\"1%\">");
|
||||
|
||||
if ( (req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_POST) != null) ||
|
||||
(req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_THREAD) != null) )
|
||||
out.write("<a href=\"#threads\" title=\"Jump to the thread navigation\">Threads</a>");
|
||||
out.write("</td>\n");
|
||||
out.write("<!-- control bar end -->\n");
|
||||
out.write("</tr>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
protected void writeTagField(User user, String selectedTags, PrintWriter out) throws IOException {
|
||||
writeTagField(user, selectedTags, out, "Threads are filtered to include only ones with posts containing these tags", "Any tags - no filtering", true);
|
||||
}
|
||||
public static void writeTagField(User user, String selectedTags, Writer out, String title, String blankTitle, boolean includeFavoritesTag) throws IOException {
|
||||
Set favoriteTags = new TreeSet(user.getFavoriteTags());
|
||||
if (favoriteTags.size() <= 0) {
|
||||
out.write("<input type=\"text\" name=\"" + ThreadedHTMLRenderer.PARAM_TAGS + "\" size=\"10\" value=\"" + selectedTags
|
||||
+ "\" title=\"" + title + "\" />\n");
|
||||
} else {
|
||||
out.write("<select name=\"" + ThreadedHTMLRenderer.PARAM_TAGS
|
||||
+ "\" title=\"" + title + "\">");
|
||||
out.write("<option value=\"\">" + blankTitle + "</option>\n");
|
||||
boolean matchFound = false;
|
||||
if (includeFavoritesTag) {
|
||||
out.write("<option value=\"");
|
||||
StringBuffer combinedBuf = new StringBuffer();
|
||||
for (Iterator iter = favoriteTags.iterator(); iter.hasNext(); ) {
|
||||
String curFavTag = (String)iter.next();
|
||||
combinedBuf.append(HTMLRenderer.sanitizeTagParam(curFavTag)).append(" ");
|
||||
}
|
||||
String combined = combinedBuf.toString();
|
||||
if (selectedTags.equals(combined)) {
|
||||
out.write(combined + "\" selected=\"true\" >All favorite tags</option>\n");
|
||||
matchFound = true;
|
||||
} else {
|
||||
out.write(combined + "\" >All favorite tags</option>\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (Iterator iter = favoriteTags.iterator(); iter.hasNext(); ) {
|
||||
String curFavTag = (String)iter.next();
|
||||
if (selectedTags.equals(curFavTag)) {
|
||||
out.write("<option value=\"" + HTMLRenderer.sanitizeTagParam(curFavTag) + "\" selected=\"true\" >"
|
||||
+ HTMLRenderer.sanitizeString(curFavTag) + "</option>\n");
|
||||
matchFound = true;
|
||||
} else {
|
||||
out.write("<option value=\"" + HTMLRenderer.sanitizeTagParam(curFavTag) + "\">"
|
||||
+ HTMLRenderer.sanitizeString(curFavTag) + "</option>\n");
|
||||
}
|
||||
}
|
||||
if ( (!matchFound) && (selectedTags != null) && (selectedTags.trim().length() > 0) )
|
||||
out.write("<option value=\"" + HTMLRenderer.sanitizeTagParam(selectedTags)
|
||||
+ "\" selected=\"true\">" + HTMLRenderer.sanitizeString(selectedTags) + "</option>\n");
|
||||
|
||||
out.write("</select>\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void renderServletDetails(User user, HttpServletRequest req, PrintWriter out,
|
||||
ThreadIndex index, int threadOffset, BlogURI visibleEntry,
|
||||
@@ -798,28 +953,41 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
return ThreadedHTMLRenderer.getViewPostLink(req.getRequestURI(), node, user, isPermalink,
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_OFFSET),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR));
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR),
|
||||
Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue());
|
||||
}
|
||||
protected String getViewPostLink(HttpServletRequest req, BlogURI post, User user) {
|
||||
return ThreadedHTMLRenderer.getViewPostLink(req.getRequestURI(), post, user, false,
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_OFFSET),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR),
|
||||
Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue());
|
||||
}
|
||||
protected String getViewThreadLink(HttpServletRequest req, ThreadNode node, User user) {
|
||||
return getViewThreadLink(req.getRequestURI(), node, user,
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_OFFSET),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR));
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR),
|
||||
Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue());
|
||||
}
|
||||
protected static String getViewThreadLink(String uri, ThreadNode node, User user, String offset,
|
||||
String tags, String author) {
|
||||
String tags, String author, boolean authorOnly) {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(uri);
|
||||
BlogURI expandTo = node.getEntry();
|
||||
if (node.getChildCount() > 0) {
|
||||
buf.append('?').append(ThreadedHTMLRenderer.PARAM_VISIBLE).append('=');
|
||||
ThreadNode child = node.getChild(0);
|
||||
buf.append(child.getEntry().getKeyHash().toBase64()).append('/');
|
||||
buf.append(child.getEntry().getEntryId()).append('&');
|
||||
} else {
|
||||
buf.append('?').append(ThreadedHTMLRenderer.PARAM_VISIBLE).append('=');
|
||||
buf.append(node.getEntry().getKeyHash().toBase64()).append('/');
|
||||
buf.append(node.getEntry().getEntryId()).append('&');
|
||||
}
|
||||
if (true) {
|
||||
// lets expand to the leaf
|
||||
expandTo = new BlogURI(node.getMostRecentPostAuthor(), node.getMostRecentPostDate());
|
||||
} else {
|
||||
// only expand one level
|
||||
expandTo = node.getChild(0).getEntry();
|
||||
}
|
||||
}
|
||||
buf.append('?').append(ThreadedHTMLRenderer.PARAM_VISIBLE).append('=');
|
||||
buf.append(expandTo.getKeyHash().toBase64()).append('/');
|
||||
buf.append(expandTo.getEntryId()).append('&');
|
||||
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_VIEW_THREAD).append('=');
|
||||
buf.append(node.getEntry().getKeyHash().toBase64()).append('/');
|
||||
buf.append(node.getEntry().getEntryId()).append('&');
|
||||
@@ -830,8 +998,11 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
if (!empty(tags))
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_TAGS).append('=').append(tags).append('&');
|
||||
|
||||
if (!empty(author))
|
||||
if (!empty(author)) {
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_AUTHOR).append('=').append(author).append('&');
|
||||
if (authorOnly)
|
||||
buf.append(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR).append("=true&");
|
||||
}
|
||||
|
||||
buf.append("#").append(node.getEntry().toString());
|
||||
return buf.toString();
|
||||
@@ -845,6 +1016,7 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_THREAD),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS),
|
||||
req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR),
|
||||
Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue(),
|
||||
offset);
|
||||
}
|
||||
|
||||
@@ -869,7 +1041,13 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
protected Collection getFilteredAuthors(HttpServletRequest req) {
|
||||
String authors = req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR);
|
||||
List rv = new ArrayList();
|
||||
rv.addAll(getAuthors(req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR)));
|
||||
//rv.addAll(getAuthors(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)));
|
||||
return rv;
|
||||
}
|
||||
|
||||
private Collection getAuthors(String authors) {
|
||||
if (authors != null) {
|
||||
StringTokenizer tok = new StringTokenizer(authors, "\n\t ");
|
||||
ArrayList rv = new ArrayList();
|
||||
@@ -886,8 +1064,13 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String BEGIN_HTML = "<style>\n" +
|
||||
".overallTable {\n" +
|
||||
private static final String BEGIN_HTML = "<link href=\"rss.jsp\" rel=\"alternate\" type=\"application/rss+xml\" >\n" +
|
||||
"</head>\n" +
|
||||
"<body>\n" +
|
||||
"<span style=\"display: none\"><a href=\"#bodySubject\">Jump to the beginning of the first post rendered, if any</a>\n" +
|
||||
"<a href=\"#threads\">Jump to the thread navigation</a>\n</span>\n" +
|
||||
"<table border=\"0\" width=\"100%\" class=\"overallTable\">\n";
|
||||
private static final String STYLE_HTML = ".overallTable {\n" +
|
||||
" border-spacing: 0px;\n" +
|
||||
" border-width: 0px;\n" +
|
||||
" border: 0px;\n" +
|
||||
@@ -926,15 +1109,29 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
" text-align: left;\n" +
|
||||
" align: left;\n" +
|
||||
"}\n" +
|
||||
".threadRight {\n" +
|
||||
" text-align: right;\n" +
|
||||
"}\n" +
|
||||
".threadNav {\n" +
|
||||
" background-color: #BBBBBB;\n" +
|
||||
"}\n" +
|
||||
".threadNavRight {\n" +
|
||||
" text-align: right;\n" +
|
||||
" float: right;\n" +
|
||||
" background-color: #BBBBBB;\n" +
|
||||
"}\n" +
|
||||
".rightOffset {\n" +
|
||||
" float: right;\n" +
|
||||
" margin: 0 5px 0 0;\n" +
|
||||
" display: inline;\n" +
|
||||
"}\n" +
|
||||
".threadInfoLeft {\n" +
|
||||
" float: left;\n" +
|
||||
" margin: 5px 0px 0 0;\n" +
|
||||
" display: inline;\n" +
|
||||
"}\n" +
|
||||
".threadInfoRight {\n" +
|
||||
" float: right;\n" +
|
||||
" margin: 0 5px 0 0;\n" +
|
||||
" display: inline;\n" +
|
||||
"}\n" +
|
||||
".postMeta {\n" +
|
||||
" background-color: #BBBBFF;\n" +
|
||||
"}\n" +
|
||||
@@ -956,14 +1153,17 @@ public abstract class BaseServlet extends HttpServlet {
|
||||
".postReplyOptions {\n" +
|
||||
" background-color: #BBBBFF;\n" +
|
||||
"}\n" +
|
||||
"</style>\n" +
|
||||
"<link href=\"style.jsp\" rel=\"stylesheet\" type=\"text/css\" >\n" +
|
||||
"<link href=\"rss.jsp\" rel=\"alternate\" type=\"application/rss+xml\" >\n" +
|
||||
"</head>\n" +
|
||||
"<body>\n" +
|
||||
"<span style=\"display: none\"><a href=\"#bodySubject\">Jump to the beginning of the first post rendered, if any</a>\n" +
|
||||
"<a href=\"#threads\">Jump to the thread navigation</a>\n</span>\n" +
|
||||
"<table border=\"0\" width=\"100%\" class=\"overallTable\">\n";
|
||||
".syndieBlogFavorites {\n" +
|
||||
" float: left;\n" +
|
||||
" margin: 5px 0px 0 0;\n" +
|
||||
" display: inline;\n" +
|
||||
"}\n" +
|
||||
".syndieBlogList {\n" +
|
||||
" float: right;\n" +
|
||||
" margin: 5px 0px 0 0;\n" +
|
||||
" display: inline;\n" +
|
||||
"}\n";
|
||||
|
||||
|
||||
private static final String END_HTML = "</table>\n" +
|
||||
"</body>\n";
|
||||
|
||||
@@ -136,10 +136,15 @@ public class ExportServlet extends HttpServlet {
|
||||
ze = new ZipEntry("meta" + i);
|
||||
ze.setTime(0);
|
||||
zo.putNextEntry(ze);
|
||||
FileInputStream in = new FileInputStream((File)metaFiles.get(i));
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
zo.write(buf, 0, read);
|
||||
zo.closeEntry();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream((File)metaFiles.get(i));
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
zo.write(buf, 0, read);
|
||||
zo.closeEntry();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
List entryFiles = getEntryFiles(entries);
|
||||
@@ -147,10 +152,15 @@ public class ExportServlet extends HttpServlet {
|
||||
ze = new ZipEntry("entry" + i);
|
||||
ze.setTime(0);
|
||||
zo.putNextEntry(ze);
|
||||
FileInputStream in = new FileInputStream((File)entryFiles.get(i));
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
zo.write(buf, 0, read);
|
||||
zo.closeEntry();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream((File)entryFiles.get(i));
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
zo.write(buf, 0, read);
|
||||
zo.closeEntry();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (zo != null) {
|
||||
|
||||
424
apps/syndie/java/src/net/i2p/syndie/web/MultiPartRequest.java
Normal file
424
apps/syndie/java/src/net/i2p/syndie/web/MultiPartRequest.java
Normal file
@@ -0,0 +1,424 @@
|
||||
// see below for license info
|
||||
package net.i2p.syndie.web;
|
||||
|
||||
import org.mortbay.servlet.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.mortbay.http.HttpFields;
|
||||
import org.mortbay.util.LineInput;
|
||||
import org.mortbay.util.MultiMap;
|
||||
import org.mortbay.util.StringUtil;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Hacked version of Jetty's MultiPartRequest handler, applying a tiny patch for
|
||||
* charset handling [1]. These changes are public domain, and will hopefully
|
||||
* be integrated into Jetty so we can drop this file altogether. Of course,
|
||||
* until then, this file is APL2 licensed.
|
||||
*
|
||||
* Original code is up at [2]
|
||||
*
|
||||
* [1] http://article.gmane.org/gmane.comp.java.jetty.general/6031
|
||||
* [2] http://cvs.sourceforge.net/viewcvs.py/jetty/Jetty/src/org/mortbay/servlet/
|
||||
* (rev 1.15)
|
||||
*
|
||||
*/
|
||||
public class MultiPartRequest
|
||||
{
|
||||
/* ------------------------------------------------------------ */
|
||||
HttpServletRequest _request;
|
||||
LineInput _in;
|
||||
String _boundary;
|
||||
String _encoding;
|
||||
byte[] _byteBoundary;
|
||||
MultiMap _partMap = new MultiMap(10);
|
||||
int _char=-2;
|
||||
boolean _lastPart=false;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Constructor.
|
||||
* @param request The request containing a multipart/form-data
|
||||
* request
|
||||
* @exception IOException IOException
|
||||
*/
|
||||
public MultiPartRequest(HttpServletRequest request)
|
||||
throws IOException
|
||||
{
|
||||
_request=request;
|
||||
String content_type = request.getHeader(HttpFields.__ContentType);
|
||||
if (!content_type.startsWith("multipart/form-data"))
|
||||
throw new IOException("Not multipart/form-data request");
|
||||
|
||||
//if(log.isDebugEnabled())log.debug("Multipart content type = "+content_type);
|
||||
|
||||
_encoding = request.getCharacterEncoding();
|
||||
if (_encoding != null)
|
||||
_in = new LineInput(request.getInputStream(), 2048, _encoding);
|
||||
else
|
||||
_in = new LineInput(request.getInputStream());
|
||||
|
||||
// Extract boundary string
|
||||
_boundary="--"+
|
||||
value(content_type.substring(content_type.indexOf("boundary=")));
|
||||
|
||||
//if(log.isDebugEnabled())log.debug("Boundary="+_boundary);
|
||||
_byteBoundary= (_boundary+"--").getBytes(StringUtil.__ISO_8859_1);
|
||||
|
||||
loadAllParts();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the part names.
|
||||
* @return an array of part names
|
||||
*/
|
||||
public String[] getPartNames()
|
||||
{
|
||||
Set s = _partMap.keySet();
|
||||
return (String[]) s.toArray(new String[s.size()]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Check if a named part is present
|
||||
* @param name The part
|
||||
* @return true if it was included
|
||||
*/
|
||||
public boolean contains(String name)
|
||||
{
|
||||
Part part = (Part)_partMap.get(name);
|
||||
return (part!=null);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the data of a part as a string.
|
||||
* @param name The part name
|
||||
* @return The part data
|
||||
*/
|
||||
public String getString(String name)
|
||||
{
|
||||
List part = (List)_partMap.getValues(name);
|
||||
if (part==null)
|
||||
return null;
|
||||
if (_encoding != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new String(((Part)part.get(0))._data, _encoding);
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
//if (log.isDebugEnabled()) log.debug("Invalid character set: " + uee);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new String(((Part)part.get(0))._data);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param name The part name
|
||||
* @return The parts data
|
||||
*/
|
||||
public String[] getStrings(String name)
|
||||
{
|
||||
List parts = (List)_partMap.getValues(name);
|
||||
if (parts==null)
|
||||
return null;
|
||||
String[] strings = new String[parts.size()];
|
||||
if (_encoding == null) {
|
||||
for (int i=0; i<strings.length; i++) {
|
||||
strings[i] = new String(((Part)parts.get(i))._data);
|
||||
}
|
||||
} else {
|
||||
try
|
||||
{
|
||||
for (int i=0; i<strings.length; i++)
|
||||
strings[i] = new String(((Part)parts.get(i))._data, _encoding);
|
||||
}
|
||||
catch (UnsupportedEncodingException uee)
|
||||
{
|
||||
//if (log.isDebugEnabled()) log.debug("Invalid character set: " + uee);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the data of a part as a stream.
|
||||
* @param name The part name
|
||||
* @return Stream providing the part data
|
||||
*/
|
||||
public InputStream getInputStream(String name)
|
||||
{
|
||||
List part = (List)_partMap.getValues(name);
|
||||
if (part==null)
|
||||
return null;
|
||||
return new ByteArrayInputStream(((Part)part.get(0))._data);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public InputStream[] getInputStreams(String name)
|
||||
{
|
||||
List parts = (List)_partMap.getValues(name);
|
||||
if (parts==null)
|
||||
return null;
|
||||
InputStream[] streams = new InputStream[parts.size()];
|
||||
for (int i=0; i<streams.length; i++) {
|
||||
streams[i] = new ByteArrayInputStream(((Part)parts.get(i))._data);
|
||||
}
|
||||
return streams;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the MIME parameters associated with a part.
|
||||
* @param name The part name
|
||||
* @return Hashtable of parameters
|
||||
*/
|
||||
public Hashtable getParams(String name)
|
||||
{
|
||||
List part = (List)_partMap.getValues(name);
|
||||
if (part==null)
|
||||
return null;
|
||||
return ((Part)part.get(0))._headers;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Hashtable[] getMultipleParams(String name)
|
||||
{
|
||||
List parts = (List)_partMap.getValues(name);
|
||||
if (parts==null)
|
||||
return null;
|
||||
Hashtable[] params = new Hashtable[parts.size()];
|
||||
for (int i=0; i<params.length; i++) {
|
||||
params[i] = ((Part)parts.get(i))._headers;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get any file name associated with a part.
|
||||
* @param name The part name
|
||||
* @return The filename
|
||||
*/
|
||||
public String getFilename(String name)
|
||||
{
|
||||
List part = (List)_partMap.getValues(name);
|
||||
if (part==null)
|
||||
return null;
|
||||
return ((Part)part.get(0))._filename;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String[] getFilenames(String name)
|
||||
{
|
||||
List parts = (List)_partMap.getValues(name);
|
||||
if (parts==null)
|
||||
return null;
|
||||
String[] filenames = new String[parts.size()];
|
||||
for (int i=0; i<filenames.length; i++) {
|
||||
filenames[i] = ((Part)parts.get(i))._filename;
|
||||
}
|
||||
return filenames;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private void loadAllParts()
|
||||
throws IOException
|
||||
{
|
||||
// Get first boundary
|
||||
String line = _in.readLine();
|
||||
if (!line.equals(_boundary))
|
||||
{
|
||||
//log.warn(line);
|
||||
throw new IOException("Missing initial multi part boundary");
|
||||
}
|
||||
|
||||
// Read each part
|
||||
while (!_lastPart)
|
||||
{
|
||||
// Read Part headers
|
||||
Part part = new Part();
|
||||
|
||||
String content_disposition=null;
|
||||
while ((line=_in.readLine())!=null)
|
||||
{
|
||||
// If blank line, end of part headers
|
||||
if (line.length()==0)
|
||||
break;
|
||||
|
||||
//if(log.isDebugEnabled())log.debug("LINE="+line);
|
||||
|
||||
// place part header key and value in map
|
||||
int c = line.indexOf(':',0);
|
||||
if (c>0)
|
||||
{
|
||||
String key = line.substring(0,c).trim().toLowerCase();
|
||||
String value = line.substring(c+1,line.length()).trim();
|
||||
String ev = (String) part._headers.get(key);
|
||||
part._headers.put(key,(ev!=null)?(ev+';'+value):value);
|
||||
//if(log.isDebugEnabled())log.debug(key+": "+value);
|
||||
if (key.equals("content-disposition"))
|
||||
content_disposition=value;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract content-disposition
|
||||
boolean form_data=false;
|
||||
if (content_disposition==null)
|
||||
{
|
||||
throw new IOException("Missing content-disposition");
|
||||
}
|
||||
|
||||
StringTokenizer tok =
|
||||
new StringTokenizer(content_disposition,";");
|
||||
while (tok.hasMoreTokens())
|
||||
{
|
||||
String t = tok.nextToken().trim();
|
||||
String tl = t.toLowerCase();
|
||||
if (t.startsWith("form-data"))
|
||||
form_data=true;
|
||||
else if (tl.startsWith("name="))
|
||||
part._name=value(t);
|
||||
else if (tl.startsWith("filename="))
|
||||
part._filename=value(t);
|
||||
}
|
||||
|
||||
// Check disposition
|
||||
if (!form_data)
|
||||
{
|
||||
//log.warn("Non form-data part in multipart/form-data");
|
||||
continue;
|
||||
}
|
||||
if (part._name==null || part._name.length()==0)
|
||||
{
|
||||
//log.warn("Part with no name in multipart/form-data");
|
||||
continue;
|
||||
}
|
||||
//if(log.isDebugEnabled())log.debug("name="+part._name);
|
||||
//if(log.isDebugEnabled())log.debug("filename="+part._filename);
|
||||
_partMap.add(part._name,part);
|
||||
part._data=readBytes();
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private byte[] readBytes()
|
||||
throws IOException
|
||||
{
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
int c;
|
||||
boolean cr=false;
|
||||
boolean lf=false;
|
||||
|
||||
// loop for all lines`
|
||||
while (true)
|
||||
{
|
||||
int b=0;
|
||||
while ((c=(_char!=-2)?_char:_in.read())!=-1)
|
||||
{
|
||||
_char=-2;
|
||||
|
||||
// look for CR and/or LF
|
||||
if (c==13 || c==10)
|
||||
{
|
||||
if (c==13) _char=_in.read();
|
||||
break;
|
||||
}
|
||||
|
||||
// look for boundary
|
||||
if (b>=0 && b<_byteBoundary.length && c==_byteBoundary[b])
|
||||
b++;
|
||||
else
|
||||
{
|
||||
// this is not a boundary
|
||||
if (cr) baos.write(13);
|
||||
if (lf) baos.write(10);
|
||||
cr=lf=false;
|
||||
|
||||
if (b>0)
|
||||
baos.write(_byteBoundary,0,b);
|
||||
b=-1;
|
||||
|
||||
baos.write(c);
|
||||
}
|
||||
}
|
||||
|
||||
// check partial boundary
|
||||
if ((b>0 && b<_byteBoundary.length-2) ||
|
||||
(b==_byteBoundary.length-1))
|
||||
{
|
||||
if (cr) baos.write(13);
|
||||
if (lf) baos.write(10);
|
||||
cr=lf=false;
|
||||
baos.write(_byteBoundary,0,b);
|
||||
b=-1;
|
||||
}
|
||||
|
||||
// boundary match
|
||||
if (b>0 || c==-1)
|
||||
{
|
||||
if (b==_byteBoundary.length)
|
||||
_lastPart=true;
|
||||
if (_char==10) _char=-2;
|
||||
break;
|
||||
}
|
||||
|
||||
// handle CR LF
|
||||
if (cr) baos.write(13);
|
||||
if (lf) baos.write(10);
|
||||
cr=(c==13);
|
||||
lf=(c==10 || _char==10);
|
||||
if (_char==10) _char=-2;
|
||||
}
|
||||
//if(log.isTraceEnabled())log.trace(baos.toString());
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private String value(String nameEqualsValue)
|
||||
{
|
||||
String value =
|
||||
nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
|
||||
|
||||
int i=value.indexOf(';');
|
||||
if (i>0)
|
||||
value=value.substring(0,i);
|
||||
if (value.startsWith("\""))
|
||||
{
|
||||
value=value.substring(1,value.indexOf('"',1));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
i=value.indexOf(' ');
|
||||
if (i>0)
|
||||
value=value.substring(0,i);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private class Part
|
||||
{
|
||||
String _name=null;
|
||||
String _filename=null;
|
||||
Hashtable _headers= new Hashtable(10);
|
||||
byte[] _data=null;
|
||||
}
|
||||
};
|
||||
@@ -74,12 +74,17 @@ public class PostBean {
|
||||
}
|
||||
|
||||
public void writeAttachmentData(int id, OutputStream out) throws IOException {
|
||||
FileInputStream in = new FileInputStream((File)_localFiles.get(id));
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.close();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream((File)_localFiles.get(id));
|
||||
byte buf[] = new byte[1024];
|
||||
int read = 0;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.close();
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void addAttachment(String filename, InputStream fileStream, String mimeType) {
|
||||
@@ -167,6 +172,7 @@ public class PostBean {
|
||||
private static final int MAX_SIZE = 256*1024;
|
||||
|
||||
private void cacheAttachments() throws IOException {
|
||||
if (_user == null) throw new IOException("User not specified");
|
||||
File postCacheDir = new File(BlogManager.instance().getTempDir(), _user.getBlog().toBase64());
|
||||
if (!postCacheDir.exists())
|
||||
postCacheDir.mkdirs();
|
||||
|
||||
@@ -8,7 +8,8 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.mortbay.servlet.MultiPartRequest;
|
||||
// temporarily, we use our overwride, until jetty applies our patches
|
||||
//import org.mortbay.servlet.MultiPartRequest;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.*;
|
||||
@@ -26,7 +27,7 @@ public class PostServlet extends BaseServlet {
|
||||
public static final String ACTION_CONFIRM = "confirm";
|
||||
|
||||
public static final String PARAM_SUBJECT = "entrysubject";
|
||||
public static final String PARAM_TAGS = "entrytags";
|
||||
public static final String PARAM_TAGS = ThreadedHTMLRenderer.PARAM_TAGS;
|
||||
public static final String PARAM_INCLUDENAMES = "includenames";
|
||||
public static final String PARAM_TEXT = "entrytext";
|
||||
public static final String PARAM_HEADERS = "entryheaders";
|
||||
@@ -48,6 +49,8 @@ public class PostServlet extends BaseServlet {
|
||||
String action = req.getParameter(PARAM_ACTION);
|
||||
if (!empty(action) && ACTION_CONFIRM.equals(action)) {
|
||||
postEntry(user, req, archive, post, out);
|
||||
post.reinitialize();
|
||||
post.setUser(user);
|
||||
} else {
|
||||
String contentType = req.getContentType();
|
||||
if (!empty(contentType) && (contentType.indexOf("boundary=") != -1)) {
|
||||
@@ -72,8 +75,8 @@ public class PostServlet extends BaseServlet {
|
||||
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
|
||||
post.reinitialize();
|
||||
post.setUser(user);
|
||||
//post.reinitialize();
|
||||
//post.setUser(user);
|
||||
|
||||
boolean inNewThread = getInNewThread(req.getString(PARAM_IN_NEW_THREAD));
|
||||
boolean refuseReplies = getRefuseReplies(req.getString(PARAM_REFUSE_REPLIES));
|
||||
@@ -101,6 +104,8 @@ public class PostServlet extends BaseServlet {
|
||||
}
|
||||
}
|
||||
|
||||
if (entryTags == null) entryTags = "";
|
||||
|
||||
if ( (entryHeaders == null) || (entryHeaders.trim().length() <= 0) )
|
||||
entryHeaders = ThreadedHTMLRenderer.HEADER_FORCE_NEW_THREAD + ": " + inNewThread + '\n' +
|
||||
ThreadedHTMLRenderer.HEADER_REFUSE_REPLIES + ": " + refuseReplies;
|
||||
@@ -148,9 +153,7 @@ public class PostServlet extends BaseServlet {
|
||||
writeAuthActionFields(out);
|
||||
out.write("Please confirm that the above is ok");
|
||||
if (BlogManager.instance().authorizeRemote(user)) {
|
||||
out.write(", and select what additional archives you want the post transmitted to. ");
|
||||
out.write("To make changes, hit your browser's back arrow and try again.\n");
|
||||
out.write("Remote archive to push this post to: ");
|
||||
out.write(", and select what additional archive you want the post transmitted to: ");
|
||||
out.write("<select class=\"b_postConfirm\" name=\"" + PARAM_REMOTE_ARCHIVE + "\">\n");
|
||||
PetNameDB db = user.getPetNameDB();
|
||||
TreeSet names = new TreeSet();
|
||||
@@ -174,6 +177,10 @@ public class PostServlet extends BaseServlet {
|
||||
out.write("</span><input class=\"b_postConfirm\" type=\"submit\" name=\"" + PARAM_ACTION
|
||||
+ "\" value=\"" + ACTION_CONFIRM + "\" />\n");
|
||||
|
||||
out.write("</form>\n");
|
||||
|
||||
displayEditForm(user, req, post, out);
|
||||
|
||||
out.write("</td></tr>\n");
|
||||
}
|
||||
|
||||
@@ -200,37 +207,42 @@ public class PostServlet extends BaseServlet {
|
||||
post.reinitialize();
|
||||
post.setUser(user);
|
||||
|
||||
String parentURI = req.getParameter(PARAM_PARENT);
|
||||
|
||||
String subject = getParam(req, PARAM_SUBJECT);
|
||||
|
||||
out.write("<form action=\"" + getPostURI() + "\" method=\"POST\" enctype=\"multipart/form-data\">\n");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<tr><td colspan=\"3\">\n");
|
||||
out.write("<span class=\"b_postField\">Post subject:</span> ");
|
||||
out.write("<input type=\"text\" class=\"b_postSubject\" size=\"80\" name=\"" + PARAM_SUBJECT
|
||||
+ "\" value=\"" + getParam(req,PARAM_SUBJECT) + "\" /><br />\n");
|
||||
out.write("<span class=\"b_postField\">Post tags:</span> ");
|
||||
out.write("<input type=\"text\" class=\"b_postTags\" size=\"20\" name=\"" + PARAM_TAGS
|
||||
+ "\" value=\"" + getParam(req, PARAM_TAGS) + "\" /><br />\n");
|
||||
out.write("<span class=\"b_postField\">Include public names?</span> ");
|
||||
out.write("<input class=\"b_postNames\" type=\"checkbox\" name=\"" + PARAM_INCLUDENAMES
|
||||
+ "\" value=\"true\" /><br />\n");
|
||||
+ "\" value=\"" + HTMLRenderer.sanitizeTagParam(subject) + "\" title=\"One line summary\" /><br />\n");
|
||||
out.write("<span class=\"b_postField\">Post content (in raw <a href=\"smlref.jsp\" target=\"_blank\">SML</a>, no headers):</span><br />\n");
|
||||
out.write("<textarea class=\"b_postText\" rows=\"6\" cols=\"80\" name=\"" + PARAM_TEXT + "\">" + getParam(req, PARAM_TEXT) + "</textarea><br />\n");
|
||||
out.write("<span class=\"b_postField\">SML post headers:</span><br />\n");
|
||||
out.write("<textarea class=\"b_postHeaders\" rows=\"3\" cols=\"80\" name=\"" + PARAM_HEADERS + "\">" + getParam(req, PARAM_HEADERS) + "</textarea><br />\n");
|
||||
out.write("<textarea class=\"b_postHeaders\" rows=\"2\" cols=\"80\" name=\"" + PARAM_HEADERS + "\" title=\"Most people can leave this empty\" >" + getParam(req, PARAM_HEADERS) + "</textarea><br />\n");
|
||||
|
||||
|
||||
String parentURI = req.getParameter(PARAM_PARENT);
|
||||
if ( (parentURI != null) && (parentURI.trim().length() > 0) )
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PARENT + "\" value=\"" + parentURI + "\" />\n");
|
||||
|
||||
out.write(" Tags: <input type=\"text\" size=\"10\" name=\"" + PARAM_TAGS + "\" value=\"" + getParam(req, PARAM_TAGS) + "\" />\n");
|
||||
out.write(" Tags: ");
|
||||
BaseServlet.writeTagField(user, getParam(req, PARAM_TAGS), out, "Optional tags to categorize your post", "No tags", false);
|
||||
//<input type=\"text\" size=\"10\" name=\"" + PARAM_TAGS + "\" value=\"" + getParam(req, PARAM_TAGS) + "\" title=\"Optional tags to categorize your response\" /><br />\n");
|
||||
out.write("<br />\n");
|
||||
|
||||
boolean inNewThread = getInNewThread(req);
|
||||
boolean refuseReplies = getRefuseReplies(req);
|
||||
|
||||
out.write(" in a new thread? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_IN_NEW_THREAD +
|
||||
(inNewThread ? "\" checked=\"true\" " : "\" " ) + " />\n");
|
||||
out.write(" refuse replies? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_REFUSE_REPLIES +
|
||||
(refuseReplies ? "\" checked=\"true\" " : "\" " ) + " />\n");
|
||||
out.write("In a new thread? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_IN_NEW_THREAD +
|
||||
(inNewThread ? "\" checked=\"true\" " : "\" " )
|
||||
+ " title=\"If true, this will fork a new top level thread\" /><br />\n");
|
||||
out.write("Refuse replies? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_REFUSE_REPLIES +
|
||||
(refuseReplies ? "\" checked=\"true\" " : "\" " )
|
||||
+ " title=\"If true, only you will be able to reply to the post\" /><br />\n");
|
||||
|
||||
out.write("<span class=\"b_postField\">Include public names?</span> ");
|
||||
out.write("<input class=\"b_postNames\" type=\"checkbox\" name=\"" + PARAM_INCLUDENAMES
|
||||
+ "\" value=\"true\" title=\"If true, everything marked 'public' in your addressbook is shared\" /><br />\n");
|
||||
|
||||
out.write(ATTACHMENT_FIELDS);
|
||||
|
||||
@@ -249,9 +261,72 @@ public class PostServlet extends BaseServlet {
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
private void displayEditForm(User user, MultiPartRequest req, PostBean post, PrintWriter out) throws IOException {
|
||||
String parentURI = req.getString(PARAM_PARENT);
|
||||
|
||||
String subject = getParam(req, PARAM_SUBJECT);
|
||||
|
||||
out.write("<hr />\n");
|
||||
out.write("<form action=\"" + getPostURI() + "\" method=\"POST\" enctype=\"multipart/form-data\">\n");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<tr><td colspan=\"3\">\n");
|
||||
out.write("<span class=\"b_postField\">Post subject:</span> ");
|
||||
out.write("<input type=\"text\" class=\"b_postSubject\" size=\"80\" name=\"" + PARAM_SUBJECT
|
||||
+ "\" value=\"" + HTMLRenderer.sanitizeTagParam(subject) + "\" /><br />\n");
|
||||
out.write("<span class=\"b_postField\">Post content (in raw <a href=\"smlref.jsp\" target=\"_blank\">SML</a>, no headers):</span><br />\n");
|
||||
out.write("<textarea class=\"b_postText\" rows=\"6\" cols=\"80\" name=\"" + PARAM_TEXT + "\">" + getParam(req, PARAM_TEXT) + "</textarea><br />\n");
|
||||
out.write("<span class=\"b_postField\">SML post headers:</span><br />\n");
|
||||
out.write("<textarea class=\"b_postHeaders\" rows=\"3\" cols=\"80\" name=\"" + PARAM_HEADERS + "\">" + getParam(req, PARAM_HEADERS) + "</textarea><br />\n");
|
||||
|
||||
if ( (parentURI != null) && (parentURI.trim().length() > 0) )
|
||||
out.write("<input type=\"hidden\" name=\"" + PARAM_PARENT + "\" value=\"" + parentURI + "\" />\n");
|
||||
|
||||
out.write(" Tags: ");
|
||||
//<input type=\"text\" size=\"10\" name=\"" + PARAM_TAGS + "\" value=\"" + getParam(req, PARAM_TAGS) + "\" /><br />\n");
|
||||
out.write(" Tags: ");
|
||||
BaseServlet.writeTagField(user, getParam(req, PARAM_TAGS), out, "Optional tags to categorize your post", "No tags", false);
|
||||
out.write("<br />\n");
|
||||
|
||||
boolean inNewThread = getInNewThread(req);
|
||||
boolean refuseReplies = getRefuseReplies(req);
|
||||
|
||||
out.write("In a new thread? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_IN_NEW_THREAD +
|
||||
(inNewThread ? "\" checked=\"true\" " : "\" " ) + " /><br />\n");
|
||||
out.write("Refuse replies? <input type=\"checkbox\" value=\"true\" name=\"" + PARAM_REFUSE_REPLIES +
|
||||
(refuseReplies ? "\" checked=\"true\" " : "\" " ) + " /><br />\n");
|
||||
|
||||
out.write("<span class=\"b_postField\">Include public names?</span> ");
|
||||
out.write("<input class=\"b_postNames\" type=\"checkbox\" name=\"" + PARAM_INCLUDENAMES
|
||||
+ "\" value=\"true\" /><br />\n");
|
||||
|
||||
int newCount = 0;
|
||||
for (int i = 0; i < 32 && newCount < 3; i++) {
|
||||
String filename = req.getFilename("entryfile" + i);
|
||||
if ( (filename != null) && (filename.trim().length() > 0) ) {
|
||||
out.write("<span class=\"b_postField\">Attachment " + i + ":</span> ");
|
||||
out.write(HTMLRenderer.sanitizeString(filename));
|
||||
out.write("<br />");
|
||||
} else {
|
||||
out.write("<span class=\"b_postField\">Attachment " + i + ":</span> ");
|
||||
out.write("<input class=\"b_postField\" type=\"file\" name=\"entryfile" + i + "\" ");
|
||||
out.write("/><br />");
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
out.write("<hr />\n");
|
||||
out.write("<input class=\"b_postPreview\" type=\"submit\" name=\"Post\" value=\"Preview...\" /> ");
|
||||
out.write("<input class=\"b_postReset\" type=\"reset\" value=\"Cancel\" />\n");
|
||||
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
private boolean getInNewThread(HttpServletRequest req) {
|
||||
return getInNewThread(req.getParameter(PARAM_IN_NEW_THREAD));
|
||||
}
|
||||
private boolean getInNewThread(MultiPartRequest req) {
|
||||
return getInNewThread(getParam(req, PARAM_IN_NEW_THREAD));
|
||||
}
|
||||
private boolean getInNewThread(String val) {
|
||||
boolean rv = false;
|
||||
String inNewThread = val;
|
||||
@@ -262,6 +337,9 @@ public class PostServlet extends BaseServlet {
|
||||
private boolean getRefuseReplies(HttpServletRequest req) {
|
||||
return getRefuseReplies(req.getParameter(PARAM_REFUSE_REPLIES));
|
||||
}
|
||||
private boolean getRefuseReplies(MultiPartRequest req) {
|
||||
return getRefuseReplies(getParam(req, PARAM_REFUSE_REPLIES));
|
||||
}
|
||||
private boolean getRefuseReplies(String val) {
|
||||
boolean rv = false;
|
||||
String refuseReplies = val;
|
||||
@@ -276,6 +354,7 @@ public class PostServlet extends BaseServlet {
|
||||
bean = new PostBean();
|
||||
req.getSession().setAttribute(ATTR_POST_BEAN, bean);
|
||||
}
|
||||
bean.setUser(user);
|
||||
return bean;
|
||||
}
|
||||
|
||||
@@ -284,6 +363,11 @@ public class PostServlet extends BaseServlet {
|
||||
if (val == null) val = "";
|
||||
return val;
|
||||
}
|
||||
private String getParam(MultiPartRequest req, String param) {
|
||||
String val = req.getString(param);
|
||||
if (val == null) return "";
|
||||
return val;
|
||||
}
|
||||
|
||||
private static final String ATTACHMENT_FIELDS = ""
|
||||
+ "<span class=\"b_postField\">Attachment 0:</span> <input class=\"b_postField\" type=\"file\" name=\"entryfile0\" /><br />"
|
||||
|
||||
@@ -96,6 +96,10 @@ public class ProfileServlet extends BaseServlet {
|
||||
out.write("<tr><td colspan=\"3\">Password: <input type=\"password\" name=\"password\" /></td></tr>\n");
|
||||
out.write("<tr><td colspan=\"3\">Password again: <input type=\"password\" name=\"passwordConfirm\" /></td></tr>\n");
|
||||
}
|
||||
if (!BlogManager.instance().authorizeRemote(user)) {
|
||||
out.write("<tr><td colspan=\"3\">To access the remote functionality, please specify the administrative password: <br />\n" +
|
||||
"<input type=\"password\" name=\"adminPass\" /></td></tr>\n");
|
||||
}
|
||||
}
|
||||
|
||||
out.write("<tr><td colspan=\"3\"><input type=\"submit\" name=\"action\" value=\"Update profile\" /></td></tr>\n");
|
||||
@@ -103,14 +107,13 @@ public class ProfileServlet extends BaseServlet {
|
||||
}
|
||||
|
||||
private void renderProfile(User user, String baseURI, PrintWriter out, Hash author, Archive archive) throws IOException {
|
||||
out.write("<tr><td colspan=\"3\">Profile for <a href=\"" + getControlTarget() + "?"
|
||||
+ ThreadedHTMLRenderer.PARAM_AUTHOR + '=' + author.toBase64()
|
||||
+ "\" title=\"View threads by the profiled author\">");
|
||||
out.write("<tr><td colspan=\"3\">Profile for ");
|
||||
PetName pn = user.getPetNameDB().getByLocation(author.toBase64());
|
||||
String name = null;
|
||||
BlogInfo info = archive.getBlogInfo(author);
|
||||
if (pn != null) {
|
||||
out.write(pn.getName());
|
||||
String name = null;
|
||||
name = null;
|
||||
if (info != null)
|
||||
name = info.getProperty(BlogInfo.NAME);
|
||||
|
||||
@@ -119,7 +122,6 @@ public class ProfileServlet extends BaseServlet {
|
||||
|
||||
out.write(" (" + name + ")");
|
||||
} else {
|
||||
String name = null;
|
||||
if (info != null)
|
||||
name = info.getProperty(BlogInfo.NAME);
|
||||
|
||||
@@ -130,6 +132,12 @@ public class ProfileServlet extends BaseServlet {
|
||||
out.write("</a>");
|
||||
if (info != null)
|
||||
out.write(" [edition " + info.getEdition() + "]");
|
||||
out.write("<br />\n");
|
||||
out.write("<a href=\"" + getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR
|
||||
+ '=' + author.toBase64() + "&" + ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR + "=true&\""
|
||||
+ " title=\"View '" + HTMLRenderer.sanitizeTagParam(name) + "'s blog\">View their blog</a> or ");
|
||||
out.write("<a href=\"" + getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR
|
||||
+ '=' + author.toBase64() + "&\">threads they have participated in</a>\n");
|
||||
out.write("</td></tr>\n");
|
||||
|
||||
out.write("<tr><td colspan=\"3\"><hr /></td></tr>\n");
|
||||
|
||||
@@ -60,7 +60,7 @@ public class RSSServlet extends HttpServlet {
|
||||
if (count > 100) count = 100;
|
||||
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
FilteredThreadIndex index = new FilteredThreadIndex(user, archive, tagSet, null);
|
||||
FilteredThreadIndex index = new FilteredThreadIndex(user, archive, tagSet, null, false);
|
||||
List entries = new ArrayList();
|
||||
// depth first search of the most recent threads
|
||||
for (int i = 0; i < count && i < index.getRootCount(); i++) {
|
||||
|
||||
@@ -362,6 +362,7 @@ public class RemoteArchiveBean {
|
||||
ArchiveIndex i = new ArchiveIndex(I2PAppContext.getGlobalContext(), false);
|
||||
if (notModified) {
|
||||
_statusMessages.add("Archive unchanged since last fetch.");
|
||||
_statusMessages.add("If you want to force a refetch, make a trivial modification to the URL, such as adding a \"?\"");
|
||||
} else {
|
||||
try {
|
||||
i.load(_archiveFile);
|
||||
|
||||
51
apps/syndie/java/src/net/i2p/syndie/web/RunStandalone.java
Normal file
51
apps/syndie/java/src/net/i2p/syndie/web/RunStandalone.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package net.i2p.syndie.web;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import net.i2p.util.FileUtil;
|
||||
import org.mortbay.jetty.Server;
|
||||
|
||||
public class RunStandalone {
|
||||
private Server _server;
|
||||
|
||||
static {
|
||||
System.setProperty("org.mortbay.http.Version.paranoid", "true");
|
||||
System.setProperty("org.mortbay.xml.XmlParser.NotValidating", "true");
|
||||
System.setProperty("syndie.rootDir", ".");
|
||||
System.setProperty("syndie.defaultSingleUserArchives", "http://syndiemedia.i2p.net:8000/archive/archive.txt");
|
||||
System.setProperty("syndie.defaultProxyHost", "");
|
||||
System.setProperty("syndie.defaultProxyPort", "");
|
||||
}
|
||||
|
||||
private RunStandalone(String args[]) {}
|
||||
|
||||
public static void main(String args[]) {
|
||||
RunStandalone runner = new RunStandalone(args);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
File workDir = new File("work");
|
||||
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
|
||||
if (!workDirRemoved)
|
||||
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
|
||||
boolean workDirCreated = workDir.mkdirs();
|
||||
if (!workDirCreated)
|
||||
System.err.println("ERROR: Unable to create Jetty temporary work directory");
|
||||
|
||||
try {
|
||||
_server = new Server("jetty-syndie.xml");
|
||||
_server.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
_server.stop();
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,9 @@ public class SwitchServlet extends BaseServlet {
|
||||
"<input type=\"submit\" name=\"action\" value=\"Logout\" /></td></tr>\n" +
|
||||
"</form>\n" +
|
||||
"<tr><td colspan=\"3\"><hr /></td></tr>\n" +
|
||||
"<form action=\"" + ThreadedHTMLRenderer.buildProfileURL(null) + "\" method=\"POST\">\n" +
|
||||
"<tr><td colspan=\"3\"><b>Register a new account</b></td></tr>\n" +
|
||||
"<form action=\"" + ThreadedHTMLRenderer.buildProfileURL(null) + "\" method=\"POST\">\n");
|
||||
writeAuthActionFields(out);
|
||||
out.write("<tr><td colspan=\"3\"><b>Register a new account</b></td></tr>\n" +
|
||||
"<tr><td colspan=\"3\">Login: <input type=\"text\" name=\"login\" /> (only known locally)</td></tr>\n" +
|
||||
"<tr><td colspan=\"3\">Password: <input type=\"password\" name=\"password\" /></td></tr>\n" +
|
||||
"<tr><td colspan=\"3\">Public name: <input type=\"text\" name=\"accountName\" /></td></tr>\n" +
|
||||
|
||||
@@ -111,12 +111,12 @@ public class SyndicateServlet extends BaseServlet {
|
||||
|
||||
out.write("</select>\n");
|
||||
out.write("<span class=\"b_remoteChooserField\">Proxy</span>\n");
|
||||
out.write("<input class=\"b_remoteChooserHost\" type=\"text\" size=\"10\" name=\"proxyhost\" value=\"");
|
||||
out.write("<input class=\"b_remoteChooserHost\" type=\"text\" size=\"12\" name=\"proxyhost\" value=\"");
|
||||
out.write(BlogManager.instance().getDefaultProxyHost());
|
||||
out.write("\" />\n");
|
||||
out.write("\" title=\"hostname that your HTTP proxy is on, or blank for no proxy\" />\n");
|
||||
out.write("<input class=\"b_remoteChooserPort\" type=\"text\" size=\"4\" name=\"proxyport\" value=\"");
|
||||
out.write(BlogManager.instance().getDefaultProxyPort());
|
||||
out.write("\" /><br />\n");
|
||||
out.write("\" title=\"port number that your HTTP proxy is on, or blank for no proxy\" /><br />\n");
|
||||
out.write("<span class=\"b_remoteChooserField\">Bookmarked archives:</span>\n");
|
||||
out.write("<select class=\"b_remoteChooserPN\" name=\"" + PARAM_PETNAME + "\">");
|
||||
out.write("<option value=\"\">Custom location</option>");
|
||||
@@ -136,7 +136,7 @@ public class SyndicateServlet extends BaseServlet {
|
||||
String reqLoc = req.getParameter("location");
|
||||
if (reqLoc != null)
|
||||
out.write(reqLoc);
|
||||
out.write("\" />\n");
|
||||
out.write("\" title=\"full URL to the remote location, to be sent to your HTTP proxy\" />\n");
|
||||
out.write("<input class=\"b_remoteChooserContinue\" type=\"submit\" name=\"action\" value=\"Continue...\" /><br />\n");
|
||||
out.write("</span>\n");
|
||||
}
|
||||
|
||||
150
apps/syndie/java/src/net/i2p/syndie/web/ThreadNavServlet.java
Normal file
150
apps/syndie/java/src/net/i2p/syndie/web/ThreadNavServlet.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package net.i2p.syndie.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
* Export the thread nav as either RDF or XML
|
||||
*
|
||||
*/
|
||||
public class ThreadNavServlet extends BaseServlet {
|
||||
public static final String PARAM_COUNT = "count";
|
||||
public static final String PARAM_OFFSET = "offset";
|
||||
public static final String PARAM_FORMAT = "format";
|
||||
|
||||
public static final String FORMAT_RDF = "rdf";
|
||||
public static final String FORMAT_XML = "xml";
|
||||
|
||||
protected void render(User user, HttpServletRequest req, HttpServletResponse resp, ThreadIndex index) throws ServletException, IOException {
|
||||
int threadCount = empty(req, PARAM_COUNT) ? index.getRootCount() : getInt(req, PARAM_COUNT);
|
||||
int offset = getInt(req, PARAM_OFFSET);
|
||||
String uri = req.getRequestURI();
|
||||
if (uri.endsWith(FORMAT_XML)) {
|
||||
resp.setContentType("text/xml; charset=UTF-8");
|
||||
render(user, index, resp.getWriter(), threadCount, offset, FORMAT_XML);
|
||||
} else {
|
||||
resp.setContentType("application/rdf+xml; charset=UTF-8");
|
||||
render(user, index, resp.getWriter(), threadCount, offset, FORMAT_RDF);
|
||||
}
|
||||
}
|
||||
|
||||
private int getInt(HttpServletRequest req, String param) {
|
||||
String val = req.getParameter(param);
|
||||
if (val != null) {
|
||||
try {
|
||||
return Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static final int DEFAULT_THREADCOUNT = 10;
|
||||
private static final int DEFAULT_THREADOFFSET = 0;
|
||||
|
||||
private void render(User user, ThreadIndex index, PrintWriter out, int threadCount, int offset, String format) throws IOException {
|
||||
int startRoot = DEFAULT_THREADOFFSET;
|
||||
if (offset >= 0)
|
||||
startRoot = offset;
|
||||
renderStart(out, format);
|
||||
|
||||
int endRoot = startRoot + (threadCount > 0 ? threadCount : DEFAULT_THREADCOUNT);
|
||||
if (endRoot >= index.getRootCount())
|
||||
endRoot = index.getRootCount() - 1;
|
||||
for (int i = startRoot; i <= endRoot; i++) {
|
||||
ThreadNode node = index.getRoot(i);
|
||||
if (FORMAT_XML.equals(format))
|
||||
out.write(node.toString());
|
||||
else
|
||||
render(user, node, out);
|
||||
}
|
||||
renderEnd(out, format);
|
||||
}
|
||||
private void renderStart(PrintWriter out, String format) throws IOException {
|
||||
out.write("<?xml version=\"1.0\" ?>\n");
|
||||
if (FORMAT_XML.equals(format)) {
|
||||
out.write("<threadTree>");
|
||||
} else {
|
||||
out.write("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" " +
|
||||
" xmlns:syndie=\"http://syndie.i2p.net/syndie.ns#\">\n");
|
||||
out.write("<rdf:Seq rdf:about=\"http://syndie.i2p.net/threads\">\n");
|
||||
}
|
||||
}
|
||||
private void renderEnd(PrintWriter out, String format) throws IOException {
|
||||
if (FORMAT_XML.equals(format)) {
|
||||
out.write("</threadTree>");
|
||||
} else {
|
||||
out.write("</rdf:Seq>\n");
|
||||
out.write("</rdf:RDF>\n");
|
||||
}
|
||||
}
|
||||
private void render(User user, ThreadNode node, PrintWriter out) throws IOException {
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
String blog = node.getEntry().getKeyHash().toBase64();
|
||||
out.write("<rdf:li rdf:resource=\"entry://" + blog + "/" + node.getEntry().getEntryId() + "\">\n");
|
||||
out.write("<rdf:Description rdf:about=\"entry://" + blog + "/" + node.getEntry().getEntryId() + "\">");
|
||||
PetName pn = user.getPetNameDB().getByLocation(blog);
|
||||
String name = null;
|
||||
if (pn != null) {
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_FAVORITE))
|
||||
out.write("<syndie:favoriteauthor />\n");
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_IGNORE))
|
||||
out.write("<syndie:ignoredauthor />\n");
|
||||
name = pn.getName();
|
||||
} else {
|
||||
BlogInfo info = archive.getBlogInfo(node.getEntry().getKeyHash());
|
||||
if (info != null)
|
||||
name = info.getProperty(BlogInfo.NAME);
|
||||
if ( (name == null) || (name.trim().length() <= 0) )
|
||||
name = node.getEntry().getKeyHash().toBase64().substring(0,6);
|
||||
}
|
||||
out.write("<syndie:author syndie:blog=\"" + blog + "\">" + HTMLRenderer.sanitizeStrippedXML(name) + "</syndie:author>\n");
|
||||
if ( (user.getBlog() != null) && (node.containsAuthor(user.getBlog())) )
|
||||
out.write("<syndie:threadself />\n");
|
||||
|
||||
EntryContainer entry = archive.getEntry(node.getEntry());
|
||||
if (entry == null) throw new RuntimeException("Unable to fetch the entry " + node.getEntry());
|
||||
|
||||
SMLParser parser = new SMLParser(I2PAppContext.getGlobalContext());
|
||||
HeaderReceiver rec = new HeaderReceiver();
|
||||
parser.parse(entry.getEntry().getText(), rec);
|
||||
String subject = rec.getHeader(HTMLRenderer.HEADER_SUBJECT);
|
||||
if ( (subject == null) || (subject.trim().length() <= 0) )
|
||||
subject = "(no subject)";
|
||||
|
||||
out.write("<syndie:subject>" + HTMLRenderer.sanitizeStrippedXML(subject) + "</syndie:subject>\n");
|
||||
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
long postId = node.getEntry().getEntryId();
|
||||
int daysAgo = (int)((dayBegin - postId + 24*60*60*1000l-1l)/(24*60*60*1000l));
|
||||
out.write("<syndie:age>" + daysAgo + "</syndie:age>\n");
|
||||
|
||||
out.write("<syndie:children>");
|
||||
out.write("<rdf:Seq rdf:about=\"entry://" + blog + "/" + node.getEntry().getEntryId() + "\">");
|
||||
for (int i = 0; i < node.getChildCount(); i++)
|
||||
render(user, node.getChild(i), out);
|
||||
out.write("</rdf:Seq>\n");
|
||||
out.write("</syndie:children>\n");
|
||||
|
||||
out.write("</rdf:Description>\n");
|
||||
out.write("</rdf:li>\n");
|
||||
}
|
||||
|
||||
protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index,
|
||||
int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException {
|
||||
throw new UnsupportedOperationException("Not relevant...");
|
||||
}
|
||||
}
|
||||
158
apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java
Normal file
158
apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package net.i2p.syndie.web;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.*;
|
||||
import net.i2p.data.*;
|
||||
import net.i2p.syndie.*;
|
||||
import net.i2p.syndie.data.*;
|
||||
import net.i2p.syndie.sml.*;
|
||||
|
||||
/**
|
||||
* List the blogs known in the archive
|
||||
*
|
||||
*/
|
||||
public class ViewBlogsServlet extends BaseServlet {
|
||||
private static final int MAX_AUTHORS_AT_ONCE = 20;
|
||||
private static final int MAX_TAGS = 50;
|
||||
|
||||
/** renders the posts from the last 3 days */
|
||||
private String getViewBlogLink(Hash blog, long lastPost) {
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
int daysAgo = 2;
|
||||
if ( (lastPost > 0) && (dayBegin - 3*24*60*60*1000l >= lastPost) ) // last post was old 3 days ago
|
||||
daysAgo = (int)((dayBegin - lastPost + 24*60*60*1000l-1)/(24*60*60*1000l));
|
||||
daysAgo++;
|
||||
return getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + '=' + blog.toBase64()
|
||||
+ '&' + ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR + "=true&daysBack=" + daysAgo;
|
||||
}
|
||||
|
||||
private String getPostDate(long when) {
|
||||
String age = null;
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
long postId = when;
|
||||
if (postId >= dayBegin) {
|
||||
age = "today";
|
||||
} else if (postId >= dayBegin - 24*60*60*1000) {
|
||||
age = "yesterday";
|
||||
} else {
|
||||
int daysAgo = (int)((dayBegin - postId + 24*60*60*1000-1)/(24*60*60*1000));
|
||||
age = daysAgo + " days ago";
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index,
|
||||
int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException {
|
||||
TreeSet orderedRoots = new TreeSet(new NewestEntryFirstComparator());
|
||||
// The thread index is ordered by last updated date, as opposed to root posting date,
|
||||
// so lets reorder things
|
||||
int count = index.getRootCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ThreadNode node = index.getRoot(i);
|
||||
orderedRoots.add(node.getEntry());
|
||||
}
|
||||
|
||||
TreeSet tags = new TreeSet();
|
||||
List writtenAuthors = new ArrayList();
|
||||
|
||||
|
||||
out.write("<tr><td colspan=\"3\" valign=\"top\" align=\"left\"><span class=\"syndieBlogFavorites\">");
|
||||
if ( (user != null) && (user.getAuthenticated()) ) {
|
||||
out.write("<b>Favorite blogs:</b> <a href=\"" + getControlTarget() + "?author=favorites&daysBack=3\" title=\"View all posts by your favorite authors in the last 3 days\">view all</a><br />\n");
|
||||
out.write("<a href=\"" + getViewBlogLink(user.getBlog(), user.getLastMetaEntry())
|
||||
+ "\" title=\"View your blog\">Your blog</a><br />\n");
|
||||
|
||||
PetNameDB db = user.getPetNameDB();
|
||||
for (Iterator iter = orderedRoots.iterator(); iter.hasNext() && writtenAuthors.size() < MAX_AUTHORS_AT_ONCE; ) {
|
||||
BlogURI uri= (BlogURI)iter.next();
|
||||
if (writtenAuthors.contains(uri.getKeyHash())) {
|
||||
// skip
|
||||
} else {
|
||||
PetName pn = db.getByLocation(uri.getKeyHash().toBase64());
|
||||
if (pn != null) {
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_FAVORITE)) {
|
||||
out.write("<a href=\"" + getViewBlogLink(uri.getKeyHash(), uri.getEntryId())
|
||||
+ "\" title=\"View " + HTMLRenderer.sanitizeTagParam(pn.getName()) +"'s blog\">");
|
||||
out.write(HTMLRenderer.sanitizeString(pn.getName(), 32));
|
||||
out.write("</a> (" + getPostDate(uri.getEntryId()) + ")<br />\n");
|
||||
writtenAuthors.add(uri.getKeyHash());
|
||||
} else if (pn.isMember(FilteredThreadIndex.GROUP_IGNORE)) {
|
||||
// ignore 'em
|
||||
writtenAuthors.add(uri.getKeyHash());
|
||||
} else {
|
||||
// bookmarked, but not a favorite... leave them for later
|
||||
}
|
||||
} else {
|
||||
// not bookmarked, leave them for later
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write("</span>\n");
|
||||
|
||||
// now for the non-bookmarked people
|
||||
out.write("<span class=\"syndieBlogList\">");
|
||||
out.write("<b>Most recently updated blogs:</b><br />\n");
|
||||
for (Iterator iter = orderedRoots.iterator(); iter.hasNext() && writtenAuthors.size() < MAX_AUTHORS_AT_ONCE; ) {
|
||||
BlogURI uri= (BlogURI)iter.next();
|
||||
String curTags[] = archive.getEntry(uri).getTags();
|
||||
if (curTags != null)
|
||||
for (int i = 0; i < curTags.length && tags.size() < MAX_TAGS; i++)
|
||||
tags.add(curTags[i]);
|
||||
if (writtenAuthors.contains(uri.getKeyHash())) {
|
||||
// skip
|
||||
} else {
|
||||
BlogInfo info = archive.getBlogInfo(uri);
|
||||
if (info == null)
|
||||
continue;
|
||||
String name = info.getProperty(BlogInfo.NAME);
|
||||
if ( (name == null) || (name.trim().length() <= 0) )
|
||||
name = uri.getKeyHash().toBase64().substring(0,8);
|
||||
String desc = info.getProperty(BlogInfo.DESCRIPTION);
|
||||
if ( (desc == null) || (desc.trim().length() <= 0) )
|
||||
desc = name + "'s blog";
|
||||
String age = null;
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
long postId = uri.getEntryId();
|
||||
if (postId >= dayBegin) {
|
||||
age = "today";
|
||||
} else if (postId >= dayBegin - 24*60*60*1000) {
|
||||
age = "yesterday";
|
||||
} else {
|
||||
int daysAgo = (int)((dayBegin - postId + 24*60*60*1000-1)/(24*60*60*1000));
|
||||
age = daysAgo + " days ago";
|
||||
}
|
||||
|
||||
out.write("<a href=\"" + getViewBlogLink(uri.getKeyHash(), uri.getEntryId())
|
||||
+ "\" title=\"View " + trim(HTMLRenderer.sanitizeTagParam(name), 32)
|
||||
+ "'s blog\">");
|
||||
out.write(HTMLRenderer.sanitizeString(desc, 32));
|
||||
out.write("</a> (" + getPostDate(uri.getEntryId()) + ")<br />\n");
|
||||
writtenAuthors.add(uri.getKeyHash());
|
||||
}
|
||||
}
|
||||
|
||||
out.write("</span>\n");
|
||||
/*
|
||||
out.write("<tr><td colspan=\"3\"><b>Topics:</b></td></tr>\n");
|
||||
out.write("<tr><td colspan=\"3\">");
|
||||
for (Iterator iter = tags.iterator(); iter.hasNext(); ) {
|
||||
String tag = (String)iter.next();
|
||||
out.write("<a href=\"" + ThreadedHTMLRenderer.getFilterByTagLink(getControlTarget(), null, user, tag, null)
|
||||
+ "\" title=\"View threads flagged with the tag '" + HTMLRenderer.sanitizeTagParam(tag) + "'\">");
|
||||
out.write(HTMLRenderer.sanitizeString(tag, 32));
|
||||
out.write("</a> ");
|
||||
}
|
||||
*/
|
||||
out.write("</td></tr>\n");
|
||||
}
|
||||
|
||||
protected String getTitle() { return "Syndie :: View blogs"; }
|
||||
}
|
||||
@@ -21,50 +21,138 @@ import net.i2p.syndie.sml.*;
|
||||
public class ViewThreadedServlet extends BaseServlet {
|
||||
protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index,
|
||||
int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException {
|
||||
renderBody(user, req, out, index);
|
||||
List posts = getPosts(user, archive, req, index);
|
||||
renderBody(user, req, out, index, archive, posts);
|
||||
|
||||
renderThreadNav(user, req, out, threadOffset, index);
|
||||
renderThreadTree(user, req, out, threadOffset, visibleEntry, archive, index);
|
||||
renderThreadTree(user, req, out, threadOffset, visibleEntry, archive, index, posts);
|
||||
renderThreadNav(user, req, out, threadOffset, index);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderBody(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index) throws IOException {
|
||||
private void renderBody(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index, Archive archive, List posts) throws IOException {
|
||||
ThreadedHTMLRenderer renderer = new ThreadedHTMLRenderer(I2PAppContext.getGlobalContext());
|
||||
Archive archive = BlogManager.instance().getArchive();
|
||||
List posts = getPosts(archive, req, index);
|
||||
|
||||
String uri = req.getRequestURI();
|
||||
String off = req.getParameter(ThreadedHTMLRenderer.PARAM_OFFSET);
|
||||
String tags = req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS);
|
||||
String author = req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR);
|
||||
|
||||
boolean authorOnly = Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR)).booleanValue();
|
||||
|
||||
for (int i = 0; i < posts.size(); i++) {
|
||||
BlogURI post = (BlogURI)posts.get(i);
|
||||
renderer.render(user, out, archive, post, posts.size() == 1, index, uri, getAuthActionFields(), off, tags, author);
|
||||
boolean inlineReply = (posts.size() == 1);
|
||||
//if (true)
|
||||
// inlineReply = true;
|
||||
renderer.render(user, out, archive, post, inlineReply, index, uri, getAuthActionFields(), off, tags, author, authorOnly);
|
||||
}
|
||||
}
|
||||
|
||||
private List getPosts(Archive archive, HttpServletRequest req, ThreadIndex index) {
|
||||
private List getPosts(User user, Archive archive, HttpServletRequest req, ThreadIndex index) {
|
||||
List rv = new ArrayList(1);
|
||||
String author = req.getParameter(ThreadedHTMLRenderer.PARAM_AUTHOR);
|
||||
String tags = req.getParameter(ThreadedHTMLRenderer.PARAM_TAGS);
|
||||
String post = req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_POST);
|
||||
String thread = req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_THREAD);
|
||||
boolean threadAuthorOnly = Boolean.valueOf(req.getParameter(ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR) + "").booleanValue();
|
||||
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
String daysStr = req.getParameter(ThreadedHTMLRenderer.PARAM_DAYS_BACK);
|
||||
int days = 1;
|
||||
try {
|
||||
if (daysStr != null)
|
||||
days = Integer.parseInt(daysStr);
|
||||
} catch (NumberFormatException nfe) {
|
||||
days = 1;
|
||||
}
|
||||
dayBegin -= (days-1) * 24*60*60*1000l;
|
||||
|
||||
if ( (author != null) && empty(post) && empty(thread) ) {
|
||||
ArchiveIndex aindex = archive.getIndex();
|
||||
PetNameDB db = user.getPetNameDB();
|
||||
if ("favorites".equals(author)) {
|
||||
for (Iterator nameIter = db.getNames().iterator(); nameIter.hasNext(); ) {
|
||||
PetName pn = db.getByName((String)nameIter.next());
|
||||
if (pn.isMember(FilteredThreadIndex.GROUP_FAVORITE) && AddressesServlet.PROTO_BLOG.equals(pn.getProtocol()) ) {
|
||||
Hash loc = new Hash();
|
||||
byte key[] = Base64.decode(pn.getLocation());
|
||||
if ( (key != null) && (key.length == Hash.HASH_LENGTH) ) {
|
||||
loc.setData(key);
|
||||
aindex.selectMatchesOrderByEntryId(rv, loc, tags, dayBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
// always include ourselves...
|
||||
aindex.selectMatchesOrderByEntryId(rv, user.getBlog(), tags, dayBegin);
|
||||
|
||||
Collections.sort(rv, BlogURI.COMPARATOR);
|
||||
} else {
|
||||
Hash loc = new Hash();
|
||||
byte key[] = Base64.decode(author);
|
||||
if ( (key != null) && (key.length == Hash.HASH_LENGTH) ) {
|
||||
loc.setData(key);
|
||||
aindex.selectMatchesOrderByEntryId(rv, loc, tags, dayBegin);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
// how inefficient can we get?
|
||||
if (threadAuthorOnly && (rv.size() > 0)) {
|
||||
// lets filter out any posts that are not roots
|
||||
for (int i = 0; i < rv.size(); i++) {
|
||||
BlogURI curURI = (BlogURI)rv.get(i);
|
||||
ThreadNode node = index.getNode(curURI);
|
||||
if ( (node != null) && (node.getParent() == null) ) {
|
||||
// ok, its a root
|
||||
} else {
|
||||
rv.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlogURI uri = getAsBlogURI(post);
|
||||
if ( (uri != null) && (uri.getEntryId() > 0) ) {
|
||||
rv.add(uri);
|
||||
} else {
|
||||
String thread = req.getParameter(ThreadedHTMLRenderer.PARAM_VIEW_THREAD);
|
||||
uri = getAsBlogURI(thread);
|
||||
if ( (uri != null) && (uri.getEntryId() > 0) ) {
|
||||
ThreadNode node = index.getNode(uri);
|
||||
if (node != null) {
|
||||
while (node.getParent() != null)
|
||||
node = node.getParent(); // hope the structure is loopless...
|
||||
// depth first traversal
|
||||
walkTree(rv, node);
|
||||
if (false) {
|
||||
// entire thread, as a depth first search
|
||||
while (node.getParent() != null)
|
||||
node = node.getParent(); // hope the structure is loopless...
|
||||
// depth first traversal
|
||||
walkTree(rv, node);
|
||||
} else {
|
||||
// only the "current" unforked thread, as suggested by cervantes.
|
||||
// e.g.
|
||||
// a--b--c--d
|
||||
// \-e--f--g
|
||||
// \-h
|
||||
// would show "a--e--f--g" if node == {e, f, or g},
|
||||
// or "a--b--c--d" if node == {a, b, c, or d},
|
||||
// or "a--e--f--h" if node == h
|
||||
rv.add(node.getEntry());
|
||||
ThreadNode cur = node;
|
||||
while (cur.getParent() != null) {
|
||||
cur = cur.getParent();
|
||||
rv.add(0, cur.getEntry()); // parents go before children...
|
||||
}
|
||||
cur = node;
|
||||
while ( (cur != null) && (cur.getChildCount() > 0) ) {
|
||||
cur = cur.getChild(0);
|
||||
rv.add(cur.getEntry()); // and children after parents
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rv.add(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -99,6 +187,7 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
}
|
||||
out.write("</td><td class=\"threadNavRight\" nowrap=\"true\">\n");
|
||||
|
||||
out.write("<span class=\"rightOffset\">");
|
||||
int max = index.getRootCount();
|
||||
if (threadOffset + 10 > max) {
|
||||
out.write("Next Page> Last Page>>\n");
|
||||
@@ -109,17 +198,18 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
out.write(getNavLink(req, -1));
|
||||
out.write("\">Last Page>></a>\n");
|
||||
}
|
||||
out.write("<!-- thread nav end -->\n");
|
||||
out.write("</span>");
|
||||
//out.write("<!-- thread nav end -->\n");
|
||||
out.write("</td></tr>\n");
|
||||
}
|
||||
|
||||
private void renderThreadTree(User user, HttpServletRequest req, PrintWriter out, int threadOffset, BlogURI visibleEntry, Archive archive, ThreadIndex index) throws IOException {
|
||||
private void renderThreadTree(User user, HttpServletRequest req, PrintWriter out, int threadOffset, BlogURI visibleEntry, Archive archive, ThreadIndex index, List visibleURIs) throws IOException {
|
||||
int numThreads = 10;
|
||||
renderThreadTree(user, out, index, archive, req, threadOffset, numThreads, visibleEntry);
|
||||
renderThreadTree(user, out, index, archive, req, threadOffset, numThreads, visibleEntry, visibleURIs);
|
||||
}
|
||||
|
||||
private void renderThreadTree(User user, PrintWriter out, ThreadIndex index, Archive archive, HttpServletRequest req,
|
||||
int threadOffset, int numThreads, BlogURI visibleEntry) {
|
||||
int threadOffset, int numThreads, BlogURI visibleEntry, List visibleURIs) {
|
||||
|
||||
if ( (visibleEntry != null) && (empty(req, ThreadedHTMLRenderer.PARAM_OFFSET)) ) {
|
||||
// we want to jump to a specific thread in the nav
|
||||
@@ -137,7 +227,7 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
for (int curRoot = threadOffset; curRoot < numThreads + threadOffset; curRoot++) {
|
||||
ThreadNode node = index.getRoot(curRoot);
|
||||
out.write("<!-- thread begin curRoot=" + curRoot + " threadOffset=" + threadOffset + " -->\n");
|
||||
renderThread(user, out, index, archive, req, node, 0, visibleEntry, state);
|
||||
renderThread(user, out, index, archive, req, node, 0, visibleEntry, state, visibleURIs);
|
||||
out.write("<!-- thread end -->\n");
|
||||
written++;
|
||||
}
|
||||
@@ -149,9 +239,13 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
}
|
||||
|
||||
private boolean renderThread(User user, PrintWriter out, ThreadIndex index, Archive archive, HttpServletRequest req,
|
||||
ThreadNode node, int depth, BlogURI visibleEntry, TreeRenderState state) {
|
||||
ThreadNode node, int depth, BlogURI visibleEntry, TreeRenderState state, List visibleURIs) {
|
||||
boolean isFavorite = false;
|
||||
boolean ignored = false;
|
||||
boolean displayed = false;
|
||||
|
||||
if ( (visibleURIs != null) && (visibleURIs.contains(node.getEntry())) )
|
||||
displayed = true;
|
||||
|
||||
HTMLRenderer rend = new HTMLRenderer(I2PAppContext.getGlobalContext());
|
||||
SMLParser parser = new SMLParser(I2PAppContext.getGlobalContext());
|
||||
@@ -171,9 +265,11 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
else
|
||||
out.write("<tr class=\"threadOdd\">\n");
|
||||
|
||||
out.write("<td class=\"threadFlag\">");
|
||||
out.write("<td class=\"thread\" colspan=\"3\">");
|
||||
out.write("<span class=\"threadInfoLeft\">");
|
||||
//out.write("<td class=\"threadFlag\">");
|
||||
out.write(getFlagHTML(user, node));
|
||||
out.write("</td>\n<td class=\"threadLeft\">\n");
|
||||
//out.write("</td>\n<td class=\"threadLeft\" colspan=\"2\">\n");
|
||||
for (int i = 0; i < depth; i++)
|
||||
out.write("<img src=\"images/threadIndent.png\" alt=\"\" border=\"0\" />");
|
||||
|
||||
@@ -212,6 +308,8 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
out.write(getProfileLink(req, node.getEntry().getKeyHash()));
|
||||
out.write("\" title=\"View the user's profile\">");
|
||||
|
||||
if (displayed) out.write("<b>");
|
||||
|
||||
if (pn == null) {
|
||||
BlogInfo info = archive.getBlogInfo(node.getEntry().getKeyHash());
|
||||
String name = null;
|
||||
@@ -223,6 +321,9 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
} else {
|
||||
out.write(trim(pn.getName(), 30));
|
||||
}
|
||||
|
||||
if (displayed) out.write("</b>");
|
||||
|
||||
out.write("</a>\n");
|
||||
|
||||
if ( (user.getBlog() != null) && (node.getEntry().getKeyHash().equals(user.getBlog())) ) {
|
||||
@@ -243,24 +344,80 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
}
|
||||
}
|
||||
|
||||
out.write(" @ ");
|
||||
out.write("<a href=\"");
|
||||
out.write(getViewPostLink(req, node, user, false));
|
||||
out.write("\" title=\"View post\">");
|
||||
out.write(rend.getEntryDate(node.getEntry().getEntryId()));
|
||||
out.write(": ");
|
||||
out.write("<a href=\"");
|
||||
if (false) {
|
||||
out.write(getViewPostLink(req, node, user, false));
|
||||
} else {
|
||||
out.write(getViewThreadLink(req, node, user));
|
||||
}
|
||||
out.write("\" title=\"View post\">");
|
||||
EntryContainer entry = archive.getEntry(node.getEntry());
|
||||
if (entry == null) throw new RuntimeException("Unable to fetch the entry " + node.getEntry());
|
||||
|
||||
HeaderReceiver rec = new HeaderReceiver();
|
||||
parser.parse(entry.getEntry().getText(), rec);
|
||||
String subject = rec.getHeader(HTMLRenderer.HEADER_SUBJECT);
|
||||
if (subject == null)
|
||||
subject = "";
|
||||
out.write(trim(subject, 40));
|
||||
out.write("</a>\n</td><td class=\"threadRight\">\n");
|
||||
out.write("<a href=\"");
|
||||
if ( (subject == null) || (subject.trim().length() <= 0) )
|
||||
subject = "(no subject)";
|
||||
if (displayed) {
|
||||
// currently being rendered
|
||||
out.write("<b>");
|
||||
out.write(trim(subject, 40));
|
||||
out.write("</b>");
|
||||
} else {
|
||||
out.write(trim(subject, 40));
|
||||
}
|
||||
//out.write("</a>\n</td><td class=\"threadRight\">\n");
|
||||
out.write("</a>");
|
||||
if (false) {
|
||||
out.write(" (<a href=\"");
|
||||
out.write(getViewThreadLink(req, node, user));
|
||||
out.write("\" title=\"View all posts in the thread\">full thread</a>)\n");
|
||||
}
|
||||
|
||||
out.write("</span><span class=\"threadInfoRight\">");
|
||||
|
||||
out.write(" <a href=\"");
|
||||
BlogURI newestURI = new BlogURI(node.getMostRecentPostAuthor(), node.getMostRecentPostDate());
|
||||
if (false) {
|
||||
out.write(getViewPostLink(req, newestURI, user));
|
||||
} else {
|
||||
List paths = new ArrayList();
|
||||
paths.add(node);
|
||||
ThreadNode cur = null;
|
||||
while (paths.size() > 0) {
|
||||
cur = (ThreadNode)paths.remove(0);
|
||||
if (cur.getEntry().equals(newestURI))
|
||||
break;
|
||||
for (int i = cur.getChildCount() - 1; i >= 0; i--)
|
||||
paths.add(cur.getChild(i));
|
||||
if (paths.size() <= 0)
|
||||
cur = null;
|
||||
}
|
||||
if (cur != null)
|
||||
out.write(getViewThreadLink(req, cur, user));
|
||||
}
|
||||
out.write("\" title=\"View the most recent post\">latest - ");
|
||||
|
||||
long dayBegin = BlogManager.instance().getDayBegin();
|
||||
long postId = node.getMostRecentPostDate();
|
||||
if (postId >= dayBegin) {
|
||||
out.write("<b>today</b>");
|
||||
} else if (postId >= dayBegin - 24*60*60*1000) {
|
||||
out.write("<b>yesterday</b>");
|
||||
} else {
|
||||
int daysAgo = (int)((dayBegin - postId + 24*60*60*1000-1)/(24*60*60*1000));
|
||||
out.write(daysAgo + " days ago");
|
||||
}
|
||||
|
||||
out.write("</a>\n");
|
||||
/*
|
||||
out.write(" <a href=\"");
|
||||
out.write(getViewThreadLink(req, node, user));
|
||||
out.write("\" title=\"View all posts in the thread\">view thread</a>\n");
|
||||
out.write("\" title=\"View all posts in the thread\">full thread</a>\n");
|
||||
*/
|
||||
out.write("</span>");
|
||||
out.write("</td></tr>\n");
|
||||
|
||||
boolean rendered = true;
|
||||
@@ -268,7 +425,7 @@ public class ViewThreadedServlet extends BaseServlet {
|
||||
if (showChildren) {
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
ThreadNode child = node.getChild(i);
|
||||
boolean childRendered = renderThread(user, out, index, archive, req, child, depth+1, visibleEntry, state);
|
||||
boolean childRendered = renderThread(user, out, index, archive, req, child, depth+1, visibleEntry, state, visibleURIs);
|
||||
rendered = rendered || childRendered;
|
||||
}
|
||||
}
|
||||
|
||||
114
apps/syndie/jetty-syndie.xml
Normal file
114
apps/syndie/jetty-syndie.xml
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure 1.2//EN" "http://jetty.mortbay.org/configure_1_2.dtd">
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Jetty Server -->
|
||||
<!-- =============================================================== -->
|
||||
<Configure class="org.mortbay.jetty.Server">
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Listeners -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add and configure a HTTP listener to port 8080 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SocketListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.util.InetAddrPort">
|
||||
<Set name="host">0.0.0.0</Set>
|
||||
<Set name="port">8001</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">10</Set>
|
||||
<Set name="MaxIdleTimeMs">30000</Set>
|
||||
<Set name="LowResourcePersistTimeMs">1000</Set>
|
||||
<Set name="ConfidentialPort">8443</Set>
|
||||
<Set name="IntegralPort">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a HTTPS SSL listener on port 8443 -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- UNCOMMENT TO ACTIVATE
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.SunJsseListener">
|
||||
<Set name="Port">8443</Set>
|
||||
<Set name="PoolName">main</Set>
|
||||
<Set name="Keystore"><SystemProperty name="jetty.home" default="."/>/etc/demokeystore</Set>
|
||||
<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
|
||||
<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
|
||||
<Set name="NonPersistentUserAgent">MSIE 5</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a AJP13 listener on port 8009 -->
|
||||
<!-- This protocol can be used with mod_jk in apache, IIS etc. -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!--
|
||||
<Call name="addListener">
|
||||
<Arg>
|
||||
<New class="org.mortbay.http.ajp.AJP13Listener">
|
||||
<Set name="PoolName">ajp</Set>
|
||||
<Set name="Port">8009</Set>
|
||||
<Set name="MinThreads">3</Set>
|
||||
<Set name="MaxThreads">20</Set>
|
||||
<Set name="MaxIdleTimeMs">0</Set>
|
||||
<Set name="confidentialPort">443</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
-->
|
||||
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Contexts -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<!-- Add a all web application within the webapps directory. -->
|
||||
<!-- + No virtual host specified -->
|
||||
<!-- + Look in the webapps directory relative to jetty.home or . -->
|
||||
<!-- + Use the default webdefault.xml in jetty's install -->
|
||||
<!-- + Upack the war file -->
|
||||
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
|
||||
<Set name="rootWebApp">syndie</Set>
|
||||
<Call name="addWebApplication">
|
||||
<Arg>/</Arg>
|
||||
<Arg>syndie.war</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Request Log -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="RequestLog">
|
||||
<New class="org.mortbay.http.NCSARequestLog">
|
||||
<Arg>./logs/yyyy_mm_dd.syndie-request.log</Arg>
|
||||
<Set name="retainDays">90</Set>
|
||||
<Set name="append">true</Set>
|
||||
<Set name="extended">false</Set>
|
||||
<Set name="buffered">false</Set>
|
||||
<Set name="LogTimeZone">GMT</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Other Server Options -->
|
||||
<!-- =============================================================== -->
|
||||
<Set name="requestsPerGC">2000</Set>
|
||||
<Set name="statsOn">false</Set>
|
||||
|
||||
</Configure>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user