Compare commits

...

103 Commits

Author SHA1 Message Date
complication
baebd1fdd2 * Update versions, package release 2009-01-24 23:57:39 +00:00
zzz
28cfd8cffe sv back in the updater 2009-01-22 17:36:03 +00:00
zzz
a4468219c7 sv take 4 2009-01-22 17:24:38 +00:00
zzz
70f07e5bc7 sv encoding take 3 2009-01-22 16:03:12 +00:00
zzz
173e8a0434 console css tweaks 2009-01-22 16:00:41 +00:00
zzz
72fd42ef9b -11 2009-01-17 17:35:14 +00:00
zzz
ba7dbf9064 propagate from branch 'i2p.i2p.zzz.test' (head d4e23b124489f9a3dd9410aa941e88823702b950)
to branch 'i2p.i2p' (head 7a54e1c58b8cf2ad43830ddec6d404229e3e6e60)
2009-01-17 17:33:17 +00:00
zzz
807f0665b1 tweak 2009-01-17 17:31:00 +00:00
zzz
416b0e4540 Prevent two NTCP Pumpers 2009-01-17 17:28:37 +00:00
zzz
011ded2ee4 -10 2009-01-14 17:06:52 +00:00
zzz
f9faf3c70d propagate from branch 'i2p.i2p.zzz.test' (head 4bd16d213231d7bd4373d4b57c449b358389f568)
to branch 'i2p.i2p' (head c7655ab1094ca15b4485ea2ac66085e87e28b0d6)
2009-01-14 17:04:35 +00:00
zzz
0ea532c72e reduce initial RTT to 8s 2009-01-14 13:50:44 +00:00
zzz
104cf8346e add .de thx echelon 2009-01-13 21:17:01 +00:00
zzz
3e7e5d6113 dont build sam tests by default 2009-01-13 19:54:07 +00:00
zzz
0275c5e13b crstrack 2009-01-13 19:53:35 +00:00
zzz
1c76d240e0 * i2psnark:
- Fix double completion message
      - Add crstrack
2009-01-13 19:52:45 +00:00
zzz
366da1b37c add b32 config for mosfet 2009-01-13 19:32:10 +00:00
zzz
bdcb625e6d fix rare NPE 2009-01-13 19:28:09 +00:00
zzz
8296723533 * HTTPClient: Fix per-tunnel settings for i2cp.gzip and i2ptunnel.httpclient.send* (thx tino) 2009-01-13 19:27:14 +00:00
zzz
957c809774 drop more syndie files 2009-01-12 16:28:36 +00:00
zzz
70b99cf4f9 prevent possible latency-measuring attack 2009-01-12 14:51:38 +00:00
zzz
05a6353142 .b32.i2p 2009-01-12 14:31:43 +00:00
zzz
85615b972b noobhelp 2009-01-12 05:21:16 +00:00
zzz
e3abea1ad2 add netdb links on tunnels.jsp 2009-01-11 15:25:23 +00:00
zzz
bc54908a22 cleanups using getProperty(String, int) 2009-01-10 22:34:07 +00:00
zzz
60bd9803f0 fix burst seconds display 2009-01-10 22:30:43 +00:00
zzz
c3360cc3d7 remove 1m fail column 2009-01-10 21:47:38 +00:00
zzz
aa71725159 -9 2009-01-08 21:00:08 +00:00
zzz
574713e608 propagate from branch 'i2p.i2p.zzz.test' (head 27dec7ffd064f6ecb40189c0438e4aee9f887a9c)
to branch 'i2p.i2p' (head 5aa9ccf6d6abec74c2d0d92ca02bc807463be93b)
2009-01-08 20:53:56 +00:00
zzz
0aaae0b0da robt4 2009-01-08 20:48:57 +00:00
zzz
ed34964747 reduce fast retx threshold to 2 2009-01-08 20:41:50 +00:00
zzz
7b758d89d0 * ExploreJob/SearchJob - more fixes:
- Disable ExploreKeySelectorJob completely, just have
        StartExplorersJob select a random key if queue is empty
      - Add netDb.alwaysQuery=[B64Hash] for debugging
      - Queue results of exploration for more exploration
      - Floodfills periodically shuffle their KBuckets, and
        FloodfillPeerSelector sorts more keys, so that
        exploration works well
2009-01-08 19:27:57 +00:00
zzz
1c7111eca0 alternate base32 check 2009-01-07 14:48:16 +00:00
zzz
831f09c91a fix corruption of update urls 2009-01-05 20:30:36 +00:00
zzz
4f836a20e1 * ExploreJob/SearchJob - fix brokenness:
- Give each search a minimum of time even at the end
      - Fix ExploreJob exclude peer list
      - Always add floodfills to exclude peer list
      - Don't queue keys for exploration or run ExploreJob
        if floodfill
      - Allow floodfills to return non-floodfills in
        a DSRM msg so exploration works
2009-01-05 15:21:00 +00:00
zzz
8faeaaa1ae Transport: Don't shitlist a peer if we are at our connection limit 2009-01-05 15:16:14 +00:00
zzz
7271289c1f Shitlist: Reduce max time to 30m (was 60m) 2009-01-05 15:13:42 +00:00
zzz
8421ae1ed4 * Streaming: Reduce default initial window size from 12 to 6,
to account for the MTU increase in the last release
2009-01-05 15:12:56 +00:00
zzz
d042c6b921 recognize robert 0.3 2009-01-05 15:11:36 +00:00
zzz
e2e4516a8f Fix display of outbound backup count 2009-01-05 15:11:00 +00:00
zzz
efc604a25c Remove readme_xx.html from updater 2009-01-05 15:06:56 +00:00
zzz
5c1864ed5e addressbook: Prevent Base32 hostnames 2009-01-05 15:06:29 +00:00
zzz
debf92fd9b history for prop., -8 2009-01-03 00:49:33 +00:00
zzz
9477b139be propagate from branch 'i2p.i2p.zzz.test' (head 014db28e7b42a25a02de0c0eee5f2fc57352e268)
to branch 'i2p.i2p' (head e4d9945a49c24434a8eaf34d142e033a3a6e0828)
2009-01-03 00:42:14 +00:00
zzz
53ce3c4802 sort torrents with a locale-based sort 2009-01-03 00:05:03 +00:00
zzz
d61af12867 clean up and fix the possibly broken browser launcher config 2009-01-02 20:09:20 +00:00
zzz
908c542b40 move buttons 2009-01-02 18:07:16 +00:00
zzz
ef998349cc require router.memoryUsed stat 2009-01-01 13:58:00 +00:00
zzz
44446d76e4 convert db to concurrent 2009-01-01 13:54:42 +00:00
zzz
a616a5f1c9 prep for upcoming torrent updater 2009-01-01 13:13:04 +00:00
zzz
c0b616e519 revert core version, -7 2009-01-01 12:45:33 +00:00
sponge
b4d3986006 router and core version bump 2008-12-31 07:53:30 +00:00
sponge
ba9108f937 bump revision to 0.0.3 2008-12-30 13:20:54 +00:00
sponge
161379f004 Removed debug line. 2008-12-30 10:29:39 +00:00
sponge
841feaedff Bugfix for getting Properties to actually work. 2008-12-30 07:52:04 +00:00
sponge
ba8de6c565 Spelling error correction. 2008-12-22 23:04:41 +00:00
zzz
d6148db455 * NetDb:
- Expire routers with introducers after 90m.
        This should improve reachability to firewalled routers
        by keeping introducer info current.
      - Expire routers with no addresses after 90m.
2008-12-21 14:43:09 +00:00
zzz
33b43f40b9 try again to kill the i2psnarkurl files 2008-12-20 02:33:57 +00:00
zzz
4336dc441e Remove spurious UDP warning on startup 2008-12-20 01:04:19 +00:00
zzz
2d86e7cf60 add router.memoryUsed stat 2008-12-20 01:00:53 +00:00
zzz
219e96d416 Remove apps/ bogobot jdom pants q rome stasher syndie 2008-12-15 21:54:52 +00:00
zzz
0c72fe7383 history for prop -5 2008-12-14 15:14:05 +00:00
zzz
369599fedd propagate from branch 'i2p.i2p.zzz.test' (head c021d3213ed91036828c43f1e93916e319d47bc1)
to branch 'i2p.i2p' (head f571e6566b12cd0ae93fd57157b849d5a963612f)
2008-12-14 15:10:12 +00:00
zzz
847c9dafce * I2CP, HostsTxtNamingService, I2PTunnel:
Implement Base32 Hash hostnames, via the naming service.
      Names are of the form [52-characters].i2p, where
      the 52 characters are the Base32 representation of our
      256-byte hash. The client requests a lookup of the hash
      via a brief I2CP session using new I2CP request/reply
      messages. The router looks up the leaseset for the hash
      to convert the hash to a dest. Convert the I2PTunnel
      'preview' links to use Base32 hostnames as a
      demonstration.
2008-12-14 15:03:11 +00:00
zzz
734818f651 * Transport:
- Cleanup max connections code
      - Add i2np.udp.maxConnections
      - Set max connections based on share bandwidth
      - Add haveCapacity() that can be used for connection
        throttling in the router
      - Reject IBGW/OBEP requests when near connection limit
      - Reduce idle timeout when near connection limit
    * Tunnel request handler:
      - Require tunnel.dropLoad* stats
      - Speed up request loop
2008-12-14 14:07:37 +00:00
zzz
dae6fd47d9 javadoc fixes 2008-12-13 18:07:20 +00:00
zzz
0956393cf3 add int getProperty(String prop, int default) 2008-12-10 16:32:26 +00:00
zzz
d16f187394 change restart/shutdown/update links to buttons 2008-12-10 16:25:09 +00:00
zzz
962a8f6f49 more splitting classes 2008-12-10 15:37:28 +00:00
sponge
9aa8707647 Prepended log LVL to messages, added INFO LVL 2008-12-08 22:34:45 +00:00
zzz
1fdd228a9d constructor fix 2008-12-08 16:38:46 +00:00
zzz
819d857550 Do not build tests 2008-12-08 15:03:45 +00:00
zzz
04fb12932f prop history, -4 2008-12-08 14:12:29 +00:00
zzz
703b6ed190 propagate from branch 'i2p.i2p.zzz.test' (head eac1d36c16cf82b0d98167c58e1562aa443ee5e5)
to branch 'i2p.i2p' (head b1fa07e8a4dabc26e731f7d486677abb165d975c)
2008-12-08 14:00:09 +00:00
zzz
bd6c63cc7e add findbugs target 2008-12-06 00:11:13 +00:00
zzz
7dbb13d6dc move atalk from core to apps 2008-12-05 20:31:54 +00:00
zzz
ebdc69cbc2 remove PRNG from summary bar 2008-12-05 14:52:15 +00:00
zzz
868fe90d7a increase max files to 256 2008-12-05 14:38:59 +00:00
sponge
9e39f34473 BOB: removed debugging (oops!)
BUMP: BOB to 00.00.02
BUMP router to -3
2008-12-05 10:12:10 +00:00
sponge
45ed744210 BUMP to -2 for bug reporting. 2008-12-05 09:55:53 +00:00
sponge
701904d119 BUGFIX: streaming lib blocking on a write() will now fail when the socket
is closed from under it.
Enhancement: BOB can now clear a destination in under 1 second with the above fix.
	BOB also will do a thread dump when something really aweful happens,
	so that developers/users can help in debugging.
2008-12-05 09:51:48 +00:00
zzz
dcf4bb595f split classes into their own files 2008-12-04 21:56:22 +00:00
zzz
e9f27c60dd avoid two NPEs on corrupt fragments 2008-12-04 00:09:52 +00:00
zzz
321f11c055 robert + xl 2008-12-03 23:38:13 +00:00
zzz
85cebc7992 * Transport:
- Fixes and cleanups when NTCP and/or UDP transports disabled
      - More TCP removal cleanup
      - Clean up bandwidth limiting, centralize defaults
      - Force burst to be >= limit
      - Increase default bw to 48/24, burst 64/32
2008-12-03 18:53:57 +00:00
zzz
8e5c4a3e22 error to warn 2008-12-03 16:55:48 +00:00
zzz
dff75de97a tweak 2008-12-03 14:46:37 +00:00
zzz
f1fd35265a enable blocklists by default 2008-12-03 14:26:39 +00:00
zzz
b73b3fc5ac * i2psnark:
- Add default i2psnark.config for new installs
      - Remove wishlist link
2008-12-03 01:49:19 +00:00
zzz
13d4ccf2e7 remove restart button if no wrapper 2008-12-02 19:07:58 +00:00
zzz
8c9ac941bf fix NPE on early shutdown 2008-12-02 16:28:29 +00:00
zzz
3fc698c7d3 disable eepsite webapps by default 2008-12-02 16:26:26 +00:00
zzz
15596c9230 add textareas to susidns 2008-12-02 16:25:43 +00:00
zzz
7fdbe9b87b post-0.6.5 netdb stats cleanup 2008-12-02 16:25:16 +00:00
zzz
5acc56c184 increase standalone to 128MB max mem 2008-12-02 16:23:54 +00:00
zzz
c524231c6d history for the propagate of the snark rewrite 2008-12-01 02:57:24 +00:00
zzz
5d4a7967cb propagate from branch 'i2p.i2p.zzz.i2psnark' (head 738b0ee2a3e938f83c8524d7ee1cbd66c83d7d56)
to branch 'i2p.i2p' (head 7bc276bf13158ca72d687031fdf5e9921efc5050)
2008-12-01 02:51:28 +00:00
complication
01101f9867 * Fix typos in news.xml 2008-12-01 01:07:22 +00:00
zzz
bad4c4a133 SAM: Convert from I2PThread to I2PAppThread so it won't
shutdown the whole router when ooming.
2008-11-20 14:59:55 +00:00
zzz
0ff8167425 i2psnark:
- Don't create SnarkManager instance until first call,
      so it doesn't create the i2psnark dir, read the config,
      etc., for single Snark instances.
    - Don't read i2psnark.config twice; fix setting
      i2psnark.dir
    - More Snark constructor changes for calling from router
    - Make max connections per torrent configurable
2008-11-18 02:18:23 +00:00
zzz
134764b154 i2psnark:
- Use new I2PAppThread that does not call global listeners on OOM,
      so that OOMing apps will not shutdown the whole router.
2008-11-16 17:24:08 +00:00
zzz
23699e46e5 i2psnark:
- Remove static instances of I2PSnarkUtil, ConnectionAcceptor,
      and PeerCoordinatorSet
    - Convert static classes in Snark to listeners
    - Fix Snark to work in single torrent mode again
    - Should now work with multiple single Snarks
2008-11-16 17:11:53 +00:00
zzz
fa23a7b066 i2psnark:
- Refactor to allow running a single Snark without a SnarkManager again,
    by moving some things from SnarkManager to I2PSnarkUtil,
    having Snark call completeListener callbacks,
    and having Storage call storageListener callbacks.
    This is in preparation for using Snark for router updates.
    Step 2 is to allow multiple I2PSnarkUtil instances.
  - Big rewrite of Storage to open file descriptors on demand, and
    close them when unused, so we can support large numbers of torrents.
2008-11-15 23:52:40 +00:00
397 changed files with 3630 additions and 54349 deletions

14
apps/BOB/bob.config Normal file
View File

@@ -0,0 +1,14 @@
#bob.config
#Tue Dec 30 00:00:00 UTC 2008
# Please leave this file here for testing.
# Thank you,
# Sponge
i2cp.tcp.port=7654
BOB.host=localhost
inbound.lengthVariance=0
i2cp.messageReliability=BestEffort
BOB.port=45067
outbound.length=1
inbound.length=1
outbound.lengthVariance=0
i2cp.tcp.host=localhost

View File

@@ -1,3 +1,4 @@
compile.on.save=false
do.depend=false
do.jar=true
javac.debug=true

View File

@@ -1,5 +1,11 @@
application.title=BOB
application.vendor=root
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=false
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80
auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
@@ -30,13 +36,31 @@ file.reference.mstreaming.jar-1=../ministreaming/java/build/mstreaming.jar
file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/
file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar
file.reference.streaming.jar-1=../streaming/java/build/streaming.jar
file.reference.wrapper-freebsd=../../installer/lib/wrapper/freebsd/
file.reference.wrapper-linux=../../installer/lib/wrapper/linux/
file.reference.wrapper-linux64=../../installer/lib/wrapper/linux64/
file.reference.wrapper-macosx=../../installer/lib/wrapper/macosx/
file.reference.wrapper-solaris=../../installer/lib/wrapper/solaris/
file.reference.wrapper-win32=../../installer/lib/wrapper/win32/
file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar
file.reference.wrapper.jar-1=../../installer/lib/wrapper/freebsd/wrapper.jar
file.reference.wrapper.jar-2=../../installer/lib/wrapper/linux64/wrapper.jar
file.reference.wrapper.jar-3=../../installer/lib/wrapper/macosx/wrapper.jar
file.reference.wrapper.jar-4=../../installer/lib/wrapper/solaris/wrapper.jar
file.reference.wrapper.jar-5=../../installer/lib/wrapper/win32/wrapper.jar
includes=**
jar.compress=false
javac.classpath=\
${file.reference.i2p.jar-1}:\
${file.reference.i2ptunnel.jar}:\
${file.reference.mstreaming.jar-1}:\
${file.reference.streaming.jar-1}
${file.reference.streaming.jar-1}:\
${file.reference.wrapper.jar-1}:\
${file.reference.wrapper.jar}:\
${file.reference.wrapper.jar-2}:\
${file.reference.wrapper.jar-3}:\
${file.reference.wrapper.jar-4}:\
${file.reference.wrapper.jar-5}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
@@ -58,6 +82,12 @@ javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
jnlp.codebase.type=local
jnlp.codebase.url=file:/root/NetBeansProjects/i2p.i2p/apps/BOB/dist/
jnlp.descriptor=application
jnlp.enabled=false
jnlp.offline-allowed=false
jnlp.signed=false
main.class=net.i2p.BOB.Main
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF

View File

@@ -114,6 +114,18 @@ public class BOB {
public final static String PROP_BOB_HOST = "BOB.host";
private static int maxConnections = 0;
private static NamedDB database;
private static Properties props = new Properties();
/**
* Log a warning
*
* @param arg
*/
public static void info(String arg) {
System.out.println("INFO:" + arg);
_log.info(arg);
}
/**
* Log a warning
@@ -121,7 +133,7 @@ public class BOB {
* @param arg
*/
public static void warn(String arg) {
System.out.println(arg);
System.out.println("WARNING:" + arg);
_log.warn(arg);
}
@@ -131,7 +143,7 @@ public class BOB {
* @param arg
*/
public static void error(String arg) {
System.out.println(arg);
System.out.println("ERROR: " + arg);
_log.error(arg);
}
@@ -147,7 +159,6 @@ public class BOB {
// Set up all defaults to be passed forward to other threads.
// Re-reading the config file in each thread is pretty damn stupid.
// I2PClient client = I2PClientFactory.createClient();
Properties props = new Properties();
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
// This is here just to ensure there is no interference with our threadgroups.
@@ -202,12 +213,12 @@ public class BOB {
props.store(fo, configLocation);
fo.close();
} catch(IOException ioe) {
warn("IOException on BOB config file " + configLocation + ", " + ioe);
error("IOException on BOB config file " + configLocation + ", " + ioe);
}
}
try {
warn("BOB is now running.");
info("BOB is now running.");
ServerSocket listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST)));
Socket server;
@@ -220,7 +231,7 @@ public class BOB {
t.start();
}
} catch(IOException ioe) {
warn("IOException on socket listen: " + ioe);
error("IOException on socket listen: " + ioe);
ioe.printStackTrace();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@ public class I2Plistener implements Runnable {
boolean g = false;
I2PSocket sessSocket = null;
serverSocket.setSoTimeout(1000);
serverSocket.setSoTimeout(100);
database.getReadLock();
info.getReadLock();
if(info.exists("INPORT")) {
@@ -107,32 +107,31 @@ public class I2Plistener implements Runnable {
// System.out.println("Exception " + e);
}
}
// System.out.println("I2Plistener: Close");
try {
serverSocket.close();
} catch(I2PException e) {
// nop
}
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
// System.out.println("STOP Thread count " + Thread.activeCount());
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(Exception e) {
// nop
}
}
// System.out.println("STOP Thread count " + Thread.activeCount());
// need to kill off the socket manager too.
I2PSession session = socketManager.getSession();
if(session != null) {
// System.out.println("I2Plistener: destroySession");
try {
session.destroySession();
} catch(I2PSessionException ex) {
// nop
}
// System.out.println("destroySession Thread count " + Thread.activeCount());
}
// System.out.println("I2Plistener: Waiting for children");
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(Exception e) {
// nop
}
}
// System.out.println("I2Plistener: Done.");
}
}

View File

@@ -119,20 +119,26 @@ die: {
// nop
}
}
// System.out.println("I2PtoTCP: Going away...");
} catch(Exception e) {
// System.out.println("I2PtoTCP: Owch! damn!");
break die;
}
} // die
try {
// System.out.println("I2PtoTCP: Close I2P");
I2P.close();
} catch(Exception e) {
tell = false;
}
//System.out.println("I2PtoTCP: Closed I2P");
try {
// System.out.println("I2PtoTCP: Close sock");
sock.close();
} catch(Exception e) {
tell = false;
}
// System.out.println("I2PtoTCP: Done");
}
}

View File

@@ -0,0 +1,56 @@
/**
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* Version 2, December 2004
*
* Copyright (C) sponge
* Planet Earth
* Everyone is permitted to copy and distribute verbatim or modified
* copies of this license document, and changing it is allowed as long
* as the name is changed.
*
* DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
*
* 0. You just DO WHAT THE FUCK YOU WANT TO.
*
* See...
*
* http://sam.zoy.org/wtfpl/
* and
* http://en.wikipedia.org/wiki/WTFPL
*
* ...for any additional details and liscense questions.
*/
package net.i2p.BOB;
import java.util.Enumeration;
import java.util.Properties;
/**
* Sets of "friendly" utilities to make life easier.
* Any "Lifted" code will apear here, and credits given.
* It's better to "Lift" a small chunk of "free" code than add in piles of
* code we don't need, and don't want.
*
* @author sponge
*/
public class Lifted {
/**
* Copy a set of properties from one Property to another.
* Lifted from Apache Derby code svn repository.
* Liscenced as follows:
* http://svn.apache.org/repos/asf/db/derby/code/trunk/LICENSE
*
* @param src_prop Source set of properties to copy from.
* @param dest_prop Dest Properties to copy into.
*
**/
public static void copyProperties(Properties src_prop, Properties dest_prop) {
for (Enumeration propertyNames = src_prop.propertyNames();
propertyNames.hasMoreElements();) {
Object key = propertyNames.nextElement();
dest_prop.put(key, src_prop.get(key));
}
}
}

View File

@@ -32,6 +32,7 @@ import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.Log;
import org.tanukisoftware.wrapper.WrapperManager;
/**
*
@@ -64,6 +65,7 @@ public class MUXlisten implements Runnable {
MUXlisten(NamedDB database, NamedDB info, Log _log) throws I2PException, IOException, RuntimeException {
int port = 0;
InetAddress host = null;
this.tg = null;
this.database = database;
this.info = info;
this._log = _log;
@@ -72,7 +74,10 @@ public class MUXlisten implements Runnable {
this.info.getReadLock();
N = this.info.get("NICKNAME").toString();
prikey = new ByteArrayInputStream((byte[])info.get("KEYS"));
Properties Q = (Properties)info.get("PROPERTIES");
// Make a new copy so that anything else won't muck with our database.
Properties R = (Properties)info.get("PROPERTIES");
Properties Q = new Properties();
Lifted.copyProperties(R, Q);
this.database.releaseReadLock();
this.info.releaseReadLock();
@@ -207,23 +212,39 @@ die: {
break die;
}
} // die
// wait for child threads and thread groups to die
// System.out.println("MUXlisten: waiting for children");
while(tg.activeCount() + tg.activeGroupCount() != 0) {
tg.interrupt(); // unwedge any blocking threads.
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(InterruptedException ex) {
// nop
}
}
}
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
} catch(Exception e) {
// System.out.println("MUXlisten: Caught an exception" + e);
break quit;
}
} // quit
socketManager.destroySocketManager();
// zero out everything, just incase.
// This is here to catch when something fucks up REALLY bad.
if(tg != null) {
System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
System.out.println("BOB: MUXlisten: Please email the following dump to sponge@mail.i2p");
WrapperManager.requestThreadDump();
System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
System.out.println("BOB: MUXlisten: Please email the above dump to sponge@mail.i2p");
}
// zero out everything, just incase.
try {
socketManager.destroySocketManager();
} catch(Exception e) {
// nop
}
try {
wlock();
try {
@@ -236,7 +257,20 @@ die: {
}
wunlock();
} catch(Exception e) {
return;
}
// This is here to catch when something fucks up REALLY bad.
if(tg != null) {
while(tg.activeCount() + tg.activeGroupCount() != 0) {
tg.interrupt(); // unwedge any blocking threads.
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(InterruptedException ex) {
// nop
}
}
tg.destroy();
// Zap reference to the ThreadGroup so the JVM can GC it.
tg = null;
}
}
}

View File

@@ -23,7 +23,6 @@
*/
package net.i2p.BOB;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -91,12 +90,16 @@ public class TCPio implements Runnable {
* the stream has been reached.
*
*/
// System.out.println("TCPio: End Of Stream");
return;
}
}
// System.out.println("TCPio: RUNNING = false");
} catch(Exception e) {
// Eject!!! Eject!!!
// System.out.println("TCPio: Caught an exception " + e);
return;
}
// System.out.println("TCPio: Leaving.");
}
}

View File

@@ -76,7 +76,6 @@ public class TCPlistener implements Runnable {
tgwatch = 2;
}
try {
// System.out.println("Starting thread count " + Thread.activeCount());
Socket server = new Socket();
listener.setSoTimeout(1000);
info.releaseReadLock();
@@ -87,7 +86,6 @@ public class TCPlistener implements Runnable {
spin = info.get("RUNNING").equals(Boolean.TRUE);
info.releaseReadLock();
database.releaseReadLock();
// System.out.println("Thread count " + Thread.activeCount());
try {
server = listener.accept();
g = true;
@@ -102,8 +100,13 @@ public class TCPlistener implements Runnable {
g = false;
}
}
//System.out.println("TCPlistener: destroySession");
listener.close();
} catch(IOException ioe) {
try {
listener.close();
} catch(IOException e) {
}
// Fatal failure, cause a stop event
database.getReadLock();
info.getReadLock();
@@ -120,17 +123,6 @@ public class TCPlistener implements Runnable {
}
}
//System.out.println("STOP!");
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
// System.out.println("STOP Thread count " + Thread.activeCount());
try {
Thread.sleep(1000); //sleep for 1000 ms (One second)
} catch(Exception e) {
// nop
}
}
// System.out.println("STOP Thread count " + Thread.activeCount());
// need to kill off the socket manager too.
I2PSession session = socketManager.getSession();
if(session != null) {
@@ -139,8 +131,16 @@ public class TCPlistener implements Runnable {
} catch(I2PSessionException ex) {
// nop
}
// System.out.println("destroySession Thread count " + Thread.activeCount());
}
//System.out.println("TCPlistener: Waiting for children");
while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
try {
Thread.sleep(100); //sleep for 100 ms (One tenth second)
} catch(Exception e) {
// nop
}
}
//System.out.println("TCPlistener: Done.");
}
}

View File

@@ -152,6 +152,7 @@ public class TCPtoI2P implements Runnable {
// nop
}
}
// System.out.println("TCPtoI2P: Going away...");
} catch(I2PException e) {
Emsg("ERROR " + e.toString(), out);
@@ -169,14 +170,17 @@ public class TCPtoI2P implements Runnable {
} catch(IOException ioe) {
}
try {
// System.out.println("TCPtoI2P: Close I2P");
I2P.close();
} catch(Exception e) {
}
try {
// System.out.println("TCPtoI2P: Close sock");
sock.close();
} catch(Exception e) {
}
// System.out.println("TCPtoI2P: Done.");
}
}

View File

