17 Commits

Author SHA1 Message Date
zzz
fa37064834 0.14.0 2015-05-28 13:00:07 +00:00
zzz
7cc2d97825 Remove use of deprecated SimpleScheduler
Remove static structures
Use RandomIterator for efficiency
untested
2015-04-16 22:33:50 +00:00
zzz
9ffef703b5 Increase startup delay, so it's after the eepsite Jetty,
so it doesn't create the static RolloverFileOutputStream Timer thread,
so PluginStarter doesn't think it's running after it stops.
2014-11-29 14:05:07 +00:00
zzz
5239dfb768 fix NPE on missing announce parameters 2014-11-26 20:52:58 +00:00
zzz
953533c6e3 0.12.0 2014-11-13 20:15:02 +00:00
zzz
2e0f7ae4bf Release all resources when shut down (requires 0.9.16-6 or higher)
log tweaks
2014-11-13 16:01:15 +00:00
zzz
17e5595dd0 Don't overwrite index.html and robots.txt in the update
More build and clean fixes
2014-11-12 23:10:19 +00:00
zzz
8350d009d4 Add zzzot.config file to set interval 2014-11-12 15:19:13 +00:00
zzz
a7225a2379 Fix parameter decoding for scrape also
Add caching for info hashes and peer ids; move from ByteArray to SDS
Stop cleaner when plugin stops
Move to the ClientApp interface, remove all static refs
Attempt to fix crash after update
2014-11-12 14:48:51 +00:00
zzz
8a9094cd8e Fix critical bug preventing announces from working
0.11.0
2014-11-11 14:08:43 +00:00
zzz
7f4394aa0f add max-jetty-version 2014-10-24 14:43:51 +00:00
zzz
38f38398ef switch Jetty to QTP 2014-10-24 13:01:28 +00:00
zzz
8317e3ce9a Change new install logging from ERROR to INFO
Move up warning about removing help file
2014-08-30 14:03:56 +00:00
zzz
c1f6a14367 add su3 url 2014-08-09 18:47:16 +00:00
zzz
7e36455b05 add support for building su3 plugins 2014-08-09 18:00:17 +00:00
zzz
a85aa6af31 more import cleanup 2014-08-09 16:25:44 +00:00
zzz
f33303f394 Use cached Destinations
Specify min I2P version of 0.9.9 for Destination.create()
2014-08-09 16:21:02 +00:00
21 changed files with 811 additions and 164 deletions

View File

@ -1,3 +1,31 @@
0.14.0
2015-05-28
Remove use of deprecated SimpleScheduler
Remove static structures
Use RandomIterator for efficiency
0.13.0
2014-11-29
Fix NPE on missing announce parameters
Increase startup delay
0.12.0
2014-11-13
Fix parameter decoding for scrape also
Add caching for info hashes and peer ids
Stop cleaner when plugin stops
Move to the ClientApp interface, remove all static refs
Attempt to fix crash after update
Add zzzot.config file to set interval
Don't overwrite index.html and robots.txt in the update
Release all resources when shut down (requires 0.9.16-6 or higher)
0.11.0
2014-11-11
Critical fix for announce parameter decoding, triggered by recent Jetty versions
Change request queueing in jetty.xml (new installs only)
SU3 plugin file format
0.10.0
2013-04-14
Updates and migration for Jetty 7 (I2P 0.9.6)

View File

@ -8,10 +8,14 @@
</target>
<target name="plugin" depends="war">
<delete file="plugin/i2ptunnel.config" />
<delete>
<!-- in installer but not update -->
<fileset dir="plugin/" includes="i2ptunnel.config zzzot.config eepsite/docroot/index.html eepsite/docroot/robots.txt" />
</delete>
<delete dir="plugin/eepsite/docroot/torrents/" />
<!-- get version number -->
<buildnumber file="scripts/build.number" />
<property name="release.number" value="0.10.0" />
<property name="release.number" value="0.14.0" />
<!-- make the update xpi2p -->
<!-- this contains everything except i2ptunnel.config -->
@ -34,18 +38,31 @@
<arg value="plugin/eepsite/webapps/tracker.war.pack" />
<arg value="src/build/tracker.war.jar" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<input message="Enter su3 signing key password:" addproperty="release.password.su3" />
<fail message="You must enter a password." >
<condition>
<equals arg1="${release.password.su3}" arg2=""/>
</condition>
</fail>
<!-- this will fail if no su3 keys exist, as it needs the password twice -->
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
<move file="zzzot.xpi2p" tofile="zzzot-update.xpi2p" overwrite="true" />
<move file="zzzot.su3" tofile="zzzot-update.su3" overwrite="true" />
<!-- make the install xpi2p -->
<copy file="scripts/i2ptunnel.config" todir="plugin/" overwrite="true" />
<copy file="scripts/plugin.config" todir="plugin/" overwrite="true" />
<!-- Files in installer but not update. Be sure to Add to delete fileset above and clean target below -->
<copy file="scripts/i2ptunnel.config" todir="plugin/" overwrite="true" />
<copy file="scripts/zzzot.config" todir="plugin/" overwrite="true" />
<copy file="scripts/index.html" todir="plugin/eepsite/docroot/" overwrite="true" />
<copy file="scripts/robots.txt" todir="plugin/eepsite/docroot/" overwrite="true" />
<mkdir dir="plugin/eepsite/docroot/torrents/" />
<exec executable="echo" osfamily="unix" failonerror="true" output="plugin/plugin.config" append="true">
<arg value="version=${release.number}-b${build.number}" />
</exec>
<exec executable="scripts/makeplugin.sh" failonerror="true" >
<exec executable="scripts/makeplugin.sh" inputstring="${release.password.su3}" failonerror="true" >
<arg value="plugin" />
</exec>
</target>
@ -56,12 +73,19 @@
<ant dir="src" target="clean" />
<delete file="plugin/i2ptunnel.config" />
<delete file="plugin/plugin.config" />
<delete file="plugin/zzzot.config" />
<delete file="plugin/eepsite/docroot/index.html" />
<delete file="plugin/eepsite/docroot/robots.txt" />
<delete file="plugin/lib/zzzot.jar.pack" />
<delete file="plugin/eepsite/webapps/tracker.war.pack" />
<delete file="plugin/LICENSE.txt" />
<delete file="plugin/README.txt" />
<delete file="zzzot.xpi2p" />
<delete file="zzzot-update.xpi2p" />
<delete file="zzzot.su3" />
<delete file="zzzot-update.su3" />
<delete file="plugin.zip" />
<delete dir="plugin/eepsite/docroot/torrents/" />
</target>
</project>