@@ -179,6 +179,11 @@ public class AddressBook {
// IDN - basic check, not complete validation
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
// Base32 spoofing (52chars.i2p)
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
// ... or maybe we do Base32 this way ...
(! host.equals("b32.i2p")) &&
(! host.endsWith(".b32.i2p")) &&
// some reserved names that may be used for local configuration someday
(! host.equals("proxy.i2p")) &&
(! host.equals("router.i2p")) &&

View File

@@ -1,347 +0,0 @@
/*
* bogobot - A simple join/part stats logger bot for I2P IRC.
*
* Bogobot.java
* 2004 The I2P Project
* http://www.i2p.net
* This code is public domain.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.jibble.pircbot.IrcException;
import org.jibble.pircbot.NickAlreadyInUseException;
import org.jibble.pircbot.PircBot;
import org.jibble.pircbot.User;
/**
* TODO 0.5 Add multi-server capability.
*
* @author hypercubus, oOo
* @version 0.4
*/
public class Bogobot extends PircBot {
private static final String INTERVAL_DAILY = "daily";
private static final String INTERVAL_MONTHLY = "monthly";
private static final String INTERVAL_WEEKLY = "weekly";
private boolean _isIntentionalDisconnect = false;
private long _lastUserlistCommandTimestamp = 0;
private Logger _logger = Logger.getLogger(Bogobot.class);
private int _currentAutoRoundTripTag = 0;
private long _lastAutoRoundTripSentTime = 0;
private Timer _tickTimer;
private String _configFile;
private String _botPrimaryNick;
private String _botSecondaryNick;
private String _botNickservPassword;
private String _botUsername;
private String _ownerPrimaryNick;
private String _ownerSecondaryNick;
private String _botShutdownPassword;
private String _ircChannel;
private String _ircServer;
private int _ircServerPort;
private boolean _isLoggerEnabled;
private String _loggedHostnamePattern;
private boolean _isUserlistCommandEnabled;
private String _logFilePrefix;
private String _logFileRotationInterval;
private long _commandAntiFloodInterval;
private String _userlistCommandTrigger;
private boolean _isRoundTripDelayEnabled;
private int _roundTripDelayPeriod;
class BogobotTickTask extends TimerTask {
private Bogobot _caller;
public BogobotTickTask(Bogobot caller) {
_caller = caller;
}
public void run() {
_caller.onTick();
}
}
private void loadConfigFile(String configFileName) {
_configFile = configFileName;
Properties config = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(configFileName);
config.load(fis);
} catch (IOException ioe) {
System.err.println("Error loading configuration file");
System.exit(2);
} finally {
if (fis != null) try {
fis.close();
} catch (IOException ioe) { // nop
}
}
_botPrimaryNick = config.getProperty("botPrimaryNick", "somebot");
_botSecondaryNick = config.getProperty("botSecondaryNick", "somebot_");
_botNickservPassword = config.getProperty("botNickservPassword", "");
_botUsername = config.getProperty("botUsername", "somebot");
_ownerPrimaryNick = config.getProperty("ownerPrimaryNick", "somenick");
_ownerSecondaryNick = config.getProperty("ownerSecondaryNick", "somenick_");
_botShutdownPassword = config.getProperty("botShutdownPassword", "take off eh");
_ircChannel = config.getProperty("ircChannel", "#i2p-chat");
_ircServer = config.getProperty("ircServer", "irc.postman.i2p");
_ircServerPort = Integer.parseInt(config.getProperty("ircServerPort", "6668"));
_isLoggerEnabled = Boolean.valueOf(config.getProperty("isLoggerEnabled", "true")).booleanValue();
_loggedHostnamePattern = config.getProperty("loggedHostnamePattern", "");
_logFilePrefix = config.getProperty("logFilePrefix", "irc.postman.i2p.i2p-chat");
_logFileRotationInterval = config.getProperty("logFileRotationInterval", INTERVAL_DAILY);
_isRoundTripDelayEnabled = Boolean.valueOf(config.getProperty("isRoundTripDelayEnabled", "false")).booleanValue();
_roundTripDelayPeriod = Integer.parseInt(config.getProperty("roundTripDelayPeriod", "300"));
_isUserlistCommandEnabled = Boolean.valueOf(config.getProperty("isUserlistCommandEnabled", "true")).booleanValue();
_userlistCommandTrigger = config.getProperty("userlistCommandTrigger", "!who");
_commandAntiFloodInterval = Long.parseLong(config.getProperty("commandAntiFloodInterval", "60"));
}
public Bogobot(String configFileName) {
loadConfigFile(configFileName);
this.setName(_botPrimaryNick);
this.setLogin(_botUsername);
_tickTimer = new Timer();
_tickTimer.scheduleAtFixedRate(new BogobotTickTask(this), 1000, 10 * 1000);
}
public static void main(String[] args) {
Bogobot bogobot;
if (args.length > 1) {
System.err.println("Too many arguments, the only allowed parameter is configuration file name");
System.exit(3);
}
if (args.length == 1) {
bogobot = new Bogobot(args[0]);
} else {
bogobot = new Bogobot("bogobot.config");
}
bogobot.setVerbose(true);
if (bogobot._isLoggerEnabled)
bogobot.initLogger();
bogobot.connectToServer();
}
protected void onTick() {
// Tick about once every ten seconds
if (this.isConnected() && _isRoundTripDelayEnabled) {
if( ( (System.currentTimeMillis() - _lastAutoRoundTripSentTime) >= (_roundTripDelayPeriod * 1000) ) && (this.getOutgoingQueueSize() == 0) ) {
// Connected, sending queue is empty and last RoundTrip is more then 5 minutes old -> Send a new one
_currentAutoRoundTripTag ++;
_lastAutoRoundTripSentTime = System.currentTimeMillis();
sendNotice(this.getNick(),"ROUNDTRIP " + _currentAutoRoundTripTag);
}
}
}
protected void onDisconnect() {
if (_isIntentionalDisconnect)
System.exit(0);
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " *** (Lost connection)");
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
// No worries.
}
connectToServer();
}
protected void onJoin(String channel, String sender, String login, String hostname) {
if (_isLoggerEnabled) {
if (sender.equals(this.getName())) {
_logger.info(System.currentTimeMillis() + " joins *** " + _botPrimaryNick + " ***");
} else {
String prependedHostname = "@" + hostname;
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " joins " + sender);
}
}
}
}
protected void onMessage(String channel, String sender, String login, String hostname, String message) {
message = message.replaceFirst("<.+?> ", "");
if (_isUserlistCommandEnabled && message.equals(_userlistCommandTrigger)) {
if (System.currentTimeMillis() - _lastUserlistCommandTimestamp < _commandAntiFloodInterval * 1000)
return;
Object[] users = getUsers(_ircChannel);
String output = "Userlist for " + _ircChannel + ": ";
for (int i = 0; i < users.length; i++)
output += "[" + ((User) users[i]).getNick() + "] ";
sendMessage(_ircChannel, output);
_lastUserlistCommandTimestamp = System.currentTimeMillis();
}
}
protected void onPart(String channel, String sender, String login, String hostname) {
if (_isLoggerEnabled) {
if (sender.equals(this.getName())) {
_logger.info(System.currentTimeMillis() + " parts *** " + _botPrimaryNick + " ***");
} else {
String prependedHostname = "@" + hostname;
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " parts " + sender);
}
}
}
}
protected void onPrivateMessage(String sender, String login, String hostname, String message) {
/*
* Nobody else except the bot's owner can shut it down, unless of
* course the owner's nick isn't registered and someone's spoofing it.
*/
if ((sender.equals(_ownerPrimaryNick) || sender.equals(_ownerSecondaryNick)) && message.equals(_botShutdownPassword)) {
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " quits *** " + this.getName() + " ***");
_isIntentionalDisconnect = true;
disconnect();
}
}
protected void onQuit(String sourceNick, String sourceLogin, String sourceHostname, String reason) {
String prependedHostname = "@" + sourceHostname;
if (sourceNick.equals(_botPrimaryNick))
changeNick(_botPrimaryNick);
if (_isLoggerEnabled) {
if (prependedHostname.endsWith(_loggedHostnamePattern)) {
_logger.info(System.currentTimeMillis() + " quits " + sourceNick + " " + reason);
}
}
}
private void connectToServer() {
int loginAttempts = 0;
while (true) {
try {
connect(_ircServer, _ircServerPort);
break;
} catch (NickAlreadyInUseException e) {
if (loginAttempts == 1) {
System.out.println("Sorry, the primary and secondary bot nicks are already taken. Exiting.");
System.exit(1);
}
loginAttempts++;
try {
Thread.sleep(5000);
} catch (InterruptedException e1) {
// Hmph.
}
if (getName().equals(_botPrimaryNick))
setName(_botSecondaryNick);
else
setName(_botPrimaryNick);
continue;
} catch (IOException e) {
System.out.println("Error during login: ");
e.printStackTrace();
System.exit(1);
} catch (IrcException e) {
System.out.println("Error during login: ");
e.printStackTrace();
System.exit(1);
}
}
joinChannel(_ircChannel);
}
protected void onNotice(String sourceNick, String sourceLogin, String sourceHostname, String target, String notice) {
if (sourceNick.equals("NickServ") && (notice.indexOf("/msg NickServ IDENTIFY") >= 0) && (_botNickservPassword != "")) {
sendRawLineViaQueue("NICKSERV IDENTIFY " + _botNickservPassword);
}
if (sourceNick.equals(getNick()) && notice.equals( "ROUNDTRIP " + _currentAutoRoundTripTag)) {
int delay = (int)((System.currentTimeMillis() - _lastAutoRoundTripSentTime) / 100);
// sendMessage(_ircChannel, "Round-trip delay = " + (delay / 10.0f) + " seconds");
if (_isLoggerEnabled)
_logger.info(System.currentTimeMillis() + " roundtrip " + delay);
}
}
private void initLogger() {
String logFilePath = "logs" + File.separator + _logFilePrefix;
DailyRollingFileAppender rollingFileAppender = null;
if (!(new File("logs").exists()))
(new File("logs")).mkdirs();
try {
if (_logFileRotationInterval.equals("monthly"))
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM'.log'");
else if (_logFileRotationInterval.equals("weekly"))
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-ww'.log'");
else
rollingFileAppender = new DailyRollingFileAppender(new PatternLayout("%m%n"), logFilePath, "'.'yyyy-MM-dd'.log'");
rollingFileAppender.setThreshold(Level.INFO);
_logger.addAppender(rollingFileAppender);
} catch (IOException ex) {
System.out.println("Error: Couldn't create or open an existing log file. Exiting.");
System.exit(1);
}
}
}

View File

@@ -1,353 +0,0 @@
/*
* bogoparser - A simple logfile analyzer for bogobot.
*
* Bogoparser.java
* 2004 The I2P Project
* http://www.i2p.net
* This code is public domain.
*/
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author hypercubus
* @version 0.4
*/
public class Bogoparser {
private static void displayUsageAndExit() {
System.out.println("\r\nUsage:\r\n\r\n java Bogoparser [--by-duration] <logfile>\r\n");
System.exit(1);
}
public static void main(String[] args) {
Bogoparser bogoparser;
if (args.length < 1 || args.length > 2)
displayUsageAndExit();
if (args.length == 2) {
if (!args[0].equals("--by-duration"))
displayUsageAndExit();
bogoparser = new Bogoparser(args[1], true);
}
if (args.length == 1)
bogoparser = new Bogoparser(args[0], false);
}
private Bogoparser(String logfile, boolean sortByDuration) {
ArrayList sortedSessions;
if (sortByDuration) {
sortedSessions = sortSessionsByDuration(calculateSessionDurations(sortSessionsByTime(readLogfile(logfile))));
formatAndOutputByDuration(sortedSessions);
} else {
sortedSessions = calculateSessionDurations(sortSessionsByQuitReason(sortSessionsByNick(sortSessionsByTime(readLogfile(logfile)))));
formatAndOutput(sortedSessions);
}
}
private ArrayList calculateSessionDurations(ArrayList sortedSessionsByQuitReasonOrDuration) {
ArrayList calculatedSessionDurations = new ArrayList();
for (int i = 0; i+1 < sortedSessionsByQuitReasonOrDuration.size(); i += 2) {
String joinsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i);
String[] joinsEntryFields = joinsEntry.split(" ");
String quitsEntry = (String) sortedSessionsByQuitReasonOrDuration.get(i+1);
Pattern p = Pattern.compile("^([^ ]+) [^ ]+ ([^ ]+) (.*)$");
Matcher m = p.matcher(quitsEntry);
if (m.matches()) {
String currentJoinTime = joinsEntryFields[0];
String currentNick = m.group(2);
String currentQuitReason = m.group(3);
String currentQuitTime = m.group(1);
long joinsTimeInMilliseconds;
long quitsTimeInMilliseconds;
long sessionLengthInMilliseconds;
joinsTimeInMilliseconds = Long.parseLong(currentJoinTime);
quitsTimeInMilliseconds = Long.parseLong(currentQuitTime);
sessionLengthInMilliseconds = quitsTimeInMilliseconds - joinsTimeInMilliseconds;
String hours = "" + sessionLengthInMilliseconds/1000/60/60;
String minutes = "" + (sessionLengthInMilliseconds/1000/60)%60;
if (hours.length() < 2)
hours = "0" + hours;
if (hours.length() < 3)
hours = "0" + hours;
if (minutes.length() < 2)
minutes = "0" + minutes;
int columnPadding = 19-currentNick.length();
String columnPaddingString = " ";
for (int j = 0; j < columnPadding; j++)
columnPaddingString = columnPaddingString + " ";
calculatedSessionDurations.add(sessionLengthInMilliseconds + " " + currentNick + columnPaddingString + " online " + hours + " hours " + minutes + " minutes " + currentQuitReason);
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + quitsEntry);
System.exit(1);
}
}
return calculatedSessionDurations;
}
private void formatAndOutput(ArrayList sortedSessions) {
String quitReason = null;
for (int i = 0; i < sortedSessions.size(); i++) {
String entry = (String) sortedSessions.get(i);
Pattern p = Pattern.compile("^[\\d]+ ([^ ]+ +online [\\d]+ hours [\\d]+ minutes) (.*)$");
Matcher m = p.matcher(entry);
if (m.matches()) {
if (quitReason == null) {
quitReason = m.group(2);
System.out.println("\r\nQUIT: " + ((m.group(2).equals("")) ? "No Reason Given" : quitReason) + "\r\n");
}
String tempQuitReason = m.group(2);
String tempSession = m.group(1);
if (tempQuitReason.equals(quitReason)) {
System.out.println(" " + tempSession);
} else {
quitReason = null;
i -= 1;
continue;
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
System.exit(1);
}
}
System.out.println("\r\n");
}
private void formatAndOutputByDuration(ArrayList sortedSessions) {
System.out.println("\r\n");
for (int i = 0; i < sortedSessions.size(); i++) {
String[] columns = ((String) sortedSessions.get(i)).split(" ", 2);
System.out.println(columns[1]);
}
System.out.println("\r\n");
}
private ArrayList readLogfile(String logfile) {
ArrayList log = new ArrayList();
try {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(logfile)));
for (String line; (line = in.readLine()) != null; )
log.add(line);
in.close();
} catch (FileNotFoundException e) {
System.out.println("\r\nError: Can't find logfile '" + logfile + "'.\r\n");
System.exit(1);
} catch (IOException e) {
System.out.println("\r\nError: Can't read logfile '" + logfile + "'.\r\n");
System.exit(1);
}
return log;
}
/*
* Performs an odd-even transposition sort.
*/
private ArrayList sortSessionsByDuration(ArrayList calculatedSessionDurations) {
for (int i = 0; i < calculatedSessionDurations.size()/2; i++) {
for (int j = 0; j+1 < calculatedSessionDurations.size(); j += 2) {
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
long currentDuration = Long.parseLong(currentDurationString[0]);
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
long nextDuration = Long.parseLong(nextDurationString[0]);
if (currentDuration > nextDuration) {
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
calculatedSessionDurations.remove(j+2);
}
}
for (int j = 1; j+1 < calculatedSessionDurations.size(); j += 2) {
String[] currentDurationString = ((String) calculatedSessionDurations.get(j)).split(" ", 2);
long currentDuration = Long.parseLong(currentDurationString[0]);
String[] nextDurationString = ((String) calculatedSessionDurations.get(j+1)).split(" ", 2);
long nextDuration = Long.parseLong(nextDurationString[0]);
if (currentDuration > nextDuration) {
calculatedSessionDurations.add(j, calculatedSessionDurations.get(j+1));
calculatedSessionDurations.remove(j+2);
}
}
}
return calculatedSessionDurations;
}
private ArrayList sortSessionsByNick(ArrayList sortedSessionsByTime) {
ArrayList sortedSessionsByNick = new ArrayList();
while (sortedSessionsByTime.size() != 0) {
String entry = (String) sortedSessionsByTime.get(0);
String[] entryFields = entry.split(" ");
String currentNick = entryFields[2];
sortedSessionsByNick.add(entry);
sortedSessionsByNick.add(sortedSessionsByTime.get(1));
sortedSessionsByTime.remove(0);
sortedSessionsByTime.remove(0);
for (int i = 0; i+1 < sortedSessionsByTime.size(); i += 2) {
String nextEntry = (String) sortedSessionsByTime.get(i);
String[] nextEntryFields = nextEntry.split(" ");
if (nextEntryFields[2].equals(currentNick)) {
sortedSessionsByNick.add(nextEntry);
sortedSessionsByNick.add(sortedSessionsByTime.get(i+1));
sortedSessionsByTime.remove(i);
sortedSessionsByTime.remove(i);
i -= 2;
}
}
}
return sortedSessionsByNick;
}
private ArrayList sortSessionsByQuitReason(ArrayList sortedSessionsByNick) {
ArrayList sortedSessionsByQuitReason = new ArrayList();
while (sortedSessionsByNick.size() != 0) {
String entry = (String) sortedSessionsByNick.get(1);
Pattern p = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
Matcher m = p.matcher(entry);
if (m.matches()) {
String currentQuitReason = m.group(1);
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(0));
sortedSessionsByQuitReason.add(entry);
sortedSessionsByNick.remove(0);
sortedSessionsByNick.remove(0);
for (int i = 0; i+1 < sortedSessionsByNick.size(); i += 2) {
String nextEntry = (String) sortedSessionsByNick.get(i+1);
Pattern p2 = Pattern.compile("^[^ ]+ [^ ]+ [^ ]+ (.*)$");
Matcher m2 = p2.matcher(nextEntry);
if (m2.matches()) {
String nextQuitReason = m2.group(1);
if (nextQuitReason.equals(currentQuitReason)) {
sortedSessionsByQuitReason.add(sortedSessionsByNick.get(i));
sortedSessionsByQuitReason.add(nextEntry);
sortedSessionsByNick.remove(i);
sortedSessionsByNick.remove(i);
i -= 2;
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + nextEntry);
System.exit(1);
}
}
} else {
System.out.println("\r\nError: Unexpected entry in logfile: " + entry);
System.exit(1);
}
}
return sortedSessionsByQuitReason;
}
/**
* Sessions terminated with "parts" messages instead of "quits" are filtered
* out.
*/
private ArrayList sortSessionsByTime(ArrayList log) {
ArrayList sortedSessionsByTime = new ArrayList();
mainLoop:
while (log.size() > 0) {
String entry = (String) log.get(0);
String[] entryFields = entry.split(" ");
if (entryFields[1].equals("quits") && !entryFields[1].equals("joins")) {
/*
* Discard entry. The specified log either doesn't contain
* the corresponding "joins" time for this quit entry or the
* entry is a "parts" or unknown message, and in both cases
* the entry's data is useless.
*/
log.remove(0);
continue;
}
for (int i = 1; i < log.size(); i++) { // Find corresponding "quits" entry.
String tempEntry = (String) log.get(i);
String[] tempEntryFields = tempEntry.split(" ");
if (tempEntryFields[2].equals(entryFields[2])) { // Check if the nick fields for the two entries match.
if (!tempEntryFields[1].equals("quits")) {
if (tempEntryFields[1].equals("joins")) { // Don't discard a subsequent "joins" entry.
log.remove(0);
continue mainLoop;
}
log.remove(i);
continue;
}
sortedSessionsByTime.add(entry);
sortedSessionsByTime.add(tempEntry);
log.remove(i);
break;
}
}
/*
* Discard "joins" entry. The specified log doesn't contain the
* corresponding "quits" time for this entry so the entry's
* data is useless.
*/
log.remove(0);
}
return sortedSessionsByTime;
}
}

View File

@@ -1,48 +0,0 @@
/*
* ============================================================================
* The Apache Software License, Version 1.1
* ============================================================================
*
* Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by the Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "log4j" and "Apache Software Foundation" must not be used to
* endorse or promote products derived from this software without prior
* written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache", nor may
* "Apache" appear in their name, without prior written permission of the
* Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
* DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the Apache Software Foundation. For more information on the
* Apache Software Foundation, please see <http://www.apache.org/>.
*
*/

View File

@@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@@ -1 +0,0 @@
java -cp .;log4j-1.2.8.jar;pircbot.jar Bogobot

View File

@@ -1,101 +0,0 @@
#####
# Bogobot user configuration
#####
###
# The bot's nick and backup nick. You will probably want to register these with
# the IRC server's NickServ.(a NickServ interface is forthcoming).
#
botPrimaryNick=somebot
botSecondaryNick=somebot_
###
# The bot's password required by Nickserv service's identify command.
# You have to register the nickname yourself first, the bot will not.
#
botNickservPassword=
###
# The bot's username. Appears in the whois replies
#
botUsername=somebot
#####
# The bot owner's nick and backup nick. One of these must match the owner's
# currently-used nick or else remote shutdown will not be possible. You will
# probably want to register these with the IRC server's NickServ.
#
ownerPrimaryNick=somenick
ownerSecondaryNick=somenick_
###
# The bot will disconnect and shut down when sent this password via private
# message (aka query) from either of the owner nicks specified above. DO NOT USE
# THIS DEFAULT VALUE!
#
botShutdownPassword=take off eh
###
# The server, channel, and port the bot will connect to.
#
ircChannel=#i2p-chat
ircServer=irc.duck.i2p
ircServerPort=6668
###
# Set to "true" to enable logging, else "false" (but don't use quotation marks).
#
isLoggerEnabled=true
###
# Restrict logging of joins and parts on the user hostname.
# Leave empty to log all of them
# Prepend with a @ for a perfect match
# Otherwise, specify the required end of the user hostname
#
loggedHostnamePattern=@free.duck.i2p
###
# The prefix to be used for the filenames of logs.
#
logFilePrefix=irc.duck.i2p.i2p-chat
###
# How often the logs should be rotated. Either "daily", "weekly", or "monthly"
# (but don't use quotation marks).
#
logFileRotationInterval=daily
###
# Set to "true" to enable the regular round-trip delay computation,
# else "false" (but don't use quotation marks).
#
isRoundTripDelayEnabled=false
###
# How often should the round-trip delay be recorded.
# (in seconds)
#
roundTripDelayPeriod=300
###
# Set to "true" to enable the userlist command, else "false" (but don't use
# quotation marks).
#
isUserlistCommandEnabled=true
###
# The userlist trigger command to listen for. It is a good idea to prefix
# triggers with some non-alphanumeric character in order to avoid accidental
# trigger use during normal channel conversation. In most cases you will
# probably want to choose a unique trigger here that no other bots in the
# channel will respond to.
#
userlistCommandTrigger=!who
###
# The number of seconds to rest after replying to a userlist command issued by
# a user in the channel. The bot will ignore subsequent userlist commands during
# this period. This helps prevent flooding.
#
commandAntiFloodInterval=60

View File

@@ -1,2 +0,0 @@
#!/bin/sh
java -cp .:log4j-1.2.8.jar:pircbot.jar Bogobot

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ********************************************************** -->
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
<!-- -->
<!-- build-eclipse.xml -->
<!-- 2004 The I2P Project -->
<!-- http://www.i2p.net -->
<!-- This code is public domain. -->
<!-- -->
<!-- authors: hypercubus, oOo -->
<!-- version 0.4 -->
<!-- ********************************************************** -->
<project basedir="." default="dist" name="Bogobot">
<!-- init:
Create distribution directory if missing and initialize time stamp for
archive naming -->
<target name="init">
<mkdir dir="dist" />
<tstamp>
<format pattern="yyyy-MM-dd" property="DSTAMP" />
</tstamp>
</target>
<!-- dist.bin:
Create the binary distribution archive -->
<target depends="init" description="Create the binary distribution archive" name="dist.bin">
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist.source:
Create the source distribution archive -->
<target depends="init" description="Create the source distribution archive" name="dist.source">
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist:
Create both the binary and source distribution archives -->
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
<echo message="Successfully created binary and source distribution archives in directory &apos;dist&apos;." />
</target>
<!-- clean:
Delete all class files and temporary directories -->
<target description="Delete all class files and temporary directories" name="clean">
<delete>
<fileset dir="${basedir}" includes="**/*.class" />
</delete>
<echo message="Clean successful." />
</target>
</project>

View File

@@ -1,64 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ********************************************************** -->
<!-- bogobot - A simple join/part stats logger bot for I2P IRC. -->
<!-- -->
<!-- build.xml -->
<!-- 2004 The I2P Project -->
<!-- http://www.i2p.net -->
<!-- This code is public domain. -->
<!-- -->
<!-- authors: hypercubus, oOo -->
<!-- version 0.4 -->
<!-- ********************************************************** -->
<project basedir="." default="compile" name="Bogobot">
<!-- init:
Create distribution directory if missing and initialize time stamp for
archive naming -->
<target name="init">
<mkdir dir="dist" />
<tstamp>
<format pattern="yyyy-MM-dd" property="DSTAMP" />
</tstamp>
</target>
<!-- compile:
Compile source code -->
<target depends="init" description="Compile source code" name="compile">
<javac classpath="${basedir};log4j-1.2.8.jar;pircbot.jar" source="1.4" srcdir="." />
</target>
<!-- dist.bin:
Create the binary distribution archive -->
<target depends="init,compile" description="Create the binary distribution archive" name="dist.bin">
<zip destfile="dist/Bogobot_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.class Bogobot$BogobotTickTask.class bogobot.sh Bogoparser.class LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist.source:
Create the source distribution archive -->
<target depends="init" description="Create the source distribution archive" name="dist.source">
<zip destfile="dist/Bogobot_source_${DSTAMP}.zip">
<zipfileset dir="${basedir}" includes="bogobot.bat bogobot.config Bogobot.java bogobot.sh Bogoparser.java build.xml build_eclipse.xml LICENSE_log4j.txt LICENSE_pircbot.txt log4j-1.2.8.jar pircbot.jar" />
</zip>
</target>
<!-- dist:
Create both the binary and source distribution archives -->
<target depends="dist.bin,dist.source" description="Create both the binary and source distribution archives" name="dist">
<echo message="Successfully created binary and source distribution archives in directory &apos;dist&apos;." />
</target>
<!-- clean:
Delete all class files and temporary directories -->
<target description="Delete all class files and temporary directories" name="clean">
<delete>
<fileset dir="${basedir}" includes="**/*.class" />
</delete>
<echo message="Clean successful." />
</target>
</project>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
i2psnark.dir=i2psnark

View File