View File

@ -2,7 +2,7 @@ clientApp.0.main=net.i2p.zzzot.ZzzOTController
clientApp.0.name=ZzzOT
clientApp.0.args=-d $PLUGIN start
clientApp.0.stopargs=-d $PLUGIN stop
clientApp.0.delay=15
clientApp.0.delay=40
clientApp.0.startOnLoad=true
# we also use i2p.jar and i2ptunnel.jar, they are in the standard router classpath
clientApp.0.classpath=$PLUGIN/lib/zzzot.jar,$I2P/lib/i2psnark.jar

View File

@ -10,6 +10,9 @@ This link is also at the top of your router console when ZzzOT is running.
<p>Report bugs or add comments on
<a href="http://zzz.i2p//forums/16">the plugin forum on zzz.i2p</a>.
<p><b>This help file is $PLUGIN/eepsite/docroot/help.html, you should probably move it
outside of the document root before you announce your eepsite as it may contain your user name.</b>
<h3>Eepsite Key and Helpful Hints for I2P</h3>
<p>Your Base 32 address is <a href="http://$B32/">$B32</a>.
@ -47,8 +50,6 @@ This link is also at the top of your router console when ZzzOT is running.
If your tracker gets over 1000 peers, you will probably want to increase the number of tunnels.
<p>The Jetty webserver port is 7662. If you must change it, edit jetty.xml, i2ptunnel.config, and plugins.config
in the directory $PLUGIN. Then stop and restart the plugin.
<p>This help file is $PLUGIN/eepsite/docroot/help.html, you should probably move it
outside of the document root before you announce your eepsite as it may contain your user name.
<p>As you probably know, an open tracker does not require torrents to be registered,
and it does not host torrent files. You can, however, host torrent files elsewhere on
the eepsite, for example at <a href="http://$B32/torrents/">/torrents</a>.

View File

@ -46,34 +46,57 @@
<!-- PICK ONE -->
<!-- If you don't have or want threadpool
Requests above the max will be queued
<!--
Recommended.
Requests above the maxThreads + queue_size will be rejected and logged.
ref:
https://wiki.eclipse.org/Jetty/Howto/High_Load
http://trac.i2p2.i2p/ticket/1395
-->
<!--
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Set name="minThreads">1</Set>
<Set name="maxThreads">16</Set>
<Set name="lowThreads">2</Set>
<Arg>
<New class="java.util.concurrent.LinkedBlockingQueue">
<Arg type="int">50</Arg>
</New>
</Arg>
<Set name="minThreads">4</Set>
<Set name="maxThreads">20</Set>
<Set name="maxIdleTimeMs">60000</Set>
<Set name="daemon">true</Set>
<Set name="name">Zzzot Jetty</Set>
</New>
-->
<!-- Optional Java 5 bounded threadpool with job queue
Requests above the max will be rejected and logged.
High-traffic sites should increase maximumPoolSize.
TODO: would be nice to use the 5-arg constructor but
how do you use an Enum as the TimeUnit argument?
Alternatively, make a custom class where we can
set the thread name, set daemon, etc.
See RouterConsoleRunner.
Args are:
corePoolSize (should be at least 3)
maximumPoolSize
keepAliveTime (milliseconds)
timeout (TimeUnit)
queue (BlockingQueue)
Not recommended.
ref:
http://trac.i2p2.i2p/ticket/1395
http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
-->
<!--
<New class="org.eclipse.jetty.util.thread.ExecutorThreadPool">
<!-- corePoolSize (must be at least 3, increase if Jetty hangs) -->
<Arg type="int">4</Arg>
<!-- maximumPoolSize -->
<Arg type="int">3</Arg>
<Arg type="int">20</Arg>
<!-- keepAliveTime (milliseconds) -->
<Arg type="long">60000</Arg>
<Arg>
<Call class="java.util.concurrent.TimeUnit" name="valueOf" >
<Arg>MILLISECONDS</Arg>
</Call>
</Arg>
<Arg>
<New class="java.util.concurrent.SynchronousQueue" />
</Arg>
</New>
-->
</Set>

View File

@ -5,11 +5,16 @@
# usage: makeplugin.sh plugindir
#
# zzz 2010-02
# zzz 2014-08 added support for su3 files
#
PUBKEYDIR=$HOME/.i2p-plugin-keys
PUBKEYFILE=$PUBKEYDIR/plugin-public-signing.key
PRIVKEYFILE=$PUBKEYDIR/plugin-private-signing.key
B64KEYFILE=$PUBKEYDIR/plugin-public-signing.txt
PUBKEYSTORE=$PUBKEYDIR/plugin-su3-public-signing.crt
PRIVKEYSTORE=$PUBKEYDIR/plugin-su3-keystore.ks
KEYTYPE=RSA_SHA512_4096
export I2P=../i2p/pkg-temp
PLUGINDIR=${1:-plugin}
@ -17,46 +22,67 @@ PLUGINDIR=${1:-plugin}
PC=plugin.config
PCT=${PC}.tmp
if [ ! -f $PRIVKEYFILE ]
then
mkdir -p $PUBKEYDIR
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
rm -rf logs/
chmod 444 $PUBKEYFILE $B64KEYFILE
chmod 400 $PRIVKEYFILE
echo "Created new keys: $PUBKEYFILE $PRIVKEYFILE"
fi
rm -f plugin.zip
if [ ! -d $PLUGINDIR ]
then
echo "You must have a $PLUGINDIR directory"
exit 1
fi
OPWD=$PWD
cd $PLUGINDIR
if [ ! -f $PC ]
if [ ! -f $PLUGINDIR/$PC ]
then
echo "You must have a $PC file"
echo "You must have a $PLUGINDIR/$PC file"
exit 1
fi
grep -q '^signer=' $PC
SIGNER=`grep '^signer=' $PLUGINDIR/$PC`
if [ "$?" -ne "0" ]
then
echo "You must have a signer in $PC"
echo 'For example signer=joe@mail.i2p'
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
exit 1
fi
SIGNER=`echo $SIGNER | cut -f 2 -d '='`
if [ ! -f $PRIVKEYFILE ]
then
echo "Creating new XPI2P DSA keys"
mkdir -p $PUBKEYDIR || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate keygen $PUBKEYFILE $PRIVKEYFILE || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.data.Base64 encode $PUBKEYFILE $B64KEYFILE || exit 1
rm -rf logs/
chmod 444 $PUBKEYFILE $B64KEYFILE
chmod 400 $PRIVKEYFILE
echo "Created new XPI2P keys: $PUBKEYFILE $PRIVKEYFILE"
fi
if [ ! -f $PRIVKEYSTORE ]
then
echo "Creating new SU3 $KEYTYPE keys for $SIGNER"
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File keygen -t $KEYTYPE $PUBKEYSTORE $PRIVKEYSTORE $SIGNER || exit 1
echo '*** Save your password in a safe place!!! ***'
rm -rf logs/
# copy to the router dir so verify will work
CDIR=$I2P/certificates/plugin
mkdir -p $CDIR || exit 1
CFILE=$CDIR/`echo $SIGNER | sed s/@/_at_/`.crt
cp $PUBKEYSTORE $CFILE
chmod 444 $PUBKEYSTORE
chmod 400 $PRIVKEYSTORE
chmod 644 $CFILE
echo "Created new SU3 keys: $PUBKEYSTORE $PRIVKEYSTORE"
echo "Copied public key to $CFILE for testing"
fi
rm -f plugin.zip
OPWD=$PWD
cd $PLUGINDIR
grep -q '^name=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a plugin name in $PC"
echo 'For example name=foo'
echo 'For example name=foo'
exit 1
fi
@ -64,7 +90,7 @@ grep -q '^version=' $PC
if [ "$?" -ne "0" ]
then
echo "You must have a version in $PC"
echo 'For example version=0.1.2'
echo 'For example version=0.1.2'
exit 1
fi
@ -72,33 +98,41 @@ fi
grep -v '^date=' $PC > $PCT
DATE=`date '+%s000'`
echo "date=$DATE" >> $PCT
mv $PCT $PC
mv $PCT $PC || exit 1
# add our Base64 key
grep -v '^key=' $PC > $PCT
B64KEY=`cat $B64KEYFILE`
echo "key=$B64KEY" >> $PCT || exit 1
mv $PCT $PC
mv $PCT $PC || exit 1
# zip it
zip -r $OPWD/plugin.zip * -x \*.jar || exit 1
zip -r $OPWD/plugin.zip * || exit 1
# get the version and use it for the sud header
VERSION=`grep '^version=' $PC | cut -f 2 -d '='`
# get the name and use it for the file name
NAME=`grep '^name=' $PC | cut -f 2 -d '='`
XPI2P=${NAME}.xpi2p
SU3=${NAME}.su3
cd $OPWD
# sign it
echo 'Signing. ...'
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign plugin.zip $XPI2P $PRIVKEYFILE $VERSION || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File sign -c PLUGIN -t $KEYTYPE plugin.zip $SU3 $PRIVKEYSTORE $VERSION $SIGNER || exit 1
rm -f plugin.zip
# verify
echo 'Verifying. ...'
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion $XPI2P || exit 1
java -cp $I2P/lib/i2p.jar -Drouter.trustedUpdateKeys=$B64KEY net.i2p.crypto.TrustedUpdate verifysig $XPI2P || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File showversion $SU3 || exit 1
java -cp $I2P/lib/i2p.jar net.i2p.crypto.SU3File verifysig -k $PUBKEYSTORE $SU3 || exit 1
rm -rf logs/
echo -n 'Plugin created: '
echo 'Plugin files created: '
wc -c $XPI2P
wc -c $SU3
exit 0

View File

@ -5,6 +5,9 @@ consoleLinkURL=http://127.0.0.1:7662/tracker/index.jsp
description=Open tracker
author=zzz
updateURL=http://stats.i2p/i2p/plugins/zzzot-update.xpi2p
updateURL.su3=http://stats.i2p/i2p/plugins/zzzot-update.su3
websiteURL=http://zzz.i2p/forums/16
license=Apache 2.0
min-jetty-version=7
max-jetty-version=8.9999
min-i2p-version=0.9.9

3
scripts/zzzot.config Normal file
View File

@ -0,0 +1,3 @@
# announce interval in seconds
# minimum 900 (15 minutes), maximum 21600 (6 hours)
interval=1620

View File