@@ -28,7 +28,7 @@ import java.io.OutputStream;
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.I2PAppThread;
import net.i2p.util.Log;
/**
@@ -36,17 +36,16 @@ import net.i2p.util.Log;
*/
public class ConnectionAcceptor implements Runnable
{
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 I2PSnarkUtil _util;
private boolean stop;
private boolean socketChanged;
private ConnectionAcceptor() {}
public ConnectionAcceptor(I2PSnarkUtil util) { _util = util; }
public synchronized void startAccepting(PeerCoordinatorSet set, I2PServerSocket socket) {
if (serverSocket != socket) {
@@ -56,29 +55,30 @@ public class ConnectionAcceptor implements Runnable
stop = false;
socketChanged = true;
if (thread == null) {
thread = new I2PThread(this, "I2PSnark acceptor");
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
}
}
public ConnectionAcceptor(I2PServerSocket serverSocket,
public ConnectionAcceptor(I2PSnarkUtil util, I2PServerSocket serverSocket,
PeerAcceptor peeracceptor)
{
this.serverSocket = serverSocket;
this.peeracceptor = peeracceptor;
_util = util;
socketChanged = false;
stop = false;
thread = new I2PThread(this, "I2PSnark acceptor");
thread = new I2PAppThread(this, "I2PSnark acceptor");
thread.setDaemon(true);
thread.start();
}
public void halt()
{
if (true) throw new RuntimeException("wtf");
if (stop) return;
stop = true;
I2PServerSocket ss = serverSocket;
@@ -95,7 +95,7 @@ public class ConnectionAcceptor implements Runnable
}
public void restart() {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
serverSocket = _util.getServerSocket();
socketChanged = true;
Thread t = thread;
if (t != null)
@@ -116,7 +116,7 @@ public class ConnectionAcceptor implements Runnable
socketChanged = false;
}
while ( (serverSocket == null) && (!stop)) {
serverSocket = I2PSnarkUtil.instance().getServerSocket();
serverSocket = _util.getServerSocket();
if (serverSocket == null)
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
@@ -129,27 +129,27 @@ public class ConnectionAcceptor implements Runnable
if (socketChanged) {
continue;
} else {
I2PServerSocket ss = I2PSnarkUtil.instance().getServerSocket();
I2PServerSocket ss = _util.getServerSocket();
if (ss != serverSocket) {
serverSocket = ss;
socketChanged = true;
}
}
} else {
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
Thread t = new I2PAppThread(new Handler(socket), "Connection-" + socket);
t.start();
}
}
catch (I2PException ioe)
{
if (!socketChanged) {
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
catch (IOException ioe)
{
Snark.debug("Error while accepting: " + ioe, Snark.ERROR);
_util.debug("Error while accepting: " + ioe, Snark.ERROR);
stop = true;
}
}
@@ -161,7 +161,6 @@ public class ConnectionAcceptor implements Runnable
}
catch (I2PException ignored) { }
throw new RuntimeException("wtf");
}
private class Handler implements Runnable {

View File

@@ -30,4 +30,8 @@ public interface CoordinatorListener
* Called when the PeerCoordinator notices a change in the state of a peer.
*/
void peerChange(PeerCoordinator coordinator, Peer peer);
public boolean overUploadLimit(int uploaders);
public boolean overUpBWLimit();
public boolean overUpBWLimit(long total);
}

View File

@@ -2,12 +2,15 @@ package org.klomp.snark;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@@ -25,12 +28,13 @@ import net.i2p.util.SimpleTimer;
/**
* I2P specific helpers for I2PSnark
* We use this class as a sort of context for i2psnark
* so we can run multiple instances of single Snarks
* (but not multiple SnarkManagers, it is still static)
*/
public class I2PSnarkUtil {
private I2PAppContext _context;
private Log _log;
private static I2PSnarkUtil _instance = new I2PSnarkUtil();
public static I2PSnarkUtil instance() { return _instance; }
private boolean _shouldProxy;
private String _proxyHost;
@@ -43,9 +47,17 @@ public class I2PSnarkUtil {
private Set _shitlist;
private int _maxUploaders;
private int _maxUpBW;
private int _maxConnections;
private I2PSnarkUtil() {
_context = I2PAppContext.getGlobalContext();
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
public static final int MAX_CONNECTIONS = 16; // per torrent
public I2PSnarkUtil(I2PAppContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(Snark.class);
_opts = new HashMap();
setProxy("127.0.0.1", 4444);
@@ -53,6 +65,8 @@ public class I2PSnarkUtil {
_shitlist = new HashSet(64);
_configured = false;
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
_maxUpBW = DEFAULT_MAX_UP_BW;
_maxConnections = MAX_CONNECTIONS;
}
/**
@@ -76,8 +90,10 @@ public class I2PSnarkUtil {
public boolean configured() { return _configured; }
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
if (i2cpHost != null)
_i2cpHost = i2cpHost;
if (i2cpPort > 0)
_i2cpPort = i2cpPort;
if (opts != null)
_opts.putAll(opts);
_configured = true;
@@ -93,6 +109,11 @@ public class I2PSnarkUtil {
_configured = true;
}
public void setMaxConnections(int limit) {
_maxConnections = limit;
_configured = true;
}
public String getI2CPHost() { return _i2cpHost; }
public int getI2CPPort() { return _i2cpPort; }
public Map getI2CPOptions() { return _opts; }
@@ -101,6 +122,7 @@ public class I2PSnarkUtil {
public boolean getEepProxySet() { return _shouldProxy; }
public int getMaxUploaders() { return _maxUploaders; }
public int getMaxUpBW() { return _maxUpBW; }
public int getMaxConnections() { return _maxConnections; }
/**
* Connect to the router, if we aren't already
@@ -189,6 +211,7 @@ public class I2PSnarkUtil {
out.delete();
return null;
}
out.deleteOnExit();
String fetchURL = url;
if (rewrite)
fetchURL = rewriteAnnounce(url);
@@ -223,6 +246,28 @@ public class I2PSnarkUtil {
}
return "unknown";
}
/** Base64 only - static (no naming service) */
static Destination getDestinationFromBase64(String ip) {
if (ip == null) return null;
if (ip.endsWith(".i2p")) {
if (ip.length() < 520)
return null;
try {
return new Destination(ip.substring(0, ip.length()-4)); // sans .i2p
} catch (DataFormatException dfe) {
return null;
}
} else {
try {
return new Destination(ip);
} catch (DataFormatException dfe) {
return null;
}
}
}
/** Base64 Hash or Hash.i2p or name.i2p using naming service */
Destination getDestination(String ip) {
if (ip == null) return null;
if (ip.endsWith(".i2p")) {
@@ -267,7 +312,40 @@ public class I2PSnarkUtil {
return rv;
}
public String getOpenTrackerString() {
String rv = (String) _opts.get(PROP_OPENTRACKERS);
if (rv == null)
return DEFAULT_OPENTRACKERS;
return rv;
}
/** comma delimited list open trackers to use as backups */
/** sorted map of name to announceURL=baseURL */
public List getOpenTrackers() {
if (!shouldUseOpenTrackers())
return null;
List rv = new ArrayList(1);
String trackers = getOpenTrackerString();
StringTokenizer tok = new StringTokenizer(trackers, ", ");
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
if (rv.size() <= 0)
return null;
return rv;
}
public boolean shouldUseOpenTrackers() {
String rv = (String) _opts.get(PROP_USE_OPENTRACKERS);
if (rv == null)
return DEFAULT_USE_OPENTRACKERS;
return Boolean.valueOf(rv).booleanValue();
}
/** hook between snark's logger and an i2p log */
void debug(String msg, int snarkDebugLevel) {
debug(msg, snarkDebugLevel, null);
}
void debug(String msg, int snarkDebugLevel, Throwable t) {
if (t instanceof OutOfMemoryError) {
try { Thread.sleep(100); } catch (InterruptedException ie) {}

View File

@@ -173,7 +173,7 @@ public class Peer implements Comparable
* If the given BitField is non-null it is send to the peer as first
* message.
*/
public void runConnection(PeerListener listener, BitField bitfield)
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield)
{
if (state != null)
throw new IllegalStateException("Peer already started");
@@ -184,7 +184,7 @@ public class Peer implements Comparable
// Do we need to handshake?
if (din == null)
{
sock = I2PSnarkUtil.instance().connect(peerID);
sock = util.connect(peerID);
_log.debug("Connected to " + peerID + ": " + sock);
if ((sock == null) || (sock.isClosed())) {
throw new IOException("Unable to reach " + peerID);

View File

@@ -32,12 +32,14 @@ import java.util.TimerTask;
*/
class PeerCheckerTask extends TimerTask
{
private final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
private static final long KILOPERSECOND = 1024*(PeerCoordinator.CHECK_PERIOD/1000);
private final PeerCoordinator coordinator;
public I2PSnarkUtil _util;
PeerCheckerTask(PeerCoordinator coordinator)
PeerCheckerTask(I2PSnarkUtil util, PeerCoordinator coordinator)
{
_util = util;
this.coordinator = coordinator;
}
@@ -102,8 +104,8 @@ class PeerCheckerTask extends TimerTask
peer.setRateHistory(upload, download);
peer.resetCounters();
Snark.debug(peer + ":", Snark.DEBUG);
Snark.debug(" ul: " + upload/KILOPERSECOND
_util.debug(peer + ":", Snark.DEBUG);
_util.debug(" ul: " + upload/KILOPERSECOND
+ " dl: " + download/KILOPERSECOND
+ " i: " + peer.isInterested()
+ " I: " + peer.isInteresting()
@@ -129,7 +131,7 @@ class PeerCheckerTask extends TimerTask
// Check if it still wants pieces from us.
if (!peer.isInterested())
{
Snark.debug("Choke uninterested peer: " + peer,
_util.debug("Choke uninterested peer: " + peer,
Snark.INFO);
peer.setChoking(true);
uploaders--;
@@ -141,7 +143,7 @@ class PeerCheckerTask extends TimerTask
}
else if (overBWLimitChoke)
{
Snark.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
_util.debug("BW limit (" + upload + "/" + uploaded + "), choke peer: " + peer,
Snark.INFO);
peer.setChoking(true);
uploaders--;
@@ -155,7 +157,7 @@ class PeerCheckerTask extends TimerTask
else if (peer.isInteresting() && peer.isChoked())
{
// If they are choking us make someone else a downloader
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
_util.debug("Choke choking peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -168,7 +170,7 @@ class PeerCheckerTask extends TimerTask
else if (!peer.isInteresting() && !coordinator.completed())
{
// If they aren't interesting make someone else a downloader
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
_util.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
coordinator.uploaders--;
@@ -183,7 +185,7 @@ class PeerCheckerTask extends TimerTask
&& download == 0)
{
// We are downloading but didn't receive anything...
Snark.debug("Choke downloader that doesn't deliver:"
_util.debug("Choke downloader that doesn't deliver:"
+ peer, Snark.DEBUG);
peer.setChoking(true);
uploaders--;
@@ -222,7 +224,7 @@ class PeerCheckerTask extends TimerTask
|| uploaders > uploadLimit)
&& worstDownloader != null)
{
Snark.debug("Choke worst downloader: " + worstDownloader,
_util.debug("Choke worst downloader: " + worstDownloader,
Snark.DEBUG);
worstDownloader.setChoking(true);
@@ -247,6 +249,10 @@ class PeerCheckerTask extends TimerTask
// store the rates
coordinator.setRateHistory(uploaded, downloaded);
// close out unused files, but we don't need to do it every time
if (random.nextInt(4) == 0)
coordinator.getStorage().cleanRAFs();
}
}
}

View File

@@ -26,7 +26,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer;
@@ -58,7 +58,7 @@ class PeerConnectionOut implements Runnable
}
public void startup() {
thread = new I2PThread(this, "Snark sender " + _id + ": " + peer);
thread = new I2PAppThread(this, "Snark sender " + _id + ": " + peer);
thread.start();
}

View File

@@ -29,7 +29,7 @@ import java.util.List;
import java.util.Random;
import java.util.Timer;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
@@ -44,7 +44,6 @@ public class PeerCoordinator implements PeerListener
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 40*1000; // 40 seconds
final static int MAX_CONNECTIONS = 16;
final static int MAX_UPLOADERS = 6;
// Approximation of the number of current uploaders.
@@ -77,13 +76,15 @@ public class PeerCoordinator implements PeerListener
private boolean halted = false;
private final CoordinatorListener listener;
public I2PSnarkUtil _util;
public String trackerProblems = null;
public int trackerSeenPeers = 0;
public PeerCoordinator(byte[] id, MetaInfo metainfo, Storage storage,
public PeerCoordinator(I2PSnarkUtil util, byte[] id, MetaInfo metainfo, Storage storage,
CoordinatorListener listener, Snark torrent)
{
_util = util;
this.id = id;
this.metainfo = metainfo;
this.storage = storage;
@@ -96,7 +97,7 @@ public class PeerCoordinator implements PeerListener
// Randomize the first start time so multiple tasks are spread out,
// this will help the behavior with global limits
Random r = new Random();
timer.schedule(new PeerCheckerTask(this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
timer.schedule(new PeerCheckerTask(_util, this), (CHECK_PERIOD / 2) + r.nextInt((int) CHECK_PERIOD), CHECK_PERIOD);
}
// only called externally from Storage after the double-check fails
@@ -235,7 +236,7 @@ public class PeerCoordinator implements PeerListener
{
synchronized(peers)
{
return !halted && peers.size() < MAX_CONNECTIONS;
return !halted && peers.size() < _util.getMaxConnections();
}
}
@@ -293,7 +294,7 @@ public class PeerCoordinator implements PeerListener
peer.disconnect(false); // Don't deregister this connection/peer.
}
// This is already checked in addPeer() but we could have gone over the limit since then
else if (peers.size() >= MAX_CONNECTIONS)
else if (peers.size() >= _util.getMaxConnections())
{
if (_log.shouldLog(Log.WARN))
_log.warn("Already at MAX_CONNECTIONS in connected() with peer: " + peer);
@@ -349,7 +350,7 @@ public class PeerCoordinator implements PeerListener
peersize = peers.size();
// This isn't a strict limit, as we may have several pending connections;
// thus there is an additional check in connected()
need_more = (!peer.isConnected()) && peersize < MAX_CONNECTIONS;
need_more = (!peer.isConnected()) && peersize < _util.getMaxConnections();
// Check if we already have this peer before we build the connection
Peer old = peerIDInList(peer.getPeerID(), peers);
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
@@ -366,18 +367,18 @@ public class PeerCoordinator implements PeerListener
{
public void run()
{
peer.runConnection(listener, bitfield);
peer.runConnection(_util, listener, bitfield);
}
};
String threadName = peer.toString();
new I2PThread(r, threadName).start();
new I2PAppThread(r, threadName).start();
return true;
}
if (_log.shouldLog(Log.DEBUG)) {
if (peer.isConnected())
_log.info("Add peer already connected: " + peer);
else
_log.info("Connections: " + peersize + "/" + MAX_CONNECTIONS
_log.info("Connections: " + peersize + "/" + _util.getMaxConnections()
+ " not accepting extra peer: " + peer);
}
return false;
@@ -846,7 +847,7 @@ public class PeerCoordinator implements PeerListener
*/
public int allowedUploaders()
{
if (Snark.overUploadLimit(uploaders)) {
if (listener != null && listener.overUploadLimit(uploaders)) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Over limit, uploaders was: " + uploaders);
return uploaders - 1;
@@ -858,12 +859,16 @@ public class PeerCoordinator implements PeerListener
public boolean overUpBWLimit()
{
return Snark.overUpBWLimit();
if (listener != null)
return listener.overUpBWLimit();
return false;
}
public boolean overUpBWLimit(long total)
{
return Snark.overUpBWLimit(total * 1000 / CHECK_PERIOD);
if (listener != null)
return listener.overUpBWLimit(total * 1000 / CHECK_PERIOD);
return false;
}
}

View File

@@ -12,11 +12,9 @@ import java.util.Set;
* 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() {
public PeerCoordinatorSet() {
_coordinators = new HashSet();
}

View File

@@ -72,7 +72,7 @@ public class PeerID implements Comparable
bevalue = (BEValue)m.get("ip");
if (bevalue == null)
throw new InvalidBEncodingException("ip missing");
address = I2PSnarkUtil.instance().getDestination(bevalue.getString());
address = I2PSnarkUtil.getDestinationFromBase64(bevalue.getString());
if (address == null)
throw new InvalidBEncodingException("Invalid destination [" + bevalue.getString() + "]");

View File

@@ -28,12 +28,15 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import net.i2p.I2PAppContext;
import net.i2p.router.client.ClientManagerFacadeImpl;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
@@ -102,7 +105,7 @@ public class Snark
public void outOfMemory(OutOfMemoryError err) {
try {
err.printStackTrace();
I2PSnarkUtil.instance().debug("OOM in the snark", Snark.ERROR, err);
System.out.println("OOM in the snark" + err);
} catch (Throwable t) {
System.out.println("OOM in the OOM");
}
@@ -225,7 +228,7 @@ public class Snark
}
catch(IOException ioe)
{
debug("ERROR while reading stdin: " + ioe, ERROR);
System.out.println("ERROR while reading stdin: " + ioe);
}
// Explicit shutdown.
@@ -234,6 +237,7 @@ public class Snark
}
}
public static final String PROP_MAX_CONNECTIONS = "i2psnark.maxConnections";
public String torrent;
public MetaInfo meta;
public Storage storage;
@@ -244,19 +248,59 @@ public class Snark
public CompleteListener completeListener;
public boolean stopped;
byte[] id;
public I2PSnarkUtil _util;
private PeerCoordinatorSet _peerCoordinatorSet;
Snark(String torrent, String ip, int user_port,
/** from main() via parseArguments() single torrent */
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener) {
this(torrent, ip, user_port, slistener, clistener, true, ".");
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
}
Snark(String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener, boolean start, String rootDir)
/** single torrent - via router */
public Snark(I2PAppContext ctx, Properties opts, String torrent,
StorageListener slistener, boolean start, String rootDir) {
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
String host = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_HOST);
int port = 0;
String s = opts.getProperty(ClientManagerFacadeImpl.PROP_CLIENT_PORT);
if (s != null) {
try {
port = Integer.parseInt(s);
} catch (NumberFormatException nfe) {}
}
_util.setI2CPConfig(host, port, opts);
s = opts.getProperty(SnarkManager.PROP_UPBW_MAX);
if (s != null) {
try {
int v = Integer.parseInt(s);
_util.setMaxUpBW(v);
} catch (NumberFormatException nfe) {}
}
s = opts.getProperty(PROP_MAX_CONNECTIONS);
if (s != null) {
try {
int v = Integer.parseInt(s);
_util.setMaxConnections(v);
} catch (NumberFormatException nfe) {}
}
if (start)
this.startTorrent();
}
/** multitorrent */
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener,
CompleteListener complistener, PeerCoordinatorSet peerCoordinatorSet,
ConnectionAcceptor connectionAcceptor, boolean start, String rootDir)
{
if (slistener == null)
slistener = this;
//if (clistener == null)
// clistener = this;
completeListener = complistener;
_util = util;
_peerCoordinatorSet = peerCoordinatorSet;
acceptor = connectionAcceptor;
this.torrent = torrent;
this.rootDataDir = rootDir;
@@ -289,7 +333,7 @@ public class Snark
while (i < 20)
id[i++] = (byte)random.nextInt(256);
Snark.debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
debug("My peer id: " + PeerID.idencode(id), Snark.INFO);
int port;
IOException lastException = null;
@@ -298,9 +342,9 @@ public class Snark
* If we are starting,
* startTorrent() will force a connect.
*
boolean ok = I2PSnarkUtil.instance().connect();
boolean ok = util.connect();
if (!ok) fatal("Unable to connect to I2P");
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
I2PServerSocket serversocket = util.getServerSocket();
if (serversocket == null)
fatal("Unable to listen for I2P connections");
else {
@@ -321,7 +365,7 @@ public class Snark
else
{
activity = "Getting torrent";
File torrentFile = I2PSnarkUtil.instance().get(torrent, 3);
File torrentFile = _util.get(torrent, 3);
if (torrentFile == null) {
fatal("Unable to fetch " + torrent);
if (false) return; // never reached - fatal(..) throws
@@ -345,7 +389,7 @@ public class Snark
/*
{
// Try to create a new metainfo file
Snark.debug
debug
("Trying to create metainfo torrent for '" + torrent + "'",
NOTICE);
try
@@ -378,8 +422,14 @@ public class Snark
try
{
activity = "Checking storage";
storage = new Storage(meta, slistener);
storage.check(rootDataDir);
storage = new Storage(_util, meta, slistener);
if (completeListener != null) {
storage.check(rootDataDir,
completeListener.getSavedTorrentTime(this),
completeListener.getSavedTorrentBitField(this));
} else {
storage.check(rootDataDir);
}
// have to figure out when to reopen
// if (!start)
// storage.close();
@@ -413,10 +463,10 @@ public class Snark
* Start up contacting peers and querying the tracker
*/
public void startTorrent() {
boolean ok = I2PSnarkUtil.instance().connect();
boolean ok = _util.connect();
if (!ok) fatal("Unable to connect to I2P");
if (coordinator == null) {
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
I2PServerSocket serversocket = _util.getServerSocket();
if (serversocket == null)
fatal("Unable to listen for I2P connections");
else {
@@ -425,12 +475,20 @@ public class Snark
}
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
activity = "Collecting pieces";
coordinator = new PeerCoordinator(id, meta, storage, this, this);
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
set.add(coordinator);
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
acceptor.startAccepting(set, serversocket);
trackerclient = new TrackerClient(meta, coordinator);
coordinator = new PeerCoordinator(_util, id, meta, storage, this, this);
if (_peerCoordinatorSet != null) {
// multitorrent
_peerCoordinatorSet.add(coordinator);
if (acceptor != null) {
acceptor.startAccepting(_peerCoordinatorSet, serversocket);
} else {
// error
}
} else {
// single torrent
acceptor = new ConnectionAcceptor(_util, serversocket, new PeerAcceptor(coordinator));
}
trackerclient = new TrackerClient(_util, meta, coordinator);
}
stopped = false;
@@ -438,11 +496,12 @@ public class Snark
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(),
if (_peerCoordinatorSet != null)
_peerCoordinatorSet.remove(coordinator);
PeerCoordinator newCoord = new PeerCoordinator(_util, coordinator.getID(), coordinator.getMetaInfo(),
coordinator.getStorage(), coordinator.getListener(), this);
set.add(newCoord);
if (_peerCoordinatorSet != null)
_peerCoordinatorSet.add(newCoord);
coordinator = newCoord;
coordinatorChanged = true;
}
@@ -460,7 +519,7 @@ public class Snark
}
fatal("Could not reopen storage", ioe);
}
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
TrackerClient newClient = new TrackerClient(_util, coordinator.getMetaInfo(), coordinator);
if (!trackerclient.halted())
trackerclient.halt();
trackerclient = newClient;
@@ -480,17 +539,20 @@ public class Snark
pc.halt();
Storage st = storage;
if (st != null) {
if (storage.changed)
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
boolean changed = storage.changed;
try {
storage.close();
} catch (IOException ioe) {
System.out.println("Error closing " + torrent);
ioe.printStackTrace();
}
if (changed && completeListener != null)
completeListener.updateStatus(this);
}
if (pc != null)
PeerCoordinatorSet.instance().remove(pc);
if (pc != null && _peerCoordinatorSet != null)
_peerCoordinatorSet.remove(pc);
if (_peerCoordinatorSet == null)
_util.disconnect();
}
static Snark parseArguments(String[] args)
@@ -512,7 +574,8 @@ public class Snark
String ip = null;
String torrent = null;
boolean configured = I2PSnarkUtil.instance().configured();
I2PSnarkUtil util = new I2PSnarkUtil(I2PAppContext.getGlobalContext());
boolean configured = util.configured();
int i = 0;
while (i < args.length)
@@ -562,7 +625,7 @@ public class Snark
String proxyHost = args[i+1];
String proxyPort = args[i+2];
if (!configured)
I2PSnarkUtil.instance().setProxy(proxyHost, Integer.parseInt(proxyPort));
util.setProxy(proxyHost, Integer.parseInt(proxyPort));
i += 3;
}
else if (args[i].equals("--i2cp"))
@@ -584,7 +647,7 @@ public class Snark
}
}
if (!configured)
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
util.setI2CPConfig(i2cpHost, Integer.parseInt(i2cpPort), opts);
i += 3 + (opts != null ? 1 : 0);
}
else
@@ -601,7 +664,7 @@ public class Snark
else
usage("Need exactly one <url>, <file> or <dir>.");
return new Snark(torrent, ip, user_port, slistener, clistener);
return new Snark(util, torrent, ip, user_port, slistener, clistener);
}
private static void usage(String s)
@@ -668,7 +731,7 @@ public class Snark
*/
public void fatal(String s, Throwable t)
{
I2PSnarkUtil.instance().debug(s, ERROR, t);
_util.debug(s, ERROR, t);
//System.err.println("snark: " + s + ((t == null) ? "" : (": " + t)));
//if (debug >= INFO && t != null)
// t.printStackTrace();
@@ -679,13 +742,12 @@ public class Snark
/**
* Show debug info if debug is true.
*/
public static void debug(String s, int level)
private void debug(String s, int level)
{
I2PSnarkUtil.instance().debug(s, level, null);
//if (debug >= level)
// System.out.println(s);
_util.debug(s, level, null);
}
/** coordinatorListener */
public void peerChange(PeerCoordinator coordinator, Peer peer)
{
// System.out.println(peer.toString());
@@ -732,7 +794,7 @@ public class Snark
checking = true;
}
if (!checking)
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
Snark.INFO);
}
@@ -743,11 +805,13 @@ public class Snark
allChecked = true;
checking = false;
if (storage.changed && completeListener != null)
completeListener.updateStatus(this);
}
public void storageCompleted(Storage storage)
{
Snark.debug("Completely received " + torrent, Snark.INFO);
debug("Completely received " + torrent, Snark.INFO);
//storage.close();
//System.out.println("Completely received: " + torrent);
if (completeListener != null)
@@ -768,44 +832,47 @@ public class Snark
public interface CompleteListener {
public void torrentComplete(Snark snark);
public void updateStatus(Snark snark);
// not really listeners but the easiest way to get back to an optional SnarkManager
public long getSavedTorrentTime(Snark snark);
public BitField getSavedTorrentBitField(Snark snark);
}
/** Maintain a configurable total uploader cap
* coordinatorListener
*/
final static int MIN_TOTAL_UPLOADERS = 4;
final static int MAX_TOTAL_UPLOADERS = 10;
public static boolean overUploadLimit(int uploaders) {
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
if (coordinators == null || uploaders <= 0)
public boolean overUploadLimit(int uploaders) {
if (_peerCoordinatorSet == null || uploaders <= 0)
return false;
int totalUploaders = 0;
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
PeerCoordinator c = (PeerCoordinator)iter.next();
if (!c.halted())
totalUploaders += c.uploaders;
}
int limit = I2PSnarkUtil.instance().getMaxUploaders();
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
int limit = _util.getMaxUploaders();
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
return totalUploaders > limit;
}
public static boolean overUpBWLimit() {
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
if (coordinators == null)
public boolean overUpBWLimit() {
if (_peerCoordinatorSet == null)
return false;
long total = 0;
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
for (Iterator iter = _peerCoordinatorSet.iterator(); iter.hasNext(); ) {
PeerCoordinator c = (PeerCoordinator)iter.next();
if (!c.halted())
total += c.getCurrentUploadRate();
}
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
Snark.debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
long limit = 1024l * _util.getMaxUpBW();
debug("Total up bw: " + total + " Limit: " + limit, Snark.WARNING);
return total > limit;
}
public static boolean overUpBWLimit(long total) {
long limit = 1024l * I2PSnarkUtil.instance().getMaxUpBW();
public boolean overUpBWLimit(long total) {
long limit = 1024l * _util.getMaxUpBW();
return total > limit;
}
}

View File

@@ -19,7 +19,7 @@ import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
@@ -32,11 +32,14 @@ public class SnarkManager implements Snark.CompleteListener {
/** map of (canonical) filename to Snark instance (unsynchronized) */
private Map _snarks;
private Object _addSnarkLock;
private String _configFile;
private String _configFile = "i2psnark.config";
private Properties _config;
private I2PAppContext _context;
private Log _log;
private List _messages;
private I2PSnarkUtil _util;
private PeerCoordinatorSet _peerCoordinatorSet;
private ConnectionAcceptor _connectionAcceptor;
public static final String PROP_I2CP_HOST = "i2psnark.i2cpHost";
public static final String PROP_I2CP_PORT = "i2psnark.i2cpPort";
@@ -51,10 +54,6 @@ public class SnarkManager implements Snark.CompleteListener {
public static final String PROP_AUTO_START = "i2snark.autoStart"; // oops
public static final String DEFAULT_AUTO_START = "false";
public static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final String DEFAULT_USE_OPENTRACKERS = "true";
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
public static final String DEFAULT_OPENTRACKERS = "http://tracker.welterde.i2p/a";
public static final String PROP_LINK_PREFIX = "i2psnark.linkPrefix";
public static final String DEFAULT_LINK_PREFIX = "file:///";
@@ -67,16 +66,28 @@ public class SnarkManager implements Snark.CompleteListener {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SnarkManager.class);
_messages = new ArrayList(16);
loadConfig("i2psnark.config");
_util = new I2PSnarkUtil(_context);
loadConfig(null);
}
/** Caller _must_ call loadConfig(file) before this if setting new values
* for i2cp host/port or i2psnark.dir
*/
public void start() {
_peerCoordinatorSet = new PeerCoordinatorSet();
_connectionAcceptor = new ConnectionAcceptor(_util);
int minutes = getStartupDelayMinutes();
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
I2PAppThread monitor = new I2PAppThread(new DirMonitor(), "Snark DirMonitor");
monitor.setDaemon(true);
monitor.start();
if (_context instanceof RouterContext)
((RouterContext)_context).router().addShutdownTask(new SnarkManagerShutdown());
}
/** hook to I2PSnarkUtil for the servlet */
public I2PSnarkUtil util() { return _util; }
private static final int MAX_MESSAGES = 5;
public void addMessage(String message) {
synchronized (_messages) {
@@ -98,9 +109,6 @@ public class SnarkManager implements Snark.CompleteListener {
public boolean shouldAutoStart() {
return Boolean.valueOf(_config.getProperty(PROP_AUTO_START, DEFAULT_AUTO_START+"")).booleanValue();
}
public boolean shouldUseOpenTrackers() {
return Boolean.valueOf(_config.getProperty(PROP_USE_OPENTRACKERS, DEFAULT_USE_OPENTRACKERS)).booleanValue();
}
public String linkPrefix() {
return _config.getProperty(PROP_LINK_PREFIX, DEFAULT_LINK_PREFIX + getDataDir().getAbsolutePath() + File.separatorChar);
}
@@ -112,17 +120,20 @@ public class SnarkManager implements Snark.CompleteListener {
return new File(dir);
}
/** null to set initial defaults */
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);
}
if (filename != null) {
_configFile = filename;
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))
@@ -169,16 +180,16 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
if (i2cpHost != null) {
I2PSnarkUtil.instance().setI2CPConfig(i2cpHost, i2cpPort, i2cpOpts);
_util.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);
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
I2PSnarkUtil.instance().setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
_util.setProxy(eepHost, eepPort);
_util.setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
_util.setMaxUpBW(getInt(PROP_UPBW_MAX, DEFAULT_MAX_UP_BW));
getDataDir().mkdirs();
}
@@ -198,12 +209,12 @@ public class SnarkManager implements Snark.CompleteListener {
String upLimit, String upBW, boolean useOpenTrackers, String openTrackers) {
boolean changed = false;
if (eepHost != null) {
int port = I2PSnarkUtil.instance().getEepProxyPort();
int port = _util.getEepProxyPort();
try { port = Integer.parseInt(eepPort); } catch (NumberFormatException nfe) {}
String host = I2PSnarkUtil.instance().getEepProxyHost();
String host = _util.getEepProxyHost();
if ( (eepHost.trim().length() > 0) && (port > 0) &&
((!host.equals(eepHost) || (port != I2PSnarkUtil.instance().getEepProxyPort()) )) ) {
I2PSnarkUtil.instance().setProxy(eepHost, port);
((!host.equals(eepHost) || (port != _util.getEepProxyPort()) )) ) {
_util.setProxy(eepHost, port);
changed = true;
_config.setProperty(PROP_EEP_HOST, eepHost);
_config.setProperty(PROP_EEP_PORT, eepPort+"");
@@ -211,11 +222,11 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
if (upLimit != null) {
int limit = I2PSnarkUtil.instance().getMaxUploaders();
int limit = _util.getMaxUploaders();
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
if ( limit != I2PSnarkUtil.instance().getMaxUploaders()) {
if ( limit != _util.getMaxUploaders()) {
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
I2PSnarkUtil.instance().setMaxUploaders(limit);
_util.setMaxUploaders(limit);
changed = true;
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
addMessage("Total uploaders limit changed to " + limit);
@@ -225,11 +236,11 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
if (upBW != null) {
int limit = I2PSnarkUtil.instance().getMaxUpBW();
int limit = _util.getMaxUpBW();
try { limit = Integer.parseInt(upBW); } catch (NumberFormatException nfe) {}
if ( limit != I2PSnarkUtil.instance().getMaxUpBW()) {
if ( limit != _util.getMaxUpBW()) {
if ( limit >= MIN_UP_BW ) {
I2PSnarkUtil.instance().setMaxUpBW(limit);
_util.setMaxUpBW(limit);
changed = true;
_config.setProperty(PROP_UPBW_MAX, "" + limit);
addMessage("Up BW limit changed to " + limit + "KBps");
@@ -239,8 +250,8 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
if (i2cpHost != null) {
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
int oldI2CPPort = _util.getI2CPPort();
String oldI2CPHost = _util.getI2CPHost();
int port = oldI2CPPort;
try { port = Integer.parseInt(i2cpPort); } catch (NumberFormatException nfe) {}
String host = oldI2CPHost;
@@ -266,7 +277,7 @@ public class SnarkManager implements Snark.CompleteListener {
if ( (i2cpHost.trim().length() > 0) && (port > 0) &&
((!host.equals(i2cpHost) ||
(port != I2PSnarkUtil.instance().getI2CPPort()) ||
(port != _util.getI2CPPort()) ||
(!oldOpts.equals(opts)))) ) {
boolean snarksActive = false;
Set names = listTorrentFiles();
@@ -282,19 +293,19 @@ public class SnarkManager implements Snark.CompleteListener {
_log.debug("i2cp host [" + i2cpHost + "] i2cp port " + port + " opts [" + opts
+ "] oldOpts [" + oldOpts + "]");
} else {
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
if (_util.connected()) {
_util.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();
_util.setI2CPConfig(i2cpHost, port, p);
boolean ok = _util.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();
_util.setI2CPConfig(oldI2CPHost, oldI2CPPort, oldOpts);
ok = _util.connect();
if (!ok)
addMessage("Unable to reconnect with the old settings!");
} else {
@@ -322,14 +333,14 @@ public class SnarkManager implements Snark.CompleteListener {
addMessage("Adjusted autostart to " + autoStart);
changed = true;
}
if (shouldUseOpenTrackers() != useOpenTrackers) {
_config.setProperty(PROP_USE_OPENTRACKERS, useOpenTrackers + "");
if (_util.shouldUseOpenTrackers() != useOpenTrackers) {
_config.setProperty(I2PSnarkUtil.PROP_USE_OPENTRACKERS, useOpenTrackers + "");
addMessage((useOpenTrackers ? "En" : "Dis") + "abled open trackers - torrent restart required to take effect");
changed = true;
}
if (openTrackers != null) {
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(getOpenTrackerString())) {
_config.setProperty(PROP_OPENTRACKERS, openTrackers.trim());
if (openTrackers.trim().length() > 0 && !openTrackers.trim().equals(_util.getOpenTrackerString())) {
_config.setProperty(I2PSnarkUtil.PROP_OPENTRACKERS, openTrackers.trim());
addMessage("Open Tracker list changed - torrent restart required to take effect");
changed = true;
}
@@ -354,7 +365,7 @@ public class SnarkManager implements Snark.CompleteListener {
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;
private static final int MAX_FILES_PER_TORRENT = 256;
/** set of filenames that we are dealing with */
public Set listTorrentFiles() { synchronized (_snarks) { return new HashSet(_snarks.keySet()); } }
@@ -364,9 +375,9 @@ public class SnarkManager implements Snark.CompleteListener {
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
public void addTorrent(String filename) { addTorrent(filename, false); }
public void addTorrent(String filename, boolean dontAutoStart) {
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
if ((!dontAutoStart) && !_util.connected()) {
addMessage("Connecting to I2P");
boolean ok = I2PSnarkUtil.instance().connect();
boolean ok = _util.connect();
if (!ok) {
addMessage("Error connecting to I2P - check your I2CP settings");
return;
@@ -407,7 +418,9 @@ public class SnarkManager implements Snark.CompleteListener {
addMessage(rejectMessage);
return;
} else {
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
torrent = new Snark(_util, filename, null, -1, null, null, this,
_peerCoordinatorSet, _connectionAcceptor,
false, dataDir.getPath());
torrent.completeListener = this;
synchronized (_snarks) {
_snarks.put(filename, torrent);
@@ -438,7 +451,8 @@ public class SnarkManager implements Snark.CompleteListener {
/**
* Get the timestamp for a torrent from the config file
*/
public long getSavedTorrentTime(MetaInfo metainfo) {
public long getSavedTorrentTime(Snark snark) {
MetaInfo metainfo = snark.meta;
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
@@ -457,7 +471,8 @@ public class SnarkManager implements Snark.CompleteListener {
* Get the saved bitfield for a torrent from the config file.
* Convert "." to a full bitfield.
*/
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
public BitField getSavedTorrentBitField(Snark snark) {
MetaInfo metainfo = snark.meta;
byte[] ih = metainfo.getInfoHash();
String infohash = Base64.encode(ih);
infohash = infohash.replace('=', '$');
@@ -574,7 +589,7 @@ public class SnarkManager implements Snark.CompleteListener {
if (remaining == 0) {
// should we disconnect/reconnect here (taking care to deal with the other thread's
// I2PServerSocket.accept() call properly?)
////I2PSnarkUtil.instance().
////_util.
}
if (!wasStopped)
addMessage("Torrent stopped: '" + sfile.getName() + "'");
@@ -618,11 +633,17 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
/** two listeners */
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)"));
updateStatus(snark);
}
public void updateStatus(Snark snark) {
saveTorrentStatus(snark.meta, snark.storage.getBitField());
}
private void monitorTorrents(File dir) {
@@ -644,7 +665,7 @@ public class SnarkManager implements Snark.CompleteListener {
if (existingNames.contains(foundNames.get(i))) {
// already known. noop
} else {
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
if (shouldAutoStart() && !_util.connect())
addMessage("Unable to connect to I2P");
addTorrent((String)foundNames.get(i), !shouldAutoStart());
}
@@ -672,6 +693,7 @@ public class SnarkManager implements Snark.CompleteListener {
, "welterde", "http://BGKmlDOoH3RzFbPRfRpZV2FjpVj8~3moFftw5-dZfDf2070TOe8Tf2~DAVeaM6ZRLdmFEt~9wyFL8YMLMoLoiwGEH6IGW6rc45tstN68KsBDWZqkTohV1q9XFgK9JnCwE~Oi89xLBHsLMTHOabowWM6dkC8nI6QqJC2JODqLPIRfOVrDdkjLwtCrsckzLybNdFmgfoqF05UITDyczPsFVaHtpF1sRggOVmdvCM66otyonlzNcJbn59PA-R808vUrCPMGU~O9Wys0i-NoqtIbtWfOKnjCRFMNw5ex4n9m5Sxm9e20UkpKG6qzEuvKZWi8vTLe1NW~CBrj~vG7I3Ok4wybUFflBFOaBabxYJLlx4xTE1zJIVxlsekmAjckB4v-cQwulFeikR4LxPQ6mCQknW2HZ4JQIq6hL9AMabxjOlYnzh7kjOfRGkck8YgeozcyTvcDUcUsOuSTk06L4kdrv8h2Cozjbloi5zl6KTbj5ZTciKCxi73Pn9grICn-HQqEAAAA.i2p/a=http://tracker.welterde.i2p/stats?mode=top5"
// , "mastertracker", "http://VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT8JD7i-C2xzYFa4jT5U2aqHzHI-Jre4HL3Ri5hFtZrLk2ax3ji7Qfb6qPnuYkuiF2E2UDmKUOppI8d9Ye7tjdhQVCy0izn55tBaB-U7UWdcvSK2i85sauyw3G0Gfads1Rvy5-CAe2paqyYATcDmGjpUNLoxbfv9KH1KmwRTNH6k1v4PyWYYnhbT39WfKMbBjSxVQRdi19cyJrULSWhjxaQfJHeWx5Z8Ev4bSPByBeQBFl2~4vqy0S5RypINsRSa3MZdbiAAyn5tr5slWR6QdoqY3qBQgBJFZppy-3iWkFqqKgSxCPundF8gdDLC5ddizl~KYcYKl42y9SGFHIukH-TZs8~em0~iahzsqWVRks3zRG~tlBcX2U3M2~OJs~C33-NKhyfZT7-XFBREvb8Szmd~p66jDxrwOnKaku-G6DyoQipJqIz4VHmY9-y5T8RrUcJcM-5lVoMpAAAA.i2p/announce.php=http://tracker.mastertracker.i2p/"
// , "Galen", "http://5jpwQMI5FT303YwKa5Rd38PYSX04pbIKgTaKQsWbqoWjIfoancFdWCShXHLI5G5ofOb0Xu11vl2VEMyPsg1jUFYSVnu4-VfMe3y4TKTR6DTpetWrnmEK6m2UXh91J5DZJAKlgmO7UdsFlBkQfR2rY853-DfbJtQIFl91tbsmjcA5CGQi4VxMFyIkBzv-pCsuLQiZqOwWasTlnzey8GcDAPG1LDcvfflGV~6F5no9mnuisZPteZKlrv~~TDoXTj74QjByWc4EOYlwqK8sbU9aOvz~s31XzErbPTfwiawiaZ0RUI-IDrKgyvmj0neuFTWgjRGVTH8bz7cBZIc3viy6ioD-eMQOrXaQL0TCWZUelRwHRvgdPiQrxdYQs7ixkajeHzxi-Pq0EMm5Vbh3j3Q9kfUFW3JjFDA-MLB4g6XnjCbM5J1rC0oOBDCIEfhQkszru5cyLjHiZ5yeA0VThgu~c7xKHybv~OMXION7V8pBKOgET7ZgAkw1xgYe3Kkyq5syAAAA.i2p/tr/announce.php=http://galen.i2p/tr/"
, "crstrack", "http://b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA.i2p/tracker/announce.php=http://crstrack.i2p/tracker/"
};
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
@@ -706,26 +728,6 @@ public class SnarkManager implements Snark.CompleteListener {
return trackerMap;
}
public String getOpenTrackerString() {
return _config.getProperty(PROP_OPENTRACKERS, DEFAULT_OPENTRACKERS);
}
/** comma delimited list open trackers to use as backups */
/** sorted map of name to announceURL=baseURL */
public List getOpenTrackers() {
if (!shouldUseOpenTrackers())
return null;
List rv = new ArrayList(1);
String trackers = getOpenTrackerString();
StringTokenizer tok = new StringTokenizer(trackers, ", ");
while (tok.hasMoreTokens())
rv.add(tok.nextToken());
if (rv.size() <= 0)
return null;
return rv;
}
private static class TorrentFilenameFilter implements FilenameFilter {
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
public static TorrentFilenameFilter instance() { return _filter; }
@@ -734,7 +736,7 @@ public class SnarkManager implements Snark.CompleteListener {
}
}
public class SnarkManagerShutdown extends I2PThread {
public class SnarkManagerShutdown extends I2PAppThread {
public void run() {
Set names = listTorrentFiles();
for (Iterator iter = names.iterator(); iter.hasNext(); ) {

View File

@@ -22,12 +22,12 @@ package org.klomp.snark;
import java.io.IOException;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
/**
* Makes sure everything ends correctly when shutting down.
*/
public class SnarkShutdown extends I2PThread
public class SnarkShutdown extends I2PAppThread
{
private final Storage storage;
private final PeerCoordinator coordinator;
@@ -51,21 +51,21 @@ public class SnarkShutdown extends I2PThread
public void run()
{
Snark.debug("Shutting down...", Snark.NOTICE);
//Snark.debug("Shutting down...", Snark.NOTICE);
Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
//Snark.debug("Halting ConnectionAcceptor...", Snark.INFO);
if (acceptor != null)
acceptor.halt();
Snark.debug("Halting TrackerClient...", Snark.INFO);
//Snark.debug("Halting TrackerClient...", Snark.INFO);
if (trackerclient != null)
trackerclient.halt();
Snark.debug("Halting PeerCoordinator...", Snark.INFO);
//Snark.debug("Halting PeerCoordinator...", Snark.INFO);
if (coordinator != null)
coordinator.halt();
Snark.debug("Closing Storage...", Snark.INFO);
//Snark.debug("Closing Storage...", Snark.INFO);
if (storage != null)
{
try
@@ -74,7 +74,7 @@ public class SnarkShutdown extends I2PThread
}
catch(IOException ioe)
{
I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
//I2PSnarkUtil.instance().debug("Couldn't properly close storage", Snark.ERROR, ioe);
throw new RuntimeException("b0rking");
}
}
@@ -82,7 +82,7 @@ public class SnarkShutdown extends I2PThread
// XXX - Should actually wait till done...
try
{
Snark.debug("Waiting 5 seconds...", Snark.INFO);
//Snark.debug("Waiting 5 seconds...", Snark.INFO);
Thread.sleep(5*1000);
}
catch (InterruptedException ie) { /* ignored */ }

View File

@@ -39,11 +39,16 @@ public class Storage
private long[] lengths;
private RandomAccessFile[] rafs;
private String[] names;
private Object[] RAFlock; // lock on RAF access
private long[] RAFtime; // when was RAF last accessed, or 0 if closed
private File[] RAFfile; // File to make it easier to reopen
private final StorageListener listener;
private I2PSnarkUtil _util;
private BitField bitfield; // BitField to represent the pieces
private int needed; // Number of pieces needed
private boolean _probablyComplete; // use this to decide whether to open files RO
// XXX - Not always set correctly
int piece_size;
@@ -62,12 +67,14 @@ public class Storage
*
* @exception IOException when creating and/or checking files fails.
*/
public Storage(MetaInfo metainfo, StorageListener listener)
public Storage(I2PSnarkUtil util, MetaInfo metainfo, StorageListener listener)
throws IOException
{
_util = util;
this.metainfo = metainfo;
this.listener = listener;
needed = metainfo.getPieces();
_probablyComplete = false;
bitfield = new BitField(needed);
}
@@ -76,9 +83,10 @@ public class Storage
* with an appropriate MetaInfo file as can be announced on the
* given announce String location.
*/
public Storage(File baseFile, String announce, StorageListener listener)
public Storage(I2PSnarkUtil util, File baseFile, String announce, StorageListener listener)
throws IOException
{
_util = util;
this.listener = listener;
// Create names, rafs and lengths arrays.
getFiles(baseFile);
@@ -119,7 +127,6 @@ public class Storage
files.add(file);
}
String name = baseFile.getName();
if (files.size() == 1) // FIXME: ...and if base file not a directory or should this be the only check?
// this makes a bad metainfo if the directory has only one file in it
{
@@ -133,7 +140,8 @@ public class Storage
}
// Creates piece hases for a new storage.
// Creates piece hashes for a new storage.
// This does NOT create the files, just the hashes
public void create() throws IOException
{
// if (true) {
@@ -197,14 +205,8 @@ public class Storage
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());
}
@@ -218,6 +220,9 @@ public class Storage
names = new String[size];
lengths = new long[size];
rafs = new RandomAccessFile[size];
RAFlock = new Object[size];
RAFtime = new long[size];
RAFfile = new File[size];
int i = 0;
Iterator it = files.iterator();
@@ -228,12 +233,13 @@ public class Storage
if (base.isDirectory() && names[i].startsWith(base.getPath()))
names[i] = names[i].substring(base.getPath().length() + 1);
lengths[i] = f.length();
rafs[i] = new RandomAccessFile(f, "r");
RAFlock[i] = new Object();
RAFfile[i] = f;
i++;
}
}
private static void addFiles(List l, File f)
private void addFiles(List l, File f)
{
if (!f.isDirectory())
l.add(f);
@@ -242,7 +248,7 @@ public class Storage
File[] files = f.listFiles();
if (files == null)
{
Snark.debug("WARNING: Skipping '" + f
_util.debug("WARNING: Skipping '" + f
+ "' not a normal file.", Snark.WARNING);
return;
}
@@ -288,40 +294,44 @@ public class Storage
* Creates (and/or checks) all files from the metainfo file list.
*/
public void check(String rootDir) throws IOException
{
check(rootDir, 0, null);
}
/** use a saved bitfield and timestamp from a config file */
public void check(String rootDir, long savedTime, BitField savedBitField) throws IOException
{
File base = new File(rootDir, filterName(metainfo.getName()));
// look for saved bitfield and timestamp in the config file
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
List files = metainfo.getFiles();
if (files == null)
{
// Create base as file.
Snark.debug("Creating/Checking file: " + base, Snark.NOTICE);
_util.debug("Creating/Checking file: " + base, Snark.NOTICE);
if (!base.createNewFile() && !base.exists())
throw new IOException("Could not create file " + base);
lengths = new long[1];
rafs = new RandomAccessFile[1];
names = new String[1];
RAFlock = new Object[1];
RAFtime = new long[1];
RAFfile = new File[1];
lengths[0] = metainfo.getTotalLength();
RAFlock[0] = new Object();
RAFfile[0] = base;
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (base.exists() && ((useSavedBitField && savedBitField.complete()) || !base.canWrite()))
rafs[0] = new RandomAccessFile(base, "r");
else
rafs[0] = new RandomAccessFile(base, "rw");
names[0] = base.getName();
}
else
{
// Create base as dir.
Snark.debug("Creating/Checking directory: " + base, Snark.NOTICE);
_util.debug("Creating/Checking directory: " + base, Snark.NOTICE);
if (!base.mkdir() && !base.isDirectory())
throw new IOException("Could not create directory " + base);
@@ -331,20 +341,21 @@ public class Storage
lengths = new long[size];
rafs = new RandomAccessFile[size];
names = new String[size];
RAFlock = new Object[size];
RAFtime = new long[size];
RAFfile = new File[size];
for (int i = 0; i < size; i++)
{
File f = createFileFromNames(base, (List)files.get(i));
lengths[i] = ((Long)ls.get(i)).longValue();
RAFlock[i] = new Object();
RAFfile[i] = f;
total += lengths[i];
if (useSavedBitField) {
long lm = base.lastModified();
if (lm <= 0 || lm > savedTime)
useSavedBitField = false;
}
if (f.exists() && ((useSavedBitField && savedBitField.complete()) || !f.canWrite()))
rafs[i] = new RandomAccessFile(f, "r");
else
rafs[i] = new RandomAccessFile(f, "rw");
names[i] = f.getName();
}
@@ -357,11 +368,17 @@ public class Storage
if (useSavedBitField) {
bitfield = savedBitField;
needed = metainfo.getPieces() - bitfield.count();
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
_probablyComplete = complete();
_util.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
} else {
// the following sets the needed variable
changed = true;
checkCreateFiles();
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
if (complete())
_util.debug("Torrent is complete", Snark.NOTICE);
else
_util.debug("Still need " + needed + " out of " + metainfo.getPieces() + " pieces", Snark.NOTICE);
}
/**
@@ -376,19 +393,14 @@ public class Storage
if (files == null)
{
// Reopen base as file.
Snark.debug("Reopening file: " + base, Snark.NOTICE);
_util.debug("Reopening file: " + base, Snark.NOTICE);
if (!base.exists())
throw new IOException("Could not reopen file " + base);
if (complete() || !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");
}
else
{
// Reopen base as dir.
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
_util.debug("Reopening directory: " + base, Snark.NOTICE);
if (!base.isDirectory())
throw new IOException("Could not reopen directory " + base);
@@ -398,10 +410,6 @@ public class Storage
File f = getFileFromNames(base, (List)files.get(i));
if (!f.exists())
throw new IOException("Could not reopen file " + f);
if (complete() || !f.canWrite()) // see above re: only seeding
rafs[i] = new RandomAccessFile(f, "r");
else
rafs[i] = new RandomAccessFile(f, "rw");
}
}
@@ -453,27 +461,43 @@ public class Storage
return base;
}
/**
* This is called at the beginning, and at presumed completion,
* so we have to be careful about locking.
*/
private void checkCreateFiles() throws IOException
{
// Whether we are resuming or not,
// if any of the files already exists we assume we are resuming.
boolean resume = false;
_probablyComplete = true;
needed = metainfo.getPieces();
// Make sure all files are available and of correct length
for (int i = 0; i < rafs.length; i++)
{
long length = rafs[i].length();
if(length == lengths[i])
long length = RAFfile[i].length();
if(RAFfile[i].exists() && length == lengths[i])
{
if (listener != null)
listener.storageAllocated(this, length);
resume = true; // XXX Could dynamicly check
}
else if (length == 0)
allocateFile(i);
else {
Snark.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
rafs[i].setLength(lengths[i]);
else if (length == 0) {
changed = true;
synchronized(RAFlock[i]) {
allocateFile(i);
}
} else {
_util.debug("File '" + names[i] + "' exists, but has wrong length - repairing corruption", Snark.ERROR);
changed = true;
_probablyComplete = false; // to force RW
synchronized(RAFlock[i]) {
checkRAF(i);
rafs[i].setLength(lengths[i]);
}
// will be closed below
}
}
@@ -497,6 +521,17 @@ public class Storage
}
}
_probablyComplete = complete();
// close all the files so we don't end up with a zillion open ones;
// we will reopen as needed
for (int i = 0; i < rafs.length; i++) {
synchronized(RAFlock[i]) {
try {
closeRAF(i);
} catch (IOException ioe) {}
}
}
if (listener != null) {
listener.storageAllChecked(this);
if (needed <= 0)
@@ -506,6 +541,8 @@ public class Storage
private void allocateFile(int nr) throws IOException
{
// caller synchronized
openRAF(nr, false); // RW
// XXX - Is this the best way to make sure we have enough space for
// the whole file?
listener.storageCreateFile(this, names[nr], lengths[nr]);
@@ -520,6 +557,7 @@ public class Storage
}
int size = (int)(lengths[nr] - i*ZEROBLOCKSIZE);
rafs[nr].write(zeros, 0, size);
// caller will close rafs[nr]
if (listener != null)
listener.storageAllocated(this, size);
}
@@ -535,12 +573,11 @@ public class Storage
for (int i = 0; i < rafs.length; i++)
{
try {
synchronized(rafs[i])
{
rafs[i].close();
}
synchronized(RAFlock[i]) {
closeRAF(i);
}
} catch (IOException ioe) {
I2PSnarkUtil.instance().debug("Error closing " + rafs[i], Snark.ERROR, ioe);
_util.debug("Error closing " + RAFfile[i], Snark.ERROR, ioe);
// gobble gobble
}
}
@@ -561,7 +598,7 @@ public class Storage
try {
bs = new byte[len];
} catch (OutOfMemoryError oom) {
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
_util.debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
return null;
}
getUncheckedPiece(piece, bs, off, len);
@@ -587,18 +624,10 @@ public class Storage
if (!correctHash)
return false;
boolean complete;
synchronized(bitfield)
{
if (bitfield.get(piece))
return true; // No need to store twice.
else
{
bitfield.set(piece);
needed--;
complete = needed == 0;
}
}
// Early typecast, avoid possibly overflowing a temp integer
@@ -618,8 +647,9 @@ public class Storage
{
int need = length - written;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(rafs[i])
synchronized(RAFlock[i])
{
checkRAF(i);
rafs[i].seek(start);
rafs[i].write(bs, off + written, len);
}
@@ -633,8 +663,21 @@ public class Storage
}
changed = true;
// do this after the write, so we know it succeeded, and we don't set the
// needed count to zero, which would cause checkRAF() to open the file readonly.
boolean complete = false;
synchronized(bitfield)
{
if (!bitfield.get(piece))
{
bitfield.set(piece);
needed--;
complete = needed == 0;
}
}
if (complete) {
// listener.storageCompleted(this);
// do we also need to close all of the files and reopen
// them readonly?
@@ -649,11 +692,10 @@ public class Storage
bitfield = new BitField(needed);
checkCreateFiles();
if (needed > 0) {
listener.setWantedPieces(this);
Snark.debug("WARNING: Not really done, missing " + needed
if (listener != null)
listener.setWantedPieces(this);
_util.debug("WARNING: Not really done, missing " + needed
+ " pieces", Snark.WARNING);
} else {
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
}
}
@@ -688,8 +730,9 @@ public class Storage
{
int need = length - read;
int len = (start + need < raflen) ? need : (int)(raflen - start);
synchronized(rafs[i])
synchronized(RAFlock[i])
{
checkRAF(i);
rafs[i].seek(start);
rafs[i].readFully(bs, read, len);
}
@@ -704,4 +747,59 @@ public class Storage
return length;
}
/**
* Close unused RAFs - call periodically
*/
private static final long RAFCloseDelay = 7*60*1000;
public void cleanRAFs() {
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
for (int i = 0; i < RAFlock.length; i++) {
synchronized(RAFlock[i]) {
if (RAFtime[i] > 0 && RAFtime[i] < cutoff) {
try {
closeRAF(i);
} catch (IOException ioe) {}
}
}
}
}
/*
* For each of the following,
* caller must synchronize on RAFlock[i]
* ... except at the beginning if you're careful
*/
/**
* This must be called before using the RAF to ensure it is open
*/
private void checkRAF(int i) throws IOException {
if (RAFtime[i] > 0) {
RAFtime[i] = System.currentTimeMillis();
return;
}
openRAF(i);
}
private void openRAF(int i) throws IOException {
openRAF(i, _probablyComplete);
}
private void openRAF(int i, boolean readonly) throws IOException {
rafs[i] = new RandomAccessFile(RAFfile[i], (readonly || !RAFfile[i].canWrite()) ? "r" : "rw");
RAFtime[i] = System.currentTimeMillis();
}
/**
* Can be called even if not open
*/
private void closeRAF(int i) throws IOException {
RAFtime[i] = 0;
if (rafs[i] == null)
return;
rafs[i].close();
rafs[i] = null;
}
}

View File

@@ -31,7 +31,7 @@ import java.util.List;
import java.util.Random;
import java.util.Set;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
@@ -40,7 +40,7 @@ import net.i2p.util.Log;
*
* @author Mark Wielaard (mark@klomp.org)
*/
public class TrackerClient extends I2PThread
public class TrackerClient extends I2PAppThread
{
private static final Log _log = new Log(TrackerClient.class);
private static final String NO_EVENT = "";
@@ -57,6 +57,7 @@ public class TrackerClient extends I2PThread
private final static int MAX_CONSEC_FAILS = 5; // slow down after this
private final static int LONG_SLEEP = 30*60*1000; // sleep a while after lots of fails
private I2PSnarkUtil _util;
private final MetaInfo meta;
private final PeerCoordinator coordinator;
private final int port;
@@ -66,10 +67,11 @@ public class TrackerClient extends I2PThread
private List trackers;
public TrackerClient(MetaInfo meta, PeerCoordinator coordinator)
public TrackerClient(I2PSnarkUtil util, MetaInfo meta, PeerCoordinator coordinator)
{
// Set unique name.
super("TrackerClient-" + urlencode(coordinator.getID()));
_util = util;
this.meta = meta;
this.coordinator = coordinator;
@@ -98,13 +100,13 @@ public class TrackerClient extends I2PThread
}
private boolean verifyConnected() {
while (!stop && !I2PSnarkUtil.instance().connected()) {
boolean ok = I2PSnarkUtil.instance().connect();
while (!stop && !_util.connected()) {
boolean ok = _util.connect();
if (!ok) {
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
}
}
return !stop && I2PSnarkUtil.instance().connected();
return !stop && _util.connected();
}
public void run()
@@ -121,7 +123,7 @@ public class TrackerClient extends I2PThread
// the primary tracker, that we don't add it twice.
trackers = new ArrayList(2);
trackers.add(new Tracker(meta.getAnnounce(), true));
List tlist = SnarkManager.instance().getOpenTrackers();
List tlist = _util.getOpenTrackers();
if (tlist != null) {
for (int i = 0; i < tlist.size(); i++) {
String url = (String)tlist.get(i);
@@ -136,7 +138,7 @@ public class TrackerClient extends I2PThread
}
if (meta.getAnnounce().startsWith(url.substring(0, slash)))
continue;
String dest = I2PSnarkUtil.instance().lookup(url.substring(7, slash));
String dest = _util.lookup(url.substring(7, slash));
if (dest == null) {
_log.error("Announce host unknown: [" + url + "]");
continue;
@@ -264,7 +266,7 @@ public class TrackerClient extends I2PThread
catch (IOException ioe)
{
// Probably not fatal (if it doesn't last to long...)
Snark.debug
_util.debug
("WARNING: Could not contact tracker at '"
+ tr.announce + "': " + ioe, Snark.WARNING);
tr.trackerProblems = ioe.getMessage();
@@ -295,12 +297,12 @@ public class TrackerClient extends I2PThread
// we could try and total the unique peers but that's too hard for now
coordinator.trackerSeenPeers = maxSeenPeers;
if (!started)
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
_util.debug(" Retrying in one minute...", Snark.DEBUG);
} // *** end of while loop
} // try
catch (Throwable t)
{
I2PSnarkUtil.instance().debug("TrackerClient: " + t, Snark.ERROR, t);
_util.debug("TrackerClient: " + t, Snark.ERROR, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
@@ -332,19 +334,18 @@ public class TrackerClient extends I2PThread
+ "?info_hash=" + infoHash
+ "&peer_id=" + peerID
+ "&port=" + port
+ "&ip=" + I2PSnarkUtil.instance().getOurIPString() + ".i2p"
+ "&ip=" + _util.getOurIPString() + ".i2p"
+ "&uploaded=" + uploaded
+ "&downloaded=" + downloaded
+ "&left=" + left
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
_util.debug("Sending TrackerClient request: " + s, Snark.INFO);
tr.lastRequestTime = System.currentTimeMillis();
File fetched = I2PSnarkUtil.instance().get(s);
File fetched = _util.get(s);
if (fetched == null) {
throw new IOException("Error fetching " + s);
}
fetched.deleteOnExit();
InputStream in = null;
try {
@@ -352,7 +353,7 @@ public class TrackerClient extends I2PThread
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
coordinator.getMetaInfo());
Snark.debug("TrackerClient response: " + info, Snark.INFO);
_util.debug("TrackerClient response: " + info, Snark.INFO);
String failure = info.getFailureReason();
if (failure != null)

View File

@@ -101,7 +101,7 @@ public class TrackerInfo
peerID = new PeerID(((BEValue)it.next()).getMap());
} catch (InvalidBEncodingException ibe) {
// don't let one bad entry spoil the whole list
Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
//Snark.debug("Discarding peer from list: " + ibe, Snark.ERROR);
continue;
}
peers.add(new Peer(peerID, my_id, metainfo));

View File

@@ -5,6 +5,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -23,7 +24,7 @@ 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.I2PAppThread;
import net.i2p.util.Log;
import org.klomp.snark.I2PSnarkUtil;
@@ -55,6 +56,7 @@ public class I2PSnarkServlet extends HttpServlet {
if ( (configFile == null) || (configFile.trim().length() <= 0) )
configFile = "i2psnark.config";
_manager.loadConfig(configFile);
_manager.start();
}
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
@@ -87,8 +89,7 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("<table border=\"0\" width=\"100%\">\n");
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
out.write("<tr><td><a href=\"http://codevoid.i2p/forums/5\" class=\"snarkRefresh\">Wishlist</a>\n");
int count = 1;
int count = 0;
Map trackers = _manager.getTrackers();
for (Iterator iter = trackers.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
@@ -116,7 +117,7 @@ public class I2PSnarkServlet extends HttpServlet {
List snarks = getSortedSnarks(req);
String uri = req.getRequestURI();
out.write(TABLE_HEADER);
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
if (_manager.util().connected() && snarks.size() > 0) {
if (peerParam != null)
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
else
@@ -124,7 +125,7 @@ public class I2PSnarkServlet extends HttpServlet {
}
out.write(TABLE_HEADER2);
out.write("<th align=\"left\" valign=\"top\">");
if (I2PSnarkUtil.instance().connected())
if (_manager.util().connected())
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
else if (snarks.size() > 0)
@@ -201,7 +202,7 @@ public class I2PSnarkServlet extends HttpServlet {
}
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
_manager.addMessage("Fetching " + newURL);
I2PThread fetch = new I2PThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
I2PAppThread fetch = new I2PAppThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
fetch.start();
} else {
// no file or URL specified
@@ -330,7 +331,7 @@ public class I2PSnarkServlet extends HttpServlet {
_manager.addMessage("Error creating torrent - you must select a tracker");
else if (baseFile.exists()) {
try {
Storage s = new Storage(baseFile, announceURL, null);
Storage s = new Storage(_manager.util(), baseFile, announceURL, null);
s.create();
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
MetaInfo info = s.getMetaInfo();
@@ -361,8 +362,8 @@ public class I2PSnarkServlet extends HttpServlet {
if (!snark.stopped)
_manager.stopTorrent(snark.torrent, false);
}
if (I2PSnarkUtil.instance().connected()) {
I2PSnarkUtil.instance().disconnect();
if (_manager.util().connected()) {
_manager.util().disconnect();
_manager.addMessage("I2P tunnel closed");
}
} else if ("StartAll".equals(action)) {
@@ -378,7 +379,8 @@ public class I2PSnarkServlet extends HttpServlet {
private List getSortedSnarks(HttpServletRequest req) {
Set files = _manager.listTorrentFiles();
TreeSet fileNames = new TreeSet(files); // sorts it alphabetically
TreeSet fileNames = new TreeSet(Collator.getInstance()); // sorts it alphabetically
fileNames.addAll(files);
ArrayList rv = new ArrayList(fileNames.size());
for (Iterator iter = fileNames.iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
@@ -573,6 +575,10 @@ public class I2PSnarkServlet extends HttpServlet {
client = "I2P-BT";
else if ("LUFa".equals(ch))
client = "Azureus";
else if ("CwsL".equals(ch))
client = "I2PSnarkXL";
else if ("AUZV".equals(ch) || "AkZV".equals(ch) || "A0ZV".equals(ch))
client = "Robert";
else
client = "Unknown";
out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;<tt>" + peer.toString().substring(5, 9) + "</tt>");
@@ -690,8 +696,8 @@ public class I2PSnarkServlet extends HttpServlet {
String uri = req.getRequestURI();
String dataDir = _manager.getDataDir().getAbsolutePath();
boolean autoStart = _manager.shouldAutoStart();
boolean useOpenTrackers = _manager.shouldUseOpenTrackers();
String openTrackers = _manager.getOpenTrackerString();
boolean useOpenTrackers = _manager.util().shouldUseOpenTrackers();
String openTrackers = _manager.util().getOpenTrackerString();
//int seedPct = 0;
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
@@ -723,9 +729,9 @@ public class I2PSnarkServlet extends HttpServlet {
out.write("</select><br />\n");
*/
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
+ _manager.util().getMaxUploaders() + "\" size=\"3\" maxlength=\"3\" /> peers<br />\n");
out.write("Up bandwidth limit: <input type=\"text\" name=\"upBW\" value=\""
+ I2PSnarkUtil.instance().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
+ _manager.util().getMaxUpBW() + "\" size=\"3\" maxlength=\"3\" /> KBps <i>(Router Up BW / 2 recommended)</i><br />\n");
out.write("Use open trackers also: <input type=\"checkbox\" name=\"useOpenTrackers\" value=\"true\" "
+ (useOpenTrackers ? "checked " : "")
@@ -735,15 +741,15 @@ public class I2PSnarkServlet extends HttpServlet {
//out.write("<hr />\n");
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
+ I2PSnarkUtil.instance().getEepProxyHost() + "\" size=\"15\" /> ");
+ _manager.util().getEepProxyHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"eepPort\" value=\""
+ I2PSnarkUtil.instance().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
+ _manager.util().getEepProxyPort() + "\" size=\"5\" maxlength=\"5\" /><br />\n");
out.write("I2CP host: <input type=\"text\" name=\"i2cpHost\" value=\""
+ I2PSnarkUtil.instance().getI2CPHost() + "\" size=\"15\" /> ");
+ _manager.util().getI2CPHost() + "\" size=\"15\" /> ");
out.write("port: <input type=\"text\" name=\"i2cpPort\" value=\"" +
+ I2PSnarkUtil.instance().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
+ _manager.util().getI2CPPort() + "\" size=\"5\" maxlength=\"5\" /> <br />\n");
StringBuffer opts = new StringBuffer(64);
Map options = new TreeMap(I2PSnarkUtil.instance().getI2CPOptions());
Map options = new TreeMap(_manager.util().getI2CPOptions());
for (Iterator iter = options.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry)iter.next();
String key = (String)entry.getKey();
@@ -877,7 +883,7 @@ class FetchAndAdd implements Runnable {
public void run() {
_url = _url.trim();
// 3 retries
File file = I2PSnarkUtil.instance().get(_url, false, 3);
File file = _manager.util().get(_url, false, 3);
try {
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
_manager.addMessage("Torrent fetched from " + _url);

View File

@@ -470,7 +470,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (_log.shouldLog(Log.INFO))
_log.info(getPrefix(requestId) + "Setting host = " + host);
} else if (lowercaseLine.startsWith("user-agent: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue()) {
line = null;
continue;
} else if (lowercaseLine.startsWith("accept")) {
@@ -479,13 +479,13 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
line = null;
continue;
} else if (lowercaseLine.startsWith("referer: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_REFERER)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_REFERER)).booleanValue()) {
// Shouldn't we be more specific, like accepting in-site referers ?
//line = "Referer: i2p";
line = null;
continue; // completely strip the line
} else if (lowercaseLine.startsWith("via: ") &&
!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_VIA)).booleanValue()) {
!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_VIA)).booleanValue()) {
//line = "Via: i2p";
line = null;
continue; // completely strip the line
@@ -498,7 +498,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
if (line.length() == 0) {
String ok = getTunnel().getContext().getProperty("i2ptunnel.gzip");
String ok = getTunnel().getClientOptions().getProperty("i2ptunnel.gzip");
boolean gzip = DEFAULT_GZIP;
if (ok != null)
gzip = Boolean.valueOf(ok).booleanValue();
@@ -509,7 +509,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
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");
}
if (!Boolean.valueOf(getTunnel().getContext().getProperty(PROP_USER_AGENT)).booleanValue())
if (!Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_USER_AGENT)).booleanValue())
newRequest.append("User-Agent: MYOB/6.66 (AN/ON)\r\n");
newRequest.append("Connection: close\r\n\r\n");
break;

View File

@@ -14,6 +14,7 @@ import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
import net.i2p.client.I2PSession;
import net.i2p.data.Base32;
import net.i2p.data.Destination;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
@@ -361,6 +362,19 @@ public class TunnelController implements Logging {
return null;
}
public String getMyDestHashBase32() {
if (_tunnel != null) {
List sessions = _tunnel.getSessions();
for (int i = 0; i < sessions.size(); i++) {
I2PSession session = (I2PSession)sessions.get(i);
Destination dest = session.getMyDestination();
if (dest != null)
return Base32.encode(dest.calculateHash().getData());
}
}
return null;
}
public boolean getIsRunning() { return _running; }
public boolean getIsStarting() { return _starting; }

View File

@@ -437,6 +437,19 @@ public class IndexBean {
}
}
public String getDestHashBase32(int tunnel) {
TunnelController tun = getController(tunnel);
if (tun != null) {
String rv = tun.getMyDestHashBase32();
if (rv != null)
return rv;
else
return "";
} else {
return "";
}
}
///
/// bean props for form submission
///

View File

@@ -180,13 +180,23 @@
</div>
<div class="targetField rowItem">
<label>Points at:</label>
<span class="text"><%=indexBean.getServerTarget(curServer)%></span>
<span class="text">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer))) {
%>
<a href="http://<%=indexBean.getServerTarget(curServer)%>/" title="Test HTTP server, bypassing I2P"><%=indexBean.getServerTarget(curServer)%></a>
<%
} else {
%><%=indexBean.getServerTarget(curServer)%>
<%
}
%></span>
</div>
<div class="previewField rowItem">
<%
if ("httpserver".equals(indexBean.getInternalType(curServer)) && indexBean.getTunnelStatus(curServer) == IndexBean.RUNNING) {
%><label>Preview:</label>
<a class="control" title="Preview this Tunnel" href="http://<%=(new java.util.Random()).nextLong()%>.i2p/?i2paddresshelper=<%=indexBean.getDestinationBase64(curServer)%>" target="_new">Preview</a>
<a class="control" title="Test HTTP server through I2P" href="http://<%=indexBean.getDestHashBase32(curServer)%>.b32.i2p">Preview</a>
<%
} else {
%><span class="comment">No Preview</span>

Binary file not shown.

View File

@@ -1 +0,0 @@
This is JDOM 1.0 from http://jdom.org/, released under an Apache style license

View File

@@ -1,236 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Ports + Ant = Pants, a simple Ant-based package manager
free (adj.): unencumbered; not under the control of others
Written by smeghead in 2005 and released into the public domain with no
warranty of any kind, either expressed or implied. It probably won't make
your computer catch on fire, or eat your children, but it might. Use at your
own risk.
-->
<project basedir="." default="help" name="pants-interface">
<!-- .......................... Global Properties .......................... -->
<!-- ........................... Internal Tasks ............................ -->
<target name="-fetchCvs" unless="cvs.source.available" if="using.cvs">
<cvs compressionlevel="${cvs.compression.level}"
date="${cvs.date}"
dest="./distfiles/cvs-src/${pbuild}"
failonerror="true"
package="${cvs.package}"
passfile="${cvs.passfile}"
port="${cvs.port}"
cvsRoot="${cvs.root}"
cvsRsh="${cvs.rsh}"
tag="${cvs.tag}"
/>
</target>
<target name="-fetchPackage" unless="using.cvs">
<get src="${package.url}"
verbose="true"
dest="./distfiles"
/>
</target>
<target name="-init">
<!--
TODO: Create dist/ and working/ folders for each pbuild subdir in case
they've been wiped.
-->
<loadproperties srcfile="world" />
<taskdef name="mergetypedproperties"
classname="net.i2p.pants.MergeTypedPropertiesTask"
classpath="./lib/pants.jar"
/>
<mergetypedproperties input="./pbuilds/${pbuild}/pbuild.properties"
output="./pbuilds/${pbuild}/merged-properties.temp"
booleanList="version.latest.find.regex.canonicaleq, version.latest.find.regex.caseinsensitive, version.latest.find.regex.comments, version.latest.find.regex.dotall, version.latest.find.regex.multiline, version.latest.find.regex.unicodecase, version.latest.find.regex.unixlines"
stringList="cvs.compression.level, cvs.date, cvs.package, cvs.passfile, cvs.port, cvs.root, cvs.rsh, cvs.tag, package.url, version.latest, version.latest.find.url, version.latest.find.regex"
/>
<loadproperties srcfile="./pbuilds/${pbuild}/merged-properties.temp" />
<delete file="./pbuilds/${pbuild}/merged-properties.temp" />
<!--
If '-Dpbuild={name}' isn't specified, the 'build', 'fetch', 'update'
and 'version' commands should default to 'world' behavior.
-->
<antcall target="-setWorld" />
<condition property="using.cvs">
<or>
<equals arg1="CVS" arg2="${version.using.${pbuild}}" />
<equals arg1="cvs" arg2="${version.using.${pbuild}}" />
</or>
</condition>
<!--
If 'version.recommended' isn't defined in pbuild.properties, default
to latest available version.
-->
</target>
<target name="-setWorld" unless="pbuild">
<property name="pbuild" value="world" />
</target>
<target name="-unpackTarBz2">
<untar src="${pbuild.package}"
compression="bzip2"
dest="./${pbuild}/working"
/>
</target>
<target name="-unpackTarGzip">
<untar src="${pbuild.package}"
compression="gzip"
dest="./${pbuild}/working"
/>
</target>
<target name="-unpackZip">
<unzip src="${pbuild.package}" dest="./${pbuild}/working" />
</target>
<target name="-updateCvs" if="using.cvs">
<cvs command="update -d"
compressionlevel="${compression.level}"
date="${cvs.date}"
dest="./distfiles/cvs-src"
failonerror="true"
package="${cvs.package}"
passfile="${cvs.passfile}"
port="${cvs.port}"
cvsRoot="${cvs.root}"
cvsRsh="${cvs.rsh}"
tag="${cvs.tag}"
/>
</target>
<target name="-updateConfirm" if="confirm.update" unless="no.prompts">
<input validargs="y,Y,n,N"
defaultvalue="n"
addproperty="confirm.update.answer">
You currently have the recommended version installed. A newer
version will be installed if you continue and this may break some
applications which depend on this package. Are you sure you want
to update? [y/N]
</input>
<condition property="abort.update">
<or>
<equals arg1="n" arg2="${confirm.update.answer}" />
<equals arg1="N" arg2="${confirm.update.answer}" />
</or>
</condition>
<fail if="abort.update">Update aborted.</fail>
</target>
<target name="-versionLatest">
<get src="${version.latest.find.url}"
dest="version.latest.in.temp"
verbose="true"
/>
<taskdef name="match"
classname="net.i2p.pants.MatchTask"
classpath="./lib/pants.jar"
/>
<match input="version.latest.in.temp"
output="version.latest.parsed.temp"
regex="${version.latest.find.regex}"
canonicaleq="${version.latest.find.regex.canonicaleq}"
caseinsensitive="${version.latest.find.regex.caseinsensitive}"
comments="${version.latest.find.regex.comments}"
dotall="${version.latest.find.regex.dotall}"
multiline="${version.latest.find.regex.multiline}"
unicodecase="${version.latest.find.regex.unicodecase}"
unixlines="${version.latest.find.regex.unixlines}"
/>
<loadproperties srcFile="version.latest.parsed.temp" />
<delete file="version.latest.in.temp" />
<delete file="version.latest.parsed.temp" />
<property name="version.latest" value="${group.1}" />
</target>
<target name="-versionRecommended">
<property name="version.recommended" value="x" />
</target>
<target name="-versionUsing">
<property name="version.using" value="x" />
</target>
<!-- .......................... Public Interface ........................... -->
<target name="build" depends="-init,fetch"
description="Build a pbuild and its dependencies">
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="clean" />
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="build" />
<!--
Perform a 'clean' on the target first (but not 'distclean')
-->
</target>
<target name="fetch" depends="-init"
description="Get package only">
<antcall target="-fetchPackage" />
<antcall target="-fetchCvs" />
<copydir dest="./pbuilds/${pbuild}/working"
src="./distfiles/cvs-src/${pbuild}"
/>
</target>
<target name="help"
description="Display usage synopsis">
<echo>
Pants usage:
ant [--usejikes] [-Dpbuild={name}] [-Dpbuild.version={version}]
[-D{property}={value}] [-Dno.prompts=true] build | fetch |
help | install | uninstall | update | version
build Build a pbuild and its dependencies
fetch Get package only
help Display usage synopsis
install Fetch, build and install a pbuild
uninstall Uninstall a pbuild
update Update pbuild(s) to the latest version(s)
version Display pbuild version information
</echo>
</target>
<!--
Install recommended version by default unless 'version' property is set.
Do not install if package is already installed.
-->
<target name="install" depends="-init, build"
description="Install a pbuild">
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}" target="dist" />
<ant antfile="pbuild.xml" dir="./pbuilds/${pbuild}"
target="distclean"
/>
</target>
<target name="uninstall" depends="-init"
description="Uninstall a pbuild" />
<target name="update" depends="-init"
description="Update pbuild(s) to the latest version(s)">
<condition property="${confirm.update}">
<equals arg1="${version.using}" arg2="${version.recommended}" />
</condition>
<antcall target="-updateConfirm" />
</target>
<target name="version"
depends="-init, -versionRecommended, -versionUsing, -versionLatest"
description="Display pbuild version information">
<echo message="Latest version: ${version.recommended}" />
<echo message="Latest version: ${version.using}" />
<echo message="Latest version: ${version.latest}" />
</target>
</project>

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="build-pants">
<target name="build"
description="Build the source">
<mkdir dir="./java/build"/>
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
</target>
<target name="clean"
description="Remove all object files">
<delete dir="./java/build" />
<delete dir="./java/jartemp" />
</target>
<target name="dist" depends="build, jar"
description="Create the jar and copy it to ../lib">
<copy todir="../lib" file="./java/build/pants.jar" />
</target>
<target name="distclean" depends="clean"
description="Remove the jar and all object files" >
<delete file="../lib/pants.jar" />
</target>
<target name="jar">
<delete dir="./java/jartemp" />
<mkdir dir="./java/jartemp" />
<copy todir="./java/jartemp">
<fileset dir="./java/build" includes="**/*.class" />
</copy>
<jar basedir="./java/jartemp" jarfile="./java/build/pants.jar">
<manifest>
<section name="net.i2p.pants">
<attribute name="Implementation-Title" value="Pants" />
<attribute name="Implementation-Version" value="0.0.1" />
<attribute name="Implementation-Vendor" value="I2P" />
<attribute name="Implementation-Vendor-Id" value="I2P" />
<attribute name="Implementation-URL" value="http://www.i2p.net" />
</section>
</manifest>
</jar>
<delete dir="./java/jartemp" />
</target>
</project>

View File

@@ -1,212 +0,0 @@
/*
* Ports + Ant = Pants, a simple Ant-based package manager
*
* free (adj.): unencumbered; not under the control of others
*
* Written by smeghead in 2005 and released into the public domain with no
* warranty of any kind, either expressed or implied. It probably won't make
* your computer catch on fire, or eat your children, but it might. Use at your
* own risk.
*/
package net.i2p.pants;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* <p>Custom Ant task for matching the contents of a file against a given
* regular expression and writing any matching groups to a file in
* <code>java.util.Properties</code> format.
* </p>
* <p>Each key in the properties file is named after the number corresponding to
* its matching group and its value is the contents of the matching group.
* </p>
* <p>Regular expressions passed to this task must conform to the specification
* used by Sun's <code>java.util.regex</code> package and thus are mostly
* compatible with Perl 5 regular expressions.
* </p>
* <p>When calling the <code>match</code> task, the attributes
* <code>input</code>, <code>output</code>, and <code>regex</code> are required.
* </p>
* <p>Optional boolean attributes may be used to toggle various modes for the
* regular expression engine (all are set to <code>false</code> by default):
* </p>
* <table>
* <tr><td><code>canonicaleq</code></td><td>Enable canonical equivalence</td></tr>
* <tr><td><code>caseinsensitive</code></td><td>Enable case-insensitive matching</td></tr>
* <tr><td><code>comments</code></td><td>Permit whitespace and comments in pattern</td></tr>
* <tr><td><code>dotall</code></td><td>Enable dotall mode</td></tr>
* <tr><td><code>multiline</code></td><td>Enable multi-line mode</td></tr>
* <tr><td><code>unicodecase</code></td><td>Enable Unicode-aware case folding</td></tr>
* <tr><td><code>unixlines</code></td><td>Enable Unix lines mode</td></tr>
* </table>
* <p>There is one additional optional boolean attribute,
* <code>failOnNoMatch</code>. If this attribute is <code>true</code> it causes
* the <code>match</code> task to throw a
* <code>org.apache.tools.ant.BuildException</code> and fail if no matches for
* the regular expression are found. The default value is <code>false</code>,
* meaning a failed match will simply result in a warning message to
* <code>STDERR</code> and an empty (0 byte) <code>output</code> file being
* created.
* </p>
* <p>
* <h4>Example</h4>
* </p>
* <p>Contents of input file <code>letter.txt</code>:
* <pre>
* Dear Alice,
*
* How's about you and me gettin' together for some anonymous foo action?
*
* Kisses,
* Bob
* </pre>
* </p>
* <p>Ant <code>match</code> task and a <code>taskdef</code> defining it:
* <pre>
* &lt;taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" /&gt;
* &lt;match input="letter.txt"
* output="matches.txt"
* regex="about (\S*?) and (\S*?) .+anonymous (\S*?)"
* /&gt;
* </pre>
* </p>
* <p>Contents of properties file <code>matches.txt</code> written by this task:
* <pre>
* group.0=about you and me gettin' together for some anonymous foo
* group.1=you
* group.2=me
* group.3=foo
* </pre>
* </p>
* <p>These values can be loaded from <code>matches.txt</code> into Ant
* properties like so:
* <pre>
* &lt;loadproperties srcFile="matches.txt" /&gt;
* </pre>
* </p>
*
* @author smeghead
*/
public class MatchTask extends Task {
private boolean _failOnNoMatch;
private String _inputFile;
private String _outputFile;
private String _regex;
private int _regexFlags;
public void execute() throws BuildException {
int charRead = 0;
FileReader fileReader = null;
FileWriter fileWriter = null;
Matcher matcher = null;
Pattern pattern = null;
PrintWriter printWriter = null;
StringBuffer text = new StringBuffer();
if (_inputFile == null)
throw new BuildException("Error: 'match' task requires 'input' attribute");
if (_outputFile == null)
throw new BuildException("Error: 'match' task requires 'output' attribute");
if (_regex == null)
throw new BuildException("Error: 'match' task requires 'regex' attribute");
pattern = Pattern.compile(_regex, _regexFlags);
try {
fileReader = new FileReader(_inputFile);
while ((charRead = fileReader.read()) != -1)
text.append((char) charRead);
fileReader.close();
matcher = pattern.matcher(text);
if (matcher.find()) {
printWriter = new PrintWriter(new FileWriter(_outputFile));
for (int i = 0; i <= matcher.groupCount(); i++)
printWriter.println("group." + Integer.toString(i) + "=" + matcher.group(i));
printWriter.flush();
printWriter.close();
} else {
if (_failOnNoMatch) {
throw new BuildException("Error: No matches found in " + _inputFile);
} else {
System.err.println("Warning: No matches found in " + _inputFile);
// Create 0 byte output file.
fileWriter = new FileWriter(_outputFile);
fileWriter.close();
}
}
} catch (FileNotFoundException fnfe) {
throw new BuildException("File " + _inputFile + " not found", fnfe);
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
public void setCanonicalEq(boolean enableCanonicalEq) {
if (enableCanonicalEq)
_regexFlags |= Pattern.CANON_EQ;
}
public void setCaseInsensitive(boolean enableCaseInsensitive) {
if (enableCaseInsensitive)
_regexFlags |= Pattern.CASE_INSENSITIVE;
}
public void setComments(boolean enableComments) {
if (enableComments)
_regexFlags |= Pattern.COMMENTS;
}
public void setDotall(boolean enableDotall) {
if (enableDotall)
_regexFlags |= Pattern.DOTALL;
}
public void setFailOnNoMatch(boolean failOnNoMatch) {
_failOnNoMatch = failOnNoMatch;
}
public void setInput(String inputFile) {
_inputFile = inputFile;
}
public void setMultiLine(boolean enableMultiLine) {
if (enableMultiLine)
_regexFlags |= Pattern.MULTILINE;
}
public void setOutput(String outputFile) {
_outputFile = outputFile;
}
public void setRegex(String regex) {
_regex = regex;
}
public void setUnicodeCase(boolean enableUnicodeCase) {
if (enableUnicodeCase)
_regexFlags |= Pattern.UNICODE_CASE;
}
public void setUnixLines(boolean enableUnixLines) {
if (enableUnixLines)
_regexFlags |= Pattern.UNIX_LINES;
}
}

View File

@@ -1,164 +0,0 @@
/*
* Ports + Ant = Pants, a simple Ant-based package manager
*
* free (adj.): unencumbered; not under the control of others
*
* Written by smeghead in 2005 and released into the public domain with no
* warranty of any kind, either expressed or implied. It probably won't make
* your computer catch on fire, or eat your children, but it might. Use at your
* own risk.
*/
package net.i2p.pants;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
/**
* <p>Custom Ant task for loading properties from a
* <code>java.util.Properties</code> file then merging them with lists of
* expected properties. When an expected property is found in the properties
* file it is set to the value given for it in the file. If an expected property
* from a list isn't found in the properties file its value will be set to "" or
* "false", depending on the property's data type.
* </p>
* <p>A property's data type is determined by membership in one of two lists
* which can be passed into an instance of this class: a string-typed list and a
* boolean-typed list. Values for string-typed properties may be any valid
* string accepted by <code>java.util.Properties</code>, and values for
* boolean-typed properties must be either "false" or "true".
* </p>
* <p>Lists holding more than one property must be comma-delimited.
* </p>
* <p>The output of this class is a temporary <code>java.util.Properties</code>
* file which is suitable for reading by the standard Ant
* <code>loadproperties</code> task.
* </p>
* <p>Note that if any properties in the given lists have already been defined
* before the <code>mergetypedproperties</code> task is called, their values
* cannot be changed since Ant properties are immutable.
* </p>
* <h4>Example</h4>
* </p>
* <p>Contents of a properties file <code>my.properties</code>:
* <pre>
* some.property.exists=true
* hasValue=false
* some.property=this is a value
* property0=bork bork
* propertyX=this property wasn't passed in a list
* </pre>
* </p>
* <p>Ant <code>mergetypedproperties</code> task and a <code>taskdef</code>
* defining it:
* <pre>
* &lt;taskdef name="mergetypedproperties" classname="net.i2p.pants.MergeTypedPropertiesTask" classpath="../../lib/pants.jar" /&gt;
* &lt;mergetypedproperties input="my.properties"
* output="merged-properties.temp"
* booleanList="some.property.exists,is.valid,hasValue"
* stringList="some.property,another.property,property0"
* /&gt;
* </pre>
* </p>
* <p>Contents of properties file <code>merged-properties.temp</code> written by this task:
* <pre>
* some.property.exists=true
* is.valid=false
* hasValue=false
* some.property=this is a value
* another.property=
* property0=bork bork
* propertyX=this property wasn't passed in a list
* </pre>
* </p>
* <p>If you don't want this task's output to include properties which weren't
* in the lists of expected properties, you can set the attribute
* <code>onlyExpected</code> to <code>true</code>. In the example, this would
* result in the file <code>merged-properties.temp</code> containing only the
* following properties:
* <pre>
* some.property.exists=true
* is.valid=false
* hasValue=false
* some.property=this is a value
* another.property=
* property0=bork bork
* </pre>
* </p>
*
* @author smeghead
*/
public class MergeTypedPropertiesTask extends Task {
private String _booleanList = "";
private String _inputFile;
private boolean _onlyExpected;
private String _outputFile;
private Properties _propertiesIn = new Properties();
private Properties _propertiesOut = new Properties();
private String _stringList = "";
public void execute() throws BuildException {
StringTokenizer strtokBoolean = new StringTokenizer(_booleanList, ",");
StringTokenizer strtokString = new StringTokenizer(_stringList, ",");
String property = "";
if (_inputFile == null)
throw new BuildException("Error: 'mergetypedproperties' task requires 'input' attribute");
if (_outputFile == null)
throw new BuildException("Error: 'mergetypedproperties' task requires 'output' attribute");
// Add some type-checking on the list elements
try {
_propertiesIn.load(new FileInputStream(_inputFile));
while (strtokBoolean.hasMoreTokens())
_propertiesOut.setProperty(strtokBoolean.nextToken().trim(), "false");
while (strtokString.hasMoreTokens())
_propertiesOut.setProperty(strtokString.nextToken().trim(), "");
for (Enumeration enumm = _propertiesIn.elements(); enumm.hasMoreElements(); ) {
property = (String) enumm.nextElement();
if (_onlyExpected && !_propertiesOut.containsKey(property))
continue;
else
_propertiesOut.setProperty(property, _propertiesIn.getProperty(property));
}
_propertiesOut.store(new FileOutputStream(_inputFile), "This is a temporary file. It is safe to delete it.");
} catch (IOException ioe) {
throw new BuildException(ioe);
}
}
public void setBooleanList(String booleanList) {
_booleanList = booleanList;
}
public void setInput(String inputFile) {
_inputFile = inputFile;
}
public void setOnlyExpected(boolean onlyExpected) {
_onlyExpected = onlyExpected;
}
public void setOutput(String outputFile) {
_outputFile = outputFile;
}
public void setStringList(String stringList) {
_stringList = stringList;
}
}

View File

@@ -1,116 +0,0 @@
What is Pants?
--------------
Pants is an Apache Ant-based package manager for the management of 3rd party
dependencies in Java development projects. It's loosely modeled after
FreeBSD's Ports and Gentoo Linux's Portage, with two major differences:
* Pants isn't intended for system-wide package management. It's tailored for
per-project 3rd party package management. You will typically have one
Pants repository per project and each repository will be located somewhere
under your project's root directory. If you're familiar with Ports or
Portage, a Pants repository is roughly analogous to /usr/ports or
/usr/portage.
* Pants is extremely portable. It goes anywhere Apache Ant goes.
Pants takes a modular approach to the standard Ant buildfile, breaking it
into 3 files for functionality and convenience:
1. The Pants public interface, pants/build.xml, provides a single consistent
way to access and manipulate dependency packages and relieves some of the
developer's burden by providing implementations for some frequently-used
and complex Ant operations.
2. pbuild.xml is a specially-structured and slimmed-down Ant buildfile in
which you implement custom handling for a package your project depends
on. This is known as the "pbuild" and is roughly analogous to a FreeBSD
port or a Gentoo ebuild. A fairly explanatory template for pbuilds,
pbuild.template.xml, is provided.
3. pbuild.properties contains those properties for a specific pbuild which
are most likely to change over time. It uses the java.util.Properties
format which is more human-friendly for hand-editing than Ant/XML. A
fairly explanatory template, pbuild.template.properties, is provided.
There is one more file that completes the Pants system: the metadata file
pants/world is a database for keeping track of all packages managed by Pants
for your project.
Pants automatically handles versioning for your project's dependency
packages and keeps track of their recommended versions, currently used
versions, and latest available versions. This makes it extremely simple for
project developers to switch back and forth between different versions of a
dependency, and makes it just as easy to update a dependency. You can even
update all your project's Pants-managed packages with a single command.
Pbuilds are designed to automatically handle the downloading, building,
repackaging and deployment of source archives, binary archives, and CVS
sources, all in a manner that's completely transparent to the project
developer. Pbuilds currently support tar + gzip, tar + bzip2, and zip
archives.
Because it is based on Ant, Pants integrates very well with Ant buildfiles
and will fit easily into your project's Ant build framework. However, its
interface is simple enough to be called just as easily by more traditional
build systems such as GNU Make.
Why Should I Use Pants?
-----------------------
There are many applications for Pants, but a few use cases should best serve
to illustrate its usefulness:
1. You have a project that you ship with several 3rd party libraries but the
versions you're using are stale. With a single command, Pants can
automatically discover the latest release versions for all of these, then
download, build, and place the fresh libraries where your project's main
build system expects them to be at build time.
2. You want to test multiple versions of a 3rd party library against your
project. Pants only requires you to issue a single command to switch
library versions, so can spend more time testing and less time hunting
packages down, unpackaging them, symlinking, etc.
3. Pants is public domain. You can ship it with your project if you need to
without having to worry about petty intellectual property or licensing
issues.
Minimum Requirements
--------------------
* Apache Ant 1.6.2 or higher is recommended
* Any Java runtime and operating system that will run Ant
Installation
------------
Not finished yet.
Why the Silly Name?
-------------------
Ports + Ant = Pants. Any other explanation is purely a product of your
twisted imagination.
Miscellaneous Pocket Fluff
--------------------------
Author: smeghead <smeghead@i2pmail.org> <smeghead@mail.i2p>
License: No license necessary. This work is released into the public domain.
Price: Free! But if you really appreciate Pants, or you're just a sicko,
please send me a picture of your worst or most unusual pair of
pants so I can add it to the Whirling Hall of Pants on pants.i2p,
the official Pants eepsite (that's an anonymous website on I2P--see
http://www.i2p.net for more information).
$Id$

View File

@@ -1,110 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is a template for Pants pbuilds. Pbuilds use standard Apache Ant syntax.
For each target in the Public Interface section you must provide either an
implementation or a stub. You may also add your own custom tasks and
properties to this file. Be careful that none of your custom properties'
names clash with the properties defined in pants/build.xml.
-->
<project basedir="." default="build" name="name-of-pbuild-here">
<!-- ....................... Begin Public Interface ........................ -->
<!--
When this target is called, the pbuild's sources and/or binaries have
already been extracted/copied by Pants into the pbuild's working/
subdirectory. This target must prepare those sources and/or binaries in
the working/ subdirectory into deployable form, for example by building
all necessary classes and jar files.
This target must not create or modify any files outside the pbuild's
working/ subdirectory. (An automatic sandboxing mechanism should be added
to Pants at some point.) It is however acceptable for a task called by
'builddep' to modify files outside of this pbuild's working/ directory.
-->
<target name="build" depends="builddep" />
<!--
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
etc. which perform tasks this pbuild's 'build' target depends on. If other
pbuilds are called here, they must be called through the Pants interface
or else it may leave Pants in an inconsistent state.
Most pbuilds probably won't need to implement this target.
-->
<target name="builddep" />
<!--
This target must undo the actions performed by the 'build' target.
-->
<target name="clean" depends="depclean" />
<!--
If the 'builddep' target is implemented, this target must be implemented
to undo its actions.
-->
<target name="depclean" />
<!--
This target must copy all deployable files generated by the 'build' target
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
processes) or to their final deployment locations outside the pants/
directory hierarchy. Note that the latter may require the user to gain
superuser/admin privileges.
-->
<target name="dist" depends="build" />
<!--
This target must remove all files from the pbuild's dist/ subdirectory
and final deployment locations, reversing the actions of the 'dist'
target. Note that removal of files from their final deployment locations
may require the user to gain superuser/admin privileges.
-->
<target name="distclean" depends="clean" />
<!-- ........................ End Public Interface ......................... -->
</project>

View File

@@ -1,112 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
version.recommended=CVS
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork
cvs.root=:ext:anoncvs@savannah.gnu.org:/cvsroot/gnu-crypto
cvs.rsh=ssh
cvs.package=gnu-crypto

View File

@@ -1,127 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="build" name="fortuna-pbuild">
<property name="gnucrypt.base.dir" value="./working/gnu-crypto" />
<property name="gnucrypt.etc.dir" value="${gnucrypt.base.dir}/etc" />
<property name="gnucrypt.lib.dir" value="${gnucrypt.base.dir}/lib" />
<property name="gnucrypt.object.dir" value="${gnucrypt.base.dir}/classes" />
<property name="gnucrypt.base.crypto.object.dir" value="${gnucrypt.object.dir}/gnu/crypto" />
<property name="gnucrypt.cipher.object.dir" value="${gnucrypt.base.crypto.object.dir}/cipher" />
<property name="gnucrypt.hash.object.dir" value="${gnucrypt.base.crypto.object.dir}/hash" />
<property name="gnucrypt.prng.object.dir" value="${gnucrypt.base.crypto.object.dir}/prng" />
<patternset id="fortuna.files">
<include name="${gnucrypt.base.crypto.object.dir}/Registry.class" />
<include name="${gnucrypt.prng.object.dir}/Fortuna*.class" />
<include name="${gnucrypt.prng.object.dir}/BasePRNG.class" />
<include name="${gnucrypt.prng.object.dir}/RandomEventListener.class" />
<include name="${gnucrypt.prng.object.dir}/IRandom.class" />
<include name="${gnucrypt.cipher.object.dir}/CipherFactory.class" />
<include name="${gnucrypt.cipher.object.dir}/IBlockCipher.class" />
<include name="${gnucrypt.hash.object.dir}/HashFactory.class" />
<include name="${gnucrypt.hash.object.dir}/IMessageDigest.class" />
</patternset>
<!--
Add this when Fortuna tests are added to GNU Crypto, else write some
-->
<target name="-test" />
<!-- ....................... Begin Public Interface ........................ -->
<!--
When this target is called, the pbuild's sources and/or binaries have
already been extracted/copied by Pants into the pbuild's working/
subdirectory. This target must prepare those sources and/or binaries in
the working/ subdirectory into deployable form, for example by building
all necessary classes and jar files.
This target must not create or modify any files outside the pbuild's
working/ subdirectory. (An automatic sandboxing mechanism should be added
to Pants at some point.) It is however acceptable for a task called by
'builddep' to modify files outside of this pbuild's working/ directory.
-->
<target name="build" depends="builddep">
<delete dir="./working/build" />
<delete dir="./working/jartemp" />
<mkdir dir="./working/build" />
<mkdir dir="./working/jartemp/${gnucrypt.object.dir}" />
<copy todir="./working/jartemp">
<fileset dir=".">
<patternset refid="fortuna.files" />
</fileset>
</copy>
<jar basedir="./working/jartemp/${gnucrypt.object.dir}" jarfile="./working/build/fortuna.jar">
<manifest>
<section name="fortuna">
<attribute name="Implementation-Title" value="I2P Custom GNU Crypto Fortuna Library" />
<attribute name="Implementation-Version" value="CVS HEAD" />
<attribute name="Implementation-Vendor" value="Free Software Foundation" />
<attribute name="Implementation-Vendor-Id" value="FSF" />
<attribute name="Implementation-URL" value="http://www.gnu.org/software/gnu-crypto" />
</section>
</manifest>
</jar>
<delete dir="./working/jartemp" />
</target>
<!--
Use this to call targets from other pbuilds, Ant buildfiles, Makefiles,
etc. which perform tasks this pbuild's 'build' target depends on. If other
pbuilds are called here, they must be called through the Pants interface
or else it may leave Pants in an inconsistent state.
Most pbuilds probably won't need to implement this target.
-->
<target name="builddep">
<ant dir="${gnucrypt.base.dir}" target="jar" />
</target>
<!--
This target must undo the actions performed by the 'build' target.
-->
<target name="clean" depends="depclean">
<delete dir="./working/jartemp" />
</target>
<!--
If the 'builddep' target is implemented, this target must be implemented
to undo its actions.
-->
<target name="depclean">
<!--
Annoyingly the GNU Crypto distclean task called here doesn't clean
*all* derived files from java/gnu-crypto/lib like it should (because
a couple of lines are commented out).....
-->
<ant dir="${gnucrypt.base.dir}" target="distclean" />
<!--
.....and so we mop up the rest ourselves.
-->
<delete dir="${gnucrypt.lib.dir}" />
</target>
<!--
This target must copy all deployable files generated by the 'build' target
into the pbuild's dist/ subdirectory (for use by other pbuilds or Ant
processes) or to their final deployment locations outside the pants/
directory hierarchy. Note that the latter may require the user to gain
superuser/admin privileges.
-->
<target name="dist" depends="build">
<copy todir="./dist/fortuna.jar" file="./working/build/fortuna.jar" />
</target>
<!--
This target must remove all files from the pbuild's dist/ subdirectory
and final deployment locations, reversing the actions of the 'dist'
target. Note that removal of files from their final deployment locations
may require the user to gain superuser/admin privileges.
-->
<target name="distclean" depends="clean">
<delete file="./dist/fortuna.jar" />
</target>
<!-- ........................ End Public Interface ......................... -->
</project>

View File

@@ -1,112 +0,0 @@
# The properties defined in this file can be overridden on the command line by
# passing them in as parameters like so:
#
# ant -Dpbuild=myapp -Dversion.recommended=2.0.5 install
#
# *** DO NOT DEFINE A PROPERTY BUT LEAVE ITS VALUE BLANK. PANTS WILL BREAK! ***
# Recommended Package Version
#
# Set this property's value to the package version you want Pants to use for the
# pbuild by default. The version string specified must match the version
# substring from the package's filename if the filename contains a version
# number.
#
# Comment out this property to force use of the latest available version.
#
# If the pbuild is CVS-based rather than package-based, this property must be
# set to 'CVS'.
#
# Example:
#
# version.recommended=2.0.4
version.recommended=5.1.2
# Latest Package Version
#
# There are currently two ways to inform Pants of the latest version number for
# your package.
#
# Method 1: Manually modify the property 'version.latest' to reflect the latest
# version number.
#
# Method 2: Provide a URL for a page on the package's website and a regular
# expression with which to parse it in order to extract the version
# number of the latest available package. For this you must define the
# properties 'version.latest.find.url', 'version.latest.find.regex',
# and any regular expression engine mode flags needed. The pattern
# defined must have exactly one capturing group to encapsulate the
# version string, otherwise the operation will fail.
#
# You may use both methods, in which case the version number specified by Method
# 1 will be used as the fallback value if Method 2 for some reason is
# unsuccessful.
#
# If neither method is enabled here or they fail to return a valid value to
# Pants, the 'ant update' operation for this pbuild may exit ungracefully unless
# the pbuild is CVS-based (none of the version.latest.* properties are used by
# CVS-based pbuilds).
#
# The following is a list of boolean properties for optional mode flags used by
# the regular expression engine. Set a value of "true" for any you wish to use.
#
# version.latest.find.regex.canonicaleq - Enable canonical equivalence
# version.latest.find.regex.caseinsensitive - Enable case-insensitive matching
# version.latest.find.regex.comments - Permit whitespace and comments
# version.latest.find.regex.dotall - Enable dotall mode
# version.latest.find.regex.multiline - Enable multi-line mode
# version.latest.find.regex.unicodecase - Enable Unicode-aware case folding
# version.latest.find.regex.unixlines - Enable Unix lines mode
#
# Examples:
#
# version.latest=5.1.2
# version.latest.find.url=http://sourceforge.net/projects/jetty/
# version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
version.latest=5.1.2
version.latest.find.url=http://sourceforge.net/projects/jetty/
version.latest.find.regex=Stable.+?Jetty-(.+?)</A>
# Package URL
#
# Specify the URL pointing to the pbuild's package from here. The token
# '${pbuild.version}' if used will automatically be expanded to the appropriate
# version string.
#
# The package URL property is not used by CVS-based pbuilds.
#
# Examples:
#
# package.url=ftp://borkbork.se/bork-${pbuild.version}.tar.bz2
# package.url=http://bork.borkbork.se/bork-${pbuild.version}-src.tar.gz
package.url=http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${pbuild.version}.zip
# CVS Repository
#
# The values expected for CVS properties here are the same as those expected by
# their corresponding Apache Ant 'Cvs' task attributes. For details see:
#
# http://ant.apache.org/manual/CoreTasks/cvs.html
#
# Not all of the 'Cvs' task's attributes have corresponding Pants properties.
# The following is a list of all valid CVS properties for Pants (and their
# default values if applicable):
#
# cvs.compression.level
# cvs.date
# cvs.package
# cvs.passfile=~/.cvspass
# cvs.port=2401
# cvs.root
# cvs.rsh
# cvs.tag
#
# Of these, only the 'cvs.root' property is required for CVS-based pbuilds.
#
# Examples:
#
# cvs.root=:pserver:anoncvs@borkbork.se:/cvsroot/bork
# cvs.rsh=ssh
# cvs.package=borkbork

View File

@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="jetty">
<!-- make this generic, place variables in properties file -->
<target name="all" depends="build"
description="Run the build target" />
<target name="assignProperties" if="group.0">
<property name="latest.jetty.version" value="${group.1}" />
<available property="jetty.package.available" file="jetty-${latest.jetty.version}.zip" />
<available property="jetty.package.unpacked.available" file="jettypkg/jetty-${latest.jetty.version}" />
<echo message="Properties assigned" />
</target>
<target name="build" depends="init, unpackJettyPackage" if="latest.jetty.version"
description="Download latest Jetty package and copy needed libs to jettylib/">
<property name="unpack.dir" value="jettypkg/jetty-${latest.jetty.version}" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/ant.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-compiler.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/jasper-runtime.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xercesImpl.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/ext/xml-apis.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/extra/lib/org.mortbay.jetty-jdk1.2.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/javax.servlet.jar" />
<copy todir="jettylib" overwrite="true" file="${unpack.dir}/lib/org.mortbay.jetty.jar" />
<copy todir="jettylib" overwrite="true">
<fileset dir="${unpack.dir}/ext" includes="xmlParserAPIs*.jar" />
</copy>
</target>
<target name="builddep"
description="Build the custom helper Ant task for this buildfile">
<mkdir dir="java/build"/>
<javac srcdir="./java/src" source="1.3" target="1.3" deprecation="on" destdir="./java/build" />
</target>
<target name="clean"
description="Remove temp files and zip only; jettypkg/ requires manual deletion">
<echo message="Not actually deleting the Jetty package directory since it's so large" />
<delete>
<fileset dir="." includes="*.zip jettytemp.html parsed.temp" />
</delete>
</target>
<target name="cleandep"
description="Remove custom helper Ant task">
<delete dir="java/build" />
</target>
<target name="compile" />
<target name="distclean" depends="clean"
description="Remove temp files, zip and jettylib/ contents" >
<delete>
<fileset dir="jettylib" includes="*.jar"/>
</delete>
</target>
<target name="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.available">
<echo message="The Jetty libs are not necessary for using I2P, but are used by some" />
<echo message="applications on top of I2P such as the routerconsole." />
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-${latest.jetty.version}.zip" verbose="true" dest="jetty-${latest.jetty.version}.zip" />
</target>
<target name="init" depends="builddep">
<echo message="Checking SourceForge for latest Jetty version....." />
<get src="http://sourceforge.net/projects/jetty/" dest="jettytemp.html" verbose="true" />
<taskdef name="match" classname="net.i2p.pants.MatchTask" classpath="../../lib/pants.jar" />
<match input="jettytemp.html"
output="parsed.temp"
regex="Stable.+?Jetty-(.+?)&lt;/A&gt;"
/>
<loadproperties srcFile="parsed.temp" />
<antcall target="assignProperties" />
</target>
<target name="jar" />
<target name="showlatest" depends="init"
description="Display latest version number for Jetty">
<echo message="Latest Jetty version: ${latest.jetty.version}" />
</target>
<target name="unpackJettyPackage" depends="fetchJettyPackage" if="latest.jetty.version" unless="jetty.package.unpacked.available">
<mkdir dir="jettypkg" />
<unzip src="jetty-${latest.jetty.version}.zip" dest="jettypkg" />
</target>
</project>

View File

@@ -1,2 +0,0 @@
version.using.fortuna=CVS
version.using.jetty=5.1.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,26 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q System Diagrams</title>
</head>
<body>
<h1>Q Diagrams</h1>
Informal system diagrams of Q network, hubs and clients.
<center>
<hr>
<img src="overall.jpg">
<hr>
<img src="client.jpg">
<hr>
<img src="hub.jpg">
</center>
<hr>
<address><a href="mailto:aum@mail.i2p">aum</a></address>
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 14:06:02 NZST 2005
<!-- hhmts end -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,80 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Quartermaster - I2P Distributed File Store</title>
</head>
<body>
<center>
<h1>Quartermaster<br>an I2P Distributed File Store</h1>
<h3>STATUS<h3>
<i>Whole new (incompatible) version currently in development;
ETA for release approx 4-7 days;
view screenshots <a href="screenshots.html">here</a>
</i>
<br>
<hr>
<small>
<a href="manual/index.html">User Manual</a> |
<a href="spec/index.html">Protocol Spec</a> |
<a href="metadata.html">Metadata Spec</a> |
<a href="diagrams.html">Q Pr0n (diagrams)</a> |
<a href="api/index.html">API Spec</a> |
<a href="qnoderefs.txt">qnoderefs.txt</a> |
Full Download |
Updated jar
</small>
</center>
<hr>
<h2>Intro</h2>
Quartermaster, or Q for short, is a distributed file storage framework for I2P.
<h2>Features</h2>
<ul>
<li>Now features 'QSites' - the Q equivalent of Freenet freesites,
static websites which are retrievable even if author is offline</li>
<li>Easy web interface - interact with Q (and view/insert QSites)
from your web browser</li>
<li>Maximum expectations of content retrievability</li>
<li>Content security akin to Freenet CHK and SSK keys</li>
<li>Powerful, flexible search engine</li>
<li>Comfortably accommodates both permanent and transient
nodes without significant network disruption (for instance,
no flooding of the I2P network with futile
calls to offline nodes)</li>
<li>Rapid query resolution, due to distributed catalogue
mirroring which eliminates all in-network query traffic</li>
<li>Modular, extensible architecture</li>
<li>Simple interfaces for 3rd-party app developers</li>
<li>Is custom-designed and built around I2P, so no duplication of
I2P's encryption/anonymity features</li>
<li>Simple XML-RPC interface for all inter-node communication, makes it easy to
implement user-level clients in any language; also allows alternative
implementations of core server and/or client nodes.</li>
</ul>
<hr>
<h2>Status</h2>
Q is presently under development, and a test release is expected soon.
<hr>
<h2>Architecture</h2>
Refer to the <a href="spec/index.html">Protocol Specification</a> for more information.
<hr>
<!-- Created: Sat Mar 26 11:09:12 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 18:55:19 NZST 2005
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,805 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q User/Programmer Manual</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bold }
-->
</style>
</head>
<body style="font-family: arial, helvetica, sans-serif">
<center>
<h1>Q User/Programmer Manual</h1>
<i>A brief but hopefully easy guide to installing and using the Q distributed file
store within the I2P network</i>
<br><br>
<i>(Return to <a href="../index.html">Q Homepage</a>)</i>
<br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<a name="intro"/>
<hr>
<h2>1. Introduction</h2>
<blockquote>
Q is a distributed Peer2Peer file storage/retrieval network that aims to deliver optimal
performance by respecting the properties of the I2P network.<br>
<br>
This manual serves as a 'walkthrough' guide, to take you through the steps from initial
download, to everyday usage. It also provides information for the benefit of higher-level
client application authors.
</blockquote>
<a name="checklist"/>
<hr>
<h2>2. Preliminary Checklist</h2>
<blockquote>
OK, we assume here that you've already cracked the tarball, and are looking at
the distribution files.<br>
<br>
In order to get Q set up and running, you'll need:
<ol>
<li>An I2P router installed, set up and (permanently or transiently) running</li>
<li>Your system shell set up with at the environment variables:
<ul>
<li><b>CLASSPATH</b> - this should include:
<ul>
<li>The regular I2P jar files and 3rd party support jar files (eg <b>i2p.jar</b>,
<b>i2ptunnel.jar</b>, <b>streaming.jar</b>,
<b>mstreaming.jar</b>, <b>jbigi.jar</b>)</li>
<li>Apache's XML-RPC support jarfile - included in this Q distro as
<b>xmlrpc.jar</b></li>
<li>Aum's jarfile <b>aum.jar</b>, which includes Q and all needed support code</li>
</ul>
</li>
<li><b>PATH</b> - your execution search path <b><i>must</i></b> include the directory
in which your main java VM execution program (<b>java</b>, or on windows systems,
<b>java.exe</b>) resides.<br>
<b>NOTE</b> - if <b>java[.exe]</b> is not on your <b>PATH</b>, then Q <i>will
not run</i>.</li>
</ul>
</ol>
</blockquote>
<a name="serverorclient"/>
<hr>
<h2>3. Q Server or Q Client?</h2>
<blockquote>
Nearly everyone will want to run a <b>Q Client Node</b>.<br>
<Br>
It is only client nodes which provide users with full access to the Q network.<br>
<br>
However, if you have a (near-) permanently running I2P Router, and you're a kind and
generous soul, you might <i>also</i> be willing to run a <b>Q Server Node</b> in addition
to your <b>Q Client Node</b>.<br>
<br>
If you do choose to run a server node, you'll be expected to keep it running as near as
possible to 24/7. While transience of client nodes - frequent entering and leaving the
Q network - causes little or no disruption, transience of server nodes can significantly
impair Q's usability for everyone, particularly if this transience occurs frequently amongst
more than the smallest percentage of the server node pool.<br>
<br>
Until you're feeling well "settled in" with Q, your best approach is to just run a
client node for now, and add a server node later when you feel ready.<br>
</blockquote>
<a name="walkthrough"/>
<hr>
<h2>4. Q Walkthrough</h2>
<h3>4.1. Introduction</h3>
<blockquote>
This chapter discusses the deployment and usage of a Q Client Node, and will take you
through the steps of:
<ol>
<li>Double-checking that you've met the installation requirements</li>
<li>Launching a Q Client Node</li>
<li>Verifying that your Q Client Node is running</li>
<li>If your node fails to launch, figuring out why</li>
<li>Importing one or more noderefs into your node</li>
<li>Observing that your node is discovering other nodes on the network</li>
<li>Observing that your node is discovering content on the network</li>
<li>Searching for items of content that match chosen criteria</li>
<li>Retrieving stuff from the network</li>
<li>Inserting stuff to the network</li>
<li>Shutting down your client node</li>
</ol>
Setup and running of Q Server Nodes will be discussed in a later chapter.
</blockquote>
<hr>
<h3>4.2. Verify Your Q Installation Is Correct</h3>
<blockquote>
Ensure that all the needed I2P jarfiles, as well as <b>xmlrpc.jar</b> and
Q's very own <b>aum.jar</b> are correctly listed in your <b>CLASSPATH</b> environment
varaible, and your main java launcher is correctly listed in your <b>PATH</b> environment
variable.<br>
<br>
Typically, you will likely copy the jarfiles <b>aum.jar</b> and <b>xmlrpc.jar</b>
into the <b>lib/</b> subdirectory of your I2P router installation, along with all
the other I2P jar files. Wherever you choose to put these files, make sure they're
correctly listed in your <b>CLASSPATH</b>.
<br>
Also, you'll want to add execute permission to your <b>qmgr</b> (or <b>qmgr.bat</b>)
wrapper script, and copy it into one of the directories listed in your <b>PATH</b>
environment variable.<br>
</blockquote>
<hr>
<h3>4.3. Get Familiar With qmgr</h3>
<blockquote>
<b>qmgr</b> (or <b>qmgr.bat</b>) is a convenience wrapper script to save your
sore fingers from needless typing. It's just a wrapper which passes arguments
to the java command <b><code>java&nbsp;net.i2p.aum.q.QMgr</code></b><br>
<br>
You can verify you've set up qmgr correctly with the command:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
This displays a brief summary of qmgr commands. On the other hand, the command:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
floods your terminal window with a detailed explanation of all the qmgr commands
and their arguments.<br>
</blockquote>
<hr>
<h3>4.4. Running A Q Client Node For The First Time</h3>
<blockquote>
Provided you've successfully completed the preliminaries, you can launch your
Q Client Node with the command:
<blockquote><code><pre>
qmgr start</pre></code></blockquote>
All going well, you should have a Q Client Node now running in background.
</blockquote>
<hr>
<h3>4.5. Verify that your Q Client Node is actually Running</h3>
<blockquote>
After typed the <b>qmgr start</b> command, you will see little or no
evidence that Q is actually running.<br>
<br>
You can test if the node is actually up by typing the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
If your Q Client Node is running, this <b>status</b> command should produce
something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
Node Ping:
status=ok
numPeers=0
dest=-3LQaE215uIYwl-DsirnzXGQBI31EZQj9u~xx45E823WqjN5i2Umi37GPFTWc8KyislDjF37J7jy5newLUp-qrDpY7BZum3bRyTXo3Udl8a3sUjuu4qR5oBEWFfoghQiqDGYDQyJV9Rtz7DEGaKHGlhtoGsAYRXGXEa8a43T2llqZx2fqaXs~836g8t6sLZjryA5A9fpq98nE5lT0hcTalPieFpluJVairZREXpUiAUmGHG7wAIjF6iszXLEHSZ8Qc622Xgwy0d1yrPojL2yhZ64o05aueYcr~xNCiFxYoHyEJO3XYmkx~q-W-mzS3nn6pRevRda74MnX1~3fFDZ0u~OG6cLZoFkWgnxrwrWGFUUVMR87Yz251xMCKJAX6zErcoGjGFpqGZsWxl4~yq7yfkjPnq3GuTxp2cB75bRAOZRIAieqBOVJDEodFYW5amCinu4AxYE7G1ezz4ghqHFe~0yaAdO74Q1XoUny138YT6P33oNOOlISO1cAAAA
uptime=4952
load=0.0
id=6LVZ9-~GgJJ52WUF1fLHt3UnH50TnXSoPQXy7WZ4GA=
numLocalItems=47
numRemoteItems=2173</pre></code></blockquote>
If you see something like this, then smile, because Q is now up on your system.<br>
<br>
If the node launch failed, you might see something like:
<blockquote><code><pre>
Pinging node at '/home/myusername/.quartermaster_client'...
java.io.IOException: Connection refused
at org.apache.xmlrpc.XmlRpcClient$Worker.execute(Unknown Source)
at org.apache.xmlrpc.XmlRpcClient.execute(Unknown Source)
at net.i2p.aum.q.QMgr.doStatus(QMgr.java:310)
at net.i2p.aum.q.QMgr.execute(QMgr.java:813)
at net.i2p.aum.q.QMgr.main(QMgr.java:869)
Failed to ping node</pre></code></blockquote>
This indicates that your Q client node has either crashed, or failed to launch in the
first place.<br>
<br>
If you're having trouble like this, you might like to try running your Q client node
in foreground, instead of spawning it off into background.<br>
<br>
The command to run a Q client node in foreground is:
<blockquote><code><pre>
qmgr foreground</pre></code></blockquote>
You should see some meaningless startup messages, and no return to your shell prompt.<br>
</blockquote>
<hr>
<h3>4.6. Diversion - Q Storage Directories</h3>
<blockquote>
By default, when you run a Q Client Node, it creates a datastore directory tree
at <b>~/.quartermaster_client</b>. (Windows users note - you'll find this directory
wherever your user home directory is - this depends on what version of Windows
you have installed).<br>
<br>
Within this directory tree, you should see a file called <b>node.log</b>, which
will contain various debug log messages, and can help you to rectify any problems
with your Q installation. If you hit a wall and can't rectify the problems
yourself, you should send this file to the Q author (aum).<br>
<br>
It's possible to run your Q node from another directory, by passing that directory
as a <b>-dir &lt;path&gt;</b> argument to the
<b>qmgr</b> <b>start</b>, <b>foreground</b> and <b>stop</b>
commands. See <b>qmgr help verbose</b> for more information.
</blockquote>
<hr>
<h3>4.7. Importing a Noderef</h3>
<blockquote>
Note from the prior <b>qmgr status</b> command the line:
<blockquote><code><pre>
numPeers=0</pre></code></blockquote>
This means that your Q client node is running standalone, and doesn't have any contact
with any Q network. As such, your node is effectively useless. We need to hook up
your node with other nodes in the Q network.<br>
<br>
Q doesn't ship with any means for new client nodes to automatically connect to any Q
server nodes. This is deliberate.<br>
<br>
In all likelihood, there will be one 'main' Q network running within I2P, largely
based around the author's own Q server node, and most people will likely want to
use this Q network. But the author doesn't want to stop other people running their
own private Q networks, for whatever purpose has meaning for them.
<blockquote><i><small>
<hr>
This is especially relevant for Q as opposed to Freenet. With Freenet, there's
no way for a user to know of the existence of any item of content without
first being given its 'key'. However, since Q works with published catalogs,
any user can know everything that's available on a Q network, which might
not be desirable to those wishing to share content in a private situation.<br>
<Br>
The Q author anticipates, and warmly supports, people running their own
private Q networks within I2P, in addition to accessing the mainstream
'official' Q network.<br>
<br>
The way Q is designed and implemented, there is no way for anyone, including
Q's author, to know of the existence of anyone else's private Q network.
It is beyond the author's control, (and thus arguably the author's
legal responsibility), what private Q networks people set up, and what
kind of content is trafficked on these networks. This claim of plausible
deniability on the part of Q's author parallels that of a hardware retailer
denying responsibility for what people do with tools that they purchase.
<hr>
</small>
</i></blockquote>
Ok, getting back on topic - your brand new virgin Q client node is useless and lonely,
and desperately needs some Q server nodes to talk to. So let's hook up your node to
the mainstream Q network.<br>
<br>
You'll need to get one or more 'noderefs' for Q server nodes.<br>
<br>
There's nothing fancy about a Q noderef. It's just a regular I2P 'destination', with
which your Q Client Node can connect with a Q Server Node.<br>
<br>
A 'semi-official' list of noderefs for the mainstream Q network can be downloaded
from the url: <a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.<br>
<br>
Download this file, save it as (say) <b>qnoderefs.txt</b>. (Alternatively, if you're
wanting to subscribe into a private Q network, then get a noderef for at least one
of that network's server nodes from someone on that network who trusts you).<br>
<br>
Import these noderefs into your Q client node via the command:
<blockquote><code><pre>
qmgr addref qnoderefs.txt</pre></code></blockquote>
If all goes well, you should see no output from this command, or (possibly) a brief
line or two suggesting success.<br>
<br>
Your client node is now subscribed into the Q network of your choice. Verify this
with the command:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
In the output from that command, you should see the <b>numPeers=</b> line showing at least
1 peer.<br>
<br>
If there is more than one Q Server Node on the Q network you've just subscribed to,
then your local node should sooner or later discover all these server nodes, and
the <b>numPeers</b> value should increase over time.<br>
<br>
<blockquote>
<hr>
While Q is in its early development and testing stages, the author may abdicate
the mainstream Q network, and publish nodrefs for a whole new mainstream Q network.
This will especially happen if the author makes any substantial changes to the
inter-node protocol, and/or releases incompatible new versions of Q client/server
nodes. Remember that
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a> will
serve as the authoritative source for noderefs for the mainstream Q network within
the mainstream I2P network.
<hr>
</blockquote>
When your client node gets its noderefs to a Q network, it will periodically,
from then on, retrieve differential peer list and catalog updates from servers
it knows about.<br>
<br>
Even if you only feed your client just one ref for a single server node, it will
in time discover all other operating server nodes on that Q network, and will
build up a full local catalog of everything that's available on that Q network.<br>
<br>
Provided that your client is running ok, and has been fed with at least one
ref for a live Q network that contains content, then over time, successive:
<blockquote><code><pre>
qmgr status</pre></code></blockquote>
commands should report increasing values in the fields:
<ul>
<li><b>numPeers</b> - number of peers this client node knows about</li>
<li><b>numLocalItems</b> - number of locally stored content items, ie items
which you have either inserted to, or retrieved from, your client node</li>
<li><b>numRemoteItems</b> - number of unique data items which are available
on remote server nodes in the Q network, and which can be retrieved through
your local client node.</li>
</ul>
<blockquote>
<hr>
<h4>4.7.1. One Big Warning</h4>
If you are participating in more than one distinct Q network, then <b>do not</b>
insert noderefs for different networks into the same running instance of a
local Q client, unless you don't plan on inserting content via that client.<br>
<Br>
For instance, let's say you are participating in two different Q networks:
<ul>
<li>The 'mainstream' Q netowrk</li>
<li>A secret Q network - "My friends' teen angst diaries"</li>
</ul>
If you get a noderef for both these networks, and insert both of these into the
same running Q client node, then this local client node will be transparently
connected to both networks.<br>
<br>
If you only ever plan on retrieving content, and never inserting content, this
won't be a problem, except that you won't be able to tell which content
resides on the mainstream Q network, and which resides in the secret Q network.<br>
<Br>
The big problem arises from inserting content. Whenever you insert data through this
'contaminated'
Q client node, this node picks 3 different servers to which upload a copy of this
data. You won't have any control over whether the data gets inserted to the mainstream
Q network, or your secret Q network. You might insert something sensitive, intending it
to go only into the secret Q network, where in fact it also ends up in the mainstream
network, with consequences you might not want.
</blockquote>
</blockquote>
<hr>
<h3>4.8. Content Data and Metadata</h3>
<blockquote>
Whenever content gets stored on Q, it is actually stored as two separate items:
<ul>
<li>The <b>raw data</b> - whether a text file, or the raw bytes of image files,
audio files etc</li>
<li>The <b>metadata</b>, which contains human-readable and machine-readable
descriptions of the data</li>
</ul>
Metadata consists of a set of <b>category=value</b> pairs.<br>
<br>
Confused yet? Don't worry, I'm confused as well. Let's illustrate this with an
example of metadata for an MP3 audio recording:
<ul>
<li>title=Fight_Last_Thursday.mp3</li>
<li>type=audio</li>
<li>mimetype=audio/mpeg</li>
<li>abstract=upcoming single recorded in our garage last April</li>
<li>keywords=grunge,country,indie</li>
<li>artist=Ring of Fire</li>
<li>size=4379443</li>
<li>contact=ring-of-fire@mail.i2p</li>
<li>key=blah37blah24-yada23hfhyada</li>
</ul>
All metadata categories are optional. In fact, you can insert content with no metadata
at all.<br>
<br>
If you fail to provide metadata when inserting an item, a blank set of metadata will
be created with at least the following categories:
<ul>
<li><b>key</b> - the derived key, under which the item will later be retrievable
by yourself and others</li>
<li><b>title</b> - if not provided at insert time, this will be set to the key</li>
<li><b>size</b> - size of the item's raw data, in bytes</li>
</ul>
Within Q, there is a convention to supply a minimal amount of metadata. While this
is not expected or enforced, including all these categories is most strongly
recommended. These core categories are:
<ul>
<li><b>title</b> - a meaningful title for the data item, consisting only of characters
which are legal in filenames on all platforms, and which ends with a file extension.</li>
<li><b>type</b> - one of a superset of eMule classifiers, such as:
<ul>
<li><b>text</b> - plain text</li>
<li><b>html</b> - HTML content</li>
<li><b>image</b> - content is in an image format, such as .png, .jpg, .gif etc</li>
<li><b>audio</b> - content is an audio sample, such as .ogg, .mp3, .wav etc</li>
<li><b>video</b> - due to the sheer size of video files, and Q's present design,
it's unlikely people will be inserting video content anytime soon (unless it's
very short)</li>
<li><b>archive</b> - packed file collections, such as .tar.gz, .zip, .rar etc</li>
<li><b>misc</b> - content does not fit into any of the above categories</li>
</ul>
</li>
<li><b>mimetype</b> - not as important as the <b>type</b> category, but providing
this category in your metadata is still strongly encouraged. Value for this category
should be one of the standard mimetypes, eg <b>text/html</b>, <b>audio/ogg</b> etc.</li>
<li><b>abstract</b> - a short description (<255 characters), intended for human reading</li>
<li><b>keywords</b> - a comma-separated list of keywords, intended for
machine-readability, should be all lowercase, no spaces</li>
</ul>
Note that you can supply extra metadata categories in addition to the above, and that
people searching for content can search on these extra categories if they know about
them.
</blockquote>
<hr>
<h3>4.9. Searching For Content</h3>
<blockquote>
As mentioned earlier - in constrast with Freenet, local Q nodes build up a complete
catalog of all available content on whatever Q network they are connected to.<br>
<br>
This is a design decision, based on the choice to eliminate query traffic.<br>
<br>
The author hopes that this will result in a distributed storage network with a
high retrievability guarantee, in contrast with freenet which offers no such
guarantee.<br>
<br>
With Freenet, you only ever know of the existence of something if someone tells
you about it.<br>
<br>
But with Q, your local client node builds up a global catalog of everything that's
available within the whole network.<br>
<br>
The QMgr client has a command for searching your Q client node:
<blockquote><code><pre>
qmgr search -m category1=pattern1 category2=pattern2 ...</pre></code></blockquote>
For example:
<blockquote><code><pre>
qmgr search -m type=audio artist=Mozart keywords=symphony</pre></code></blockquote>
or:
<blockquote><code><pre>
qmgr search -m type=text title="bible|biblical|(Nag Hammadi)" keywords="apocrypha|Magdalene"</pre></code></blockquote>
As implied in the latter example, search patterns are regular expressions. This example will
locate all text items, whose <b>title</b> metadata category contains one of <b>bible</b>, <b>biblical</b> or <b>Nag&nbsp;Hammadi</b>, <i>and</i> whose <b>keywords</b> category contains either
or both the words <b>apocrypha</b> or <b>Magdalene</b>.<br>
<br>
Please use the search function carefully, otherwise (if and when Q usage grows) you
could be inundated with thousands or even millions of entries.<br>
<br>
If a search turns up nothing, qmgr will simply exit. But if it turns up one or more items,
it will the items out one at a time, with the key first, then each metadata entry
on an indented line following.
</blockquote>
<hr>
<h3>4.10. Retrieving Content</h3>
<blockquote>
Now, we're actually going to retrieve something.<br>
<br>
Presumably, after following the previous section, you will have seen one or more search
results come up, with the 'keys' under which the items can be accessed.<br>
<br>
Now, choose one of the keys, preferably for a short text item. Try either of the following
commands:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; something.txt</pre></code></blockquote>
<i>or</i>:
<blockquote><code><pre>
qmgr get &lt;keystring&gt; &gt; something.txt</pre></code></blockquote>
(both have the same effect - the first one explicitly writes to the named file, the second
one dumps the raw data to stdout, which we shell-redirect into the file.<br>
<br>
<b><i>Note - redirection of fetched data to a file via shell is not working at present. Use only
the first form till we fix the bug.</i></b>
</blockquote>
<hr>
<h3>4.11. Inserting Content</h3>
<blockquote>
Our last example in this walkthrough relates to inserting content.<br>
<br>
Firstly, create a small text file with 2-3 lines of text, and save it as (say)
myqinsert.txt.<br>
<br>
Now, think of some metadata to insert along with the file. Or, you can just use
the set:
<blockquote><code><pre>
type=text
keywords=test
abstract=My simple test of inserting into Q
title=myqinsert.txt</pre></code></blockquote>
Now, let's insert the file. Ensure your Q client node is running, then type:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q"</pre></code></blockquote>
If all went well, this command should produce half a line of gibberish, followed
immediately by your shell prompt, eg:
<blockquote><code><pre>
aRoFC~9MU~pM2C-uCTDBp5B7j79spFD8gUeu~BNkUf0=<b>$</b>
</pre></code></blockquote>
The '$' at the end is your shell prompt, and all the characters before it are the 'key'
which was derived from the content you just inserted.<br>
<br>
To avoid the hassle of copying/pasting the key, you could just add output redirection
to the above command, eg:
<blockquote><code><pre>
qmgr put myqinsert.txt -m type=text keywords=test title="myqinsert.txt" \
abstract="My simple test of inserting into Q" \
> myqinsert.key</pre></code></blockquote>
This will cause the generated key to be written safe and sound into the file
<b>myqinsert.key</b>.<br>
<br>
You can verify that this insert worked by a 'get' command, as in:
<blockquote><code><pre>
qmgr get `cat myqinsert.key` somefilename.ext</pre></code></blockquote>
(Note that this won't work on windows because the DOS shell is irredeemably brain-damaged. If
you're using Windows, you <b>will</b> have to cut/paste the key.
</blockquote>
<hr>
<h3>4.12. Shutting Down your Node</h3>
<blockquote>
If you've worked through to here, then congratulations! You've got your Q Client Node set up
and working, and ready to meet all your distributed file storage and retrieval needs.<br>
<br>
You can leave your client node running 24/7 if you want. In fact, we recommend you keep your
client node running as much of the time as possible, so that you get prompt catalog updates,
and can more quickly stay in touch with new content.<br>
<br>
However, if you need to shut down your node, the command for doing this is:
<blockquote><code><pre>
qmgr stop</pre></code></blockquote>
This command will take a while to complete (since the node has to wait for the I2P
java shutdown hooks to complete before it can rest in peace). But once your node is
shut down, you can start it up again at any time and pick up where you left off.
</blockquote>
<a name="server"/>
<hr>
<h2>5. Running a Q Server Node</h2>
<h3>5.1. Introduction</h3>
<blockquote>
This section describes the requirements for, and procedures involved with, running
a Q Server Node.<br>
<br>
We'll use a similar 'walkthrough' style to that which we used in the previous section
on client nodes.
</blockquote>
<hr>
<h3>5.2. Requirements and Choices</h3>
<blockquote>
Running a Q server is a generous thing to do, and helps substantially with making
Q work at its best for everyone. However, please do make sure you can meet some
basic requirements:
<ul>
<li>You are running a permanent (24/7) I2P Router, on a box with at least (say)
98% uptime.</li>
<li>You have a little bandwidth to spare, and don't mind the extra memory, disk and
CPU-usage footprint of running a fulltime Q server node</li>
<li>You have already been able to successfully run a Q client node.</li>
</ul>
Also, please decide whether you want your server node to contribute to the mainstream
Q network, or whether you want to create your own private Q network, or join someone
else's private network. Your contribution will be most appreciated, though, if you
can run a server within the mainstream Q network.
</blockquote>
<hr>
<h3>5.3. Starting Your Server Node</h3>
<blockquote>
Starting up a Q Server node is very similar to starting up a Q client node, except
that with the qmgr command line, you must put the keyword arg <b>server</b> before the
command word. So the command is:
<blockquote><code><pre>
qmgr server start</pre></code></blockquote>
Similar to Q client nodes, you can check the status of a running Q server node with
the command:
<blockquote><code><pre>
qmgr server status</pre></code></blockquote>
(Note that this command will take longer to complete than with client nodes, because
the communication passes through a multi-hop I2P tunnel, rather than just through
localhost TCP).<br>
<br>
If the status command succeeds, then you'll know your new Q Server Node is happily
running in background.
</blockquote>
<hr>
<h3>5.4. Joining A Q Network</h3>
<blockquote>
When a Q Server node starts up for the first time, it is in a private network
all by itself.<br>
<br>
If you want to link your server into an existing Q network, you'll have to add a
noderef for at least one other server on that network. The command to do this
is similar to that for subscribing a client node to a network:
<blockquote><code><pre>
qmgr server addref &lt;noderef-file&gt;</pre></code></blockquote>
where &lt;noderef-file&gt; is a file into which you've saved the noderef for
the network you want to join.
<blockquote>
<hr><i><small>
Recall from the section on client nodes that the authoritative noderefs
for the mainstream Q network can be downloaded from
<a href="http://aum.i2p/q/qnoderefs.txt">http://aum.i2p/q/qnoderefs.txt</a>.
</small></i><hr>
</blockquote>
After you've added the noderef, subsequent <b>qmgr server status</b> commands
should show <b>numPeers</b> having a value of at least 1 (and growing, as more
server nodes come online in the mainstream Q network.)
</blockquote>
<hr>
<h3>5.5. Private Networks - Exporting Your Server's Noderef</h3>
<blockquote>
If you're planning to start your own private Q network, and want to include other
server operators in this network, then you'll have to export your server's noderef
and make it available to the others you want to invite into your network.<br>
<br>
The command to export your Q Server noderef is:
<blockquote><code><pre>
qmgr server getref &lt;noderef-file&gt;</pre></code></blockquote>
This will extract the <i>I2P Destination</i> of your running server node, and
write it into &lt;noderef-file&gt;. You can then privately share this file with
others who you want to invite into your private network. Each recipient of
this file will do a <b>qmgr server addref &lt;noderef-file&gt;</b> command
to import your ref into their servers.<br>
<br>
Don't forget that if you're running, or participating in, a private Q network, then
you'll need to run a separate client for accessing this network, separate from any
mainstream Q network client you may already be running.<br>
<br>
To start this extra client, you'll have to choose a directory where you want this
client to reside, a port number you want your client to listen on locally for
user commands, and run the command:
<blockquote><code><pre>
qmgr -dir /path/to/my/new/client -port &lt;portnum&gt; start</pre></code></blockquote>
You need the <b>-port &lt;portnum&gt;</b> command, because otherwise it'll fail
to launch (if you already have a client node running off the mainstream Q network).<br>
<br>
This will create, and launch, a new instance of a Q client, accessing your private
Q network. Don't forget to import your server's noderef into this client. Also,
note that you'll have to use this same <b>-port &lt;portnum&gt;</b> argument when
doing any operation on this client instance, such as get, put, status, search.
</blockquote>
<a name="qmgr"/>
<hr>
<h2>6. About the qmgr Utility</h2>
qmgr (or, to people fluent in Java, <b>net.i2p.aum.q.QMgr</b>), is just one simple
Q client application, that happens to be bundled in with the Q distro.<br>
<br>
It is by no means the only, or even main facility for accessing the Q network. We
anticipate that folks will write all manner of client apps, including fancy GUI
apps.<br>
<br>
Anyway, qmgr does give you a rudimentary yet workable client for basic access
to the Q network. Until fancy apps get written, qmgr will have to do.<br>
<br>
Don't forget that qmgr has very detailed inbuilt help. Run:
<blockquote><code><pre>
qmgr help</pre></code></blockquote>
for a quick help summary, or:
<blockquote><code><pre>
qmgr help verbose</pre></code></blockquote>
for the 'War and Peace' treatise.<br>
<br>
<blockquote><hr>
One crucial concept to remember with qmgr is that client and server node instances
are uniquely identified by the directories at which they reside. If you are running
multiple server and/or client instances, you can specify an instance with the
<b>-dir &lt;dirpath&gt;</b> option - see the help for details.
<hr></blockquote>
<hr>
One last note - we strongly discourage any writing of client apps that spawn a qmgr
process, pass it arguments and parse its results. This is most definitely a path to
pain, since qmgr's shell interface is subject to radical change at any time without
notice.<br>
<br>
qmgr is for human usage, or at most, inclusion in init/at/cron scripts. Please respect
this.<br>
<br>
If you want to write higher-level clients, your best course of action is to use the
official client api library, which we anticipate will have versions available in
Java, Python, Perl and C++. If you want to write in another language, such as
OCaml, Scheme etc, then the existing api lib implementations should serve as an excellent
reference to support you in writing a native port for your own language.
<a name="contact"/>
<hr>
<h2>8. Contacting the Author</h2>
I am <b>aum</b>, and can be reached as <b>aum</b> on in-I2P IRC networks, and also
at the in-I2P email address of <b>aum@mail.i2p</b>.<br>
<br>
<hr>
<center>
Return to <a href="../index.html">Q Homepage</a><br>
<br>
<small>
<a href="#intro">Introduction</a> |
<a href="#checklist">Checklist</a> |
<a href="#serverorclient">Server?orClient?</a> |
<a href="#walkthrough">Walkthrough</a> |
<a href="#server">Server Nodes</a> |
<a href="#qmgr">About QMgr</a> |
<a href="#contact">Contact us</a>
</small>
</center>
<hr>
<!-- Created: Fri Apr 1 11:03:27 NZST 2005 -->
<!-- hhmts start -->
Last modified: Sun Apr 3 20:06:53 NZST 2005
<!-- hhmts end -->
</body>
</html>

View File

@@ -1,23 +0,0 @@
rise on each hit:
dy = (1 - y) / kRise
fall after each time unit:
dy = y / kFall
fall after time dt:
dy = - y ** - (dt / kFall)
after the next hit:
y = y - y ** (- dt / kFall) + (1 - y) / kRise
first attempt at a load measurement algorithm:
- kFall is an arbitrary constant which dictates decay rate of load
in the absence of hits
- kRise is another constant which dictates rise of load with each hit
- dt is the time between each hit

View File

@@ -1,372 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q Metadata Specification</title>
<style type="text/css">
<!--
td { vertical-align: top; }
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
-->
</style>
</head>
<body>
<h1>Q Metadata Specification</h1>
<h2>1. Introduction</h2>
This document lists the standard metadata keys for Q data items,
discussing the rules of metadata insertion, processing and validation.<br>
<hr>
<h3>1.1. Definitions</h3>
To avoid confusions in terminology, this document will strictly abide the following definitions:
<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Term</td>
<td>Definition</td>
</tr>
<tr>
<td><code>key</code></td>
<td>A metadata category name, technically a <code>key</code> as the word is used with
Java <code>Hashtable</code> and Python <code>dict</code> objects.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>A Uniform Resource Indicator for an item of content stored within the Q network.<br>
Q URIs have the form: <code>Q:&lt;basename&gt;[,&lt;cryptoKey&gt;][&lt;path&gt;]</code>
<br>
<br>
Some examples:
<ul>
<li><code>Q:fhvnr3HFSK234khsf90sdh42fsh</code> (a plain hash uri, no cryptoKey)</li>
<li><code>Q:e54fhjeo39schr2kcy4osEH478D/files/johnny.mp3</code> (a secure space URI,
no cryptoKey)</li>
<li><code>Q:vhfh4se987WwfkhwWFEwkh3234S,47fhh2dkhseiyu</code> (a plain hash URI, with
a cryptoKey)</li>
</td>
</tr>
<tr>
<td><code>basename</code></td>
<td>The basic element of a Q uri. This will be a base64-encoded hash - refer below to
URI calculation procedures</td>
</tr>
<tr>
<td><code>cryptoKey</code></td>
<td>An optional session encryption key for the stored data, encoded as base64.
This affords some protection to server node operators, and gives them a level
of plausible deniability for whatever gets stored in their server's
datastore without their direct human awareness.</td>
</tr>
<tr>
<td><code>path</code></td>
<td>Whever an item of content is inserted in <code>secure space</code> mode, this path
serves as a pseudo-pathname, and is conceptually similar to the <code>path</code>
component in (for example) standard HTTP URLs
<code>http://&lt;domainname&gt;[:&lt;port&gt;][&lt;path&gt;]</code>, such as
<code>http://slashdot.org/faq/editorial.shtml</code> (whose <code>path</code>
is <code>/faq/editorial.shtml</code>).<br>
<br>
Paths, if not empty, should contain a leading slash ("/").
If an application specifies a non-empty <code>path</code> that doesn't begin with a
leading '/', a '/' will be automatically prepended by the receiving node.
</td>
</tr>
<tr>
<td><code>plain hash</code></td>
<td>A mode of inserting items, whereby the security of the resulting URI comes from
computing the URI from a hash of the item's data and metadata (and imposing a
mathematical barrier against spoofing content under a given URI). Corresponds to
Freenet's <code>CHK@</code> keys.</td>
</tr>
<tr>
<td><code>secure space</code></td>
<td>A mode of inserting items where the security of the URI is based not on a hash of the
item's data and metadata (as with <code>plain hash</code> mode),
but on the <code>privateKey</code> provided by the
application, and a content signature created from that private key.
Corresponds to Freenet's <code>SSK@</code> keys. Within a secure space, you
can insert any number of items under different pseudo-pathnames (as is the case
with Freenet SSK keys).
</li>
</table>
<br><br>
<hr>
<h3>2.1. Keys Inserted By Application Before sending <code>putItem</code> RPCs</h3>
As the heading suggests, this is a list of metadata keys which should be inserted by a
Q application prior to invoking a <code>putItem</code> RPC on the local Q client node.<br>
<br>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>title</code></td>
<td>String</td>
<td>Optional but strongly recommended. A free-text short description of the item,
should be less than 80 characters. The idea is that applications should
support a 'view' of catalogue data that shows item titles. (Prior Q convention of
titles expressed as valid filename syntax has been abandoned).
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional but strongly recommended.
A virtual 'pathname' for the item, which should be in valid *nix
absolute pathname syntax (beginning with '/', containing no '//', consisting
only of alphanumerics, '-', '_', '.' and '/'.<br>
<br>
In Q web interfaces, the <code>filename</code> component of this path will
serve as the recommended filename when downloading/saving the item.<br>
<br>
If the application also provides a
<code>privateKey</code> key, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below), and ultimately the final <code>uri</code>
under which the item can be retrieved by others.<br>
<br>
Refer also to <code>mimetype</code> below.
</td>
</tr>
<tr>
<td><code>encrypt</code></td>
<td>String</td>
<td>Optional. If this key is present, and has a value "1", "yes" or "true",
this indicates that the application wishes the data to be stored on servers in
encrypted form.<br>
<br>
If this key is present and set to a positive value, the Q node, on receiving the
<code>putItem</code> RPC, will:
<ol>
<li>Generate a random symmetric encryption key</li>
<li>Encrypt the item's data using this encryption key</li>
<li>Delete the <code>encrypt</code> key from the metadata</li>
<li>Enclose a base64 representation of this encryption key in the RPC response
it sends back to the application (embedded in the <code>uri</code></li>
</ol>
</td>
</tr>
<tr>
<td><code>type</code></td>
<td>String</td>
<td>Optional but strongly recommended. A standard ed2k specifier, one of <code>text html image
audio video archive other</code></td>
</tr>
<tr>
<td><code>mimetype</code></td>
<td>String</td>
<td>Optional but moderately recommended. Mimetype designation of data, eg <code>text/html</code>,
<code>image/jpeg</code> etc. If not specified, an attempt will be made to guess
a mometype from the value of the <code>path</code> key. If this attempt fails, then
this key will be set to <code>application/x-octet-stream</code> by the node receiving
the <code>putItem</code> RPC.</td>
</tr>
<tr>
<td><code>keywords</code></td>
<td>String</td>
<td>Optional but moderately recommended.
A set of keywords, under which the inserting app would like this item to be
discoverable. Keywords should be entirely lower case and comma-separated. Content
inserts should consider keywords carefully, and only use space characters inside
keywords when necessary (eg, for flagging a distinctive phrase containing very
common words).</td>
<tr>
<td><code>privateKey</code></td>
<td>String</td>
<td>Optional. A Base64-encoded signing private key, in cases where the application wishes
to insert an item in <code>signed space</code> mode. This can be accompanied by another key,
<code>path</code>, indicating a 'path' within the signed space. If 'path'
is not given, it will default to '/'.<br>
<br>
Either way, when a node receives a
<code>putItem</code> RPC containing a <code>privateKey</code> in its metadata,
it removes this key and replaces it with <code>publicKey</code> and
<code>signature</code>.
</td>
</tr>
<tr>
<td><code>path</code></td>
<td>String</td>
<td>Optional. The virtual pathname, within signed space, under which to store the item.
This gets ignored and deleted unless the application also provides a
<code>privateKey</code> as well. But if the private key is given, the path
is used in conjunction with the private key to generate <code>publicKey</code>
and <code>signature</code> keys (see below).<br>
<code>path</code> should be a 'unix-style pathname', ie, containing only slashes
as (pseudo) directory delimiters, and alphanumeric, '-', '_' and '.' characters,
and preferably ending in a meaningful file extension such as <code>.html</code>
</td>
</tr>
<tr>
<td><code>expiry</code></td>
<td>int</td>
<td>Unixtime at which the inserted item should expire. When this expiry time
is reached, the item won't necessarily be deleted straight away, but may
be deleted whenever a node's data store is full.<br>
<br>
If this is not provided, it will default to a given duration according to
the client node's configuration.<br>
<br>
If it is provided, by an application, then the client node will transparently
generate the required 'rent payment' before caching the data item and uploading
it to servers.
</td>
</tr>
</table>
<br><br>
<hr>
<h3>2.2. Keys Inserted By Node Upon Receipt Of <code>putItem</code> RPC</h3>
<table width=80% cellspacing=0 cellpadding=4 border=1 align=center>
<tr style="font-weight: bold">
<td>Key</td>
<td>Data Type</td>
<td>Description</td>
</tr>
<tr>
<td><code>size</code></td>
<td>Integer</td>
<td>Size of the data to be inserted, in bytes.</td>
</tr>
<tr>
<td><code>dataHash</code></td>
<td>String</td>
<td>base64-encoded SHA256 hash of data.</td>
</tr>
<tr>
<td><code>uri</code></td>
<td>String</td>
<td>This depends on whether the item is being inserted in <i>plain</i> or
<i>signed space</i> mode.<br>
<br>
If inserting in <i>plain</i> mode, then the uri is in the form
<code>Q:somebase64hash</code>, where the hash is computed according to
the <a href="#plainhash">plain hash calculation procedure</a>.<br>
<br>
If inserting in <i>signed space</i> mode, then the uri will be in the form
<code>Q:somebase64hash/path.ext</code>, where the hash is computed as per
the <a href="#signedhash">signed space hash calculation procedure</a>, and
the <code>/path.ext</code> is the verbatim value of the app-supplied
<code>path</code> key.
</td>
</tr>
<tr>
<td><code>publicKey</code></td>
<td>String</td>
<td>Base64-encoded signing public key. In cases where app provides
<code>privateKey</code>,
a node will derive the signing public key from the private key,
delete the private key from the metadata, and replace it with its corresponding
public key
key.</td>
</tr>
<tr>
<td><code>signature</code></td>
<td>String</td>
<td>Base64-encoded signature of <code>path+dataHash</code>, created using
the app-provided <code>privateKey</code>.</td>
</tr>
<tr>
<td><code>rent</code></td>
<td>String</td>
<td>A rent payment for the data's accommodation on the server.<br>
Intention is to support a variety of payment tokens. Initially, the
only acceptable form of payment will be a hashcash-like token,
in the form <code>hashcash:base64string</code>. The <code>hashcash:</code>
prefix indicates that this payment is in hashcash currency, in which case
the <code>base64String</code> should decode to a 16-byte string whose
SHA256 hash partially collides with <code>dataHash</code>.
The greater the number of bits in the collision,
the longer the data's accommodation will be 'paid up for'.<br>
<br>
If this key is already present, a Q node will verify the hashcash,
and adjust the <code>expiry</code> key value to the time the item's accommodation
is paid up till.<br>
<br>
If the key is not present:
<ul>
<li>A client node will generate a value for this key with enough collision bits
to pay the accommodation up till the given app-specified <code>expiry</code> date.</li>
<li>A server node will grant temporary free accommodation, and adjust the <code>expiry</code>
key to the end of the free accommodation period.</li>
</ul>
</td>
</tr>
</table>
<br><br>
<a name="plainhash"/>
<hr>
<h2>3. URI Determination Procedures</h2>
<h3>3.1. Plain Hash URI Calculation Procedure</h3>
When items are inserted in <code>plain</code> mode, the final URI is determined from
a hash of the data and metadata. Security of the item is based on the mathematical difficulty
of creating an arbitrary data+metadata set whose hash collides with the target URI.<br>
<br>
Specifically, the recipe for calculating plain hash URIs is:
<ol>
<li>If the key <code>size</code> is missing, set this to the size of the data,
in bytes</li>
<li>If the key <code>dataHash</code> is missing, set this to the base64-encoded
SHA256(data)</li>
<li>If the key <code>title</code> is missing, set this to the value of <code>dataHash</code></li>
<li>From the metadata, create a set of strings, each in the form <code>key=value</code>,
where each line contains a metadata <code>key</code> and its <code>value</code>, and
is terminated by an ASCII linefeed (\n, 0x10).</li>
<li>Ensure that key <code>uri</code> is omitted</li>
<li>Sort the strings into ascending ASCII sort order</li>
<li>Concatenate the strings together into one big string</li>
<li>Calculate the SHA256 hash of this string</li>
<li>Encode the hash into Base64</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
<a name="signedhash"/>
<hr>
<h3>3.2. Signed Space URI Calculation Procedure</h3>
This is much simpler than determining plain hash URI, since the security of the URI
is based not on hashes of data and metadata, but on the cryptographic <code>privateKey</code>
given by the application.<br>
<br>
Calculation recipe for Signed Space URIs is:
<ol>
<li>Calculate the SHA256 hash of the private key's binary data (not its base64 representation)</li>
<li>Encode this hash into base64, dropping any trailing '=' characters</li>
<li>Append to this the value of metadata item <code>path</code> (recall that <code>path</code>,
if not empty, must begin with a '/')</li>
<li>Prepend the string <code>Q:</code> to this</li>
</ol>
The resulting URI then is in the form <code>Q:pubkeyHash/path.ext</code>
<hr>
<!-- Created: Tue Apr 5 00:56:45 NZST 2005 -->
<!-- hhmts start -->
Last modified: Wed Apr 6 00:36:37 NZST 2005
<!-- hhmts end -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1 +0,0 @@
rxvXpHKfWGWsql4PJaHglAERSUYyrdKKAzK6jPHT4QXRf9jgcVd4mInq0j6H4inVOzT9dG4L6c9GrlQwe4ysUm5jSTyZemxiZpQDCAazsoRzNDv6gevA40J6uGl10JtVtOjqXW8Ej0JUKubz88g~ogPb1h4Xibc-RrtqrvsJebg5xYFkLlnr7DxDtiWzIMRSZ9Ri2P~eq0SwZzd81tvASPj5fb3nySHeABAuY8HrNu0gqRLjeayDpd3OK1ogrxf1lMvfutn5pnLrlVcvKHa~6rNWWGSulsuEYWtpUd4Itj9aKqIgF9ES7RF77Z73W1f6NRTHO48ZLyLLaKVLjDIsHQP-0mOevszcPjFWtheqRKvT2D28WEMpVC-mPtfw91BkdgBa3pwWhwG~7KIhvWhGs8bj2NOKkqrwYU7xhNVaHdDDkzv4gsweCutHNiiCF~4yL54WzCIfSKDjcHjQxxVkh2NKeaItzgw9E~mPAKNZD22X~2oAuuL9i~0lldEV1ddUAAAA

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1,23 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Q Screenshots</title>
</head>
<body>
<h1>Q Screenshots</h1>
<ul>
<li><a href="screenshot-search.jpg">Search Screen</li>
<li><a href="screenshot-qsite.jpg">QSite Insertion Form</li>
<li><a href="screenshot-iewarn.jpg">Q Security Features</li>
</ul>
<hr>
<address><a href="mailto:aum@mail.i2p">aum</a></address>
<!-- Created: Sat Apr 16 17:24:02 NZST 2005 -->
<!-- hhmts start -->
Last modified: Mon Apr 18 14:06:02 NZST 2005
<!-- hhmts end -->
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="aum">
<!-- Written to assume that classpath is rooted in the current directory. -->
<!-- So this should be OK if you make this script in the root of a filesystem. -->
<!-- If not, just change src.dir to be the root of your sources' package tree -->
<!-- and use e.g. View over a Filesystem to mount that subdirectory with all capabilities. -->
<!-- The idea is that both Ant and NetBeans have to know what the package root is -->
<!-- for the classes in your application. -->
<!-- Don't worry if you don't know the Ant syntax completely or need help on some tasks! -->
<!-- The standard Ant documentation can be downloaded from AutoUpdate and -->
<!-- and then you can access the Ant manual in the online help. -->
<target name="init">
<property location="build" name="classes.dir"/>
<property location="src" name="src.dir"/>
<property location="doc/q/api" name="javadoc.dir"/>
<property name="project.name" value="${ant.project.name}"/>
<property location="${project.name}.jar" name="jar"/>
<property location="q.war" name="war"/>
</target>
<target name="builddep">
<ant dir="../../i2ptunnel/java/" target="build" />
<!-- i2ptunnel builds ministreaming and core -->
</target>
<target depends="init,builddep" name="compile">
<!-- Both srcdir and destdir should be package roots. -->
<mkdir dir="${classes.dir}"/>
<javac debug="true"
deprecation="true"
destdir="${classes.dir}"
srcdir="${src.dir}"
classpath="xmlrpc.jar:../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../i2ptunnel/java/build/i2ptunnel.jar" >
<!-- To add something to the classpath: -->
<!-- <classpath><pathelement location="${mylib}"/></classpath> -->
<!-- To exclude some files: -->
<!-- <exclude name="com/foo/SomeFile.java"/><exclude name="com/foo/somepackage/"/> -->
</javac>
</target>
<target depends="init,compile" name="jar">
<!-- To make a standalone app, insert into <jar>: -->
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
<!-- <jar basedir="${classes.dir}" compress="true" jarfile="${jar}"> -->
<jar compress="true" jarfile="${jar}">
<!-- <jar basedir="." compress="true" jarfile="${jar}" includes="**/*.class,doc/**/*.html"> -->
<fileset dir="${classes.dir}"/>
<fileset dir="." includes="qresources/**"/>
<manifest>
<attribute name="Main-Class" value="net.i2p.aum.q.QMgr"/>
<attribute name="Class-Path" value="i2p.jar xmlrpc.jar mstreaming.jar streaming.jar jbigi.jar"/>
</manifest>
</jar>
</target>
<target depends="init,compile" name="war">
<!-- To make a standalone app, insert into <jar>: -->
<!-- <manifest><attribute name="Main-Class" value="com.foo.Main"/></manifest> -->
<war compress="true" jarfile="${war}" webxml="web.xml">
<!-- <fileset file="build/net/i2p/aum/q/QConsole.class"/> -->
<classes dir="build" includes="**/QConsole.class"/>
<classes dir="build" includes="**/HTML/**"/>
<!-- <fileset includes="**/HTML/*.class"/> -->
<lib file="xmlrpc.jar"/>
</war>
</target>
<target depends="init,jar,war" description="Build everything." name="all"/>
<target depends="init" description="Javadoc for my API." name="javadoc">
<mkdir dir="${javadoc.dir}"/>
<javadoc destdir="${javadoc.dir}" packagenames="*">
<sourcepath>
<pathelement location="${src.dir}"/>
</sourcepath>
<sourcepath>
<pathelement location="/java/xmlrpc-1.2-b1/src/java"/>
</sourcepath>
</javadoc>
</target>
<target depends="init" description="Clean all build products." name="clean">
<delete dir="${classes.dir}"/>
<delete dir="${javadoc.dir}"/>
<delete file="${jar}"/>
</target>
</project>

View File

@@ -1,10 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Item Not Found</td>
</tr>
<tr>
<td align=center>Failed to retrieve item:<br>
<code><tmpl_var 404_uri></code>
</td>
</tr>
</table>

View File

@@ -1,27 +0,0 @@
<TABLE align="center" class="mainpaneitem">
<TR>
<TD class="formHeading">About Q</TD>
</tr>
<tr class="formBody">
<TD align=center>
<p>Version
<tmpl_if version>
<tmpl_var version></p>
<tmpl_else>
0.0.1
</tmpl_if>
<p>Designed and engineered by <a href="mailto:aum@mail.i2p">aum</a></p>
<p>Copyright &copy; 2005 by aum.
<br>Released under the
<br>GNU Lesser General Public License</p>
<p style="font-size: smaller; font-style: italic">
Many thanks to jrandom, smeghead and frosk<br>
for their patient and knowledgeable support<br>
in helping this python programmer<br>
get partly proficient in java.</p>
</TD>
</TR>
</TABLE>

View File

@@ -1,40 +0,0 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading">Add Hub Noderef</td>
</tr>
<form action="/tools" method="POST" class="formBody">
<input type="hidden" name="cmd" value="addref">
<tr>
<td align="center">
<table width=70%>
<tr>
<td>
In order for your node to join a Q network, it must have a ref to at
least one of the running Q Hubs. You need to get a Q Hub noderef and
paste it here.
</td>
</tr>
<tr>
<td align=center>
<textarea name="noderef" cols=40 rows=6></textarea>
</td>
</tr>
<tr>
<td align="center">
<input type="submit" name="submit" value="Add Hub Noderef!">
</td>
</tr>
</table>
</td>
</tr>
</form>
</table>

View File

@@ -1,29 +0,0 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via the Internet Explorer web browser.
</p>
<p>We have blocked the connection to protect your anonymity.
</p>
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
Internet Explorer (MSIE), because of that browser's abysmal track record with
regard to security. If we did allow you to browse Q via MSIE, it would
be easy for a malicious QSite author to embed hostile content which
undermines your computer's security and compromises your anonymity.
</p>
<p>If you want to surf I2P QSites, you'll need to use a more secure web
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,52 +0,0 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via an unprotected link.
</p>
<p>We have blocked the connection to protect your anonymity. If we don't
do this, then a malicious QSite author can insert content into the QSite
which triggers oubound hits to arbitrary servers on the mainstream web,
which in turn can easily reveal a lot of personal information about you
and the QSite you are accessing.
</p>
<p>If you want to browse QSites with your web browser with greater safety,
you'll have to follow these simple steps:
</p>
<ol>
<li>Edit your I2P <code>hosts.txt</code> file and add the following entry
(all on one line):
<blockquote><code><textarea rows=5 cols=50>q.i2p=<tmpl_var dest></textarea></code><blockquote>
(and make sure you do NOT reveal this entry to anyone else)
</li>
<li>Configure one of your web browsers to use the proxy server:
<blockquote><code>localhost:4444</code></blockquote>
(or whatever address you have configured your I2P EEProxy to listen on,
if you've changed it)<br><br>
</li>
<li>Start up the browser you have just configured, and enter the web address:
<blockquote><code>http://q.i2p</code></blockquote>
</li>
</ol>
<p>Even if you do this, you still won't have a 100% guarantee of anonymity, since
a malicious QSite author can send code to your browser which exploits a vulnerability
in the browser to compromise your anonymity (eg, accessing third party cookies, executing
arbitrary code, accessing your local filesystem, uploading compromising information
about you to hostile I2P EEPsites etc). But you can relax (somewhat) in the knowledge
that such attacks are much more difficult, particularly if you use a decent web browser.
</p>
<p>Thank you for your co-operation. We wish you happy and safe browsing.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,9 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Downloads</td>
</tr>
<tr>
<td>Not implemented yet</td>
</tr>
</table>

View File

@@ -1,28 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Generate New Keypair</td>
</tr>
<tr>
<td align="center">
<table>
<tr>
<td>
Click the button to generate a new keypair for
future inserts of signed space keys.
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
<form action="/tools" method="POST" class="formBody">
<input type="hidden" name="cmd" value="genkeys">
<input type="submit" name="submit" value="Generate Keys!">
</form>
</td>
</tr>
</table>

View File

@@ -1,32 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td colspan=2 class="formHeading">Your New Keys</td>
</tr>
<tr>
<td align="center" colspan=2>
<table>
<tr>
<td>
Please save these keys in a safe place,
preferably on an encrypted partition:
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Public Key: </td>
<td>
<input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var publickey>">
</td>
</tr>
<tr>
<td>Private Key: </td>
<td><input readonly type="text" size=32 style="font-family: courier, monospace" value="<tmpl_var privatekey>"></td>
</tr>
</table>

View File

@@ -1,15 +0,0 @@
<table align="center" class="mainpaneitem" border=0>
<TR>
<TD class="formHeading" colspan="3">Retrieve Content</TD>
</tr>
<tr class="formBody">
<form action="/home" method="POST">
<input type="hidden" name="cmd" value="get">
<TD>URI:</TD>
<td><INPUT type="text" name="uri" value="Q:" size="60" maxlength="128"></td>
<td><INPUT type="submit" name="submit" value="Retrieve it"></td>
</form>
</TR>
</table>

View File

@@ -1,11 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Help</td>
</tr>
<tr>
<td>
Not written yet
</td>
</tr>
</table>

View File

@@ -1,34 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading" colspan=2>Current Background Jobs</td>
</tr>
<tr>
<td>
<tmpl_if items>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td><b>Secs From Now</b></td>
<td><b>Description</b></td>
</tr>
<tmpl_loop items>
<tr>
<td><tmpl_var future></td>
<td><tmpl_var description></td>
</tr>
</tmpl_loop>
</table>
<tmpl_else>
<tr><td colspan=2 align=center><i>(No current jobs)</i></td></tr>
</tmpl_if>
</td>
</tr>
</table>

View File

@@ -1,122 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<TITLE>Q Web Interface</TITLE>
<style type="text/css">
<!--
body { font-family: helvetica, arial, sans-serif; background-color: #000080 }
td { }
code { font-family: courier, monospace; font-weight: bolder; font-size:smaller }
.logocell {
border-color: #ffe000;
border-width: 1px;
border-top-style:none;
border-bottom-style:solid;
border-left-style:none;
border-right-style:none;
text-align: right;
font-weight: bold;
color: #f0f0ff;
}
.tabcell {
font-weight: bold;
background-color: #fffff0;
padding:5px;
border-color:#ffe000;
border-width: 1px;
border-top-style:solid;
border-bottom-style:none;
border-left-style:solid;
border-right-style:solid;
}
.tabcell_inactive {
background-color: #f0f0ff;
padding:5px;
border-color:#ffe000;
border-width: 1px;
border-top-style:solid;
border-bottom-style:solid;
border-left-style:solid;
border-right-style:solid;
}
.tablink {
font-size: smaller;
text-decoration: none;
}
.mainpane { border-color:#ffe000;
border-width: 1px;
border-top-style:none;
border-bottom-style:solid;
border-left-style:solid;
border-right-style:solid;
}
.mainpaneitem1 { border-style:solid; border-color:#0000ff; border-width: thin; }
.mainpaneitem { border-style:solid;
border-color:#0000ff;
border-width: thin;
background-color: #f0f0ff;
}
.formHeading { font-size:larger; font-weight: bolder; text-align:center; }
.btn1 { font-weight: bold;
}
input1 { background-color: #fffff0;
color: #000080;
}
-->
</style>
</head>
<body bgcolor="#ffffff">
<TABLE width="99%" height=99% align="center" cellspacing="0" cellpadding="0" border=0>
<TR>
<TD>
<table width="100%" cellspacing=0 cellpadding=0>
<tr>
<td>
<table align=right cellspacing=0 cellpadding=0 border=0>
<tr>
<tmpl_loop tabs>
<td class=<tmpl_if active>"tabcell"<tmpl_else>"tabcell_inactive"</tmpl_if>>
<a class="tablink" href="/<tmpl_var name>"><tmpl_var label></a>
</td>
</tmpl_loop>
<tmpl_if _ignore>
<TD class="tabcell_inactive">
<a class="tablink" href="/status">Status</a>
</TD>
<TD class="tabcell">
<a class="tablink" href="/settings">Settings</a>
</TD>
</tmpl_if>
</tr>
</table>
</td>
<TD width=100% class="logocell">
Q <tmpl_var nodeType> Node
</TD>
</TR>
</table>
</TD> </TR>
<tr height=99% bgcolor="#fffff0">
<TD class="mainpane">
<br>
<center>
<tmpl_loop items>
<br>
<tmpl_var item>
<br>
</tmpl_loop>
</center>
</TD>
</tr>
</TABLE>
</body>
</html>

View File

@@ -1,29 +0,0 @@
<table align="center" class="mainpaneitem" width=80%>
<tr>
<td class="formHeading" colspan=2>Critical Anonymity Alert!</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td>
<p>You are attempting to view a QSite via the Internet Explorer web browser.
</p>
<p>We have blocked the connection to protect your anonymity.
</p>
<p>As a matter of policy, Q does not support QSite browsing via Microsoft
Internet Explorer (MSIE), because of that browser's abysmal track record with
regard to security. If we did allow you to browse Q via MSIE, it would
be easy for a malicious QSite author to embed hostile content which
undermines your computer's security and compromises your anonymity.
</p>
<p>If you want to surf I2P QSites, you'll need to use a more secure web
browser such as <a href="http://www.mozilla.org">Mozilla or Firefox</a>.
</td>
</tr>
</table>
</td>
</tr>
</table>

View File

@@ -1,10 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Error Inserting <tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if></td>
</tr>
<tr>
<td align=center>Your insert failed because:<br>
<code><tmpl_var error></code>
</td>
</tr>
</table>

View File

@@ -1,139 +0,0 @@
<TABLE align="center" class="mainpaneitem" width=90% >
<TR>
<TD class="formHeading" colspan="2">Insert Content</TD>
</tr>
<TR>
<TD colspan="2" align=center><a href="/insert?mode=site">Insert A QSite Instead</a></TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<form method="POST" ENCTYPE="multipart/form-data" action="/insert" class="formBody">
<!-- <form method="POST" action="/insert" class="formBody"> -->
<input type=hidden name="cmd" value="put">
<tr>
<td valign=top>Type: </td>
<td style="font-size:smaller">
<input type="radio" name="type" value="text">Text
<input type="radio" name="type" value="html">HTML
<input type="radio" name="type" value="image">Image
<input type="radio" name="type" value="audio">Audio
<input type="radio" name="type" value="video">Video
<input type="radio" name="type" value="archive">Archive
<input type="radio" name="type" value="other" checked>Other
</td>
</tr>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Title: </TD>
<td>
<input type="text" name="title" size="60" maxlength="58">
<div style="font-style: italic; font-size: smaller">
(A short descriptive title for this item)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Path: </TD>
<td>
<input type="text" name="path" size="60" maxlength="128">
<div style="font-style: italic; font-size: smaller">
(If you're inserting in plain-hash mode, this should be a suggested
filename for people to save the item as when downloading it; If you're
inserting in signed-space mode, this can be a longer pathname)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Mimetype: </TD>
<td>
<input type="text" name="mimetype" size="40" maxlength="40">
<div style="font-style: italic; font-size: smaller">
(Leave blank unless you know exactly what this means)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Keywords: </TD>
<td>
<input type="text" name="keywords" size="60" maxlength="80">
<div style="font-style: italic; font-size: smaller">
(Comma-separated list of short descriptive keywords)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Abstract: </TD>
<td>
<textarea name="abstract" cols="50" rows="2"></textarea>
<div style="font-style: italic; font-size: smaller">
(A short descriptive summary for the item, preferably no
longer than 256 characters)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>File: </TD>
<td>
<INPUT type="file" name="data" size="40" maxlength="128">
<div style="font-style: italic; font-size: smaller">
(Leave this blank if you are entering the raw data in the text box below)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Private Key: </TD>
<td>
<INPUT type="text" name="privkey" size="30" maxlength="60">
<div style="font-size: smaller; font-style: italic">
(Only if inserting in signed space mode)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD colspan=2 align=center>
<INPUT class="btn" type="submit" name="submit" value="Insert it">
</TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<tr>
<td valign=top>Raw Data:</td>
<td>
<div style="font-size: smaller; font-style:italic">
(You may enter the raw data here as text instead of selecting a
file above)
</div>
<textarea name="rawdata" cols=60 rows=16></textarea>
</td>
</tr>
</form>
</TABLE>

View File

@@ -1,10 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading"><tmpl_if is_site>QSite<tmpl_else>Item</tmpl_if> Inserted Successfully</td>
</tr>
<tr>
<td align=center>Item URI:<br>
<code><input type="text" size=60 readonly value="<tmpl_var uri>"></code>
</td>
</tr>
</table>

View File

@@ -1,101 +0,0 @@
<table align="center" class="mainpaneitem" width=90% >
<TR>
<TD class="formHeading" colspan="2">Insert A QSite</TD>
</tr>
<TR>
<TD colspan="2" align=center><a href="/insert?mode=file">Insert A Single File Instead</a></TD>
</tr>
<tr><td colspan=2><hr></td></tr>
<form method="POST" action="/insert" class="formBody">
<!-- <form method="POST" action="/insert" class="formBody"> -->
<input type=hidden name="cmd" value="putsite">
<input type=hidden name="type" value="qsite">
<tr>
<TD valign=top>Name: </TD>
<td>
<input type="text" name="name" size="32" maxlength="50">
<div style="font-style: italic; font-size: smaller">
(A short name for the QSite - should contain only alphanumerics, '-', and '_';
should definitely <i>not</i> contain '/', ':', '\', ' ' etc)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Private Key: </TD>
<td>
<INPUT type="text" name="privkey" size="30" maxlength="60">
<div style="font-size: smaller; font-style: italic">
(Mandatory - if you don't have a signed-space keypair, get one by
clicking on <b>Tools</b>)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Directory: </TD>
<td>
<INPUT type="text" name="dir" size="60" maxlength="128">
<div style="font-size: smaller; font-style: italic">
(Absolute directory path where the QSite's files reside)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Title: </TD>
<td>
<input type="text" name="title" size="60" maxlength="58">
<div style="font-style: italic; font-size: smaller">
(A short descriptive title for this QSite)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Keywords: </TD>
<td>
<input type="text" name="keywords" size="60" maxlength="80">
<div style="font-style: italic; font-size: smaller">
(Comma-separated list of short descriptive keywords)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD valign=top>Abstract: </TD>
<td>
<textarea name="abstract" cols="50" rows="2"></textarea>
<div style="font-style: italic; font-size: smaller">
(A short descriptive summary for the QSite, preferably no
longer than 256 characters)
</div>
</td>
</TR>
<tr><td colspan=2><hr></td></tr>
<tr>
<TD colspan=2 align=center>
<INPUT class="btn" type="submit" name="submit" value="Insert it">
</TD>
</tr>
</form>
</table>

View File

@@ -1,76 +0,0 @@
<TABLE align="center" class="mainpaneitem">
<TR>
<TD class="formHeading" colspan="3">Search For Content</TD>
</tr>
<form method="GET" action="/search" class="formBody">
<input type=hidden name=cmd value="search">
<tr>
<td valign=top>Type: </td>
<td style="font-size:smaller" colspan=2>
<input type="radio" name="type" value="any" checked>ANY
<input type="radio" name="type" value="qsite">QSite
<input type="radio" name="type" value="text">Text
<input type="radio" name="type" value="html">HTML
<input type="radio" name="type" value="image">Image
<br>
<input type="radio" name="type" value="audio">Audio
<input type="radio" name="type" value="video">Video
<input type="radio" name="type" value="archive">Archive
<input type="radio" name="type" value="other">Other
</td>
</tr>
<tr>
<td>Title: </td>
<td><input type="text" name="title" size="60" value="<tmpl_var title>" maxlength="128"></td>
<td><input type="checkbox" name="title_re" <tmpl_if title_re>checked</tmpl_if>>regexp</td>
</tr>
<tr>
<TD>Path: </TD>
<td><INPUT type="text" name="path" size="60" value="<tmpl_var title>" maxlength="128"></td>
<td><input type="checkbox" name="path_re" <tmpl_if path_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Mimetype: </TD>
<td><INPUT type="text" name="mimetype" size="40" maxlength="40" value="<tmpl_var mimetype>"></td>
<td><input type="checkbox" name="mimetype_re" <tmpl_if mimetype_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Keywords: </TD>
<td><INPUT type="text" name="keywords" size="60" maxlength="80" value="<tmpl_var keywords>"></td>
<td><input type="checkbox" name="keywords_re" <tmpl_if keywords_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<TD>Summary: </TD>
<td><textarea name="summary" cols="50" rows=2><tmpl_var summary></textarea></td>
<td><input type="checkbox" name="summary_re" <tmpl_if summary_re>checked</tmpl_if>>regexp</td>
</TR>
<tr>
<td>
Search Mode:
</td>
<td>
<input type=radio name="searchmode" value="and" checked>AND
<input type=radio name="searchmode" value="or">OR
</td>
</tr>
<tr><td colspan=3><hr></td></tr>
<tr>
<TD colspan=3 align=center>
<INPUT class="btn" type="submit" name="submit" value="Search!">
</TD>
</tr>
</form>
</TABLE>

View File

@@ -1,27 +0,0 @@
<table align="center" class="mainpaneitem" <tmpl_if results>width="95%"</tmpl_if>>
<tr>
<td class="formHeading" colspan=2>Search Results</td>
</tr>
<tr>
<td style="font-style: italic" align="center"><tmpl_var numresults> items found</td>
</tr>
<tr>
<td width="90%">
<table cellspacing=0 cellpadding=5 align=center border=0 width=95%>
<tmpl_loop results>
<tr>
<td>
<div style="font-size: larger"><a href="/<tmpl_var uri>"><tmpl_var title></a></div>
<tmpl_if abstract>
<div style="font-style: italic"><tmpl_var abstract></div>
</tmpl_if>
<div style="font-size: smaller; color: #008000;"><tmpl_var uri></div>
<div style="font-size:smaller;"><tmpl_var type> (<tmpl_var mimetype>) <tmpl_var size> bytes</div>
<div/>
</td>
</tr>
</tmpl_loop>
</table>
</td>
</tr>
</table>

View File

@@ -1,9 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Settings</td>
</tr>
<tr>
<td>Not implemented yet</td>
</tr>
</table>

View File

@@ -1,24 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading" colspan=2>Q Node Status</td>
</tr>
<tr>
<td>
<table cellspacing=0 cellpadding=5 align=center border=1>
<tr>
<td><b>Item</b></td>
<td><b>Value</b></td>
</tr>
<tmpl_loop items>
<tr>
<td><tmpl_var key></td>
<td><tmpl_var value></td>
</tr>
</tmpl_loop>
</table>
</td>
</tr>
</table>

View File

@@ -1,6 +0,0 @@
<table align="center" class="mainpaneitem">
<tr>
<td class="formHeading">Q Tools</td>
</tr>
</table>

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