@ -16,22 +16,20 @@ package net.i2p.zzzot;
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
import net.i2p.data.SimpleDataStructure;
/**
* A 20-byte SHA1 info hash
*/
public class InfoHash extends ByteArray {
public class InfoHash extends SimpleDataStructure {
public InfoHash(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public static final int LENGTH = 20;
public InfoHash(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad infohash length: " + data.length);
}
public int length() {
return LENGTH;
}
}

View File

@ -16,22 +16,20 @@ package net.i2p.zzzot;
*
*/
import java.io.UnsupportedEncodingException;
import net.i2p.data.ByteArray;
import net.i2p.data.SimpleDataStructure;
/**
* A 20-byte peer ID
*/
public class PID extends ByteArray {
public class PID extends SimpleDataStructure {
public PID(String data) throws UnsupportedEncodingException {
this(data.getBytes("ISO-8859-1"));
}
public static final int LENGTH = 20;
public PID(byte[] data) {
super(data);
if (data.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + data.length);
}
public int length() {
return LENGTH;
}
}

View File

@ -18,14 +18,12 @@ package net.i2p.zzzot;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.i2p.crypto.SHA256Generator;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/*
* A single peer for a single torrent.
@ -38,15 +36,9 @@ public class Peer extends HashMap<String, Object> {
private long lastSeen;
private long bytesLeft;
private static final ConcurrentHashMap<String, String> destCache = new ConcurrentHashMap();
private static final Integer PORT = Integer.valueOf(6881);
private static final long CLEAN_TIME = 3*60*60*1000;
static {
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
public Peer(byte[] id, Destination address) {
public Peer(byte[] id, Destination address, ConcurrentMap<String, String> destCache) {
super(3);
if (id.length != 20)
throw new IllegalArgumentException("Bad peer ID length: " + id.length);
@ -83,10 +75,4 @@ public class Peer extends HashMap<String, Object> {
return new String(h.getData(), "ISO-8859-1");
} catch (UnsupportedEncodingException uee) { return null; }
}
private static class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
destCache.clear();
}
}
}

View File

@ -0,0 +1,225 @@
package net.i2p.zzzot;
/*
* Modified from:
* http://www.lockergnome.com/awarberg/2007/04/22/random-iterator-in-java/
* No license, free to use
*/
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import net.i2p.util.RandomSource;
import net.i2p.util.SystemVersion;
/**
* Copied from net.i2p.router.util
*
* This is some Java code I wrote for a school project to save some time when iterating in
* random order over a part of list (until some condition becomes true):
*
* Here is a sample on how to use the code:
*
<pre>
for(Iterator<Object> iter = new RandomIterator<Object>(myObjList); iter.hasNext();){
Object o = iter.next();
if(someCondition(o) )
return o; // iteration stopped early
}
</pre>
*
* I wrote it to replace a Collection.shuffle call and this code gave us an overall increase in program execution speed of about 25%.
* As the javadoc description says, you are better off calling Collection.shuffle if you need to iterate over the entire list. But if you may stop early this class can save you some time, as it did in our case.
*
* Provides a random iteration over the given list.
*
* This effect can be achieved by using Collections.shuffle,
* which shuffles the entire collection in linear time.
*
* If the iteration process may end before all items
* are processed, this class may give a speed increase
* because the shuffling process is performed as items are requested
* rather than in the beginning.
*
* I2P changes:
*<pre>
* - Use BitSet instead of boolean[]
* - Use I2P RandomSource
* - Done check in next(), throw NSEE
* - Ensure lower and upper bounds are always clear
* - Replace unbounded loop in next(). It is now O(N) time, but now
* the iterator will tend to "clump" results and thus is not truly random.
* *** This class is not recommended for small Lists,
* *** or for iterating through a large portion of a List.
* *** Use Collections.shuffle() instead.
* - Add test code
*</pre>
*
* @since zzzot 0.14.0
*/
public class RandomIterator<E> implements Iterator<E> {
/**
* Mapping indicating which items were served (by index).
* if served[i] then the item with index i in the list
* has already been served.
*
* Note it is possible to save memory here by using
* BitSet rather than a boolean array, however it will
* increase the running time slightly.
*/
private final BitSet served;
/** The amount of items served so far */
private int servedCount = 0;
private final List<E> list;
private final int LIST_SIZE;
/**
* The random number generator has a great influence
* on the running time of this iterator.
*
* See, for instance,
* <a href="http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation" title="http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation" target="_blank">http://www.qbrundage.com/michaelb/pubs/e&#8230;</a>
* for some implementations, which are faster than java.util.Random.
*/
private final Random rand = RandomSource.getInstance();
/** Used to narrow the range to take random indexes from */
private int lower, upper;
private static final boolean hasAndroidBug;
static {
if (SystemVersion.isAndroid()) {
// only present on Gingerbread (API 11), but set if version check failed also
int ver = SystemVersion.getAndroidVersion();
hasAndroidBug = ver == 11 || ver == 0;
if (hasAndroidBug)
testAndroid();
} else {
hasAndroidBug = false;
}
}
public RandomIterator(List<E> list){
this.list = list;
LIST_SIZE = list.size();
served = new BitSet(LIST_SIZE);
upper = LIST_SIZE - 1;
}
public boolean hasNext() {
return servedCount < LIST_SIZE;
}
public E next() {
if (!hasNext())
throw new NoSuchElementException();
int range = upper - lower + 1;
// This has unbounded behavior, even with lower/upper
//int index;
//do {
// index = lower + rand.nextInt(range);
//} while (served.get(index));
// This tends to "clump" results, escpecially toward the end of the iteration.
// It also tends to leave the first and last few elements until the end.
int start = lower + rand.nextInt(range);
int index;
if ((start % 2) == 0) // coin flip
index = served.nextClearBit(start);
else
index = previousClearBit(start);
if (index < 0)
throw new NoSuchElementException("shouldn't happen");
servedCount++;
served.set(index);
// check if the range from which random values
// are taken can be reduced
// I2P - ensure lower and upper are always clear
if (hasNext()) {
if (index == lower)
// workaround for Android ICS bug - see below
lower = hasAndroidBug ? nextClearBit(index) : served.nextClearBit(index);
else if (index == upper)
upper = previousClearBit(index - 1);
}
return list.get(index);
}
/** just like nextClearBit() */
private int previousClearBit(int n) {
for (int i = n; i >= lower; i--) {
if (!served.get(i)) {
return i;
}
}
return -1;
}
/**
* Workaround for bug in Android (ICS only?)
* http://code.google.com/p/android/issues/detail?id=31036
* @since 0.9.2
*/
private int nextClearBit(int n) {
for (int i = n; i <= upper; i++) {
if (!served.get(i)) {
return i;
}
}
return -1;
}
/**
* @throws UnsupportedOperationException always
*/
public void remove() {
throw new UnsupportedOperationException();
}
/****
public static void main(String[] args) {
testAndroid();
test(0);
test(1);
test(2);
test(1000);
}
private static void test(int n) {
System.out.println("testing with " + n);
List<Integer> l = new ArrayList<Integer>(n);
for (int i = 0; i < n; i++) {
l.add(Integer.valueOf(i));
}
for (Iterator<Integer> iter = new RandomIterator<Integer>(l); iter.hasNext(); ) {
System.out.println(iter.next().toString());
}
}
****/
/**
* Test case from android ticket above
* @since 0.9.2
*/
private static void testAndroid() {
System.out.println("Checking for Android BitSet bug");
BitSet theBitSet = new BitSet(864);
for (int exp =0; exp < 864; exp++) {
int act = theBitSet.nextClearBit(0);
if (exp != act) {
System.err.println(String.format("Test failed for: exp=%d, act=%d", exp, act));
System.err.println("Android BitSet bug detected, workaround implemented!");
return;
}
theBitSet.set(exp);
}
System.err.println("Android BitSet bug NOT detected, no workaround needed!");
}
}

View File

@ -18,13 +18,30 @@ package net.i2p.zzzot;
import java.util.concurrent.ConcurrentHashMap;
import net.i2p.CoreVersion;
import net.i2p.data.DataHelper;
import net.i2p.data.SDSCache;
import net.i2p.util.VersionComparator;
/**
* All the torrents
*/
public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
public Torrents() {
private static final int CACHE_SIZE = 2048;
private final SDSCache<InfoHash> _hashCache;
private final SDSCache<PID> _pidCache;
private final Integer _interval;
/**
* @param interval in seconds
*/
public Torrents(int interval) {
super();
_hashCache = new SDSCache<InfoHash>(InfoHash.class, InfoHash.LENGTH, CACHE_SIZE);
_pidCache = new SDSCache<PID>(PID.class, PID.LENGTH, CACHE_SIZE);
_interval = Integer.valueOf(interval);
}
public int countPeers() {
@ -34,4 +51,60 @@ public class Torrents extends ConcurrentHashMap<InfoHash, Peers> {
}
return rv;
}
/**
* @return in seconds
* @since 0.12.0
*/
public Integer getInterval() {
return _interval;
}
/**
* Pull from cache or return new
*
* @throws IllegalArgumentException if data is not the correct number of bytes
* @since 0.12.0
*/
public InfoHash createInfoHash(String data) throws IllegalArgumentException {
byte[] d = DataHelper.getASCII(data);
if (d.length != InfoHash.LENGTH)
throw new IllegalArgumentException("bad infohash length " + d.length);
return _hashCache.get(d);
}
/**
* Pull from cache or return new
*
* @throws IllegalArgumentException if data is not the correct number of bytes
* @since 0.12.0
*/
public PID createPID(String data) throws IllegalArgumentException {
byte[] d = DataHelper.getASCII(data);
if (d.length != PID.LENGTH)
throw new IllegalArgumentException("bad peer id length " + d.length);
return _pidCache.get(d);
}
/**
* @since 0.12.0
*/
@Override
public void clear() {
super.clear();
clearCaches();
}
/**
* @since 0.12.0
*/
private void clearCaches() {
// not available until 0.9.17
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") >= 0) {
try {
_hashCache.clear();
_pidCache.clear();
} catch (Throwable t) {}
}
}
}

View File

@ -17,34 +17,74 @@ package net.i2p.zzzot;
*/
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
import net.i2p.I2PAppContext;
import net.i2p.util.SimpleTimer2;
/**
* Instantiate this to fire it up
*/
class ZzzOT {
private Torrents _torrents;
private static final long CLEAN_TIME = 4*60*1000;
private static final long EXPIRE_TIME = 60*60*1000;
private final Torrents _torrents;
private final Cleaner _cleaner;
private final ConcurrentHashMap<String, String> _destCache = new ConcurrentHashMap<String, String>();
private final long EXPIRE_TIME;
ZzzOT() {
_torrents = new Torrents();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
private static final String PROP_INTERVAL = "interval";
private static final long CLEAN_TIME = 4*60*1000;
private static final long DEST_CACHE_CLEAN_TIME = 3*60*60*1000;
private static final int DEFAULT_INTERVAL = 27*60;
private static final int MIN_INTERVAL = 15*60;
private static final int MAX_INTERVAL = 6*60*60;
ZzzOT(I2PAppContext ctx, Properties p) {
String intv = p.getProperty(PROP_INTERVAL);
int interval = DEFAULT_INTERVAL;
if (intv != null) {
try {
interval = Integer.parseInt(intv);
if (interval < MIN_INTERVAL)
interval = MIN_INTERVAL;
else if (interval > MAX_INTERVAL)
interval = MAX_INTERVAL;
} catch (NumberFormatException nfe) {}
}
_torrents = new Torrents(interval);
EXPIRE_TIME = 1000 * (interval + interval / 2);
_cleaner = new Cleaner(ctx);
}
Torrents getTorrents() {
return _torrents;
}
void stop() {
_torrents.clear();
// no way to stop the cleaner
/** @since 0.9.14 */
ConcurrentHashMap<String, String> getDestCache() {
return _destCache;
}
private class Cleaner implements SimpleTimer.TimedEvent {
void start() {
_cleaner.forceReschedule(CLEAN_TIME);
}
void stop() {
_cleaner.cancel();
_torrents.clear();
_destCache.clear();
}
private class Cleaner extends SimpleTimer2.TimedEvent {
private final AtomicInteger _runCount = new AtomicInteger();
/** must schedule later */
public Cleaner(I2PAppContext ctx) {
super(ctx.simpleTimer2());
}
public void timeReached() {
long now = System.currentTimeMillis();
@ -61,6 +101,10 @@ class ZzzOT {
if (recent <= 0)
iter.remove();
}
if (_runCount.incrementAndGet() % (DEST_CACHE_CLEAN_TIME / CLEAN_TIME) == 0) {
_destCache.clear();
}
schedule(CLEAN_TIME);
}
}
}

View File

@ -21,19 +21,27 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentMap;
import net.i2p.CoreVersion;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.app.ClientAppState;
import static net.i2p.app.ClientAppState.*;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.data.Base32;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.PrivateKeyFile;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.i2ptunnel.TunnelController;
import net.i2p.apps.systray.UrlLauncher;
import net.i2p.util.VersionComparator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.xml.XmlConfiguration;
/**
@ -47,39 +55,93 @@ import org.eclipse.jetty.xml.XmlConfiguration;
*
* @author zzz
*/
public class ZzzOTController {
private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ZzzOTController.class);
private static Server _server;
private static TunnelController _tunnel;
private static ZzzOT _zzzot;
private static Object _lock = new Object();
public class ZzzOTController implements ClientApp {
private final I2PAppContext _context;
private final Log _log;
private final String[] _args;
private final ClientAppManager _mgr;
private Server _server;
private TunnelController _tunnel;
private final ZzzOT _zzzot;
/** only for main() */
private static volatile ZzzOTController _controller;
private ClientAppState _state = UNINITIALIZED;
private static final String NAME = "ZzzOT";
private static final String CONFIG_FILE = "zzzot.config";
private static final String BACKUP_SUFFIX = ".jetty6";
private static final String[] xmlFiles = {
"jetty.xml", "contexts/base-context.xml", "contexts/cgi-context.xml",
"etc/realm.properties", "etc/webdefault.xml" };
public static void main(String args[]) {
if (args.length != 3 || (!"-d".equals(args[0])))
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
if ("start".equals(args[2]))
start(args);
else if ("stop".equals(args[2]))
stop();
else
throw new IllegalArgumentException("Usage: PluginController -d $PLUGIN [start|stop]");
}
public static Torrents getTorrents() {
synchronized(_lock) {
if (_zzzot == null)
_zzzot = new ZzzOT();
/**
* @since 0.12.0
*/
public ZzzOTController(I2PAppContext ctx, ClientAppManager mgr, String args[]) {
_context = ctx;
_log = ctx.logManager().getLog(ZzzOTController.class);
_mgr = mgr;
_args = args;
File cfile = new File(_context.getAppDir(), "plugins/zzzot/" + CONFIG_FILE);
Properties props = new Properties();
if (cfile.exists()) {
try {
DataHelper.loadProps(props, cfile);
} catch (IOException ioe) {
_log.error("Failed loading zzzot config from " + cfile, ioe);
}
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("No config file " + cfile);
}
return _zzzot.getTorrents();
_zzzot = new ZzzOT(ctx, props);
_state = INITIALIZED;
}
private static void start(String args[]) {
File pluginDir = new File(args[1]);
/**
* No longer supported, as we now need the ClientAppManager for the webapp to find us
*/
public synchronized static void main(String args[]) {
throw new UnsupportedOperationException("Must use ClientApp interface");
}
/**
* @return null if not running
*/
public static Torrents getTorrents() {
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
if (mgr == null)
return null;
ClientApp z = mgr.getRegisteredApp(NAME);
if (z == null)
return null;
ZzzOTController ctrlr = (ZzzOTController) z;
return ctrlr._zzzot.getTorrents();
}
/**
* @return null if not running
* @since 0.9.14
*/
public static ConcurrentMap<String, String> getDestCache() {
ClientAppManager mgr = I2PAppContext.getGlobalContext().clientAppManager();
if (mgr == null)
return null;
ClientApp z = mgr.getRegisteredApp(NAME);
if (z == null)
return null;
ZzzOTController ctrlr = (ZzzOTController) z;
return ctrlr._zzzot.getDestCache();
}
/**
* @param args ignored
*/
private void start(String args[]) {
//File pluginDir = new File(args[1]);
File pluginDir = new File(_context.getAppDir(), "plugins/zzzot");
if (!pluginDir.exists())
throw new IllegalArgumentException("Plugin directory " + pluginDir.getAbsolutePath() + " does not exist");
@ -96,20 +158,21 @@ public class ZzzOTController {
_log.error("Unable to create " + key.getAbsolutePath() + ' ' + e);
throw new IllegalArgumentException("Unable to create " + key.getAbsolutePath() + ' ' + e);
}
_log.error("NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
_log.error("NOTICE: ZzzOT: You should back up this file!");
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: New eepsite keys created in " + key.getAbsolutePath());
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: You should back up this file!");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
String b64 = dest.toBase64();
_log.error("NOTICE: ZzzOT: Your base 32 address is " + b32);
_log.error("NOTICE: ZzzOT: Your base 64 address is " + b64);
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: Your base 32 address is " + b32);
_log.logAlways(Log.INFO, "NOTICE: ZzzOT: Your base 64 address is " + b64);
}
startJetty(pluginDir, dest);
startI2PTunnel(pluginDir, dest);
_zzzot.start();
// SeedlessAnnouncer.announce(_tunnel);
}
private static void startI2PTunnel(File pluginDir, Destination dest) {
private void startI2PTunnel(File pluginDir, Destination dest) {
File i2ptunnelConfig = new File(pluginDir, "i2ptunnel.config");
Properties i2ptunnelProps = new Properties();
try {
@ -131,15 +194,15 @@ public class ZzzOTController {
_tunnel = tun;
}
private static void startJetty(File pluginDir, Destination dest) {
private void startJetty(File pluginDir, Destination dest) {
if (_server != null)
throw new IllegalArgumentException("Jetty already running!");
migrateJettyXML(pluginDir);
I2PAppContext context = I2PAppContext.getGlobalContext();
File tmpdir = new File(context.getTempDir().getAbsolutePath(), "/zzzot-work");
File tmpdir = new File(_context.getTempDir().getAbsolutePath(), "/zzzot-work");
tmpdir.mkdir();
File jettyXml = new File(pluginDir, "jetty.xml");
try {
Resource.setDefaultUseCaches(false);
XmlConfiguration xmlc = new XmlConfiguration(jettyXml.toURI().toURL());
Server serv = (Server) xmlc.configure();
//HttpContext[] hcs = serv.getContexts();
@ -155,18 +218,29 @@ public class ZzzOTController {
launchHelp(pluginDir, dest);
}
private static void stop() {
private void stop() {
stopI2PTunnel();
stopJetty();
if (_zzzot != null)
_zzzot.stop();
_zzzot.stop();
}
private static void stopI2PTunnel() {
private void stopI2PTunnel() {
if (_tunnel == null)
return;
try {
_tunnel.stopTunnel();
// destroyTunnel() not available until 0.9.17, but we put 0.9.16 here for now
// so we get testing in 0.9.16-6 or later dev builds.
// No access to RouterVersion here.
// TODO change to 0.9.17
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.16") >= 0) {
try {
_tunnel.destroyTunnel();
} catch (Throwable t) {
_tunnel.stopTunnel();
}
} else {
_tunnel.stopTunnel();
}
} catch (Throwable t) {
_log.error("ZzzOT tunnel stop failed", t);
throw new IllegalArgumentException("Tunnel stop failed " + t);
@ -174,7 +248,7 @@ public class ZzzOTController {
_tunnel = null;
}
private static void stopJetty() {
private void stopJetty() {
if (_server == null)
return;
try {
@ -190,7 +264,7 @@ public class ZzzOTController {
* Migate the jetty configuration files.
* Save old jetty.xml if moving from jetty 5 to jetty 6
*/
private static void migrateJettyXML(File pluginDir) {
private void migrateJettyXML(File pluginDir) {
// contexts dir does not exist in Jetty 5
File file = new File(pluginDir, "contexts");
file.mkdir();
@ -220,7 +294,7 @@ public class ZzzOTController {
* @return success
* @since Jetty 7
*/
private static boolean backupAndMigrateFile(File toDir, String filename) {
private boolean backupAndMigrateFile(File toDir, String filename) {
File to = new File(toDir, filename);
boolean rv = backupFile(to);
boolean rv2 = migrateJettyFile(toDir, filename);
@ -249,7 +323,7 @@ public class ZzzOTController {
/**
* Migate a single jetty config file, replacing $PLUGIN as we copy it.
*/
private static boolean migrateJettyFile(File pluginDir, String name) {
private boolean migrateJettyFile(File pluginDir, String name) {
File templateDir = new File(pluginDir, "templates");
File fileTmpl = new File(templateDir, name);
File outFile = new File(pluginDir, name);
@ -271,7 +345,7 @@ public class ZzzOTController {
}
/** put the directory, base32, and base64 info in the help.html file and launch a browser window to display it */
private static void launchHelp(File pluginDir, Destination dest) {
private void launchHelp(File pluginDir, Destination dest) {
File fileTmpl = new File(pluginDir, "templates/help.html");
File outFile = new File(pluginDir, "eepsite/docroot/help.html");
String b32 = Base32.encode(dest.calculateHash().getData()) + ".b32.i2p";
@ -298,4 +372,83 @@ public class ZzzOTController {
UrlLauncher.main(new String[] { "http://127.0.0.1:7662/help.html" } );
}
}
/////// ClientApp methods
/** @since 0.12.0 */
public synchronized void startup() {
if (_mgr != null) {
// this is really ugly, but thru 0.9.16,
// stopping a ClientApp plugin with $PLUGIN in the args fails,
// and it tries to start a second one instead.
// Find the first one and stop it.
ClientApp z = _mgr.getRegisteredApp(NAME);
if (z != null) {
if (VersionComparator.comp(CoreVersion.VERSION, "0.9.17") < 0) {
ZzzOTController ctrlr = (ZzzOTController) z;
_log.warn("Got start when another zzzot running, stopping him instead");
ctrlr.shutdown(null);
} else {
_log.error("ZzzOT already running");
}
changeState(START_FAILED);
return;
}
}
if (_state != STOPPED && _state != INITIALIZED && _state != START_FAILED) {
_log.error("Start while state = " + _state);
return;
}
changeState(STARTING);
try {
start(_args);
changeState(RUNNING);
if (_mgr != null)
_mgr.register(this);
} catch (Exception e) {
changeState(START_FAILED, "Start failed", e);
}
}
/** @since 0.12.0 */
public synchronized void shutdown(String[] args) {
if (_state == STOPPED)
return;
changeState(STOPPING);
if (_mgr != null)
_mgr.unregister(this);
stop();
changeState(STOPPED);
}
/** @since 0.12.0 */
public ClientAppState getState() {
return _state;
}
/** @since 0.12.0 */
public String getName() {
return NAME;
}
/** @since 0.12.0 */
public String getDisplayName() {
return NAME;
}
/////// end ClientApp methods
/** @since 0.12.0 */
private synchronized void changeState(ClientAppState state) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, null, null);
}
/** @since 0.12.0 */
private synchronized void changeState(ClientAppState state, String msg, Exception e) {
_state = state;
if (_mgr != null)
_mgr.notify(this, state, msg, e);
}
}

View File

@ -1,4 +1,4 @@
<%@page import="java.util.ArrayList" %><%@page import="java.util.Collections" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.Destination" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
<%@page import="java.io.ByteArrayInputStream,java.util.ArrayList,java.util.Collections,java.util.List,java.util.Map,java.util.HashMap,java.util.concurrent.ConcurrentMap,net.i2p.data.Base64,net.i2p.data.Destination,net.i2p.zzzot.*,org.klomp.snark.bencode.BEncoder" %><%
/*
* Above one-liner is so there is no whitespace -> IllegalStateException
@ -27,12 +27,15 @@
*/
// would be nice to make these configurable
final int MAX_RESPONSES = 25;
final int INTERVAL = 27*60;
final boolean ALLOW_IP_MISMATCH = false;
final boolean ALLOW_COMPACT_RESPONSE = true;
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
// above doesn't work for the query string
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
@ -69,6 +72,11 @@
msg = "no info hash";
}
if (!fail && info_hash.length() != 20) {
fail = true;
msg = "bad info hash length " + info_hash.length();
}
if (ip == null && !fail) {
fail = true;
msg = "no ip (dest)";
@ -79,11 +87,22 @@
msg = "no peer id";
}
if (!fail && peer_id.length() != 20) {
fail = true;
msg = "bad peer id length " + peer_id.length();
}
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null && !fail) {
fail = true;
msg = "tracker is down";
}
InfoHash ih = null;
if (!fail) {
try {
ih = new InfoHash(info_hash);
} catch (Exception e) {
ih = torrents.createInfoHash(info_hash);
} catch (IllegalArgumentException e) {
fail = true;
msg = "bad infohash " + e;
}
@ -94,7 +113,10 @@
try {
if (ip.endsWith(".i2p"))
ip = ip.substring(0, ip.length() - 4);
d = new Destination(ip); // from b64 string
byte[] b = Base64.decode(ip);
if (b == null)
throw new Exception();
d = Destination.create(new ByteArrayInputStream(b)); // cache
} catch (Exception e) {
fail = true;
msg = "bad dest " + e;
@ -104,8 +126,8 @@
PID pid = null;
if (!fail) {
try {
pid = new PID(peer_id);
} catch (Exception e) {
pid = torrents.createPID(peer_id);
} catch (IllegalArgumentException e) {
fail = true;
msg = "bad peer id " + e;
}
@ -155,15 +177,14 @@
} catch (NumberFormatException nfe) {};
}
Torrents torrents = ZzzOTController.getTorrents();
Map<String, Object> m = new HashMap();
Map<String, Object> m = new HashMap(8);
if (fail) {
m.put("failure reason", msg);
} else if ("stopped".equals(event)) {
Peers peers = torrents.get(ih);
if (matchIP && peers != null)
peers.remove(pid);
m.put("interval", Integer.valueOf(INTERVAL));
m.put("interval", torrents.getInterval());
} else {
Peers peers = torrents.get(ih);
if (peers == null) {
@ -176,7 +197,8 @@
// fixme same peer id, different dest
Peer p = peers.get(pid);
if (p == null) {
p = new Peer(pid.getData(), d);
ConcurrentMap<String, String> destCache = ZzzOTController.getDestCache();
p = new Peer(pid.getData(), d, destCache);
// don't add if spoofed
if (matchIP) {
Peer p2 = peers.putIfAbsent(pid, p);
@ -188,7 +210,7 @@
if (matchIP)
p.setLeft(left);
m.put("interval", Integer.valueOf(INTERVAL));
m.put("interval", torrents.getInterval());
int size = peers.size();
int seeds = peers.countSeeds();
m.put("complete", Integer.valueOf(seeds));
@ -200,8 +222,17 @@
List<Peer> peerlist = new ArrayList(peers.values());
peerlist.remove(p); // them
if (want < size - 1) {
Collections.shuffle(peerlist);
peerlist = peerlist.subList(0, want);
if (size > 150) {
// If size is huge, use random iterator for efficiency
List<Peer> rv = new ArrayList<Peer>(size);
for (RandomIterator<Peer> iter = new RandomIterator(peerlist); iter.hasNext(); ) {
rv.add(iter.next());
}
peerlist = rv;
} else {
Collections.shuffle(peerlist);
peerlist = peerlist.subList(0, want);
}
}
if (compact) {
// old experimental way - list of hashes

View File

@ -1,4 +1,4 @@
<%@page import="net.i2p.zzzot.ZzzOTController" %>
<%@page import="net.i2p.zzzot.ZzzOTController,net.i2p.zzzot.Torrents" %>
<html>
<head>
<title>ZzzOT</title>
@ -6,9 +6,20 @@
<p>
zzzot
<p>
<%
Torrents torrents = ZzzOTController.getTorrents();
if (torrents != null) {
%>
<table cellspacing="8">
<tr><td>Torrents:<td align="right"><%=ZzzOTController.getTorrents().size()%>
<tr><td>Peers:<td align="right"><%=ZzzOTController.getTorrents().countPeers()%>
<tr><td>Torrents:<td align="right"><%=torrents.size()%>
<tr><td>Peers:<td align="right"><%=torrents.countPeers()%>
</table>
<%
} else {
%>
ZzzOT is not running
<%
}
%>
</body>
</html>

View File

@ -1,4 +1,4 @@
<%@page import="java.util.ArrayList" %><%@page import="java.util.List" %><%@page import="java.util.Map" %><%@page import="java.util.HashMap" %><%@page import="net.i2p.zzzot.*" %><%@page import="org.klomp.snark.bencode.BEncoder" %><%
<%@page import="java.util.ArrayList,java.util.List,java.util.Map,java.util.HashMap,net.i2p.zzzot.*,org.klomp.snark.bencode.BEncoder" %><%
/*
* Above one-liner is so there is no whitespace -> IllegalStateException
@ -27,6 +27,10 @@
*/
// so the chars will turn into bytes correctly
request.setCharacterEncoding("ISO-8859-1");
// above doesn't work for the query string
// https://wiki.eclipse.org/Jetty/Howto/International_Characters
// we could also do ((org.eclipse.jetty.server.Request) request).setQueryEncoding("ISO-8859-1")
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
java.io.OutputStream cout = response.getOutputStream();
response.setCharacterEncoding("ISO-8859-1");
response.setContentType("text/plain");
@ -46,20 +50,24 @@
boolean all = info_hash == null;
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null && !fail) {
fail = true;
msg = "tracker is down";
}
InfoHash ih = null;
if ((!all) && !fail) {
try {
ih = new InfoHash(info_hash);
ih = torrents.createInfoHash(info_hash);
} catch (Exception e) {
fail = true;
msg = "bad infohash " + e;
}
}
Torrents torrents = ZzzOTController.getTorrents();
// build 3-level dictionary
Map<String, Object> m = new HashMap();
Map<String, Object> m = new HashMap(4);
if (fail) {
m.put("failure reason", msg);
} else {

View File

@ -1,4 +1,4 @@
<%@page import="net.i2p.crypto.SHA256Generator" %><%@page import="net.i2p.data.Base32" %><%@page import="net.i2p.data.Base64" %><%@page import="net.i2p.data.DataHelper" %><%@page import="net.i2p.zzzot.*" %><%
<%@page import="net.i2p.crypto.SHA256Generator,net.i2p.data.Base32,net.i2p.data.Base64,net.i2p.data.DataHelper,net.i2p.zzzot.*" %><%
/*
* Copyright 2010 zzz (zzz@mail.i2p)
@ -62,6 +62,10 @@
} else if (req.startsWith("locate dG9ycmVud")) { // locate b64(torrent)
// all the peers
Torrents torrents = ZzzOTController.getTorrents();
if (torrents == null) {
response.setStatus(503, "Down");
return;
}
for (InfoHash ihash : torrents.keySet()) {
Peers peers = torrents.get(ihash);
if (peers == null)