From f81a24a0cc1856ee18f76db8ba6d2cf68c6c8639 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 1 Apr 2009 04:54:49 +0000 Subject: [PATCH 01/27] I2PTunnel: Fix tunnel close http://forum.i2p/viewtopic.php?t=3231 broken in 0.7-8 --- .../java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java | 1 + history.txt | 4 ++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index b6eb392243..f5f0df6b98 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -278,6 +278,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } } sockManager.setName("Client"); + tunnel.addSession(sockManager.getSession()); return sockManager; } diff --git a/history.txt b/history.txt index f027db23a5..9ac00e87b6 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2009-04-01 zzz + * I2PTunnel: Fix tunnel close + http://forum.i2p/viewtopic.php?t=3231 + 2009-03-30 zzz * I2CP: - Implement BandwidthLimitsMessage diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 1d4797d6fc..a727710d68 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 1; + public final static long BUILD = 2; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From d2fc3972955e686d61506d9295378468e60ae7cc Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 21:00:26 +0000 Subject: [PATCH 02/27] -3 --- history.txt | 22 +++++++++++++++++++ .../src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index 9ac00e87b6..789aca373f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,25 @@ +2009-04-02 zzz + * Profiles: + - Remove unused calculators and RateStats: + CapacityCalculator, StrictSpeedCalculator, IsFailingCalculator; + sendFailureSize, processSuccessRate, processfailureRate, commErrorRate, + tunnelTestResponseTimeSlow + - Reduced number of Rates in these RateStats: + sendSuccessSize, receiveSize, rejectRate, failRate + - ~5KB/profile savings total + - Deflate speed calculation once an hour instead of once a day, + to improve fast tier selection + - Remove dup comment in persisted files + * StatisticsManager - effective in 0.7.2: + - Spoof uptime to 90m for all + - Change tunnel stats from 10m to 60m + * Transport: + - Maintain a router hash -> IP map in transport, + to support additional IP checks (unused for now) + - Catch error on pre-2.6 kernels + - Some concurrent conversion + - Fix an HTML error on peers.jsp + 2009-04-01 zzz * I2PTunnel: Fix tunnel close http://forum.i2p/viewtopic.php?t=3231 diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index a727710d68..a34e9238c0 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 2; + public final static long BUILD = 3; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From c7d815b5b886131de11b1ee2244a058dd8919761 Mon Sep 17 00:00:00 2001 From: zzz Date: Thu, 2 Apr 2009 22:08:39 +0000 Subject: [PATCH 03/27] -4 (-3 didnt build) --- router/java/src/net/i2p/router/RouterVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index a34e9238c0..5f58c2cdd1 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 3; + public final static long BUILD = 4; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 25d5883a0e0bb88addf5b2ef445c9869ceffe110 Mon Sep 17 00:00:00 2001 From: sponge Date: Fri, 3 Apr 2009 13:31:41 +0000 Subject: [PATCH 04/27] 2009-04-03 sponge * Fix broken dependencies for BOB.jar --- apps/BOB/nbproject/project.properties | 41 ++++++++------------------- history.txt | 2 ++ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties index 7a94ff6ddd..ee493f7643 100644 --- a/apps/BOB/nbproject/project.properties +++ b/apps/BOB/nbproject/project.properties @@ -24,40 +24,23 @@ dist.dir=dist dist.jar=${dist.dir}/BOB.jar dist.javadoc.dir=${dist.dir}/javadoc excludes= -file.reference.build-javadoc=../../../i2p.i2p/build/javadoc -file.reference.core.jar=../i2p.i2p/core/dist/core.jar -file.reference.i2p.jar=../../bob/i2p/i2p.i2p/build/i2p.jar -file.reference.i2p.jar-1=../../build/i2p.jar -file.reference.i2p.jar-2=../i2p.i2p/core/java/build/i2p.jar -file.reference.i2p.jar-3=../../../i2p.i2p/build/i2p.jar -file.reference.i2ptunnel.jar=../../../i2p.i2p/build/i2ptunnel.jar -file.reference.java-src=../i2p.i2p/core/java/src/ -file.reference.jbigi.jar=../../bob/i2p/i2p.i2p/build/jbigi.jar -file.reference.mstreaming.jar=../../bob/i2p/i2p.i2p/build/mstreaming.jar -file.reference.mstreaming.jar-1=../../build/mstreaming.jar -file.reference.mstreaming.jar-2=../../../i2p.i2p/build/mstreaming.jar -file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/ -file.reference.router.jar=../../build/router.jar -file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar -file.reference.streaming.jar-1=../../../i2p.i2p/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.build-javadoc=../../i2p.i2p/build/javadoc +file.reference.i2p.jar=../../core/java/build/i2p.jar +file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar +file.reference.mstreaming.jar=../ministreaming/java/build/mstreaming.jar +file.reference.router.jar=../../router/java/build/router.jar +file.reference.streaming.jar=../streaming/java/build/streaming.jar file.reference.wrapper.jar=../../installer/lib/wrapper/linux/wrapper.jar includes=** jar.compress=false javac.classpath=\ - ${file.reference.wrapper.jar}:\ - ${file.reference.streaming.jar-1}:\ - ${file.reference.i2ptunnel.jar}:\ - ${file.reference.i2p.jar-1}:\ ${file.reference.router.jar}:\ - ${file.reference.mstreaming.jar-1}:\ - ${file.reference.mstreaming.jar-2}:\ - ${file.reference.i2p.jar-3} + ${file.reference.i2ptunnel.jar}:\ + ${file.reference.mstreaming.jar}:\ + ${file.reference.streaming.jar}:\ + ${file.reference.wrapper.jar}:\ + ${file.reference.i2p.jar}:\ + ${file.reference.router.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/history.txt b/history.txt index 789aca373f..a7bfb78b57 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,5 @@ +2009-04-03 sponge + * Fix broken dependencies for BOB.jar 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: From e5b1450e83c44c6d4fc79af7ff55c77974816089 Mon Sep 17 00:00:00 2001 From: sponge Date: Fri, 3 Apr 2009 15:33:51 +0000 Subject: [PATCH 05/27] 2009-04-03 sponge * Router build version incremented to 5 now that the build succeeds. --- history.txt | 1 + router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index a7bfb78b57..283be6c21e 100644 --- a/history.txt +++ b/history.txt @@ -1,5 +1,6 @@ 2009-04-03 sponge * Fix broken dependencies for BOB.jar + * Router build version incremented to 5. 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5f58c2cdd1..5203f2360b 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 4; + public final static long BUILD = 5; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From fe9b891b37d8775ffb95a7f58e4c4cdaa6649149 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 3 Apr 2009 21:46:10 +0000 Subject: [PATCH 06/27] -6 --- history.txt | 10 ++++++++++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index 283be6c21e..b4ed774806 100644 --- a/history.txt +++ b/history.txt @@ -1,6 +1,16 @@ +2009-04-03 zzz + * Console: + - Fix bug with IE buttons not working, + because it sends the label instead of the value + - Display version of downloaded update + * Update: + - Change default to "Download and verify" + - Change news fetch default to 24h (was 12h) + 2009-04-03 sponge * Fix broken dependencies for BOB.jar * Router build version incremented to 5. + 2009-04-02 zzz * Profiles: - Remove unused calculators and RateStats: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5203f2360b..c089489691 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 5; + public final static long BUILD = 6; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From a4b5c63702e04fa1799d6abf0a20efe3873d7995 Mon Sep 17 00:00:00 2001 From: zzz Date: Sat, 4 Apr 2009 17:17:34 +0000 Subject: [PATCH 07/27] -7 --- history.txt | 3 +++ router/java/src/net/i2p/router/RouterVersion.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/history.txt b/history.txt index b4ed774806..acc8a79b0f 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,6 @@ +2009-04-04 zzz + * NTCP: Don't bid on messages too big to handle + 2009-04-03 zzz * Console: - Fix bug with IE buttons not working, diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index c089489691..d1e87948b1 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 6; + public final static long BUILD = 7; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From bd489cf4391d10bbd238cc8a640be64d6c43b252 Mon Sep 17 00:00:00 2001 From: sponge Date: Sat, 4 Apr 2009 19:47:36 +0000 Subject: [PATCH 08/27] 2009-04-04 sponge * Hopeful fixups to the infamous orpahned tunnel problem. * BOB now 0.0.5 --- apps/BOB/nbproject/project.properties | 1 + apps/BOB/src/net/i2p/BOB/BOB.java | 3 +- apps/BOB/src/net/i2p/BOB/DoCMDS.java | 2 +- apps/BOB/src/net/i2p/BOB/I2Plistener.java | 110 +++++++---- apps/BOB/src/net/i2p/BOB/I2PtoTCP.java | 4 +- apps/BOB/src/net/i2p/BOB/MUXlisten.java | 14 +- apps/BOB/src/net/i2p/BOB/Main.java | 5 +- apps/BOB/src/net/i2p/BOB/TCPio.java | 21 +- apps/BOB/src/net/i2p/BOB/TCPlistener.java | 180 ++++++++++++------ apps/BOB/src/net/i2p/BOB/TCPtoI2P.java | 21 +- .../src/net/i2p/router/RouterVersion.java | 2 +- 11 files changed, 236 insertions(+), 127 deletions(-) diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties index ee493f7643..9eeffd29f1 100644 --- a/apps/BOB/nbproject/project.properties +++ b/apps/BOB/nbproject/project.properties @@ -27,6 +27,7 @@ excludes= file.reference.build-javadoc=../../i2p.i2p/build/javadoc file.reference.i2p.jar=../../core/java/build/i2p.jar file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar +file.reference.jbigi.jar=../../installer/lib/jbigi/jbigi.jar file.reference.mstreaming.jar=../ministreaming/java/build/mstreaming.jar file.reference.router.jar=../../router/java/build/router.jar file.reference.streaming.jar=../streaming/java/build/streaming.jar diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java index 59b46b8d74..48a0e067c6 100644 --- a/apps/BOB/src/net/i2p/BOB/BOB.java +++ b/apps/BOB/src/net/i2p/BOB/BOB.java @@ -34,6 +34,7 @@ import java.util.Properties; import net.i2p.client.I2PClient; import net.i2p.client.streaming.RetransmissionTimer; import net.i2p.util.Log; + /** * * ################################################################################
@@ -157,12 +158,12 @@ public class BOB { boolean save = false; // 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(); String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config"); // This is here just to ensure there is no interference with our threadgroups. RetransmissionTimer Y = RetransmissionTimer.getInstance(); i = Y.hashCode(); + { try { FileInputStream fi = new FileInputStream(configLocation); diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java index 099d69feca..12d8d61560 100644 --- a/apps/BOB/src/net/i2p/BOB/DoCMDS.java +++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java @@ -46,7 +46,7 @@ public class DoCMDS implements Runnable { // FIX ME // I need a better way to do versioning, but this will do for now. - public static final String BMAJ = "00", BMIN = "00", BREV = "04", BEXT = ""; + public static final String BMAJ = "00", BMIN = "00", BREV = "05", BEXT = ""; public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT; private Socket server; private Properties props; diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java index c59683270e..813bb5d343 100644 --- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java +++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java @@ -62,6 +62,26 @@ public class I2Plistener implements Runnable { tgwatch = 1; } + private void rlock() throws Exception { + database.getReadLock(); + info.getReadLock(); + } + + private void runlock() throws Exception { + database.releaseReadLock(); + info.releaseReadLock(); + } + + private void wlock() throws Exception { + database.getWriteLock(); + info.getWriteLock(); + } + + private void wunlock() throws Exception { + info.releaseWriteLock(); + database.releaseWriteLock(); + } + /** * Simply listen on I2P port, and thread connections * @@ -70,68 +90,86 @@ public class I2Plistener implements Runnable { boolean g = false; I2PSocket sessSocket = null; - serverSocket.setSoTimeout(50); - database.getReadLock(); - info.getReadLock(); - if(info.exists("INPORT")) { - tgwatch = 2; - } - info.releaseReadLock(); - database.releaseReadLock(); - boolean spin = true; - while(spin) { +die: { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); + serverSocket.setSoTimeout(50); try { + if (info.exists("INPORT")) { + tgwatch = 2; + } + } catch (Exception e) { try { - sessSocket = serverSocket.accept(); - g = true; - } catch(ConnectException ce) { - g = false; - } catch(SocketTimeoutException ste) { - g = false; - } - if(g) { - g = false; - // toss the connection to a new thread. - I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database); - Thread t = new Thread(conn_c, "BOBI2PtoTCP"); - t.start(); + runlock(); + } catch (Exception e2) { + break die; } + break die; + } + boolean spin = true; + while (spin) { - } catch(I2PException e) { - // System.out.println("Exception " + e); + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + try { + sessSocket = serverSocket.accept(); + g = true; + } catch (ConnectException ce) { + g = false; + } catch (SocketTimeoutException ste) { + g = false; + } + if (g) { + g = false; + // toss the connection to a new thread. + I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database); + Thread t = new Thread(conn_c, "BOBI2PtoTCP"); + t.start(); + } + + } catch (I2PException e) { + // System.out.println("Exception " + e); + } } } // System.out.println("I2Plistener: Close"); try { serverSocket.close(); - } catch(I2PException e) { + } catch (I2PException e) { // nop } // need to kill off the socket manager too. I2PSession session = socketManager.getSession(); - if(session != null) { + if (session != null) { // System.out.println("I2Plistener: destroySession"); try { session.destroySession(); - } catch(I2PSessionException ex) { + } catch (I2PSessionException ex) { // nop } } // System.out.println("I2Plistener: Waiting for children"); - while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish + 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) { + } catch (Exception e) { // nop } } - // System.out.println("I2Plistener: Done."); + // System.out.println("I2Plistener: Done."); } } diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java index 5d24e19d36..06c3131fee 100644 --- a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java +++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java @@ -105,8 +105,8 @@ die: { out.flush(); // not really needed, but... } // setup to cross the streams - TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P - TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app + TCPio conn_c = new TCPio(in, Iout /*, info, database */ ); // app -> I2P + TCPio conn_a = new TCPio(Iin, out /* , info, database */); // I2P -> app Thread t = new Thread(conn_c, "TCPioA"); Thread q = new Thread(conn_a, "TCPioB"); // Fire! diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index 89ab53fe62..879cf9a64a 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -100,7 +100,7 @@ public class MUXlisten implements Runnable { // Everything is OK as far as we can tell. this.database.getWriteLock(); this.info.getWriteLock(); - this.info.add("STARTING", Boolean.TRUE); + this.info.add("STARTING", new Boolean(true)); this.info.releaseWriteLock(); this.database.releaseWriteLock(); } @@ -134,8 +134,8 @@ public class MUXlisten implements Runnable { try { wlock(); try { - info.add("RUNNING", Boolean.TRUE); - info.add("STARTING", Boolean.FALSE); + info.add("RUNNING", new Boolean(true)); + info.add("STARTING", new Boolean(false)); } catch(Exception e) { wunlock(); return; @@ -198,7 +198,7 @@ die: { try { wlock(); try { - info.add("RUNNING", Boolean.FALSE); + info.add("RUNNING", new Boolean(false)); } catch(Exception e) { wunlock(); break die; @@ -255,9 +255,9 @@ die: { try { wlock(); try { - info.add("STARTING", Boolean.FALSE); - info.add("STOPPING", Boolean.FALSE); - info.add("RUNNING", Boolean.FALSE); + info.add("STARTING", new Boolean(false)); + info.add("STOPPING", new Boolean(false)); + info.add("RUNNING", new Boolean(false)); } catch(Exception e) { wunlock(); return; diff --git a/apps/BOB/src/net/i2p/BOB/Main.java b/apps/BOB/src/net/i2p/BOB/Main.java index 2d81fb30ed..b32e4758ef 100644 --- a/apps/BOB/src/net/i2p/BOB/Main.java +++ b/apps/BOB/src/net/i2p/BOB/Main.java @@ -24,7 +24,7 @@ package net.i2p.BOB; import net.i2p.client.streaming.RetransmissionTimer; - +import net.i2p.util.SimpleScheduler; /** * Start from command line * @@ -39,6 +39,9 @@ public class Main { public static void main(String[] args) { // THINK THINK THINK THINK THINK THINK RetransmissionTimer Y = RetransmissionTimer.getInstance(); + // needs SimpleScheduler + // no way to stop the scheduler?!? + SimpleScheduler.getInstance(); BOB.main(args); Y.stop(); } diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index c9f4ab64cd..109d8e8cb5 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -35,7 +35,7 @@ public class TCPio implements Runnable { private InputStream Ain; private OutputStream Aout; - private NamedDB info, database; + // private NamedDB info, database; /** * Constructor @@ -43,13 +43,14 @@ public class TCPio implements Runnable { * @param Ain * @param Aout * @param info - * @param database + * + * param database */ - TCPio(InputStream Ain, OutputStream Aout, NamedDB info, NamedDB database) { + TCPio(InputStream Ain, OutputStream Aout /*, NamedDB info , NamedDB database */) { this.Ain = Ain; this.Aout = Aout; - this.info = info; - this.database = database; + // this.info = info; + // this.database = database; } /** @@ -86,11 +87,11 @@ public class TCPio implements Runnable { boolean spin = true; try { while(spin) { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); + // database.getReadLock(); + // info.getReadLock(); + // spin = info.get("RUNNING").equals(Boolean.TRUE); + // info.releaseReadLock(); + // database.releaseReadLock(); b = Ain.read(a, 0, 1); // System.out.println(info.get("NICKNAME").toString() + " " + b); if(b > 0) { diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java index 30380a55dd..7e931768c9 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java +++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java @@ -63,6 +63,26 @@ public class TCPlistener implements Runnable { tgwatch = 1; } + private void rlock() throws Exception { + database.getReadLock(); + info.getReadLock(); + } + + private void runlock() throws Exception { + database.releaseReadLock(); + info.releaseReadLock(); + } + + private void wlock() throws Exception { + database.getWriteLock(); + info.getWriteLock(); + } + + private void wunlock() throws Exception { + info.releaseWriteLock(); + database.releaseWriteLock(); + } + /** * Simply listen on TCP port, and thread connections * @@ -70,77 +90,121 @@ public class TCPlistener implements Runnable { public void run() { boolean g = false; boolean spin = true; - database.getReadLock(); - info.getReadLock(); - if(info.exists("OUTPORT")) { - tgwatch = 2; - } - try { - Socket server = new Socket(); - listener.setSoTimeout(50); // Half of the expected time from MUXlisten - info.releaseReadLock(); - database.releaseReadLock(); - while(spin) { - database.getReadLock(); - info.getReadLock(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); - try { - server = listener.accept(); - g = true; - } catch(SocketTimeoutException ste) { - g = false; - } - if(g) { - // toss the connection to a new thread. - TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info, database); - Thread t = new Thread(conn_c, "BOBTCPtoI2P"); - t.start(); - 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(); - spin = info.get("RUNNING").equals(Boolean.TRUE); - info.releaseReadLock(); - database.releaseReadLock(); - if(spin) { - database.getWriteLock(); - info.getWriteLock(); - info.add("STOPPING", new Boolean(true)); - info.add("RUNNING", new Boolean(false)); - info.releaseWriteLock(); - database.releaseWriteLock(); - } - } +die: { + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + if (info.exists("OUTPORT")) { + tgwatch = 2; + } + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + runlock(); + } catch (Exception e) { + break die; + } + try { + Socket server = new Socket(); + listener.setSoTimeout(50); // Half of the expected time from MUXlisten + while (spin) { + try { + rlock(); + } catch (Exception e) { + break die; + } + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + try { + runlock(); + } catch (Exception e2) { + break die; + } + break die; + } + try { + server = listener.accept(); + g = true; + } catch (SocketTimeoutException ste) { + g = false; + } + if (g) { + // toss the connection to a new thread. + TCPtoI2P conn_c = new TCPtoI2P(socketManager, server /* , info, database */); + Thread t = new Thread(conn_c, "BOBTCPtoI2P"); + t.start(); + g = false; + } + } + //System.out.println("TCPlistener: destroySession"); + listener.close(); + } catch (IOException ioe) { + try { + listener.close(); + } catch (IOException e) { + } + // Fatal failure, cause a stop event + try { + rlock(); + try { + spin = info.get("RUNNING").equals(Boolean.TRUE); + } catch (Exception e) { + runlock(); + break die; + } + } catch (Exception e) { + break die; + } + if (spin) { + try { + wlock(); + try { + info.add("STOPPING", new Boolean(true)); + info.add("RUNNING", new Boolean(false)); + } catch (Exception e) { + wunlock(); + break die; + } + } catch (Exception e) { + break die; + } + try { + wunlock(); + } catch (Exception e) { + break die; + } + } + } + } // need to kill off the socket manager too. I2PSession session = socketManager.getSession(); - if(session != null) { + if (session != null) { try { session.destroySession(); - } catch(I2PSessionException ex) { + } catch (I2PSessionException ex) { // nop } } //System.out.println("TCPlistener: Waiting for children"); - while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish + 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) { + } catch (Exception e) { // nop - } + } } - //System.out.println("TCPlistener: Done."); + //System.out.println("TCPlistener: Done."); } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java index df61e78e1b..fe1ca32788 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java +++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java @@ -45,7 +45,7 @@ import net.i2p.i2ptunnel.I2PTunnel; public class TCPtoI2P implements Runnable { private I2PSocket I2P; - private NamedDB info, database; + // private NamedDB info, database; private Socket sock; private I2PSocketManager socketManager; @@ -84,13 +84,13 @@ public class TCPtoI2P implements Runnable { * Constructor * @param i2p * @param socket - * @param info - * @param database + * param info + * param database */ - TCPtoI2P(I2PSocketManager i2p, Socket socket, NamedDB info, NamedDB database) { + TCPtoI2P(I2PSocketManager i2p, Socket socket /*, NamedDB info, NamedDB database */) { this.sock = socket; - this.info = info; - this.database = database; + // this.info = info; + // this.database = database; this.socketManager = i2p; } @@ -110,6 +110,7 @@ public class TCPtoI2P implements Runnable { /** * TCP stream to I2P stream thread starter + * */ public void run() { String line, input; @@ -138,8 +139,8 @@ public class TCPtoI2P implements Runnable { InputStream Iin = I2P.getInputStream(); OutputStream Iout = I2P.getOutputStream(); // setup to cross the streams - TCPio conn_c = new TCPio(in, Iout, info, database); // app -> I2P - TCPio conn_a = new TCPio(Iin, out, info, database); // I2P -> app + TCPio conn_c = new TCPio(in, Iout /*, info, database */); // app -> I2P + TCPio conn_a = new TCPio(Iin, out /*, info, database */); // I2P -> app Thread t = new Thread(conn_c, "TCPioA"); Thread q = new Thread(conn_a, "TCPioB"); // Fire! @@ -167,7 +168,8 @@ public class TCPtoI2P implements Runnable { } catch(Exception e) { Emsg("ERROR " + e.toString(), out); } - } catch(IOException ioe) { + } catch(Exception e) { + // bail on anything else } try { // System.out.println("TCPtoI2P: Close I2P"); @@ -181,6 +183,5 @@ public class TCPtoI2P implements Runnable { } catch(Exception e) { } // System.out.println("TCPtoI2P: Done."); - } } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index d1e87948b1..eeea3faa97 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 7; + public final static long BUILD = 8; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 884663d077d2608ee6a0f854cfa4d81efa6c08dc Mon Sep 17 00:00:00 2001 From: sponge Date: Sat, 4 Apr 2009 19:48:27 +0000 Subject: [PATCH 09/27] Forgot the history.txt entry, oops!! --- history.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/history.txt b/history.txt index acc8a79b0f..1b09ebb63c 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,7 @@ +2009-04-04 sponge + * Hopeful fixups to the infamous orpahned tunnel problem. + * BOB now 0.0.5 + 2009-04-04 zzz * NTCP: Don't bid on messages too big to handle From 495558a949b47931516b9ab2cda542cc2aa22b71 Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Mon, 6 Apr 2009 17:53:32 +0000 Subject: [PATCH 10/27] New application for I2P: desktopgui. Should eventually replace systray, and have more functionality. To use the application, you need to (by default) add the following to clients.config : # desktopgui clientApp.6.args= clientApp.6.delay=5 clientApp.6.main=desktopgui.Main clientApp.6.name=desktopgui clientApp.6.startOnLoad=true --- apps/desktopgui/LICENSE | 15 + apps/desktopgui/build.xml | 69 ++ .../desktopgui/resources/howto/howto.html | 261 ++++++++ .../desktopgui/resources/logo/logo.jpg | Bin 0 -> 1105 bytes apps/desktopgui/lib/appframework.jar | Bin 0 -> 264341 bytes apps/desktopgui/lib/swing-worker.jar | Bin 0 -> 11103 bytes apps/desktopgui/manifest.mf | 3 + apps/desktopgui/nbproject/build-impl.xml | 629 ++++++++++++++++++ apps/desktopgui/nbproject/genfiles.properties | 8 + apps/desktopgui/nbproject/project.properties | 68 ++ apps/desktopgui/nbproject/project.xml | 19 + .../org.jdesktop.application.Application | 1 + apps/desktopgui/src/desktopgui/Main.java | 109 +++ .../src/desktopgui/resources/Main.properties | 11 + apps/desktopgui/src/gui/SpeedSelector.form | 160 +++++ apps/desktopgui/src/gui/SpeedSelector.java | 176 +++++ apps/desktopgui/src/gui/SpeedSelector2.form | 116 ++++ apps/desktopgui/src/gui/SpeedSelector2.java | 174 +++++ apps/desktopgui/src/gui/SpeedSelector3.form | 223 +++++++ apps/desktopgui/src/gui/SpeedSelector3.java | 286 ++++++++ .../src/gui/SpeedSelectorConstants.java | 25 + apps/desktopgui/src/gui/Tray.java | 138 ++++ .../gui/resources/SpeedSelector.properties | 7 + .../gui/resources/SpeedSelector2.properties | 6 + .../gui/resources/SpeedSelector3.properties | 16 + .../src/persistence/PropertyManager.java | 72 ++ apps/desktopgui/src/router/RouterHandler.java | 38 ++ apps/desktopgui/src/router/RouterHelper.java | 13 + .../router/configuration/SpeedHandler.java | 34 + .../src/router/configuration/SpeedHelper.java | 28 + apps/desktopgui/src/util/IntegerVerifier.java | 32 + build.xml | 9 + installer/resources/wrapper.config | 4 + 33 files changed, 2750 insertions(+) create mode 100644 apps/desktopgui/LICENSE create mode 100644 apps/desktopgui/build.xml create mode 100644 apps/desktopgui/desktopgui/resources/howto/howto.html create mode 100644 apps/desktopgui/desktopgui/resources/logo/logo.jpg create mode 100644 apps/desktopgui/lib/appframework.jar create mode 100644 apps/desktopgui/lib/swing-worker.jar create mode 100644 apps/desktopgui/manifest.mf create mode 100644 apps/desktopgui/nbproject/build-impl.xml create mode 100644 apps/desktopgui/nbproject/genfiles.properties create mode 100644 apps/desktopgui/nbproject/project.properties create mode 100644 apps/desktopgui/nbproject/project.xml create mode 100644 apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application create mode 100644 apps/desktopgui/src/desktopgui/Main.java create mode 100644 apps/desktopgui/src/desktopgui/resources/Main.properties create mode 100644 apps/desktopgui/src/gui/SpeedSelector.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector.java create mode 100644 apps/desktopgui/src/gui/SpeedSelector2.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector2.java create mode 100644 apps/desktopgui/src/gui/SpeedSelector3.form create mode 100644 apps/desktopgui/src/gui/SpeedSelector3.java create mode 100644 apps/desktopgui/src/gui/SpeedSelectorConstants.java create mode 100644 apps/desktopgui/src/gui/Tray.java create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector.properties create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector2.properties create mode 100644 apps/desktopgui/src/gui/resources/SpeedSelector3.properties create mode 100644 apps/desktopgui/src/persistence/PropertyManager.java create mode 100644 apps/desktopgui/src/router/RouterHandler.java create mode 100644 apps/desktopgui/src/router/RouterHelper.java create mode 100644 apps/desktopgui/src/router/configuration/SpeedHandler.java create mode 100644 apps/desktopgui/src/router/configuration/SpeedHelper.java create mode 100644 apps/desktopgui/src/util/IntegerVerifier.java diff --git a/apps/desktopgui/LICENSE b/apps/desktopgui/LICENSE new file mode 100644 index 0000000000..61febe9011 --- /dev/null +++ b/apps/desktopgui/LICENSE @@ -0,0 +1,15 @@ +Desktop GUI: provides a simple GUI for I2P. +Copyright (C) 2009 Mathias De Maré + +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; only version 2 of the License. + +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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. \ No newline at end of file diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml new file mode 100644 index 0000000000..c7ba1be112 --- /dev/null +++ b/apps/desktopgui/build.xml @@ -0,0 +1,69 @@ + + + + + + Builds, tests, and runs the project desktopgui. + + + diff --git a/apps/desktopgui/desktopgui/resources/howto/howto.html b/apps/desktopgui/desktopgui/resources/howto/howto.html new file mode 100644 index 0000000000..ea1b025c7b --- /dev/null +++ b/apps/desktopgui/desktopgui/resources/howto/howto.html @@ -0,0 +1,261 @@ + + + Small Guide to I2P + + +

Small Guide to I2P

+ +

So, what's this all about?

+ +

I2P builds up a new net inside the usual internet, connecting nodes together + via encrypted connections. + It is a JAVA prgram with its most used part (the encryption of the data) written + in handoptimized assembler code. + It will use your bandwith, your RAM and your CPU. It will use them all up if you + do not limit it. + I2P will route unknown traffic through your node, even stuff you dislike. + As that data is encrypted, nobody knows whats data went to or drom your node. +

+ +

+ First, ALWAYS use the latest stable release. + Development releases are called "mtn version" and are marked with a -, e.g. + 0.6.5-1. Those are usually useable by all but could do harm to your I2P + experience. + You can get the latest MTN builds from my eepsite echelon.i2p, but always + remember: I built them, you need to trust me not to changed the code! + After you get the right "i2pupdate.zip" file, put that file into the I2P + directory and hit restart on the router console http://127.0.0.1:7657. + Do NOT deflate the zip file! +

+ +

+ I2P is very dynamic - after startup it tries to get known to other I2P routers + and measures their speed - you need to wait some 10-120 minutes until your + I2P router knows enough other ones to obtain full power of I2P. +

+ +

Filesharing

+ +

+ I2P is able to do anonymous filesharing. + But as there are NO gateways between real net and I2P, you can only share/ + download torrents from within I2P. Look e.g. postman.i2p or planet.i2p. + You CANNOT use azureus, utorrent or any other usual client. + You cannot download anonymous torrents from mininova, piratebay or else. + You need to use I2P internal torrents and I2P aware programs like + I2Psnark (builtin, suitable for 1-20 torrents) + I2PRufus (external, python, high CPU load, suitable >20 torrents) http://echelon.i2p/i2prufus + I2P-BT (external, python) + I2PsnarkXL (mod of I2Psnark made by fwd) +

+ +

+ There are also gnutella and edonkey clients: + i2phex for gnutella (http://echelon.i2p/i2phex) + imule for edonkey (http://echelon.i2p/imule) +

+ +

+ Remember, as I2P uses other routers to route your traffic via 1-6 other PCs, + your transferrates in P2P are slower than in usual internet. + But you are anonymous, no one can easily (within 2 months-2 years) get your IP! + torrents inside of I2P reaches up to 50 kb/sec, usual are 10-20 kb/sec per torrent + i2phex reaches up to 20 kb/sec, usually 5-10 kb/sec + imule in times reaches 10 kb/sec, usually 5-10 kb/sec +

+ +

+ In I2PHex and imule you can just tell "share file or directory", in torrent + you need to create a .torrent file and upload that (and ONLY that small .torrent) + file to the trackers like tracker.postman.i2p/ +

+ +

+ I2P is a smaller net (1000 users) which grows slowly. As of which amount of shared + data will slowly rise. +

+ +

+ I2P is anonymous and it does not censor - there is no administrator. + There IS unwanted stuff like kiddyporn, nazism, or else. + If you dislike all this, do not use I2P. + There is NO way to prohibite this stuff to appear in a anonymous net like I2P. + (as that stuff is available shows the anonymity and transfer function of I2P + is working well enough) + You can delete the destinations in question from your local hosts.txt file (or + deface them) which will partly prevent you to reach those bad sies by accident. +

+ +

Internet (the websites)

+ +

+ Only one outproxy (gateway I2P - webpages in usual Internet) is working. + It is NOT official from I2P, I2P does work without. + If that outproxy is slow, offline, gone,.. I2P still works on and cannot do + anything to change that failure. + That outproxy translates usual internet webpages into the I2P net and you can + reach them via your Router. + The best way for usual webpages is TOR, not that outproxy. + Remember: the owner of the outproxy got ALL traffic from all I2P users + visiting Internet pages and will risk that into the police! +

+ +

+ This proxy is false.i2p. In newer I2P routers it is enabled, but not in + older ones. Go to http://127.0.0.1:7657/i2ptunnel/index.jsp tunnels page and + click on the eepProxy tunnel. Change the entry for the "Outproxies" to false.i2p + and save. On the tunnels page, stop the epproxy tunnel and start it again, + now your router will use the false.i2p outproxy. +

+ +

+ No other (known) gateways are setup and running. No one we know will run + the gateway for torrent or any other P2P data (and risk his life). +

+ +

Bandwidth

+ +

http://127.0.0.1:7657/config.jsp

+ +

Setup your bandwith wisely. Know your linespeed!

+ +

+ E.g. most common terms are: + 1Mbit = roughly 100 kbyte/sec + 10 MBit = roughly 1100 kbyte/sec + 512 kbit = roughly 50 kbyte/sec + or in germany: + 16000er = roughly 1500 kbyte/sec + 6000er = roughly 600 kbyte/sec + 1000er = roughly 100 kb/sec +

+ +

+ Set your bandwith limits to 10% under your line speed and burst rate to + your line speed. + Set the bandwith share percentage to: + >80% if lowest bandwith setting is >50k + >50% if lowest bandwith setting is >30k + >20% if lowest bandwith setting is >16k +

+ +

There is no shared bandwith under 16k.

+ +

+ Limit your participating tunnels (shared bandwith) on: + http://127.0.0.1:7657/configadvanced.jsp + with the line: + router.maxParticipatingTunnels=500 +

+ +

+ 2000 is for roughly 600 kb/sec - very high value with high CPU load + 1000 is for roughly 300 kb/sec + 600 is a good value for 150-200kb/sec + 300 is roughly 90 kb/sec + 150 roughly 50 kb/sec + Remember: even failed tunnel requests will result in a part tunnel on the hops in between! + Those said, there are far more part tunnels unused than used in the live net under load, which + results in slower bandwith per tunnel in the end. + It is wise to first limit the bandwith and afterwards the part tunnels, e.g. set some more part + tunnels and let I2P reach the bandwith limit instead of the part tunnels limit! +

+ +

What is shared bandwidth?

+ +

+ I2P transports your date from the client to the server through 1-6 hops + (other I2P routers). Each of this hops sees the data from you as "participating + tunnel" - which is the shared bandwith of them. + With this in mind, I2P needs some amount of this shared bandwith at some + amount of routers. + Share as much as you are able of - others will thank you! +

+ +

+ With the "share percentage" set like above, you will obtain enough speed for + your own traffic and obtain some participating tunnels (if >16kb/sec) with some + noise traffic to hide your traffic in the stream. +

+ +

+ With release 0.6.5 there is some method to prefer your own traffic ahead + of shared traffic which will result in better experience to you! +

+ +

Addressbook

+ +

+ I2P uses a local addressbook to link short DNS names with the internal used 512bit + hashes (which are destination IDs). + Those links are saved insside the hosts.txt and userhosts.txt files in the i2p + directory. + Hosts which are not in those files cannot be reached via the short DNS names + and a error message with "jumper links" will appear. Those links will ask + some hosts services and forward to the correct site (if the site is known to them). + Those hosts services just made a form to add new "hosts" and those results public + available. + You can subscribe to those hosts service and let your hosts.txt file be updated + automatic. Go to http://127.0.0.1:7657/susidns/subscriptions.jsp SusiDNS + and enter the hosts services into the textbox (and save afterwards): + http://www.i2p2.i2p/hosts.txt + http://stats.i2p/cgi-bin/newhosts.txt + http://tino.i2p/hosts.txt + http://i2host.i2p/cgi-bin/i2hostag + You can add one of them, two or all. + SusiDNS will now ask those hosts for new entries to the hosts.txt and those + will be added to your hosts.txt. The userhosts.txt will ONLY be updated by + yourself (the user) and not be published into the net! + Remember, names once set could not be changed! If you loose your key (destination + ID) to your eepsite, service,..., there is no way to change the linking + between the DNS name and the (lost) destination ID automatic! Only manual by each + user itself - great topic to discuss of need to renew DNS hostnames. + As this subscription will not update old entries, you can "deface" unwanted + eepsites with a false key and if you hit the bad name in browser by accident, + you will not see the bad stuff! +

+ +

Out of Memory errors

+ +

+ If your router hits the Out of Memory error - check your logs! + Usual point for OOM are to much torrents in i2psnark - i2psnark is a real + memory hogg and >10 torrents it requiers hell a lot of memory! +

+ +

+ Maybe it is possible for you to increase the wrapper memory config. + This ONLY works if you start the I2P service restartable with console + (on Windows). + In I2P directory edit the wrapper.config file and change the values: + wrapper.java.maxmemory=256 (or even to 512, IF possible) + Afterwards shutdown I2P complete (the service) and restart it. +

+ +

Blocklists

+ +

+ Sometimes attackers trying to flood the I2P net and try to do some harm. + And some folks setting localnet IPs as their internet reachable address. + To prevent those bad router to harm the local router, I2P implemented + a local blocklist system. It is NOT integrated automatic as it could + really harm your I2P experience if setup the wrong way. + The way to enable blocklists is: + Get the file http://zzz.i2p/files/blocklist.txt and copy this file into the + I2P directory. + On http://127.0.0.1:7657/configadvanced.jsp set the option + router.blocklist.enable=true - click on Apply and restart the router + with the restart link left on router console. + The blockfile.txt file follows a special order, you´ll get it if you read it. + The first entry is the reason to be shown on http://127.0.0.1:7657/profiles.jsp + at the bottom in the shitlist section. + The second entry is the IP or the dest ID of a router. + Right now there are only private subnets in the blocklist AND one chinese router + which floods the floodfill DB while restarting every few minutes with a different + router ID and far to less bandwith for being a floodfill router. +

+ +

(By echelon -- echelon.i2p )

+ + diff --git a/apps/desktopgui/desktopgui/resources/logo/logo.jpg b/apps/desktopgui/desktopgui/resources/logo/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1b5ccfc8728618281387d7a4b8ed11dedb8f39e GIT binary patch literal 1105 zcmex=>Qk2%s@GP0R~1ECMHHk7A|Ip0#;@gHg+ZsMotDHVG&VD zK`|-i@QBFC(`H=0b_1wEk`ZVavQZ4I42(?7EI=t`L12h5GP5u-!z^QD5@Z%qWMDB2 z6n0cnE}Z!P76T7Z5-2LjV9(%jJ@4S+OL6n{+*w zeZ`}!kPTbTpL6Z#pQn*i<5MhNG-;+uKp1~?`89K)X0ZE^o@$*VFd^iK9DY*3rIB~;yC zQulT2#uu~87uJ@(I9?{5Z6<&6N_Op1se9TY2XkhxHr}vQZ%N<|5r5Ik2VJGN8>*=s zH0lrb^=W7GnW$Iz#rX6*Q7f6froVH)_Z_}3d+ScYwD2@*PO0q+c70rvbRaVBrC)gB z;-cwk7k|vUD6;xNMXe7@HrGEnsems98OFoKGL^?&^S#olr(Bq(!qk{wo0+*#!CI|y*HVjfcE8rFu+OmG@IB`Id#$_4rM4DV ztu}q^zcekfY*wasL+z0Z8x~A*o0uxOB=*dfZ098Y&ZJuj$E4YE-22i=y_vyc(Qn4RrJhN^RI->RA|#S$y;yfo-e~zdGuOr z=aJ8A(@nm`=6&6LHZk?aBi);Cw7XXQcp$Ut6vK_n2A7%M{#X1&2e_IZ1y>_zyj!1jd-t24JN&<~F-PI<}n)Wp-$EiplWwG1Gv|YPZ zwsC&&;4DhI^H($VoUh#acA3ZL;@0u=bG+$!p7Z3xTcg@v-}s&De0|hUR`{sBI#lKJ zslIBW)#1B;cQ3uj$-^A_<+;;~A1Uhs)?9LxX}-3-Td_6On?r;%I4`hxqu~iidCyt?OF9SKI`Y} G|2F|xQl?`7 literal 0 HcmV?d00001 diff --git a/apps/desktopgui/lib/appframework.jar b/apps/desktopgui/lib/appframework.jar new file mode 100644 index 0000000000000000000000000000000000000000..0b8ff0145f010a8e1e9bfe065704cb4ad675f761 GIT binary patch literal 264341 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

b3C#0adb5Et>9)K}}&1LqoU{fLLrP^m)I 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

+&eO1NyNTy);90KwDiffI8) z--!nry4QCSfm7-v12m<`F{v;gZc4#*={w~C>P!86r!sK*JE_1K;0z3$DrZn33=A=b z8u{*fGe|WC#u*$qL!9bB>`sgEc`jog;j;tMrx zYjg!Dr>B+tpS ztfON$8eijAEZMNQW^&E8miF;`Ybd{F@9sS{yWlD8s=3fGYdULo0UmYORtAWk-tIfA zbih{o&YD0Y(*+2Yt_d{aobQ|*5Ce{7vYxPHGNve4%YZ*Eb))ZW@`)cMIj)$x93?2o z7cH+aa0oP}6UTSaY!T_xu~9gx6leOP=RoGMNBz8GR&$G}wY@>pfxanCDUB9Ek1*)% z?r@8wWRn&j{zy9Zep~xFxLwU41uXC)%7~nXz31c?(QYlQ+SF@R8ymLA1tflNpUyCgCd9J3%VYx^2OZnZI)_1Ilw zx+F4<@j+D84{Dk@5pfy^g@8(}PV0)+wjDd+oqBs(ww(+5UD)4(N6J%3#0`L^1+Bdh z@hz(@jiu&5`6|Xa?Y3cED_$GgFvr2CiaMDH@#u{9V?%d4IX5xuxVM_%SXzZ=d>_q` z@^MDaXmvT!!Vt|3iJAyTSx})gvH>%Mj>SdIahfqp(<6>{ZeFymd1dp7xb3hRyC$OF z-Dv&BC=eVhY+QldUE34+vSDY(?hDnmpjk4-RK^XDOij^n4?>V2RuoIK7bBbDzk@ok zCPHe6g*4$*Bc@j@g>{&rsO*}wsuPYZ>|ww|OQ=Y1a9*g|z*B7rnQ=2Smirs?ic7WK z=Td#nyoN0PGu-L1f^y7rZ)8Wsob#MYY`F+#WxmqU zH$lPMYsb>CD6p|G=xi?C&>6xFhUXsTTprq0TWNUf|=D=X6EMph(NpH~$PFAlS;rgbrXACI~hO0Z;t zxoR!22Wb#V#Mkgz%TfUsY}d2(8)tDSe|jhdm!J$>26N9-W1XU&ZK z@2B``@2Ct$<(GPJp3+bqzek2v$5oTIo>0BRP}C*T?;#m+Kx)D~LrfM`FMU)-npElu zMSmQoaA zn&CYr$C=>rM^LIw9F&;{Wfq}KDQEU);Wxx#0Sq#}Xmp(4i|N0}KO>3!982Ia7}<{_ z{5y(}?D5r2m;fImZt__uIa|01ulTwg<@_UGuZ{)U$P(itMZawf>H zotcCf^g>NV-59Ehp-XRq)ij*5B{df<+9z}#mA^l)AvGV3+In>P#&QO)ZE<7ijd^McF-Kw9K74 ziXBU_<0*E63~?r!;?i5JYjKNnii@<;7Iy;0EugrC6t{@t7SkFSOJVDD*CST7Dxp(U z19Vh4=^6=l$pNWswlkbs#x~FiW*P?}#aPNyb|{vi!nz!-loj}nUV|HrRWj6BjohP| z${=LJ${M-rP>f+OF5iy=I}836>Cf%NTO`R14Nl)^0AcIq@V~@$2L(+sUyVp!l&TyJ1Iea?~jZFgG^RHQhh_by-+LAh1 zRdLrnG5HxeezI=nXdK!xVH{fG$R3vU?C}QGU*13vQWH@22M1*S5!t9ARr{W|;Q>0K z4E&+&kI+Lb;#k2c660T87PKH+k}o z%mJ~EFS-w=)a%kmVvkb5rbh%m!5W|-*zkU-;sJaZK-Z-Ef$*Qd$Szw0iJfa`_qBv# zxDGAFcQVm1D#K}?Lv4D73_)7vJ@hSgp^J>v9c* zY>u(h!d6bTrKDIcrvd@%(E&b{f>=_gACk8ol`~8W7fhn9?5;Dj&9suaudo%KW0ii5 z;ru!)^m!z6FQ9qw9&8Y7MLD`tp>9%UC2~wA0BY&8HKG@M)*)#*B3mDnnb|7M;+3;# z`TNYc-(W0mhy@31JIe_3#&F-kdKk%lC+UWAZ;J}|K8HT)#+MQ^Z(^6~-&hJNigavf z={&cKcJBgp$3g6ja@B4as@e@hjn<65)aV7((=k%33F-)urh2DiWTZY3@N&F~>(yAd z{=&As+td_T;FDmTFl=Rh6x>0Dj*Bkey1SDjI5q4$YE9 z&AZp{+qQFgTkEdvhJ&Y?1JeVftHfd=e$Nz{8=$vBZd|nnc!UKv9o56kunW3qRzz$y zEn*CNuG?a|v%@Pig5`N5T7rM&ed-u;n&Q{pR#lgil@69HTC!o~+GdP7`5{6OgPx z^9+JVc_@$4%&m{($6kKpT2bCn*{e@PJCbS_>4UTv<= z8gzFc$B}AY)uM17N4H=Gi#$%W(^aTj#j)4gf;ocvb<|Kv29NhzWES=%F)#r=PBf5` zD%Pv+Ct8|-6m^`=8*p_f_sI*ZfhMzL&cf=fW|soCD_d(m@&Z6j|E80%4~sEZV?jDA$rH{a<$%P(NMjs$~Q!NvzgHR zjgzMm)z>YM**m3O*FKEaMJJuKX62Gac6<4Cn-;CvxNJSnDf_%d-@x`RDoOWEp=7 zwr+LnU{2#Dj4^-;&~Q+kL*gD4VnQf5tKn)fp#9L)qfI(ZUn`a&Q8obl?})_Feearq z$lK^U*?-dmXd?gZ~Yek zAYq#yOzW`MP@%yyVMvq%!-rzvI37FU^Ph!}o$|G34EELN;BzQ!zSU)}URSgI9Dx ziTVs#_qKGva7PjwS@s#uWl77ny-2>%Y$)EuhHN3+gBz-qtH@P*<;dvnZuY1!4$+OqxPnysynLN%=ywQj=~({^7@4lpI%5Wpj& z$vG~m(!(&&Zibrn-Fs_V+H2CEqjI;0Pg1G3<&JO3Yxws!9AXxX-Ru@MIM<%BNoDX6CtR>eZv=Jl-|>Q%o7 zv3w*YaHLm6;W5+4hPuwBuDsE}XO3&ml$6dSPbqU?>z(;D?^w)mPjFZ=Rb1#~}>D}_q}n?Z z8|@ZZ>TQ$N-gepOwaMw;IdZnwF6Vmq;_&v!KJR?F)YDKfa4^E29Y?v0BOQ-W+LC;qU7)hvxU zvdYu*evT4oxdhq0ul($(mq0f3ZxRI*-STDw8_;#qk~Mj>*uEr{PA%mE0X9OWt2NE9!loFY`+oIS`M8Mz|TS5pIUd!h{C7A2X;~tSaHB6P^|DGY ztM#%bAx(O3PRLqcPV!})FVIaHo1C5zlMFf1oqnix*ogd!(nu9)r>oY+0;k6zTD}!P z)j2ioc+&oAOfpP)WqTE_0EmI_9GLpH6k@=~D@6fr5z_B&=cFfGq<}SbmQ|({%$Pv! z(e-JgFPj3K6uIFEsO6^_HFw221+d$SH-& zGOGw(Lr$mk^0E%#a?z!Xaz><~_8a@8_56J;yWj>E6Va;%Saa)z;r!(moWZekRnLkn z1dGw7O^x0-PdWG0gQ{<_7Dm&jp%}~N>vksRovQxOq5~4fZtK5oA5kIO_ikuAueGUd z*RD3khIznHDqa4JUX{N}vlLQa%kokNp&SySSh7lNsk7TUGC^+B;3P+TBb?Gm?ldux zMKHrJU6k6>{WxCxF}$PoJbXH&myfM1yGGNBdWBYBI+X!q=|X2tn+D-O+_;d(13hM0 zfD=xVI7brDh10B-w_;{liiManHTF$Xjy31ErhkdtIO>oq!9%>JR2x^9bGN6y3EUF~u8dqp~9Kane9uTmahEgY5e!BG@TnQ5BPWSVe9 z`X7@FO1E@r( zjL%I)zgDZmBCktphvREQ?R%RbmGo{~U(eYtvES1A-_iQt)A~QkX#5b(zz@+}{12_d z|4dq$3-skb6Y^jB{<^OM zcf8!{#nnp;StYrR{YKKkeuLyrA|co7y)+>==sl@!Zv%ZQ_nitth&uf=Po=MM1V%cJ z{*p?nu?8%1r^}kfiLjzg5Ry5C7AYu*Pd0B#5=na4iK1Vy!b$FQZKOfLf;} z>5O*9l%jfHm*j#3vDO)2G!Y(Aw6iqZW;eaUC zI@3$h_diZA$Ls4%y?`&)IosCy_}(!Gxc(oURt#Jt@^ahceW>-)}*x4Z+U0ZIosKx zE!ycjZN76(R}9a0U`QZnh?2q?Ov87OpXKw;dS{$h){>R8c59m#q2-}nD?qZ0L7zrT zZo4R#!=hrb_wp&JGzwZ9}3@wAWS`#M`YmbA3?l$m{m4*h z-EldiB^Ss4q6HW3YjSDpo{m-`Qz%JLy>4IoUS>5^>Rr?hYt1Orvt|cH@@MnV-0sFY zr7J)s=*5i07RNh7<`6oTp|ElKBSy8yKd*H_h=c z#S#y=yG!?YY3DwO$5vvX7NLYq!#GN`l(IIpW$f68{~wa4**s=wS7v|7@bpcdlCxVd z8Yg+WbtZYP+I-?{V0GtCP1O&x1Kl0zQw(N$Hvl!7($qGimx(inqjoE0Nx^7g%r6rk z{lci%cTuuw$s!k1qlwuyGkTpk(-!35A^oefoE`xx9;Tm{?q1ox7!#fy9UAm7^6Mb| z6_qVJjrl*=GnN^M{4hjwL}o1V(*XTxxtX%a*DCWhoTSM2!TMdrCoqxNwUJ+xH50`m zdJt3T;N@c->WDnIh^alyZf*%WwQ1_S53G@NmOgMh(pij)mtHYb(>V+)BK-mj0ce}G zm_KFyhQ`P-8?rSv75KIw_slQChl(t;qN2W6<+(sUAn(&qD?GhlpKg>71?Vzg?K|fM zPP?;P4fL-9Ynu`{dmLg~7d6AjGU2(v>2L@)vR4iUa=&~!hxKH8@XtMBrTET-g(U)_49Xkk2?Y_1)Uu59$!zBp(iN2Yh9K+xGnd zzS=(;$j9ZLzAWj&-tD}H66ApZD}jf6=lZ~TuR}k3C1$P0 zvPqAWnhtev)mU?0TYJrd6Kc+DxrqMDLy#Eh1SH$n8aOvN?+@fD=K}z{^e-C90(Kvm zd*FP~xzTrS3Y-r+HwVssE?5?PDR6F)#{=vUn8RD0j|5mHd_5h`x+u&Kuv>T$E53q5 zpE=$$M?~O!)VVFdY5F$<3>{c-pOX7%{n*YLdu%k;jAilMu8ZSsa=Gt(EO72{?)06z z0_Se$rEoQT=$}9MpmQqVr(jJj6x5rkAhk55_0u-Zv~#cODMVS~?gwhn*vEoN`dmA^@Z}GTlDOzWXM`jxw$H zb{O<1FS<(tw5fA*n}&NSh=_u>y&0DAxq}r&0wS0*xD{uhMS=575JSAU5FQ$V_Zjp$ z+V38j^-FpSi`D4sPVGBVj#^b4>&8{x zDg~x3N^_Qbkr`x{zv%*rhzevA(F{S!0on)}C#*7Z6YI(@|Hw+yI?#VjE7z=9xqjJ_ zwarV{vmGO3Kq1-)UJRcvXzf5ptaiex9#X#n!A;yCrErQMWw1LBZS9Q~luA%R#EjH1 z6s8@Qud!4F-b5s*%thaYuKr2OnwJ{O>pT~=y{a#(9ADPfnL(qOF}8J4l~wKXk{0+| zphfgD%jC#XJiwr|!Fh3`@sjn+HZ5DX3?@uu(sB&T8Ik`cn{m5lGgKubU~I#4AZ%LZ zrV!SWQ>=nc<9yxUyW1dI%5*3M@(O%&q&v63dce+$3~jn@@clQ>NE(0IMPWqdgf}#2 z#r7P^EXM^-2-mXyTHcrebS-jwL^Vjcz=n8ADZ0sBEgP>gchGcm2cJbAbe(yA2fwRT znx+F=HFr>La|hctcW`WT2k|y{5Px%r6fk!XTyuvOFn4fW?z1QnT$h?|;q6aI(PG^1 zG@-yTL{tlA{}xPzQ;CPA^nf%>N~uMLTCbt~#D}4n#06L~mXokTe^Y~Hej!B1iS-Tj zsmeprA4A3zhR6@gK>bn`{W3_uV1k@YqY}CyRE&Cc^~0w4VaCpQICjX?HIlkUJuEd= z@99?BYLu;n08cnNti&d|nt;n=z{u;d*UU&@P_(H1jCt>?OG!S?n1CjkEKw` zQmDZ6aR7}U%2F7`LKp|VH5qbi1_ah@rg=WT7#Cqoe4_kLK1NQVgSO3ga@RKB6}I_i z?mE$H-gukUQ?iB=%5OK4x?Z;%8Q~&~kykrGk#^r5PLS%M>jX8uN$pN03N(CR9~xdo z!v~=lJ{XlZD8;auQAIk;W_9c{tH&8eQi#+rX$^Ok@2i`FDXhBu)HrV@YS+DC>S)3B z`AMfrjZ$A>(sSvBDjqb{Z)x;YAYb34i>zLsnyWpe@@0ga@>DmVJ~dTc1x!;xeB6lO zRjfRUzjVv3+4vm%9Lz=f*(m*-c?6Q5j|$QWg#1~6@E*=USn(7}(K*5if`JTFSPEQV zODSx~Z}eJ50~)$O|3Z%q*?~Kqq4=y|s$sTWbSMVqm)cI}9~6x&pgXk7Pz*FfF_4xG z>@pOUy&8&Z7>a8dit8APcQ6$1WGLRnP`sNqK3uSkgYw(>5!!gDcpC?qHV&eVgSxb_ zs#k6N3~hXXHhz{i;{U;UkTx>;fEx!miFKYCoZFdlvm;G;#9PG0o$Xa8%o-`> zUS0Y`4Up_Zw@7K}?2-dAe2`~f%Yo80t@^2Skf#p~rGrY2$n=9UgD7VAoAqQ2bkOP@ zW;A>4PB5ww&KGImmuTRZrPBEd!u{uDoby$ggmv>wtefBBe1kYw-=dk16dcenQeAd^ zyvBgSi3$64LAd^Bq}TrtGoa>tX%0`9{B1BeGOi<;N!7|2XYRJyW6-d`{!!v?e@VJ2 z>E{lRf$l&V=2po>7vB-?V2*(rmqa=;;(iDrzx zhIZH56XW^7ok|VUq|}`*L*3(Llsk*^K9`xX^V!$Bv*)IJHgr*LN5Z-CIiG`4Gebrn<`+g2!-w(RJfZyf2g09F6Wq z`uhYYu1%_HLE6&k>6T7Bp?;X2dk@LP1-WgmPt6*BL}riRuV+Z)o=XdN(ZchXf_A20 zw~TT3$TatSTKZ(cmd5j2dNn}nDe!?bh_DLe^M;SOLzTtTE;WV?ze5tkM%*2Ko2MQt zh7I3-?Fjzb=Xmvlz0~}3j+p3m*>l@KOZP*x@kZMBVX1a+mRk1~nd06mjqXQG#h(u= z)+M6Qvaa^}ikokhWbO6+K73nP+gpr(jM@S<7{>+fJyPKwV2(aX>GzsazYvzXi*qtg zsq^c`-iSxj8s7ao*F_vHA+$ZS^kIdVN)EZ5>9$c$#kK1Nj5ATo`(s!Q>N32Ddm&wt$IHM08|oZ5%RTOROGK z#fJ4q_9e=ExtC>bDp2Nj%50^~9hA8< zQs#GY4P@Inox66Y8faOgf5x%NV{!@(O*HD^sd)%RtRpPu`(%Xs9~N^8W2B4y_($cm zEsCt4-ee0ygT0vmlqkdRmueo)bSU%`^(aHs!?RpD@cOIy197n=W0%N;*rjq@>}_Ff zU#1=;P2n6cRQxY8!_d8!yY|PJ#g>eK##T+?9qp*>t=eipB~*x9moa3I*x6Q zwioQt9n^9sJ-Uk?eVkelJiMV=S_{_lDQdZoTJEQo&rl1JjW;k}XBQl=hp7W6Vc0)U zk3B*iNH)Vd?&M9Oh6au`0Q|7*NCWW0veN?a!_t-k;D_ZLD8_S9hgQn*+(|}@Hy=DJ zI9A&uY<|q$f?um1eH1O;C)uuFkU_EM*s{;c(AXDc6whN~UzXa~SEM2KyiAR~fOhZm zX!pLz@%9?Ny}F`IyYjZJ7bEG<!J=NY@T_ARO1hh@(bX&h>o!DDx^ zt#)-G49?FWu!B&KR`uWpjH>i*J7Xh=4&qFPVD&JK_lC(ZZ@AQWBV?>MQjYUR$vm$H zRpMXDufi6o4ZA{ezkV3*it_O&xq`&MhVfu~Ix2+ce*LJo;@jx{OwHy!o8uhhC_4*?27ki~pIkmWbb73{r%4G|yof#-5e<*4luVFfGzI5@t zC5);5O`xEj`INc(*y{QtCTKu=Qsv7aABS5KH-cgW9$opW2(s3d9 zR%R12^K*5=LS8I5LNR0Q_=Jh3h>Tw2F|vB`*xPN#=Y>j3lM8B5EQqkxjj%S;@Ep~M ziVUmQ_*5ctuasz{%^|%K03(IW%E2SGHA$LBV8LO&angktA?+;1 z8iN&GPhHsWz31FV*ELo~-8SD$Ul^dnNGCV#ZrhG-wQ3n<74sJKp_Gh3#j5GqqN4hE zlX_Jc%iLzoXxjCq5qa8pVIv|6g1I_>D|^+X2n>m6nPrqd!uCeA{?0A9M+<8@WkqV? zzUhA6(#x)x@>74^;ps%#O^&CpZkIzkM|w502*P#ER$SSRGKvxXsJpR<_};d-w@N91 z1KqPqZ&{1=EJ(-#04&z+fdg+T1OOV5+`9O$1dJ}LN z%n=M}l43Nh_HZf91&H$-Ut6#RD64;pHuY)=k(EI;C(3}58 zfSzhpL}J!=g<`!0G6msN-(X*f%Agd-2g~<3xIr4g5P2i~D(}h2dl~NATxaMp?Y&F7S|VxbcS%J$D#_9qkoj~1bTcICH>xM2E8%8qCA(NSv{jZI@C;X z$}_S!($o;XI>re$mqq*W|7K7J*u}@@Nb|E~H;WshAm2l#A^;(>;i!uhl>jM$qC|qn z{!}Am8@m%9l!m??2rg3_rDJt3{^Ih6xkKSr=-*s2^lC3gRQ|epU^zKSE5?*^sZ|%4 zbxAd)$2ne7SGB&>`|2!ntV}9ntR|OX1U*$R)BMm|OOoNk2)A5j`Z5cjn7+&fprr$y z_h5S%u6M}e4DHgLJzHc^8!DnWg@fG>+S+@=c*a@Fn?lYB44p!G__!;y?N06iflv3L zd!#zEvrWUh2Rs2Fd2j$wtkNfSeG|wDLO{S^FCZ#3ZtKMRULXr)QJ~@r&V(%8teAFy z+w~;@$~epM!WSSDAx_vTP1ilh2n+XRbs%e`DZr)c+JFG0YaKc67#17N%3Xa0&(7X> zK$F0}KAZ=KK?CHh+fo`B%=8L43Cz zDCEl`+F6%D)1zl}=D-=QymxrY{khMUJRcSnA3bU7yBQGuQ&xSiV1R<@t|tv%4hBno zDHqO@Au`DqkU~xqWvb%Tftc(1bevw){ixn?=`dYp<76YrY1e36fYqt@uYShysFt?~ z*P{tpqUo0UvJ6<~3vm|zr_Y~$YUOA2x6>|p`ogk}8UY{ML54y(>miS;@;`Bq;oV0) z84nAf{PA3|1v# ziXFM?Ow=5?P6g@NIbr&l9JsI+jM{~(-n1Jg;T9^6%c?jYyg7L7fvDMyo{>|DEU7=4 ztv6Dw@_7nS8SXncrxQ(FN{>pCf9mS1jKlgF*)v&BWITP{Bdz+R2YSlr(C?QGn(C`3 z&_YLg?qE)!CKvCRKZMqlcD4H+{^aQKA3R9_}STN>`f|MYo%Ob$6g zU|1agD~{Pp>8~=@egTYyDTK5zacVdO_cC1&iZcR)jmK+1npp)1#o2~#H<1i_OXcM* z1Io<*5P^kh3C{nc@_K#XrKz&x)xAMa0>to`49&YTBGNAJ6E^{L%h;DHyHa{dCEW_! zTo#SOQ{_b!1K!G^vh>n_ky0rwn|>4dRoP9|H?#E8NCmGs^pZkGVh0|Pq=k)Io{h4F zzek4!cKkN(w@u{U^Hp1|oq62;QfOS$0FZJL~+PiN}oEMHnQNWxZq+2+gkBpku5zO*W2 zG#}6QWk-^sH+gm@v64DRFXxu2|JgDjDyLrd_;S9+Vc@q~>CnqL#?$ds=}f5SW1?R|4#|IN>jgGKVPj+*Z6pI zkn4PTMhiHSb7!1xQ8M}iW&6w&YCXKAy)I>|$XOqF4^)m}Yrcc1p6kpjrsoV%|2qEwj@v)z z*vJ!{c9D~XaVF- zeAF~B%05rSl>q#CzAx`qKRef}R~>xDzfWE5yx*4(s0$k!`ysipr*l1Qo4UK9ln=|z zzB;?PMQ&A(H*0)#dUKmb{1}jJL&VR{61hE$2-1`P^N-0L0g;5b+$DFbBORJ|r@S+b z2L`fIRt4Bh-WXv20GBM6_>919oRopvDWQ}F~ zUKHmnBc~0)LAS@2?q}sgL>3_MCQlNBj1k94IOp5l!TFT~+E#|&IeBc*6$7HuUpWJJ zwsbZd7aiTlA@70JUXXwCMtp=HBlE~_>s)r;p1l|6D0wc~jaxkvNQt2ll3C_17%6y_~A2fPt#TlLr(iMHE-++{GqqM?5 z-9x}@M`1+R{GH^|=1-&cxJG_1o5WMOy7$JyIj&#T*s@G3=4*9*YN+bP56jkWMHxF8 zhBj(H2YO={=qxIYH5^mO5&D5!PP=XB_*-fBN0`!&(%##cYT`3- z)rmcava~d{@~+&nM$LKgz%Y_1c1L7X!_yBuDm7d5+hG~aTT{c+lb#?wD&$Di)%iM_ zB;3a`3wpO5wE8%Akm^px=`K>;!>^wRm)gmsT9>Im+wsFPCVz|`&SY^l6qIc;5u??g zy2x*03s{X~8a*t6SV&fuWtI|w>BF^D+gLKGy5te5Qwyp7+7S)!K6XFsxdjJae}N^J zn{#3`XFB<~TcjfWJYMsCL*Ix(AE~l7n$bk3CziH;tB0#jmX}BDhrOoFEvZj6*eLVE zD8}25_XlFuKT9xjTPkDkI|tY_rjE%+)uF_c>_Q|~Hhq7Lv3MMPhNG~|o`gB}6eIOC z`}T`+7Is{_h%CPk^TkWBfObqGUSe0jj9vG4Dd7jC z{2`LMABQ8p5#kduM7ZR#lLQr5 z!<5*?(|f6mFiPk(z6wMtv5csTy3h9UZ=Ua}JWn{)NB`QrWf+EC;DF62m~8BZ5Q`k- zJ$e0#`q}KBT8nk;Bc!0!OX#Zj5w@_X##vp!<{zg@N)>@>D`_O@fdK%Mt=c`tm6<+6 z(ih;!S)9Y@v*WkAy=CjJR$nGKeGA6cJrWtV0i@`>mW#KxnqZDP$Z+p_m9~9U zUwdBx7ghH5f5?Gh5X1s=P%IQsRBUM!5JbR^b$}t1lD5UJwYvkmb;VX}*Y56)RdJBi1|72 zh%-=%LbI15K6_~epWTcUxg1IAUQ;1;H)DvVfE^AU>RSLQr0$qJD7F^D97F03vuV0e z%H5%Y)KWwWNdLiSSCn3tqGE-AO8<~g@2-ofRTTZ9EKuQkUCRE@5iN@&loijp&zqwA z+9{N~Y?%iFrz&)}IJvQi`EdGL3gWErA2*3==5SWX`w5W?)~?ayZ&|3G`|;Z zY&0}Oq!b$am4>;{C3Z1aLuF`KEJ^(O0YM3tB2q}>MJ=={kiuEPq85^<^Q%xZY(N6jIzDCJ?n`C`2tJZ2`$;K8JBJnILHC(#%v5778M8TDfo!YHJesM&Jm<9Ym_p z#vBjGGnMHf@=zth*-pfVj1O01#0sQv9R% zD73^sd(BDcLy{i|ed@7ZqTp?ppD2hm%qiErz|R26r6X8f6d)-l4TBA2#OvA_b~J;|1aX;gP0L zr@q$XqPgv;N*cdQf((FvyNFp$MI#_2O94tv+%#NBeTXGkpjx_s0i~hkr*?KJG!0iZ zuS#sf#W(=BP{2l1wAMcpoBXqlxrpy?@XR#CE@r8*>1k0(nJQ|>LU|)NDU%PNqULZ@ zp*4+zl@tgkhZ7@)hbAn@-Xw9U;qsJ(I9Sz_s-3kgQ-)wgiaT7;c0Jtn0*+W^R13uj zu?#NlJ=W&Ke?$7XbIE z7)j7Sx~8Ca4>@Fa^pcrAbHvW)Wa*VOycoMK`2i?Q4W?mnW(sw@ctr zZ<_?33b#w(fo>-SJJ`(`aERC*vf)7Z1BGP!i<0Q5=4O2*I{Y@AV-V=F zN;hEt6Esv&4!uRi@a64~fI&t1@YYyYG)9tO3~@whzJ2z;C(bCG*5j!i;V$<7>!y}- zN)Ew2W_#_Bf-YEy3Kjb~C>t`20ANioSVYHdz6IH_lm@p8_JwDGVj|&;F|%AaeOi~W zOw0ux(&FOd(&EUwKpBXkGBEe~#HA+7(jsH|X$BjSi1?&PaHjsJ7=-W(k&)C#-WCfO zB!T5Lw-~C$kG2n*!L0skVbcnCSpKg_PXBe<&q3#!pjGIygn0WQhJfow zT*1pRXeh84&56Y*=A?Ps8Dx1z_|uXT?3Dojk!K`&0vP9?6=b3@2t_jxK{g-}$%CsD z8~y4ag&ZLzYlJH=jy%HhtSC%lPzr{4zk*|(-OQ?sskK-P4d&gfB;wGBdx8*helSGz5&{Bl-#2de=0lDMw8l9k-eDs^#$6s2;o6NGW%_jXU!##}nMqC|wK)?l`audZUHoQHLbFkql9FUvzW|+AkG*g*33K(^)hK!#*Ip zDK<3;QI&nG9tJ3&_AnTr>Wq@K{ZMp94V(>7C^40FC#z$CqAEL;A8kX>#6vOKf>mg$ z3g3Zkx;C!i3S8u>8XFu1F0J>Vd(8`a)kAeO@?$TgJHK=O{Ns_Imy3F!)DCDw5^_tZ zE>*xOaWT$^VG;!7G;6_iuDa>a&DzzmM%FGmvg&AM%Gwnj%+EBMP!&F_ z)RiSyQ^bHVg!rKW&Lj%7#5d%MJLGYNep%@0r4T#x$9aXOEjhWOT6=$j7yMe1Z_6S? zfzUwl8;*MR*4{ZbN1GATNchCQ(OPWjl}E^Pay?Rs&Je*>fe0H;KJ=7z zsqy~njhO#UTq3lwlXK&MqDzg;Y#HHke%0rHCRUGozDLPSC8_EEE)I1Z6VVWJ@QTMAK-vgHC) z4TiC&gF+%9n6%UVh8}QfYloxY9H*wp2j^zy7Y#VQH8Gl4`*9u=3yE7R4JfTO`%-|905DFrKy zia!T&mU7mDnTNl{@x(Y5{=a01tKN^OZ8`qe>+AgYCp7vjLHKcW7O$B|mbYrn#DaEF zYNf%&Blts#u{hF#+&0}0v&#)!$dkvqQhi5kZBgR^>n(JA!0Mh#&s55 zkl7WfTyPh23F15vn=eKUW|3u#BGN&|k~xu!1gO1l*df zqQh!Btf2>MEx7qL?!gv(|+rUq>$gkbUzY-4qkR1_#T4gFTAcqYHy6^NDc^qPu#(L!Zk&~8(? z9|8bt0RV2I(@(?0DnXXgN1np-Rs!WPMbfFxV(Fcf2(LbgX$lu^63y4JYZ4TZ2ZmBv zka4Ror$-HPoeYM>k?Cow88W2bU~WYtoW`Y!JtuathHH=+L*mG;%RakHv(;&xF1bEgQ?IkF; z<_!~Wv_m~LE;U3j?yZnhc*m6g5+LV>A)fG~utsLBTS`vH|8~v^zJ5)A)7%8i_d%+H zJqF(o63CgNBw*p6f%5_NC`xtKoNyEd*02(iKsta!H#&5u1MJ$0K^|iJ(Sw~;cu4zC z30;D9PGOn+&o-D{)@N}fTf)Auz7qD8eUp&?5E1(hKa>Ur5(7g6 zBP>e%$K{lWc};I&g1!nuJj=5r*J2-I5S(`eb`o?*Q%OKqlH{?$*wOu$m|Zcym3 zx*?@3E?^4An{ubcf#PlgOSoo%_?x;)Sm3;?F|t*a1z6)g@_wX5MSkG9F4Zi*QYVzY zMQ+McxOpL~49$HM_dfqRW%cY7&gBMp>JN=w7V#=Wz>H2rgO%kTrKW|-BbEEHaBPFP ztylo;G&Jk0=m39>A}V%T0si!LaHfyKj=}arrY$+vgH`$ueIfkI%1@AFkhwmC$kv84 zb|+Gw%+%=&|Eb)WbRD4HhQBEkiw)FBI&puK`7zzY-hw-0d%>Nt!@!wbDffWlhynWv zR|~;9An|TjNP>+VZH&qM-)I{%rGtnLW^^zo-6vk{3Hek$49L3yc`<+zR7p+=%4Qp9 z)m?fwE5luv&1L0yW<@XHaK(NFF$YoB24F8V0-M4OQk=$MK{NqJvpJX-Eug65j-v+@ zbv#j8FO&@%0YzCg^(Vxe0Lp+7SYB%Fi>%mtDY8m&23rxO-_C5IPNbdNAGxtl(Ve@z zcJ3fRis7VQ)MUAM7pz{^v(5ohPqM9($0~3>8ejuokxX$AnYt2WV0cy?@~;ZPj!sZo zqJsfFMXoA}T+)@S0yLh=IU-D>Jsqmjp&A_=)XnAx9tT5Li_q1y*CXAgifEi_=-^t| zJ33+O=!!0FhEDZDr~0B*{LyT|Xt*%6NoTZ557gC=oRcYPyaZRI8xRL>Q+4t=Q3HzJ zJ1L;WB1OF6R(Mj&%^2-hdJC(a%N*TI9N|mB-m#4F9A*LYeUb4>gkYsIYu|C;?nuf& z2&l|dOoZW!23!!goFKwkw>O4%r=HjPFl$DpalqJY@WDT+G=4NkUFGjz0^kzB}bsvd=5-REXvVb;hIqp)A{I!1yH0| z2y}~~6}SWfu4TB7ou2|tPgI>M?@E==Wp2R;@!gm@riq*BZq~3%osve+>y}0JwzHeB zry5}pTRPGxwrR1QwN$qi&v+z1C|fWnw?bmF4Qgo!t_N_bNNK4Uod(cJ%fOK!Yks^U zbwltUFFp<0s4KE+y7UlnEVFwM()KCE1NxWe@mrdjY4PK2`G6 zQ6N%%3mc@?J6BZCJ-QsteRCa<*Ed~7hZ}gnd+|636$g{ zs&oofIt@4j^{JA#jw+o8pbMzdMO5jsc9lpuZII3?nQ2$)Dzbu-(orQdu1aQ(Ha?0f zflIBcmu{dWH&LZqsM2k~8KO^>e05amJ^(#Hm0qAqziC&Ato?`TtdhBQl_-)b=t3P; zGUuvf?r7tus1iKD=&I5mD9Kw?=^d){9_oI>Fr{e^50z$MFMJ66h<8EO5t#M(ti-0L zRhSFJuF$m7$ctyA@YE2qV*t*i8dwth1AnafC2$VJxeU&2aVB*R{M1?y1dRVQb9TS~IrJj0KA^F@op6k!_-SY!zs64+7 zU{KCiaeF`YR=Iug}fH3&WG2`Qj(Ie zqb6@x<+xHQH^Bj+iU-JK9;sBt0#{0yOU1QEHW4hy(!FG;An%q_1uBTYPw3KMy@f2G z7>cm(RP5eWX70RgpbBI-4~Mf3v{VA6dc>o+VBDsqr*b7xanTE7N@$Qa9C=kB`<%=# zG&1uxj0JuT8aYw*|B&@gs5QE-2-F%mftvi;E4wp4O{>|{aPVgV2nIEC~0NxnOM z)Lu+PY%5taMk_o|1~FJH!Vo}DgH&tI{#0XjBs{Q;dIjYY=g3aJwE?kHN&cXJJSe^u~y$;gW?l2~EZ!jI?sAf0TU4ot7QI6#- zVaMRVpILH6O~V`&fw@jXq@t1)!i^LScb@IoaS42koe;5;5^U8@OV}B9R*SEu{wTCK z6#?9gCxK70^CEVEc4(J4ezwAYscKj&`k0D-)d0@l5x86Jtq~uRrccL$4B!h^&(N4tv4m(doD_V^cv&hC z>1Kl!k97;pSv8J)Of&vQAr_CqY1kZ}Q@dnYUvgZnke<=3t})l5-<2~8@xkZ`yjsdr z@D20MxdeAH2zk!3+IolWN$jcp-97^JaQLrK*U)X^CC+Y6Z@zJE$WM zusS@!TJZvF#TzzZKG^yPgJsqZe8={vVkip4^HHfdnoJ5yo(o7|d6KOMvUULfjW4;H z!w9cCxy(vCcOT^A4ax{`D-?hEam!!eoHw zmkIW0KfH^mR|?GDct)_Q;1#1|tSq>%rC}aghT08xz#A(|XEiXMS2GsC#-SU>qf38A zqhsAu01N~`6AmEQwXnj33(=#@+|C$0AUi0ciS3NZh_Sr7nR#x~6g2TvaFeE?iD!VV zJQG~4*#OBim$jSISX~~zIhP>oPheH@&FPN@BF2+Fif$Z4>}F#}GDwHss%!&2uJYJL!0$ZAY1PIp(ysk+#Rn3DSFn1cKj@WLD1!*+ z1*%bAl^f+?3J0N^9lF4dwE7NKgT4qj4h}ZpJVAlk2ga@OVrEkZ#=Er)Sstt14BS~? z$~R!A$@^=C;pH4wo_w>yq%x1Wz%!IFxrM6NY326^jtKH$Q=jVr zSLy+9PBmJAw^lucLZ8N`b{6B{9GDa5!NI%$!uujVu}k>ouiyi_iotdbe5UJQ?%o0` z^)_hoJBlVC@4?v0pdqLUnxZLaMyQ0wuhtg!vZ&fz-UY0xb}s7HQbI1`vk-3DF*g44D9FV)|VLAzhUlp z1-MC^)I_^$6&BX&Vf8z*ChHv7=G?G?4LMrU+@>+ttWCHJL9_A$`#nnX0R#I_4D62> z*q^|>g+nPt{i@?kKTB=`X}W_o+sc}6#tj2bIK+Qq30&jWsquQld4{p;)RU3D7iF- zOt7Go)lZt9DvwT&$Ln#4WY)=XOQdPBvNUO=EKwRErwmbeD5VG<^f;1xjRydaLDHx= z85XV7y3(|yWPpi>{5@WZBEVmfG%YSIUXDldv`A+vU#eWAEC3c63siDxKycd-P5b}@ zZKVXz1$Ctfhzuf?MPMJzmnv4CB5#Hav1w_^&0Jh))pbryPjrqw^GSwv}9d&i-otf$qMWv$1#i{(g)PC3yW;w^ECB#?LsJK|H(QM9K>05TocS{Pr zFllPCJd#w_qIwA4f$~9AQi|{;{$62k#U>@llVve-;d{!vg}oUiPmN58OC~Kf=hRq` zBSH}Q9_tCKKq%VpqbGdi(X#aTG>x9cK$2>@RjHYZZj6$}E5?enwpgrmN8-bc9=@ri zEu~)Rsr|H{VH`%vMSO`R%iZgWmUb?Youx3j*Co61S&eEx={9H6iVYY3OpRo zhebd(ZcN~*w6kU&?yiq|=pbWIQih!S7!?lXq^W?yiS6-W^AD6Ns3;|>p;gQpq@*Z# zAS@`o!If0w)dGs@EdZmVtb|JMvU07hU06;}QY?5RC@%km$}7lIK!H07bLl8ChmS8Z zDGev4rAm{cg{>;ZluMJLvi%W+0fE->T}zXbFowlqe!ytg#D^P3S`P(vQM^Z$5rPob zMkgR0RpIlgim4hVVlfS6t>XFC$cjr7JP;z~;M4GIFUX_=tuFv^?ITlj>&0T>YP#B~ z!c8#E7^V99t)`04NHIFch6J~^6qgs{T}%HqKD3pvreucYnN@lBU51l+a+@w#%PkNo z&7u^xMDr02Tih2{ap8ntAk}=(U1*_!$gcFuO(=TWrjFos5il$U>Z0ubM-iTx3HRe3~=BfOVU0 zU&dhZ6ecYtALGMfo3nLiJC5^Zo$ImE(Se7XvTIJPo`0w4Hf+;&mUqV5Vhb}v@Wl3G zM%HG=16Z%))v9H&5wNN8d)ukSA9XfZKXpV)&Dqjor$bZU>~3aGeMOx*IajWA#=l&h z(rz+C*<26jgZ<{%xn?)Cy7%rBV~eMhL2ciC%{o(J!G!G;5f8sT zKD##AgR%Q*gU^4f>$0`{(B`H?6TU62{@OUUr^Vv*kK?1aSb1z?i=W@<^e&m&#&^l& zo!75Fe)Q;Cz+ta$vJ;&KeTzBxQ(mX9@t+5``10|?y?qZHvR!**w>JKC@8GHTZI@R( zJJozd#H}ZjT7TJY@%>Asi&YQ%N9^qESo&dmo4yl$=1zF$y1eRGuc@_cQQ* ze;c*B+lX!I-O6CV6sJ`^B8~nGtZCgjE$_ic#@_BuI&i6$nbFYL;rGvGet+Zswe{la zY-qIq)V|Pe@o{htKe5#v1}V&Eakk?o&kFAk*w)Q6t$4uX)&Ywzz1&84IUGLdW$h)7 z8d@=>SM7DfylRa)y{VF8FPBRml^W)?j~z8FxrFn<4m(P;Ot$(d( z=hntr-I&zM`&hlaXSSjShvVxxA3fx?xk+8~X^|&4S6wGrd$jzfj_ZtekNpsNc5|k& zO}1~IxtnpOmsD2SvrXr=orABnStYIL(6HnU^Ty^zBibLVS#{c~z+Yz6 z(Vg4Wt#-v`QYjbf0b{%mH+HZY>+M=|=VU|w%{k#0Y%cg+NDQs^yzc6G4cnBjHtp9N zQGK?Enoh4Pt#f?jqXmx+J!mRch4EzDi4A*Vuge`oIjw-Lt<|+C9GIn$=61dN&AY&}nPvyXn#HqKC8lewv>a zZ2q*?%31FQkGoU)P3;G@E39bPc4vKg_daW8&YIbIZJV|2*S3DS*KNbAl?ztv_1)d& z@9Ywu#{z10uh}|g(ixKlPs6T<-CikPSvII;vkx1b*YsIlVaaZv%>9WgU(a5+ujkN( zZIf2s>9_akUlj+rd=3Aa@Yl)@ttFjJw@MsLZ%WSCwm8zLZ&Hg&F2`>E(cZhqArJ56 zvKg{j_eV_GdA`N6%YHxmSxtyL+Wn!!!-S&=N0-$wtzlmyFz7|l>$#ypJ6F_R(Y$T* zw#QDNI=%Gtl{t;(wXA>D_36B)^RCXjyS-P}tgdx)SLN=I`)_&Fbw!ugxqA{vc5T{a zdFKvYdu;VvzhHg!E4$b4URiSG&fsamYZqRLIkf4I++TB7Z@1g@_?qd>4Y>)qgJdxg zLFfw7t^??Hcs+*Ons7(br3?t<<&B;M8AIN;m7+?9SXX^Iy9UI`V$zbC;OT zXRXg(Yw$3))XCb1;}(R?yVyFWT+GhY;qS(b9~(IAr1Pv+&hsi9y?uP|qkyyzeS6&R zf8oP|d0Xcle6{N4#@BAI<6e)xwdhpCMYR`pX^?vE&~fj3?H`|aw^%fzUZ1<`2DkHW zmmRb1X*HKfmnogrU){8QdFGmrvNz>g{n;vI(Zf~`TQt7odZ)`zryA#NKfe8ByF*C} ze;Rq$dq}nKQQ!N2KhIVUt!@xwG&b8ayPM(PpZX89-#^j4*-iPMTdu$IoUnMOXBp2) z!`2Ubd|p0F$~lqjiER9#!IGLIN000@%6($;n9k+T z*PqoW^ZYIE)Vn@^`ONgWGcTroyQUkR0vb8CTI1Zi#f`?h8t!sG>KxtW)aA|#I{U{5 zR&46*xWO$ke&5|MuilM*u;78k!vW=HyBzP4+;7R5gYQSKEW6s-rf##D9&N`2H<|vs zr?um%vJY+uQS+Ei5}XLz>{)vf1AH!>3M}+}CT3ebBVrm=`fmqBBmelaJi;?MUMI z?JkvPh<@#}b@DosE>7ZHuNBY2yVknuDB2Pu?-afN^w<+kXRcjX=k%*nTTk4a+<5+* zMc-f99JIMVHFx=_z)nt;IzQRFu6NTdE&BGmDSlYSq|T_0rt4bl7?6_(d zl^Jra)~&3^6-FnW*gxmi^j}u5J-jpOZ1jcb=TGi9E$mie=9*bgW|c^^ynf>s%ex(a zdG+B>-s?ARk;`Rkd}&E3Ou!rzDg74G)>!tHIctDh3jzpgM~ z&z}u{yL_|To{wK!{krdF2ZIhlm!mJs|MvfT>qqwuW7j`o215biwGW zs9K5Kt2-}Uu5_Vgc<7`nbzZhEajeR|ZwuaJI$63|cKCVz(FfI%efm9m{?YvI$I6z? zZe&atuUfk*brS;7!R`VOS=+ZCX zUHGqYH=Yl>I4$Pw*$E3~EEqJ{`SYz_H<#uPcp7=;Zf~okH@mFU?qBfzKK^O*J!>AlA*&+Fc| zM_+dD_U+cSH@AlL&dXfCLjcR_z`Qln$x&J@)hF4`Jt4{yoiZ+O+S+Mzi02iEQ9> z6Z=i$7|$S2L^_)|@bC81a4n5&6AC@p{?!>9I-If3-*JATbUec$E^^;vgCV#Uj74Y`unVp$84wt!qr;q5)k@*eYrJPB%%^28sW3N&- zpPP*O?o(r4C-|cI)+ts6Yx=HpogMrgejAQhwFI zs|laYRtL6mtr#7Q@l$tW)o@7{qZmoum={0x7cmq!}tA_VI z8&)(v=()CIRvj-})B7*p-b%kZceH1zic{L;)OqtxBuz}MS%2l|v5(W@`Z{N~3d&n+ z-mR5a?G72PZ4xT=s#-Sn>BB3{N*-9-ulvikwVzr#G|uS%&7_mj(Q8>Q!|%dQp!^?p ziP1Ie&}YowH`r%`SFd40dL`S$Z>BId~&?`LI|ASolL9YBDSALKyKgg9I zcGU%A3lbjVx!M-FQh@ zjXSf;-EK4Od0KsI$DBW- zWh^;U#W%`j{`fQRlWgYQwx1T(Ai}j<ppXc;S z_U6mM>SIs8wtn+v*lb)b{$i85tyG9?bH?!p7q;x_-?dKgaKEy%W*&)nWxe=8@19LZ z+O7&}WEeEm;9g)x^|P-xF4;Zr*PHF#2MlN(S{T5 z@7|1R5=tH3@zEXZ+HuTrLMaw80E#CJ9YVT?C0O>PdHoU&GYXS-?;D3 z+Oc(m-}q_gDte?f_8T`YAl<=vZFaA9d;V(g#xC9ou_-&niTSqw>#Re&7wx{)ww_RO z?8!BMef}bQ`SRhb#@ib;zijvX#ob=47ZH|bid$sb(?jdm$r(zE^aQ0d;E$S^*c|0g->f2J1?D$TJ%#_i|31PcAvN9 z)S7p9lP4d!-sDOAwg=-P>=&Hy_~V}E3 z!KvE*H`6L_ZZvhWPx<~a_qsaUobTFV!`y_xiOc3D^l_@(2{$C&1HIqWsJ7a5!as2bddb9l#r_?jq*DZN(>7N5W&;R9pNZ^#M zb1l+)?^^Ajx+K`i_sof)Thre*ow2n2?O`v&?oBg(n0&C=pnxr9myaCq;YOG09v(5} z_GFr$x%=kLtOjLgt@G`&vRz!o;5Nf#V`glWw3(B$E@|IP*I)aOH~HFSRzt(}dfywje%tiHCi}R4(fC*1VTgZ|K92wlUAL&xJgdC0G2^XJ+Pab-j;1t2x_cMT2`y8ro(V4rc94 ze!G!Ud5!ZOkD;Df=PZ`E-+7U}#$s#jiLHLI9buUyWgTYSNLeVp#umEfR5|N1^yQYd zyDdbmrZ&7TT5@goo8d3Lde605@0*dHnf>x@*5GW{9D^1nw!hY>(f+c$UAuB;-;Z{& zw0-wjo|P=|GO98{woJUeX4d1a4_CdI8q_v)Xvv|YIvyVo+TcJ-m){1xejd~O+8_OE zw)>npx^ef9ZGVzIUc2CcQIp)ClkT1jahA1fTYumm$2M0;zW*@w=}gOWS*ESrfBE?N zO^G3Y{W|_zW3Sm)+^+2_HFvwG+%mv-`q$CFEhyc1$oM<$>ed}R#9;7@@CH-s9sOng zh=(o*%C$Z?=ys)zCf8oy4Z4^cy{1o@fa}%Q-K>3dV~L5khc6m3^yJhNEc)HgM`zF6 ze*bb~``?Cp_k>vRRioqUy`PkHNX)z3e)jDV-)nwqcfss8)^%v~V)IpoWfn`;1*}OJ z^lrZq^E;R)slGh-aKTh!oemQNuR@)Wl526OM3n~V4@x>Hh6d51P;Rlk|cSdr_v{C$qkA6KKNojNtRWqgpE z{gVTEl6Q9=?)|*PXGG5(&1Up8PJH7uYurY0>fWpXpV_QZ2Z|zN;c53;X`4yXw zusrypgom}+t}ch>HoF@AIyLf1*_(^DH+x&TsoT-LqIUzk4VajC#cZjmn|0cuLk$Oo zwhn92f6L+Tzb}8jtU}Ki!#+l77Z1f&Szh{UuU{r5_U~MQ)muD0^on@Jec852rNRb{ zzC8H&wkgI(#a*w~TQRXzg=dTME_rWw)x>DiuIxs0v+7>CnpeJ3$|2JM+j|)pTR#Yn z8FqDf$}HdG7LC@6U%m3#)4+3E*T*L=HQeyh)wPAs>*o!Y&h0pIh;2ZVN~WIHliSv? ztCZgF^QV+g-^%nTQ`OYG<`sFbCRurpj~oga_$7JjDnEl44{F~zTgZ#81DBqy75;c$f819f!bzyHn0I2v;aiL2qRK(PLRlBB~awqpPy;>Tk z;00Q67Y3%9vH)Cz1r`QIWy>w7l6JXj3jj{g4y4w1jYJz!fH}=MW0ax)zfbCm*cC}55fmesjdK3R0Vxe>8`}2s*e-j zq5_nJkpD&>6kG|lX=yg(Nsh(?>!i zc@5L<>z0k2vdu6xQ$pEQzntvjXhuD;q{T(|g9$#@WV$Oe;e*xphE*7&(7O5Xx-l)HzVYRGnN=A1y4JBdoyoo9nd`z=h{R;fz ze%`;k_Z(-&C~>MWihAT^FO4e>8r_X_#9unW1VfclBV`NWoRd8kMk)F{Y+$hk)~Vmz9u?ItDG4#mxL8aKt_9Q~S`zgbeN9P-Ve8dmVrZZnL$fQryDh@nOi760#qDBZ zXs8>5e;cO__0d9P%ATy011pN`s4@(|!Nc=?~;(_Opnu8A9mIWR^^IJ>`lT|8{M z;Y%hw?lTuIBq-Gq)aD30NEA3R`qAD`?Fn3aNsasDb!HFRhmw;3>hki1Ky8}@4^Cle zNh#Q=>1ux31kMPs_TG-JrX=j{b(Qr6?HPd$zHe5foO4c8_#Wi11Jf3o0~aOXM(2>d zzWCaPsgULG1*+7N`V}q?gA)ZxLL~MMdLyC6^MhLt|8}~OEOyJj-WMH0Nw{cyf}^(r zyIRmG+(khMlKYOG8fwfKCE*gjtDe3H+J*T?=`Q}E;qHMqK$lPwes^6P=?gssUbf(I zR1U9AVe*uWxX1z<>sidqTIbMEl!QGp)kj}!2wH@AqVVw<;-kAt2jtq)Tr%zHk{POCF_1S^pjj917rPczhJ4X}?t6zMQOy&B%h~^#gW&IN= zmMcoaUiBPPETsOy=?M|?6g}&DH}dnb8K@^EA=={;i-#6ow!n$d6Rqvtc`I}ero&T< zg|=-{VvL?RXZP5-CJ^f#rFMdD-88dUID_Eu4r@6W3h=0?C-RP6{$AG&dumF;ksLdx zSjdajyJauTVR67nDNs=5+KY;XHD4`@So&{0Z8T*Ph7={?C-iqtu@Hxmi^Y7{bE zOWU0b1VT!_g4(`WRV<_(LjCnb8CuTOBpB!@2~jRsS4@;`^+ajs_4VihaL_3U_aniZ zii1*j8}85P`PjNVSouicS*U(F***5@0bRk5)D64hpcl^&VTPj2XtDBkUegP9vID%X zC#CRSyo;DmcMRNA=L>p;lDDcFM2sK2uO~7exSC8%g+oBS5dL=6ePn4Y$&`dcp~@3I z5pw)VucVYHxqfw=cge-#A!aj5LgviO7kZ-fgLg!|syA6a=G0BJB_$!Q%dhmrlA&cV&3TBid>NlkHjQur$j*_>a9eu6zM5!Xv^s1w%Td$s{fr*lk zjen|?eh76>`QHtFruGH#LkZ#<)er3BPKCe?!w!p^cX_6XQk1D1N0Zw&m!eDN^mRk8P->y74lOG&Km7w_6r)J4yIQ|SM0h;{7D^rhOdlil z#}qD$h>%B7u${u|RWBj@?ge~Jl!Q{qrg8rSTOk#jHO+HHDySSv5O}G6IoZ((#X-GX0ze$JeX`cM)I+Fa9$iKvk3ylIywTW5}44U%v~z>uKeIZtbdcjElF7ha!f zC9Ze9i9t?DD6^_Othks8sZgN!-EEOAW0Zt!&X6&BqsiYR#K9RNUo_tWQvoF*|7q#s z!a#E|WjOAvhTKyZFO`s3EXs@xKoe0ClC*|Pi;slE>Xnq97?oNuOO?=iM)eHohml4} zNb+o5S$woXaDL9AE*xp2FY63Tu1h(M;5gQguP?rk*MUMYt@rkFLQ%AcxAO^UnhuC7}qg z&-tQ2DqZM@!5e!4llINhZ|YYAc|%D^oQ=Oy6nISmpziiqbN|V~Yk*8isAcixPEpXg z557Rr>(emHRRQ?eDOD4+bmeD7K+a!O`Wj{r@M`W2vV>AC0l?K|j0&C=sp|^RHXx`C zd$0K^4?Rao$n1BuEdr`?Fgtig$&w4y?`hHOajP!)fGG)C%-)ssM3BEdIHv8@5;N=djX^mWh^vc{xZ;0P(PAS7bEh%rh+k=+AF zJ<(9-76`v%oyKuX(5;k&)!6G=4EPEGNP%dcjq7H&4YC$W!j0~>#`>Yr-M_CoT6POS z-%=9x?-suz;3|q-Q2+M4-Zi2!8l94`;r)QDK>Cy#TM4<)hOfJI-lYT9;3pvoTjZDa z`oYiNBJ{1Uy+6SV`)@``*dlu)3WEzNjY6iPt3?QM6T7LAwiqUqggdTYay=n?D?@82 zrv=@0u=4pm+5lK6`3h#S3vqg4BB_%qZlSjW3(Qo6$_C9dcXQY&qH2#uWlrx7eKusvWXP4eHh68B~pyb2fWIYt7Pe+?c8*p+$G^J$m=n_Z;%iJW5Re9N5D?aX>LzS#>|*0$ZRl*_ z3RHHnwKX)d1~M318#+1JDBCDZDx&*-ohvO~+-wX(6&)M47~X@{el6=j?72wRr+V=u9Ekz)LOEraHzsGE@W4AR z!-k~ox8=M(O{t-s|?fZbUC8K@~=~b4SY}>Ve8ne*BgV`(J|iQ>KTL>&LiPhFIRU8 zZ@gsbMElB3>C03BUOjuz)A_lUM4+ntn>qU_P^oqIgSo^c>bcJhImrdTwoP=;oU?9l z>Fl@yb?x5P-8)0M%!~xEVQ>dRW#X6yIVr3L&J!rt!SH>d$mTQt4}^m|Xx&4X(VsvaC$G91)O-q`M~9-|18 zAkFCpA%U!ea5yGR)K@rFDU!1vtTV4|C}x|bkS-p;{}l2MnYkwty)ys>0m*ul8t*^J zOy#>$6&>yDfsW1|BIbs+WM=L=c|io>Rj3py z#z(CujRX*@B-T@Ds z&!Q>Q^}(Nx10KDi*jS}EI+`Sau*o;M7e%CK+{t*x!a*Z&Bt0HfV_c?mFC4MFS4R{J zeztHgXj3KlG+33OU}nouFy0>xk$)UV`qTD+1;x3urxWrsWjRfnyu3&;{#aqTebG|wc z{>CWX3ES^n<@lLmkXXkQwH@j2hMVo>`$qX_wD*tu1NZ;UaDSR@Q~^yG{XV$v{i1Eq ztFiD9^0FN>CJ}mb>jV|tt0$HrSlL4V3%#KEiD=%r%kkOZ5N&WGd~G2? z`M|^kq>>xvsxi%jK1ClR%!|5#8a(t<16Q6#SIGoWqTHi#tb`ELIBf9ojZOKOCQ1*r)!q1 z**YW@Mw$KEwUXi`y4y6uX`R|dLcBe@m|nuj1^2(KTjLDUlfT1e?aC>$)o@UW5HfGa zxFS_8aFPMAd+W~5e~xI-2fD{G>Sq@RJseY#o6z#D05fn$V)+F@M^lxrn?NZEYkRUl zA+BRP)!-Gup?I|*MS1|?>(1iQ6DfJt*vtUXLHe#agF^!Wk-jr%tyji11q%Q?pRbot zUA}RBbA-DvMYykLJ2-Pkke6*qkGtDtvH2(O5`*WXot!!gA$%O3t z(G_91W4d`v$$+VjDISq}537YqPG~noN<^{-7j^k#*T97MIXZ7-21F?5u|H8;3?S_V zO#bsMgWdhpb+4}@ZWrs@^@kvy?qj0hGrC-wcG&@V408<@^)C=P@U3|o2FI8RlFu)& zU87aw#Mec4!j#P71V&9f%fiE`va>$v%biJdeI7M2Ma1sN#3ko5Exb@8!Cq*e*Gx;6 zl{zqpSb4k6-KmXC7Z2%v{ufcO z{MdzFD=R7B3M1dEQ)!H#AzKwiVRWnbb8xR<7^P%J$<_Gyjpf?yRkkM#>NzzPw3f(4 zb%or6yi9b!l=~u*@GjMQD|Ip1&UAZzzA04@WUg>Ku9FrIQ{5)Ej{twq(W^VK1Hm$O121vrOx?SE`^qZ} z5ilB)o6%JnJGXbrv{8_?!yyho>&u2|(i2CPP?TDIOZ--{EJ5?_)_wmmHS>%}S$^|s zMU%oX22vi8czAdQv=3$!y){>blKl~?M?f(mM3SlB64#dfN`QS(OprysSGk1SV?1_lpTqi!Zl{ zc>&F3VE#@0W}U^A*Bs;N0#6i5e@f0vdDPd^;?@AT8XG#?acH%^(O6yUmR2w;--ezJ zg}1M}QDI|*@z6zCau#k8I7E8h>i3Nttv%-mSW9aN-1IQTq1EI(c24T-ERLE1cquJjr{z7F*56)Mz*nI|@)lc`w_g zP~V@|tm~@cXjivPzJo6O(oay@J6`>n@D8diS3m#uLPF>1=}bKli0imjM|W_D+QP|L z_rXc!?wb77W?6(610gc5Yqo5K$Y)%WE3OoZiL}qwn8{9gz1v#8B&X(@Yi<}zX5Ic1 z1B9ratB&zY%vrvPEg%vbWkOQB?C-93aG3)x0>yH`NR}PNv4aKb9 z8aWr!c?E6mRX>=y6ZUpn#KD$~Y@`CLn58!tLdHC^*4bEPeaR2VWOVqx9Yg!aQ1B61 z?OeJ?CdJV6J%6uZsGQY1PDD!d9L&}x8S)iB81%hNDbP0&);RY$U$d$`pkhernVWi~ zGLRzB_R{c(^Kx%owdXW{QR`jzggvA}7diB~t2>x$=hEiD1|dNlS;MGP-2kb?fvXk~ zX~+z&_x&zyG`w>G4j+?H1>P{&kEiL)12U>^dR@dpmUd z9F-l==00N0_XMGwxqQH^a{l4aGTn$z8KBfVzTU8UHU2!FT!wB#-g-lNw6`*p9nNj!*a8KuX*d1Ea+V$aUye9?uMwiHL(d zBkR&UI-l(6F8vACs)n1QK8 z3G`nuF;A%$PGQ8ElHYKEB$o#SdsK5ey5jT3wSWE!0Wc-+_FV~ymNhD7eJK6>cBEH+ zXMR!9F~gmcRa1{F;6MnqK3SgVp|wDwEY+IQSOSvwRtz%A-!0m82|N{af?slO#EJ%=ks{}h*lH}+zhvSQk7Lx8v$TRulT zxuypoP@-k&^Q?qAsS1f5YH+R>zi1s;&A(C5=Hcnc1&#qHDxhnE;)D+RnsIE(j_RYj z_8mWZ0mvqZBj)x_>4ZazU#f+~v+-)!Td=>2>=^nGiR^KU`TThQw#e9jh%8EJP`*P5 zogWJ}Xii#e&1*HASrO|~5N#BmiD5RgQqCTLQqkO0vY^i``BGBn64W>G7%`;*7~kl$ zwJ?*+tG`T?0TNxQ5fa1>g~3L^R$-e1G@k6i%+iijCcsBQEK?PT>D49=MXNvcW4JPY95;h;G8{mL%t>)7@`# zGR)b^wjE>OQ?HYKvl0mM%8NR$uSx5D?bx%I_=4R^n1hDSlSC21!shoX^{mwEzC9ma z0kx6$MWx1@8n!R61APdsf|}HYTkK3uYh*s|*}v5_tZ8@ae#P0wg;xdW*PhoYcRVsU_Z&1I>}g|2Hl z>N5>qgGHuM=0L1lHy-cFS<=CdP_r48pv^o~9frkdeVPzYoHD?2Z}S~$9j_2Ob`O!~ z#AFQi#Op7JBBrGF5F+y7wy#VIRZ$gM=wD1nNJ@LnU`2fyGkis6rjj~w959wpYLj&V zHX{^ejN=mR^p>Nd!vPMn7abN~Y=~)ykk`Ue>|<}-b`ya4$n;HJIan(F*Lsq))D54dnsR7(I!$8Ab@>gM0AAF$%fpmYwyTih=M!>tmyC^Bv>x za1UkJpUz$aSQ51Xl&%d|@?0o!sQ?1^=uld1{ugOzlEpg<`VNWW4w*M$tva_Vp(@w& z>Cv%drvnv3Vc4W3F*}pkMaxPB*omjCH}pWw_i6d4lqCUO6xQy6>D;tOh+Dg;vhmg) zxIqoToupiIMauDhk1B?VcZm??1g}1|5TD`f#)J}Ky3K&p2D3G{DIcIs4C=>888+P$ z7?&e43)p;%Exm}%$%Cp63A+wy2|#BUOKYct&>|U(yeW7fzrp*vl*pY6EFK=E)DH1K zEL_f?Qc^uoo_t)mG9oI{6@EmTVi02VppjIOu%UpUWS+J{s>pkmd@Cs!JPIOoAG8d= z9su<<9s?KmW%#8M!{~js^ z*_Sr~3?ZPqc!`~gN%@>){pQ_r@G{Oa+4D5ws*%NyCx$B9V1Y+pTn*Bv%+mI)IQHjg z!^RF+{+Nh1d-HM5cMcuzsKIb}M`B^bMCn;|c*8ZE#byz$H6)`C1f{t4F+^yWH`KAo z3JNH#4WSa?t%8(yk5^v^)X!YPh2si(duS6{L<~&EhTIA=k81GQCYj06+Bdtc-^svB zkJCFrvMY6tsf89rQ`7WRD?*<}b>=7zd*j*Lwfi9s<%y(d#AmBf{@P5Lt2cZw_V&K7_am&M4GopJbem~(s5 z!v#t9Aw2($m*j!a_?Zn=llhXcc0r?OMdB6om0^`i{G!0P#juH$=)it9`BvRdIGH$C zQ&8R2GsLfZ2eZ00U%5fr0BT;k$>jj&YxDeHyeBn+Nh}%ZMZiR z6I|#7hk$LR^hF!JX1s-Gj+9rr=?iw~KGj!Ib<6@zWqTZrJ}46@35lBct0!)jg{9~> zm&b*)_2_0v{O<8u)K104$i~9?KL>q7tkL#3KUXV=B*{Yf_qmRs=ksf zB?b-&?k2)J9psv{KZ2>(O?fnj2c3dl?t`)nxHE%;+%QNtF}pvtXqi0}6nqK66op0Y zY{VAC5Sbs>JDb{_UjR4#YXYCYjbBzX^mf3lv zaY93<7()+9G||xn8FYrxZ=A4|kZp$1eJMbxs(&rPO*H zU3s>9%tUx{!+M1f9D67}u_Lv_X&FmEWQnFpZ+NK?_N7YWi@Mf>zPY+R8`x33>2LF~ zBl00vC1!Y7w68N!rWzen)+)ZR@^CjRE`G?IfTe(BGGA#4gL+(vabdmgM%jKi!7T}( zayo6qBIVQjHPThfTnMYXTnchSYsq2TJVtR@pVg~RzdCYlPkI_PIJS76w2F5zIeuls z?wM;M3NBk`GLqzxcGqb{H|ZN75Z2li(&NJ4VWti^es>dLBoP#|-@OQ76C-IU$m{~|ioTm!Ej{W`=)Wq(f*eNV0Y zl{op=Y{(z+=AVHh6=y?dpt_;8%g@9{`eOuX;xVzofUYgwOF^8#t zIXn!wF=g;&QI)iTrY!aRvLl^?c+RZZ%f#aa88odbr0DsLj|&3Z+>95u_osJQeyFa- zu@$9jppGmoEQCdpE6i9P5G6EsboQc3!r^-a5Ef+_eeMIqtsF1T#P=`)EU)Sgo%hG5 z499TG_S|dZ_>;NNf|Z^2&(2n}Sh-JnGtT<^+YT}!n@M{o>1|ulT)z?p2oFsQpAGwK znZ*d$lztP9)f51Yz{I5T7o#jWdV#7;e+9dM7qC^ z*bdk#V@HhAMLqZs4*(29L72u@WCLshAAK$0dA~4q;4b*a+Q=4nb^(~ZhTm7=cAt2s zI-g>{j4w!%i@Ep$A&$Xs?Nz0?99GRyR^N-hjK_L6KH!{(v!>3;(i!do>54nWq)-)v z_YpywWcB$p&1~!6wbW`|yZr_l1cVX;1cc@{we-W;NY@P0N58;$K%^UDiG+10p?Ygs zMMskDE*gr^fvSNL8j;dn)E!hswyNQT38BRA+9xC$9lh-r(q&CLh8w zj-JMwMq6;~S^46xy{QSsBC|F=U|h>{T!<*R+TDA9i$|iDG&V4zOW(HO7)^#E3t>1<8Y|0e}#mro`n%9WgY)Fcxj-!mw#aS0ex)^MuqO8Q! zOu3To6Y8-DsB)LO^;tu_bEu9>?z+DU<#jY6A^|_U{gHWL4lRigG@RzN#ZFS`P8}Rw zzEkY8OO^aMCbdCSMWae|PBzJJ2$tX2j#mz1GfhVpEJ9!?)~i$pa` zp9|;9;j}ka&I@DxJB~87H`})1jL4?i9wZl}+){63hXNxsrH#W0RT3fzt+B~!OkFlgyozzsrI@?2 zY}>Mi++@AJetnS;D4E}LKo*^+!QIMEnaN&Z;>(G~oet0icZM{Pg)|PbZ87wJFBq}R zEEVQpYG*s!qT6O*C|24|P7Q5VP*l za-dIDeo~(zo7%7O445V)+C%ED*u#4yk3)G(tefx(@R<-?MAjImT}?0C!#5FdyX{hVa&4U19Ge5*=VxydbVVuIU-Gd_(W4Ed}fMH+MX zicWg|SG$b^@?6J^$<+WQLd_Y%Do=@C3;qyh7&u9cXS_;l%?i-Jd9c0=$^2{wvtVo`Y+d;9M8B`gR+M_)qM;5#FC@sSj`2qEH zZoej5B}JInfQ2dpIQFy!6G2V$+F{yq6)B3v>$SR|dqq2qh6{mrrduhCes?Nnj(2bB&6Z5Z7!$5;&;MBZH0I6Ot(7_JSa4z8v!5E{c8Qa zA?64sKU{l?fK9VrS@&EF;oy@dr<4|1XP5@{NZGzI%|qwqU|}*UmQI6W^;y7H^P=je zD&<^o^dqq&$r@?VyO&d|?|V;fK!=RT$Pu?mxoqE#)n>APsBVOw?PQCN3x_YQ4Sg^# z+A4pM16`iKTmsaLLH_W3Mc`UXGcbUwjXdQYl0A5KbNCC?aW7p@qGlCihm4~7As?d5 zhhE2}n1ls1?n5M@wJNF6y;CyXV)l`l2_B?KqbcRACc(b7-<6k&t+1D;WSS zd(;W^f!$Pmglp3ZYuLen4s`V+WoF@_qpKtBG!o(Rbu83UcgqmPe%(=(x^UMogGW#q zBC~8Xx_W66k7B|Zms=2+3uxbodjnIuhFc_+6F#ghMN_ZW4E^$Ox`e!VSU@W3$qQ1RGFCrB3221KnOB{KdAWAsAQIig|@R3JOX zu~`LO)Ti-cc2>>7p7$jluq`mY4V0R!K`!kUsv4yk;=^dS+;f9VswN+cfOZ^;`zd{R z78&KcmbEcrs)--{qQ=+ew5G(eTX2{7pe~dlza^ab@@)Ig)JR7PU&xkx>(K6)%Q)-n zNmH?s1WCx#nu01fP(h*KgRiwjs!n0TPh}BEPO(^s!w`w$dsuCziKxH=SdWmHKNE5g z=t`1dc$JW!I2E%lD?c|jwBk^D91k!m`;c$3;E_J6fW5`L?a=a$%VO;9HfA~`a8Ork zlt>R5wGUt}c}#4@f49kCLW?sN3o%aodK)=O|D?~ZbDrO^RHBR+ryUNNa@;*Eb`!@0 zWwf5KD%$xtBUGK`O0puZT<_C#sfqSvAe67kS8F`zfoVHQV>~l8r%}2(eZK{F2sTjt zz|UL}1UPlXF&2dLgvGqpa_eNSdYxNr+BvMBUAUqa$Jbz`Qo7!;olj7f!QLlByqFK|pf-Y5%qBzZ6*g zEBW`LtUr5v|NLGA_2c?Z-t_qRqa^B2&VOGN^{dC?&mMny`u{}sy-4cE^_>j*@v!h8 z4++0u{(aHZpFp|(g8u~bpHIwB37(eA{36JI44?kTasKz>nWscg%R7D%=|2Y7{trYy z9N(ujPxJV{XcnRV8_nOd`cILcCWC(=pTPVk^3T-pQ{<-^$Y01aaKDNCGavaB`Du3R z7xJgaBENs0^q;fwGuQPL`Dsey7cvj>ZzBIpa6CnR8ovL9T!`|U$UlSmPm!NSbAKV9 zqWvcF&)Du$jkz(ThCh5~S#8Zl=2hv{@M) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/nbproject/genfiles.properties b/apps/desktopgui/nbproject/genfiles.properties new file mode 100644 index 0000000000..1b326007c7 --- /dev/null +++ b/apps/desktopgui/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=c4b345cd +build.xml.script.CRC32=9785bb9a +build.xml.stylesheet.CRC32=be360661 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=c4b345cd +nbproject/build-impl.xml.script.CRC32=74d3fda2 +nbproject/build-impl.xml.stylesheet.CRC32=487672f9 diff --git a/apps/desktopgui/nbproject/project.properties b/apps/desktopgui/nbproject/project.properties new file mode 100644 index 0000000000..5e888e698b --- /dev/null +++ b/apps/desktopgui/nbproject/project.properties @@ -0,0 +1,68 @@ +application.desc=An anonymous communication network. +application.homepage=http://www.i2p2.de +application.title=I2P Desktop GUI +application.vendor=I2P Developers +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/desktopgui.jar +dist.javadoc.dir=${dist.dir}/javadoc +excludes= +file.reference.i2p.jar=../../core/java/build/i2p.jar +file.reference.router.jar=../../router/java/build/router.jar +includes=** +jar.compress=false +javac.classpath=\ + ${libs.swing-app-framework.classpath}:\ + ${file.reference.router.jar}:\ + ${file.reference.i2p.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.source=1.5 +javac.target=1.5 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit.classpath}:\ + ${libs.junit_4.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=desktopgui.Main +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/apps/desktopgui/nbproject/project.xml b/apps/desktopgui/nbproject/project.xml new file mode 100644 index 0000000000..09409a64cd --- /dev/null +++ b/apps/desktopgui/nbproject/project.xml @@ -0,0 +1,19 @@ + + + org.netbeans.modules.java.j2seproject + + + desktopgui + 1.6.5 + + + + + + + + + + + + diff --git a/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application new file mode 100644 index 0000000000..6cd2ac1fb1 --- /dev/null +++ b/apps/desktopgui/src/META-INF/services/org.jdesktop.application.Application @@ -0,0 +1 @@ +desktopgui.Main \ No newline at end of file diff --git a/apps/desktopgui/src/desktopgui/Main.java b/apps/desktopgui/src/desktopgui/Main.java new file mode 100644 index 0000000000..9d9708c8dd --- /dev/null +++ b/apps/desktopgui/src/desktopgui/Main.java @@ -0,0 +1,109 @@ +package desktopgui; + +/* + * Main.java + */ + + + +import gui.Tray; +import gui.SpeedSelector; +import java.awt.SystemTray; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import org.jdesktop.application.Application; +import org.jdesktop.application.SingleFrameApplication; +import persistence.PropertyManager; + +/** + * The main class of the application. + */ +public class Main extends SingleFrameApplication { + + /** + * At startup create and show the main frame of the application. + */ + @Override protected void startup() { + Properties props = PropertyManager.loadProps(); + + //First load: present screen with information (to help choose I2P settings) + if(props.getProperty(FIRSTLOAD).equals("true")) { + props.setProperty(FIRSTLOAD, "false"); + PropertyManager.saveProps(props); + new SpeedSelector(); //Start speed selector GUI + } + + if(SystemTray.isSupported()) { + tray = new Tray(); + } + else { //Alternative if SystemTray is not supported on the platform + } + } + + /** + * This method is to initialize the specified window by injecting resources. + * Windows shown in our application come fully initialized from the GUI + * builder, so this additional configuration is not needed. + */ + @Override protected void configureWindow(java.awt.Window root) { + } + + /** + * A convenient static getter for the application instance. + * @return the instance of Main + */ + public static Main getApplication() { + return Application.getInstance(Main.class); + } + + /** + * Main method launching the application. + */ + public static void main(String[] args) { + System.setProperty("java.awt.headless", "false"); //Make sure I2P is running in GUI mode for our application + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (InstantiationException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch (UnsupportedLookAndFeelException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + + Main main = getApplication(); + main.launchForeverLoop(); + main.startup(); + } + + /** + * Avoids the app terminating because no Window is opened anymore. + * More info: http://java.sun.com/javase/6/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown + */ + public void launchForeverLoop() { + Runnable r = new Runnable() { + public void run() { + try { + Object o = new Object(); + synchronized (o) { + o.wait(); + } + } catch (InterruptedException ie) { + } + } + }; + Thread t = new Thread(r); + t.setDaemon(false); + t.start(); + } + + private Tray tray = null; + ///Indicates if this is the first time the application loads + ///(is only true at the very start of loading the first time!) + private static final String FIRSTLOAD = "firstLoad"; +} diff --git a/apps/desktopgui/src/desktopgui/resources/Main.properties b/apps/desktopgui/src/desktopgui/resources/Main.properties new file mode 100644 index 0000000000..f79fe9a005 --- /dev/null +++ b/apps/desktopgui/src/desktopgui/resources/Main.properties @@ -0,0 +1,11 @@ +# Application global resources + +Application.name = desktopgui +Application.title = I2P Desktop GUI +Application.version = 0.7.1 +Application.vendor = I2P Developers +Application.homepage = http://www.i2p2.de +Application.description = An anonymous communication network. +Application.vendorId = I2P +Application.id = ${Application.name} +Application.lookAndFeel = system diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/gui/SpeedSelector.form new file mode 100644 index 0000000000..b256265de0 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector.form @@ -0,0 +1,160 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/src/gui/SpeedSelector.java b/apps/desktopgui/src/gui/SpeedSelector.java new file mode 100644 index 0000000000..19f4875341 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector.java @@ -0,0 +1,176 @@ +/* + * ProfileSelector.java + * + * Created on 3 april 2009, 13:57 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Properties; +import javax.swing.JTextField; +import persistence.PropertyManager; +import util.IntegerVerifier; + +/** + * + * @author mathias + */ +public class SpeedSelector extends javax.swing.JFrame { + + /** Creates new form ProfileSelector */ + public SpeedSelector() { + this.props = PropertyManager.getProps(); + initComponents(); + initComponentsCustom(); + initSpeeds(props); + this.setVisible(true); + } + + public SpeedSelector(Point point, Dimension dimension) { + this(); + this.setLocation(point); + this.setSize(dimension); + } + + public void initComponentsCustom() { + ((JTextField)uploadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier()); + ((JTextField)downloadChoice.getEditor().getEditorComponent()).setInputVerifier(new IntegerVerifier()); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + nextButton = new javax.swing.JButton(); + uploadLabel = new javax.swing.JLabel(); + downloadLabel = new javax.swing.JLabel(); + uploadChoice = new javax.swing.JComboBox(); + downloadChoice = new javax.swing.JComboBox(); + kbps1 = new javax.swing.JLabel(); + kbps2 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N + nextButton.setName("nextButton"); // NOI18N + nextButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + nextButtonMouseClicked(evt); + } + }); + + uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N + uploadLabel.setName("uploadLabel"); // NOI18N + + downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N + downloadLabel.setName("downloadLabel"); // NOI18N + + uploadChoice.setEditable(true); + uploadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); + uploadChoice.setSelectedIndex(3); + uploadChoice.setName("uploadChoice"); // NOI18N + + downloadChoice.setEditable(true); + downloadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); + downloadChoice.setSelectedIndex(3); + downloadChoice.setName("downloadChoice"); // NOI18N + + kbps1.setText(resourceMap.getString("kbps1.text")); // NOI18N + kbps1.setName("kbps1"); // NOI18N + + kbps2.setText(resourceMap.getString("kbps2.text")); // NOI18N + kbps2.setName("kbps2"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(49, 49, 49) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadLabel) + .addComponent(uploadLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(kbps2)) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(kbps1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(nextButton) + .addGap(34, 34, 34)))) + .addGap(40, 40, 40)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(67, 67, 67) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadLabel) + .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(kbps1)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(downloadLabel) + .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(kbps2)) + .addContainerGap(173, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(271, Short.MAX_VALUE) + .addComponent(nextButton) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, uploadChoice.getSelectedItem().toString()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, downloadChoice.getSelectedItem().toString()); + PropertyManager.saveProps(props); + new SpeedSelector2(this.getLocationOnScreen(), this.getSize()); + this.dispose(); +}//GEN-LAST:event_nextButtonMouseClicked + +private void initSpeeds(Properties props) { + String up = props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE); + String down = props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE); + + if(up == null) + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, "1000"); + if(down == null) + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, "1000"); + + uploadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE)); + downloadChoice.setSelectedItem(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE)); +} + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox downloadChoice; + private javax.swing.JLabel downloadLabel; + private javax.swing.JLabel kbps1; + private javax.swing.JLabel kbps2; + private javax.swing.JButton nextButton; + private javax.swing.JComboBox uploadChoice; + private javax.swing.JLabel uploadLabel; + // End of variables declaration//GEN-END:variables + + Properties props; +} diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/gui/SpeedSelector2.form new file mode 100644 index 0000000000..2f0abc7863 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector2.form @@ -0,0 +1,116 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/gui/SpeedSelector2.java new file mode 100644 index 0000000000..a02ef9b53d --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector2.java @@ -0,0 +1,174 @@ +/* + * ProfileSelector2.java + * + * Created on 3 april 2009, 14:36 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Enumeration; +import java.util.Properties; +import javax.swing.AbstractButton; +import persistence.PropertyManager; + +/** + * + * @author mathias + */ +public class SpeedSelector2 extends javax.swing.JFrame { + Properties props; + + /** Creates new form ProfileSelector2 */ + public SpeedSelector2(Point point, Dimension dimension) { + this.props = PropertyManager.getProps(); + initComponents(); + this.setLocation(point); + this.setSize(dimension); + loadButtonSelection(); + this.setVisible(true); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + nextButton = new javax.swing.JButton(); + returnButton = new javax.swing.JButton(); + questionLabel = new javax.swing.JLabel(); + browseButton = new javax.swing.JRadioButton(); + downloadButton = new javax.swing.JRadioButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N + nextButton.setName("nextButton"); // NOI18N + nextButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + nextButtonMouseClicked(evt); + } + }); + + returnButton.setText(resourceMap.getString("returnButton.text")); // NOI18N + returnButton.setName("returnButton"); // NOI18N + returnButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + returnButtonMouseClicked(evt); + } + }); + + questionLabel.setText(resourceMap.getString("questionLabel.text")); // NOI18N + questionLabel.setName("questionLabel"); // NOI18N + + buttonGroup1.add(browseButton); + browseButton.setText(resourceMap.getString("browseButton.text")); // NOI18N + browseButton.setName("browseButton"); // NOI18N + + buttonGroup1.add(downloadButton); + downloadButton.setText(resourceMap.getString("downloadButton.text")); // NOI18N + downloadButton.setName("downloadButton"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(406, Short.MAX_VALUE) + .addComponent(returnButton) + .addGap(18, 18, 18) + .addComponent(nextButton) + .addGap(74, 74, 74)) + .addGroup(layout.createSequentialGroup() + .addGap(42, 42, 42) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(questionLabel) + .addGroup(layout.createSequentialGroup() + .addGap(12, 12, 12) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadButton) + .addComponent(browseButton)))) + .addContainerGap(32, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(54, 54, 54) + .addComponent(questionLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(browseButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(downloadButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nextButton) + .addComponent(returnButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void returnButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_returnButtonMouseClicked + saveButtonSelection(); + PropertyManager.saveProps(props); + new SpeedSelector(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_returnButtonMouseClicked + +private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked + saveButtonSelection(); + PropertyManager.saveProps(props); + new SpeedSelector3(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_nextButtonMouseClicked + +private void loadButtonSelection() { + Enumeration elements = buttonGroup1.getElements(); + while(elements.hasMoreElements()) { + AbstractButton button = elements.nextElement(); + if(button == null) + continue; + if(props.getProperty(SpeedSelectorConstants.USERTYPE) == null) + break; + String type = button.getText().split(":")[0]; + if(type.equals(props.getProperty(SpeedSelectorConstants.USERTYPE))) { + button.setSelected(true); + break; + } + } +} + +private void saveButtonSelection() { + Enumeration elements = buttonGroup1.getElements(); + while(elements.hasMoreElements()) { + AbstractButton button = elements.nextElement(); + if(button == null) + continue; + if(button.isSelected()) { + String type = button.getText().split(":")[0]; + props.setProperty(SpeedSelectorConstants.USERTYPE, type); + break; + } + } +} + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton browseButton; + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JRadioButton downloadButton; + private javax.swing.JButton nextButton; + private javax.swing.JLabel questionLabel; + private javax.swing.JButton returnButton; + // End of variables declaration//GEN-END:variables + +} diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/gui/SpeedSelector3.form new file mode 100644 index 0000000000..419a22fc15 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector3.form @@ -0,0 +1,223 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/gui/SpeedSelector3.java new file mode 100644 index 0000000000..e226f74226 --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelector3.java @@ -0,0 +1,286 @@ +/* + * ProfileSelector3.java + * + * Created on 3 april 2009, 15:17 + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.Properties; +import persistence.PropertyManager; +import router.configuration.SpeedHandler; +import router.configuration.SpeedHelper; + +/** + * + * @author mathias + */ +public class SpeedSelector3 extends javax.swing.JFrame { + Properties props; + + /** Creates new form ProfileSelector3 */ + public SpeedSelector3(Point point, Dimension dimension) { + this.props = PropertyManager.getProps(); + initComponents(); + this.setLocation(point); + this.setSize(dimension); + initSpeeds(); + this.setVisible(true); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + finishButton = new javax.swing.JButton(); + previousButton = new javax.swing.JButton(); + jLabel1 = new javax.swing.JLabel(); + uploadLabel = new javax.swing.JLabel(); + downloadLabel = new javax.swing.JLabel(); + uploadBurstLabel = new javax.swing.JLabel(); + downloadBurstLabel = new javax.swing.JLabel(); + uploadUsageLabel = new javax.swing.JLabel(); + downloadUsageLabel = new javax.swing.JLabel(); + uploadField = new javax.swing.JTextField(); + uploadBurstField = new javax.swing.JTextField(); + downloadField = new javax.swing.JTextField(); + downloadBurstField = new javax.swing.JTextField(); + uploadMonth = new javax.swing.JLabel(); + downloadMonth = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class); + setTitle(resourceMap.getString("Form.title")); // NOI18N + setName("Form"); // NOI18N + + finishButton.setText(resourceMap.getString("finishButton.text")); // NOI18N + finishButton.setName("finishButton"); // NOI18N + finishButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + finishButtonMouseClicked(evt); + } + }); + + previousButton.setText(resourceMap.getString("previousButton.text")); // NOI18N + previousButton.setName("previousButton"); // NOI18N + previousButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + previousButtonMouseClicked(evt); + } + }); + + jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N + jLabel1.setName("jLabel1"); // NOI18N + + uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N + uploadLabel.setName("uploadLabel"); // NOI18N + + downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N + downloadLabel.setName("downloadLabel"); // NOI18N + + uploadBurstLabel.setText(resourceMap.getString("uploadBurstLabel.text")); // NOI18N + uploadBurstLabel.setName("uploadBurstLabel"); // NOI18N + + downloadBurstLabel.setText(resourceMap.getString("downloadBurstLabel.text")); // NOI18N + downloadBurstLabel.setName("downloadBurstLabel"); // NOI18N + + uploadUsageLabel.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N + uploadUsageLabel.setName("uploadUsageLabel"); // NOI18N + + downloadUsageLabel.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N + downloadUsageLabel.setName("downloadUsageLabel"); // NOI18N + + uploadField.setText(resourceMap.getString("uploadField.text")); // NOI18N + uploadField.setMinimumSize(new java.awt.Dimension(77, 27)); + uploadField.setName("uploadField"); // NOI18N + uploadField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + speedFieldKeyReleased(evt); + } + }); + + uploadBurstField.setText(resourceMap.getString("uploadBurstField.text")); // NOI18N + uploadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); + uploadBurstField.setName("uploadBurstField"); // NOI18N + + downloadField.setText(resourceMap.getString("downloadField.text")); // NOI18N + downloadField.setMinimumSize(new java.awt.Dimension(77, 27)); + downloadField.setName("downloadField"); // NOI18N + downloadField.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + speedFieldKeyReleased(evt); + } + }); + + downloadBurstField.setText(resourceMap.getString("downloadBurstField.text")); // NOI18N + downloadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); + downloadBurstField.setName("downloadBurstField"); // NOI18N + + uploadMonth.setText(resourceMap.getString("uploadMonth.text")); // NOI18N + uploadMonth.setName("uploadMonth"); // NOI18N + + downloadMonth.setText(resourceMap.getString("downloadMonth.text")); // NOI18N + downloadMonth.setName("downloadMonth"); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel1) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(uploadLabel) + .addComponent(uploadBurstLabel) + .addComponent(uploadUsageLabel)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(uploadBurstField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(uploadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(uploadMonth, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(46, 46, 46) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(downloadLabel) + .addComponent(downloadBurstLabel) + .addComponent(downloadUsageLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(downloadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(downloadMonth, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(downloadBurstField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addGap(18, 18, 18)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(previousButton) + .addGap(18, 18, 18) + .addComponent(finishButton) + .addGap(33, 33, 33))) + .addGap(400, 400, 400)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGap(81, 81, 81) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadLabel) + .addComponent(downloadLabel) + .addComponent(uploadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(downloadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadBurstLabel) + .addComponent(downloadBurstLabel) + .addComponent(downloadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(uploadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(uploadUsageLabel) + .addComponent(downloadUsageLabel) + .addComponent(uploadMonth) + .addComponent(downloadMonth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(previousButton) + .addComponent(finishButton)) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + +private void previousButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_previousButtonMouseClicked + saveSpeeds(); + PropertyManager.saveProps(props); + new SpeedSelector2(this.getLocationOnScreen(), this.getSize()).setVisible(true); + this.dispose(); +}//GEN-LAST:event_previousButtonMouseClicked + +private void finishButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_finishButtonMouseClicked + saveSpeeds(); + PropertyManager.saveProps(props); + + int maxDownload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOAD)); + int maxUpload = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOAD)); + int maxUploadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXUPLOADBURST)); + int maxDownloadBurst = Integer.parseInt(props.getProperty(SpeedSelectorConstants.MAXDOWNLOADBURST)); + + //Working in kB, not kb! + SpeedHandler.setInboundBandwidth(maxDownload/8); + SpeedHandler.setOutboundBandwidth(maxUpload/8); + SpeedHandler.setInboundBurstBandwidth(maxDownloadBurst); + SpeedHandler.setOutboundBurstBandwidth(maxUploadBurst/8); + + this.dispose(); +}//GEN-LAST:event_finishButtonMouseClicked + +private void speedFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_speedFieldKeyReleased + try { + initUsage(uploadField.getText(), downloadField.getText()); + } + catch(NumberFormatException e) { + return; + } +}//GEN-LAST:event_speedFieldKeyReleased + + protected void initSpeeds() { + String up = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String upBurst = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String down = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String downBurst = "" + SpeedHelper.calculateSpeed( + props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); + String userType = props.getProperty(SpeedSelectorConstants.USERTYPE); + + uploadField.setText(up); + uploadBurstField.setText(upBurst); + downloadField.setText(down); + downloadBurstField.setText(downBurst); + + initUsage(up, down); + } + + protected void saveSpeeds() { + props.setProperty(SpeedSelectorConstants.MAXUPLOAD, uploadField.getText()); + props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, uploadBurstField.getText()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, downloadField.getText()); + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, downloadBurstField.getText()); + } + + protected void initUsage(String upload, String download) { + uploadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)/8) + " GB"); + downloadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)/8) + " GB"); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JTextField downloadBurstField; + private javax.swing.JLabel downloadBurstLabel; + private javax.swing.JTextField downloadField; + private javax.swing.JLabel downloadLabel; + private javax.swing.JLabel downloadMonth; + private javax.swing.JLabel downloadUsageLabel; + private javax.swing.JButton finishButton; + private javax.swing.JLabel jLabel1; + private javax.swing.JButton previousButton; + private javax.swing.JTextField uploadBurstField; + private javax.swing.JLabel uploadBurstLabel; + private javax.swing.JTextField uploadField; + private javax.swing.JLabel uploadLabel; + private javax.swing.JLabel uploadMonth; + private javax.swing.JLabel uploadUsageLabel; + // End of variables declaration//GEN-END:variables + +} diff --git a/apps/desktopgui/src/gui/SpeedSelectorConstants.java b/apps/desktopgui/src/gui/SpeedSelectorConstants.java new file mode 100644 index 0000000000..ea5e32427b --- /dev/null +++ b/apps/desktopgui/src/gui/SpeedSelectorConstants.java @@ -0,0 +1,25 @@ +package gui; + +/** + * + * @author mathias + */ +public class SpeedSelectorConstants { + ///Maximum upload speed for the internet connection + public static final String MAXUPLOADCAPABLE = "maxUploadCapable"; + ///Maximum download speed for the internet connection + public static final String MAXDOWNLOADCAPABLE = "maxDownloadCapable"; + + //User profile type: what behaviour does this user have while using IP2? + public static final String USERTYPE = "userType"; + + //Maximum upload speed for I2P + public static final String MAXUPLOAD = "maxUpload"; + //Maximum upload burst speed for I2P + public static final String MAXUPLOADBURST = "maxUploadBurst"; + + //Maximum download speed for I2P + public static final String MAXDOWNLOAD = "maxDownload"; + //Maximum download burst speed for I2P + public static final String MAXDOWNLOADBURST = "maxDownloadBurst"; +} diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/gui/Tray.java new file mode 100644 index 0000000000..6ed5ea0d49 --- /dev/null +++ b/apps/desktopgui/src/gui/Tray.java @@ -0,0 +1,138 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package gui; + +import desktopgui.*; +import java.awt.AWTException; +import java.awt.Desktop; +import java.awt.Image; +import java.awt.MenuItem; +import java.awt.Menu; +import java.awt.PopupMenu; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.awt.TrayIcon; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.logging.Level; +import java.util.logging.Logger; +import router.RouterHandler; + +/** + * + * @author mathias + */ +public class Tray { + + public Tray() { + tray = SystemTray.getSystemTray(); + loadSystemTray(); + } + + private void loadSystemTray() { + + Image image = Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo.jpg"); + + PopupMenu popup = new PopupMenu(); + + //Create menu items to put in the popup menu + MenuItem browserLauncher = new MenuItem("Launch browser"); + browserLauncher.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI("http://localhost:7657")); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + MenuItem howto = new MenuItem("How to use I2P"); + howto.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + File f = new File("desktopgui/resources/howto/howto.html"); + desktop.browse(new URI("file://" + f.getAbsolutePath())); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + Menu config = new Menu("Configuration"); + MenuItem speedConfig = new MenuItem("Speed"); + speedConfig.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + (new SpeedSelector()).setVisible(true); + } + + }); + MenuItem advancedConfig = new MenuItem("Advanced Configuration"); + advancedConfig.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + if(Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI("http://localhost:7657/config.jsp")); + } catch (URISyntaxException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + }); + MenuItem shutdown = new MenuItem("Shutdown I2P"); + shutdown.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent arg0) { + RouterHandler.setStatus(RouterHandler.SHUTDOWN_GRACEFULLY); + } + + }); + + //Add menu items to popup menu + popup.add(browserLauncher); + popup.add(howto); + + config.add(speedConfig); + config.add(advancedConfig); + popup.add(config); + + popup.add(shutdown); + + //Add tray icon + trayIcon = new TrayIcon(image, "I2P: the anonymous network", popup); + try { + tray.add(trayIcon); + } catch (AWTException ex) { + Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private SystemTray tray = null; + private TrayIcon trayIcon = null; + +} diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/gui/resources/SpeedSelector.properties new file mode 100644 index 0000000000..1a476d8741 --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector.properties @@ -0,0 +1,7 @@ + +Form.title=I2P Configuration +nextButton.text=Next +uploadLabel.text=What is your maximum upload speed? +downloadLabel.text=What is your maximum download speed? +kbps1.text=kbit/second +kbps2.text=kbit/second diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties new file mode 100644 index 0000000000..704d9909ec --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties @@ -0,0 +1,6 @@ +returnButton.text=Previous +Form.title=I2P Configuration +questionLabel.text=Which of these descriptions fits you best? +browseButton.text=Browsing: I want to use I2P to browse websites anonymously, no heavy usage. +downloadButton.text=Downloading: I want to use I2P for downloads and filesharing, heavy usage. +nextButton.text=Next diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties new file mode 100644 index 0000000000..49ee18ba00 --- /dev/null +++ b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties @@ -0,0 +1,16 @@ +Form.title=I2P Configuration +jLabel1.text=The profile information your entered, indicates that these are your optimal settings: +previousButton.text=Previous +finishButton.text=Finish +uploadLabel.text=Upload Speed: +uploadBurstLabel.text=Burst Upload Speed: +downloadLabel.text=Download Speed: +downloadBurstLabel.text=Burst Download Speed: +uploadUsageLabel.text=Monthy upload usage: +downloadUsageLabel.text=Monthy Download Usage: +uploadField.text=jTextField1 +uploadBurstField.text=jTextField2 +uploadMonth.text=jLabel8 +downloadMonth.text=jLabel9 +downloadField.text=jTextField4 +downloadBurstField.text=jTextField5 diff --git a/apps/desktopgui/src/persistence/PropertyManager.java b/apps/desktopgui/src/persistence/PropertyManager.java new file mode 100644 index 0000000000..bacbf348ac --- /dev/null +++ b/apps/desktopgui/src/persistence/PropertyManager.java @@ -0,0 +1,72 @@ +package persistence; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author mathias + */ +public class PropertyManager { + + public static void setProps(Properties props) { + PropertyManager.props = props; + } + + public static Properties getProps() { + return props; + } + + public static Properties loadProps() { + Properties defaultProps = new Properties(); + defaultProps.setProperty("firstLoad", "true"); + + // create application properties with default + Properties applicationProps = new Properties(defaultProps); + + // now load properties from last invocation + FileInputStream in; + try { + in = new FileInputStream(PROPSLOCATION); + applicationProps.load(in); + in.close(); + } catch (FileNotFoundException ex) { + //Nothing serious, just means it's being loaded for the first time. + } catch(IOException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.INFO, null, ex); + } + props = applicationProps; + return applicationProps; + } + + public static void saveProps(Properties props) { + FileOutputStream out; + try { + File d = new File(PROPSDIRECTORY); + if(!d.exists()) + d.mkdir(); + File f = new File(PROPSLOCATION); + if(!f.exists()) + f.createNewFile(); + out = new FileOutputStream(f); + props.store(out, PROPSLOCATION); + } catch (FileNotFoundException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(IOException ex) { + Logger.getLogger(PropertyManager.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static Properties props; + + ///Location where we store the Application Properties + public static final String PROPSDIRECTORY = "desktopgui"; + public static final String PROPSFILENAME = "appProperties"; + public static final String PROPSLOCATION = PROPSDIRECTORY + File.separator + PROPSFILENAME; +} diff --git a/apps/desktopgui/src/router/RouterHandler.java b/apps/desktopgui/src/router/RouterHandler.java new file mode 100644 index 0000000000..0752f877b5 --- /dev/null +++ b/apps/desktopgui/src/router/RouterHandler.java @@ -0,0 +1,38 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package router; + +import java.util.logging.Level; +import java.util.logging.Logger; +import net.i2p.router.RouterContext; + +/** + * + * @author mathias + */ +public class RouterHandler { + public static final int SHUTDOWN_GRACEFULLY = 0; + public static void setStatus(int status) { + if(status == SHUTDOWN_GRACEFULLY) { + Thread t = new Thread(new Runnable() { + + public void run() { + RouterContext context = RouterHelper.getContext(); + context.router().shutdownGracefully(); + while(context.router().getShutdownTimeRemaining()>0) + try { + Thread.sleep(context.router().getShutdownTimeRemaining()); + } catch (InterruptedException ex) { + Logger.getLogger(RouterHandler.class.getName()).log(Level.SEVERE, null, ex); + } + System.exit(0); + } + + }); + t.start(); + } + } +} diff --git a/apps/desktopgui/src/router/RouterHelper.java b/apps/desktopgui/src/router/RouterHelper.java new file mode 100644 index 0000000000..c5fc78e959 --- /dev/null +++ b/apps/desktopgui/src/router/RouterHelper.java @@ -0,0 +1,13 @@ +package router; + +import net.i2p.router.RouterContext; + +/** + * + * @author mathias + */ +public class RouterHelper { + public static RouterContext getContext() { + return (RouterContext) RouterContext.listContexts().get(0); + } +} diff --git a/apps/desktopgui/src/router/configuration/SpeedHandler.java b/apps/desktopgui/src/router/configuration/SpeedHandler.java new file mode 100644 index 0000000000..235790792a --- /dev/null +++ b/apps/desktopgui/src/router/configuration/SpeedHandler.java @@ -0,0 +1,34 @@ +package router.configuration; + +import net.i2p.router.RouterContext; +import net.i2p.router.transport.FIFOBandwidthRefiller; +import router.RouterHelper; + +/** + * + * @author mathias + */ +public class SpeedHandler { + + public static void setInboundBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setOutboundBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setInboundBurstBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BURST_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + public static void setOutboundBurstBandwidth(int kbytes) { + context.router().setConfigSetting(FIFOBandwidthRefiller.PROP_OUTBOUND_BURST_BANDWIDTH, "" + kbytes); + context.router().saveConfig(); + } + + private static final RouterContext context = RouterHelper.getContext(); +} diff --git a/apps/desktopgui/src/router/configuration/SpeedHelper.java b/apps/desktopgui/src/router/configuration/SpeedHelper.java new file mode 100644 index 0000000000..1cce2f9c03 --- /dev/null +++ b/apps/desktopgui/src/router/configuration/SpeedHelper.java @@ -0,0 +1,28 @@ +package router.configuration; + +/** + * + * @author mathias + */ +public class SpeedHelper { + public static final String USERTYPE_BROWSING = "Browsing"; + public static final String USERTYPE_DOWNLOADING = "Downloading"; + + public static int calculateSpeed(String capable, String profile) { + int capableSpeed = Integer.parseInt(capable); + int advisedSpeed = capableSpeed; + if(capableSpeed > 1000) { + if(profile.equals(USERTYPE_BROWSING)) //Don't overdo usage for people just wanting to browse (we don't want to drive them away due to resource hogging) + advisedSpeed *= 0.6; + else if(profile.equals(USERTYPE_DOWNLOADING)) + advisedSpeed *= 0.8; + } + else + advisedSpeed *= 0.6; //Lower available bandwidth: don't hog all the bandwidth + return advisedSpeed; + } + + public static int calculateMonthlyUsage(int kbytes) { + return (kbytes*3600*24*31)/1000000; + } +} diff --git a/apps/desktopgui/src/util/IntegerVerifier.java b/apps/desktopgui/src/util/IntegerVerifier.java new file mode 100644 index 0000000000..74f87961da --- /dev/null +++ b/apps/desktopgui/src/util/IntegerVerifier.java @@ -0,0 +1,32 @@ +package util; + +import javax.swing.InputVerifier; +import javax.swing.JComponent; +import javax.swing.JTextField; + +/** + * + * @author mathias + */ + +public class IntegerVerifier extends InputVerifier { + + @Override + public boolean verify(JComponent arg0) { + JTextField jtf = (JTextField) arg0; + return verify(jtf.getText()); + } + + @Override + public boolean shouldYieldFocus(JComponent input) { + return verify(input); + } + + public static boolean verify(String s) { + for(int i=0;i '9' || s.charAt(i) < '0') + return false; + return true; + } + +} \ No newline at end of file diff --git a/build.xml b/build.xml index ef84b2aab3..26254cd15a 100644 --- a/build.xml +++ b/build.xml @@ -37,6 +37,7 @@ + @@ -104,6 +105,7 @@ + @@ -214,6 +216,13 @@ + + + + + + + diff --git a/installer/resources/wrapper.config b/installer/resources/wrapper.config index 4d09ba12c2..42958ba779 100644 --- a/installer/resources/wrapper.config +++ b/installer/resources/wrapper.config @@ -54,6 +54,10 @@ wrapper.java.classpath.17=lib/systray.jar wrapper.java.classpath.18=lib/systray4j.jar # BOB wrapper.java.classpath.19=lib/BOB.jar +# desktopgui +wrapper.java.classpath.20=lib/appframework.jar +wrapper.java.classpath.21=lib/swing-worker.jar +wrapper.java.classpath.22=lib/desktopgui.jar # Java Library Path (location of Wrapper.DLL or libwrapper.so) wrapper.java.library.path.1=. From 37667247c3d2c0b7a5d26bb6d136497cddd05294 Mon Sep 17 00:00:00 2001 From: sponge Date: Mon, 6 Apr 2009 22:40:22 +0000 Subject: [PATCH 11/27] 2009-04-06 sponge SimpleScheduler SimpleTimer2 debugging added. Fix build files for desktopgui. --- apps/desktopgui/build.xml | 5 ++++ apps/desktopgui/nbproject/build-impl.xml | 25 ++++++++++++++----- apps/desktopgui/nbproject/genfiles.properties | 8 +++--- apps/desktopgui/nbproject/project.properties | 4 +-- .../src/net/i2p/util/SimpleScheduler.java | 7 +++++- core/java/src/net/i2p/util/SimpleTimer2.java | 7 +++++- history.txt | 4 +++ .../src/net/i2p/router/RouterVersion.java | 2 +- 8 files changed, 47 insertions(+), 15 deletions(-) diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml index c7ba1be112..dbcbab67c9 100644 --- a/apps/desktopgui/build.xml +++ b/apps/desktopgui/build.xml @@ -2,6 +2,11 @@ + + + + + Builds, tests, and runs the project desktopgui. diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml index f8fea458d1..039f8788f1 100644 --- a/apps/desktopgui/nbproject/build-impl.xml +++ b/apps/desktopgui/nbproject/build-impl.xml @@ -152,7 +152,7 @@ is divided into following sections: - + @@ -218,13 +218,13 @@ is divided into following sections: - + - + @@ -255,6 +255,12 @@ is divided into following sections: + + + + + + @@ -264,7 +270,7 @@ is divided into following sections: - + @@ -311,6 +317,13 @@ is divided into following sections: =================== --> + + + + + + + @@ -331,7 +344,7 @@ is divided into following sections: - + @@ -345,7 +358,7 @@ is divided into following sections: - + From 91de396821334b9c88fc5cfbd1b4f76df778ddad Mon Sep 17 00:00:00 2001 From: amiga4000 Date: Tue, 7 Apr 2009 12:05:00 +0000 Subject: [PATCH 16/27] added echelon.i2p to readme of I2P router --- readme.html | 1 + readme_de.html | 3 ++- readme_fr.html | 1 + readme_sv.html | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/readme.html b/readme.html index 0e4891b5d8..c359913350 100644 --- a/readme.html +++ b/readme.html @@ -16,6 +16,7 @@ you can:

  • ugha.i2p: ugha's eepsite, a wiki that anyone can edit, and lots of links
  • fproxy.tino.i2p: Freenet proxy
  • +
  • echelon.i2p: software archive and information for I2P
  • There are many more eepsites - just follow the links from the ones you see, bookmark your favorites, and visit them often! diff --git a/readme_de.html b/readme_de.html index 1534585dab..e6fd916188 100644 --- a/readme_de.html +++ b/readme_de.html @@ -12,7 +12,8 @@
  • eepsites.i2p: Eine anonym gehostete Suchseite für Eepsites
  • ugha.i2p: Ugha's Eepsite, ein öffentliches Wiki mit vielen Links
  • fproxy.tino.i2p: Freenet proxy
  • - +
  • echelon.i2p: Software Archive und Informationen zu I2P
  • + Es gibt viel mehr Eepsites - folge einfach den Links die du findest, bookmarke Deine Favoriten und besuche sie oft!
  • Im Internet surfen - Es gibt einen HTTP "outproxy" in I2P in Deinem diff --git a/readme_fr.html b/readme_fr.html index 5e619cff2b..f2accf826c 100644 --- a/readme_fr.html +++ b/readme_fr.html @@ -11,6 +11,7 @@
  • eepsites.i2p: un moteur de recherche d'eepsites
  • ugha.i2p: l'eepsite d'ugha, un wiki que chaucun peut éditer ainsi que
  • fproxy.tino.i2p: un proxy Freenet 0.5
  • +
  • echelon.i2p: archive
  • Il y a bien plus d'eepsites - suivez juste les liens au départ de ceux sur lesquels vous êtes, mettez-les dans vos favoris et visitez-les souvent!
  • Parcourez le web - Il y a pour l'instant un outproxy HTTP sur I2P attaché à votre propre proxy HTTP sur le port 4444 - vous devez simplement configurer le proxy de votre navigateur pour l'utiliser (comme expliqué ci-dessus) et aller sur n'importe quel URL normale - vos requêtes seront relayées par le réseau i2p.
  • diff --git a/readme_sv.html b/readme_sv.html index b1f59fa04d..eab177cd14 100644 --- a/readme_sv.html +++ b/readme_sv.html @@ -27,6 +27,7 @@ sökmotor wiki som alla kan förändra, innehåller många länkar
  • fproxy.tino.i2p: Freenet proxy
  • +
  • echelon.i2p: software archive and information for I2P
  • Det finns många fler eepsidor - följ bara länkarna från dom du ser, spara dina favoriter och besök dom ofta! From 0764e19441a43651c144ec9d0d23c0440f34eed2 Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Tue, 7 Apr 2009 17:55:59 +0000 Subject: [PATCH 17/27] New build file for desktopgui. Should work fine for anyone using java 1.6. --- apps/desktopgui/build.xml | 113 +++++++----------- apps/desktopgui/nbproject/build-impl.xml | 25 +--- apps/desktopgui/nbproject/genfiles.properties | 8 +- apps/desktopgui/nbproject/project.properties | 15 ++- .../src/desktopgui/resources/Main.properties | 2 +- 5 files changed, 67 insertions(+), 96 deletions(-) diff --git a/apps/desktopgui/build.xml b/apps/desktopgui/build.xml index dbcbab67c9..cf262b2ec4 100644 --- a/apps/desktopgui/build.xml +++ b/apps/desktopgui/build.xml @@ -1,74 +1,51 @@ - - - - - - - - - - - Builds, tests, and runs the project desktopgui. - - + + + + + + + + + + - There exist several targets which are by default empty and which can be - used for execution of your tasks. These targets are usually executed - before and after some main targets. They are: + + + + + + + - -pre-init: called before initialization of project properties - -post-init: called after initialization of project properties - -pre-compile: called before javac compilation - -post-compile: called after javac compilation - -pre-compile-single: called before javac compilation of single file - -post-compile-single: called after javac compilation of single file - -pre-compile-test: called before javac compilation of JUnit tests - -post-compile-test: called after javac compilation of JUnit tests - -pre-compile-test-single: called before javac compilation of single JUnit test - -post-compile-test-single: called after javac compilation of single JUunit test - -pre-jar: called before JAR building - -post-jar: called after JAR building - -post-clean: called after cleaning build products + + + + + + + + + - (Targets beginning with '-' are not intended to be called on their own.) + + + - Example of inserting an obfuscator after compilation could look like this: + + + - - - - - - - For list of available properties check the imported - nbproject/build-impl.xml file. - - - Another way to customize the build is by overriding existing main targets. - The targets of interest are: - - -init-macrodef-javac: defines macro for javac compilation - -init-macrodef-junit: defines macro for junit execution - -init-macrodef-debug: defines macro for class debugging - -init-macrodef-java: defines macro for class execution - -do-jar-with-manifest: JAR building (if you are using a manifest) - -do-jar-without-manifest: JAR building (if you are not using a manifest) - run: execution of project - -javadoc-build: Javadoc generation - test-report: JUnit report generation - - An example of overriding the target for project execution could look like this: - - - - - - - - Notice that the overridden target depends on the jar target and not only on - the compile target as the regular run target does. Again, for a list of available - properties which you can use, check the target you are overriding in the - nbproject/build-impl.xml file. - - --> + + + + + diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml index 039f8788f1..f8fea458d1 100644 --- a/apps/desktopgui/nbproject/build-impl.xml +++ b/apps/desktopgui/nbproject/build-impl.xml @@ -152,7 +152,7 @@ is divided into following sections: - + @@ -218,13 +218,13 @@ is divided into following sections: - + - + @@ -255,12 +255,6 @@ is divided into following sections: - - - - - - @@ -270,7 +264,7 @@ is divided into following sections: - + @@ -317,13 +311,6 @@ is divided into following sections: =================== --> - - - - - - - @@ -344,7 +331,7 @@ is divided into following sections: - + @@ -358,7 +345,7 @@ is divided into following sections: - + - - - - - - - - - - + + + + + + + + + + + Builds, tests, and runs the project desktopgui. + + - - - - + There exist several targets which are by default empty and which can be + used for execution of your tasks. These targets are usually executed + before and after some main targets. They are: - + + + + + + For list of available properties check the imported + nbproject/build-impl.xml file. + + + Another way to customize the build is by overriding existing main targets. + The targets of interest are: + + -init-macrodef-javac: defines macro for javac compilation + -init-macrodef-junit: defines macro for junit execution + -init-macrodef-debug: defines macro for class debugging + -init-macrodef-java: defines macro for class execution + -do-jar-with-manifest: JAR building (if you are using a manifest) + -do-jar-without-manifest: JAR building (if you are not using a manifest) + run: execution of project + -javadoc-build: Javadoc generation + test-report: JUnit report generation + + An example of overriding the target for project execution could look like this: + + + + + + + + Notice that the overridden target depends on the jar target and not only on + the compile target as the regular run target does. Again, for a list of available + properties which you can use, check the target you are overriding in the + nbproject/build-impl.xml file. + + --> + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + - - + + - - - + + + - - - - - + + + + diff --git a/apps/desktopgui/nbproject/project.properties b/apps/desktopgui/nbproject/project.properties index b5bf07f649..b7a05f8ed6 100644 --- a/apps/desktopgui/nbproject/project.properties +++ b/apps/desktopgui/nbproject/project.properties @@ -44,6 +44,7 @@ javac.test.classpath=\ javadoc.additionalparam= javadoc.author=false javadoc.encoding=${source.encoding} +javadoc.encoding.used=${javadoc.encoding} javadoc.noindex=false javadoc.nonavbar=false javadoc.notree=false @@ -53,6 +54,7 @@ javadoc.use=true javadoc.version=false javadoc.windowtitle= jnlp.codebase.type=local +jnlp.codebase.url=file:/home/mathias/Documenten/Programmeren/i2p_monotone/repo/i2p.i2p/apps/desktopgui/dist/ jnlp.enabled=false jnlp.offline-allowed=false jnlp.signed=false diff --git a/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties b/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties new file mode 100644 index 0000000000..81107aca91 --- /dev/null +++ b/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties @@ -0,0 +1,11 @@ +# Application global resources + +Application.name = desktopgui +Application.title = I2P Desktop GUI +Application.version = 0.7.1 +Application.vendor = I2P Ontwikkelaars +Application.homepage = http://www.i2p2.de +Application.description = Een anoniem communicatienetwerk. +Application.vendorId = I2P +Application.id = desktopgui +Application.lookAndFeel = system diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/gui/SpeedSelector.form index b256265de0..6ccf414b5b 100644 --- a/apps/desktopgui/src/gui/SpeedSelector.form +++ b/apps/desktopgui/src/gui/SpeedSelector.form @@ -4,7 +4,11 @@ + + + + @@ -20,85 +24,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -121,6 +91,11 @@ + + + + + @@ -143,18 +118,60 @@ + + + + + - + - - + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/src/gui/SpeedSelector.java b/apps/desktopgui/src/gui/SpeedSelector.java index 19f4875341..bce18621f2 100644 --- a/apps/desktopgui/src/gui/SpeedSelector.java +++ b/apps/desktopgui/src/gui/SpeedSelector.java @@ -9,6 +9,7 @@ package gui; import java.awt.Dimension; import java.awt.Point; import java.util.Properties; +import javax.swing.JComboBox; import javax.swing.JTextField; import persistence.PropertyManager; import util.IntegerVerifier; @@ -26,12 +27,13 @@ public class SpeedSelector extends javax.swing.JFrame { initComponentsCustom(); initSpeeds(props); this.setVisible(true); + this.setLocationRelativeTo(null); + this.requestFocus(); } - public SpeedSelector(Point point, Dimension dimension) { + public SpeedSelector(Point point) { this(); this.setLocation(point); - this.setSize(dimension); } public void initComponentsCustom() { @@ -53,101 +55,115 @@ public class SpeedSelector extends javax.swing.JFrame { downloadLabel = new javax.swing.JLabel(); uploadChoice = new javax.swing.JComboBox(); downloadChoice = new javax.swing.JComboBox(); - kbps1 = new javax.swing.JLabel(); - kbps2 = new javax.swing.JLabel(); + speedExplanation = new javax.swing.JLabel(); + uploadkbps = new javax.swing.JComboBox(); + downloadkbps = new javax.swing.JComboBox(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class); setTitle(resourceMap.getString("Form.title")); // NOI18N + setMinimumSize(new java.awt.Dimension(610, 330)); setName("Form"); // NOI18N + setResizable(false); + getContentPane().setLayout(null); nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N + nextButton.setMaximumSize(new java.awt.Dimension(72, 29)); + nextButton.setMinimumSize(new java.awt.Dimension(72, 29)); nextButton.setName("nextButton"); // NOI18N nextButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { nextButtonMouseClicked(evt); } }); + getContentPane().add(nextButton); + nextButton.setBounds(440, 250, 90, 29); uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N uploadLabel.setName("uploadLabel"); // NOI18N + getContentPane().add(uploadLabel); + uploadLabel.setBounds(20, 60, 246, 30); downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N downloadLabel.setName("downloadLabel"); // NOI18N + getContentPane().add(downloadLabel); + downloadLabel.setBounds(20, 110, 263, 30); uploadChoice.setEditable(true); uploadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); uploadChoice.setSelectedIndex(3); uploadChoice.setName("uploadChoice"); // NOI18N + getContentPane().add(uploadChoice); + uploadChoice.setBounds(300, 60, 154, 27); downloadChoice.setEditable(true); downloadChoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "100", "200", "500", "1000", "2000", "4000", "8000", "10000", "20000", "50000", "100000" })); downloadChoice.setSelectedIndex(3); downloadChoice.setName("downloadChoice"); // NOI18N + getContentPane().add(downloadChoice); + downloadChoice.setBounds(300, 110, 154, 27); - kbps1.setText(resourceMap.getString("kbps1.text")); // NOI18N - kbps1.setName("kbps1"); // NOI18N + speedExplanation.setText(resourceMap.getString("speedExplanation.text")); // NOI18N + speedExplanation.setName("speedExplanation"); // NOI18N + getContentPane().add(speedExplanation); + speedExplanation.setBounds(20, 160, 570, 60); - kbps2.setText(resourceMap.getString("kbps2.text")); // NOI18N - kbps2.setName("kbps2"); // NOI18N + uploadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + uploadkbps.setName("uploadKbit"); // NOI18N + uploadkbps.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + uploadkbpsActionPerformed(evt); + } + }); + getContentPane().add(uploadkbps); + uploadkbps.setBounds(470, 60, 68, 27); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(49, 49, 49) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(downloadLabel) - .addComponent(uploadLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(kbps2)) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(kbps1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(nextButton) - .addGap(34, 34, 34)))) - .addGap(40, 40, 40)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(67, 67, 67) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(uploadLabel) - .addComponent(uploadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(kbps1)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(downloadLabel) - .addComponent(downloadChoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(kbps2)) - .addContainerGap(173, Short.MAX_VALUE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap(271, Short.MAX_VALUE) - .addComponent(nextButton) - .addContainerGap()) - ); + downloadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + downloadkbps.setName("downloadKbit"); // NOI18N + downloadkbps.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + downloadkbpsActionPerformed(evt); + } + }); + getContentPane().add(downloadkbps); + downloadkbps.setBounds(470, 110, 68, 27); pack(); }// //GEN-END:initComponents private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked - props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, uploadChoice.getSelectedItem().toString()); - props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, downloadChoice.getSelectedItem().toString()); + if(uploadkbps.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, uploadChoice.getSelectedItem().toString()); + else + props.setProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE, "" + Integer.parseInt(uploadChoice.getSelectedItem().toString())*8); + if(downloadkbps.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, downloadChoice.getSelectedItem().toString()); + else + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE, "" + Integer.parseInt(downloadChoice.getSelectedItem().toString())*8); PropertyManager.saveProps(props); - new SpeedSelector2(this.getLocationOnScreen(), this.getSize()); + new SpeedSelector2(this.getLocationOnScreen()); this.dispose(); }//GEN-LAST:event_nextButtonMouseClicked +private void uploadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uploadkbpsActionPerformed + kbpsSwitchPerformed(uploadkbps, uploadChoice); +}//GEN-LAST:event_uploadkbpsActionPerformed + +private void downloadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadkbpsActionPerformed + kbpsSwitchPerformed(downloadkbps, downloadChoice); +}//GEN-LAST:event_downloadkbpsActionPerformed + +private void kbpsSwitchPerformed(JComboBox kbps, JComboBox speed) { + int index = kbps.getSelectedIndex(); + int previous = Integer.parseInt(speed.getSelectedItem().toString()); + if(index == KILOBIT) { + speed.setSelectedItem("" + previous*8); + } + else { + speed.setSelectedItem("" + previous/8); + } +} + private void initSpeeds(Properties props) { String up = props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE); String down = props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE); @@ -165,12 +181,15 @@ private void initSpeeds(Properties props) { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JComboBox downloadChoice; private javax.swing.JLabel downloadLabel; - private javax.swing.JLabel kbps1; - private javax.swing.JLabel kbps2; + private javax.swing.JComboBox downloadkbps; private javax.swing.JButton nextButton; + private javax.swing.JLabel speedExplanation; private javax.swing.JComboBox uploadChoice; private javax.swing.JLabel uploadLabel; + private javax.swing.JComboBox uploadkbps; // End of variables declaration//GEN-END:variables Properties props; + private static final int KILOBIT = 0; + private static final int KILOBYTE = 1; } diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/gui/SpeedSelector2.form index 2f0abc7863..215d407f0f 100644 --- a/apps/desktopgui/src/gui/SpeedSelector2.form +++ b/apps/desktopgui/src/gui/SpeedSelector2.form @@ -8,7 +8,11 @@ + + + + @@ -24,50 +28,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -78,6 +40,11 @@ + + + + + @@ -87,12 +54,22 @@ + + + + + + + + + + @@ -100,8 +77,14 @@ + + + + + + @@ -109,8 +92,25 @@ + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/gui/SpeedSelector2.java index a02ef9b53d..d929e4ccd2 100644 --- a/apps/desktopgui/src/gui/SpeedSelector2.java +++ b/apps/desktopgui/src/gui/SpeedSelector2.java @@ -21,13 +21,13 @@ public class SpeedSelector2 extends javax.swing.JFrame { Properties props; /** Creates new form ProfileSelector2 */ - public SpeedSelector2(Point point, Dimension dimension) { + public SpeedSelector2(Point point) { this.props = PropertyManager.getProps(); initComponents(); this.setLocation(point); - this.setSize(dimension); loadButtonSelection(); this.setVisible(true); + this.requestFocus(); } /** This method is called from within the constructor to @@ -45,11 +45,15 @@ public class SpeedSelector2 extends javax.swing.JFrame { questionLabel = new javax.swing.JLabel(); browseButton = new javax.swing.JRadioButton(); downloadButton = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class); setTitle(resourceMap.getString("Form.title")); // NOI18N + setMinimumSize(new java.awt.Dimension(610, 330)); setName("Form"); // NOI18N + setResizable(false); + getContentPane().setLayout(null); nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N nextButton.setName("nextButton"); // NOI18N @@ -58,6 +62,8 @@ public class SpeedSelector2 extends javax.swing.JFrame { nextButtonMouseClicked(evt); } }); + getContentPane().add(nextButton); + nextButton.setBounds(440, 250, 90, 29); returnButton.setText(resourceMap.getString("returnButton.text")); // NOI18N returnButton.setName("returnButton"); // NOI18N @@ -66,54 +72,32 @@ public class SpeedSelector2 extends javax.swing.JFrame { returnButtonMouseClicked(evt); } }); + getContentPane().add(returnButton); + returnButton.setBounds(336, 250, 90, 29); questionLabel.setText(resourceMap.getString("questionLabel.text")); // NOI18N questionLabel.setName("questionLabel"); // NOI18N + getContentPane().add(questionLabel); + questionLabel.setBounds(30, 40, 265, 17); buttonGroup1.add(browseButton); browseButton.setText(resourceMap.getString("browseButton.text")); // NOI18N + browseButton.setActionCommand(resourceMap.getString("browseButton.actionCommand")); // NOI18N browseButton.setName("browseButton"); // NOI18N + getContentPane().add(browseButton); + browseButton.setBounds(40, 130, 520, 40); buttonGroup1.add(downloadButton); downloadButton.setText(resourceMap.getString("downloadButton.text")); // NOI18N + downloadButton.setActionCommand(resourceMap.getString("downloadButton.actionCommand")); // NOI18N downloadButton.setName("downloadButton"); // NOI18N + getContentPane().add(downloadButton); + downloadButton.setBounds(40, 80, 499, 40); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap(406, Short.MAX_VALUE) - .addComponent(returnButton) - .addGap(18, 18, 18) - .addComponent(nextButton) - .addGap(74, 74, 74)) - .addGroup(layout.createSequentialGroup() - .addGap(42, 42, 42) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(questionLabel) - .addGroup(layout.createSequentialGroup() - .addGap(12, 12, 12) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(downloadButton) - .addComponent(browseButton)))) - .addContainerGap(32, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(54, 54, 54) - .addComponent(questionLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(browseButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(downloadButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 120, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nextButton) - .addComponent(returnButton)) - .addContainerGap()) - ); + jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N + jLabel1.setName("jLabel1"); // NOI18N + getContentPane().add(jLabel1); + jLabel1.setBounds(30, 190, 530, 50); pack(); }// //GEN-END:initComponents @@ -121,18 +105,19 @@ public class SpeedSelector2 extends javax.swing.JFrame { private void returnButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_returnButtonMouseClicked saveButtonSelection(); PropertyManager.saveProps(props); - new SpeedSelector(this.getLocationOnScreen(), this.getSize()).setVisible(true); + new SpeedSelector(this.getLocationOnScreen()); this.dispose(); }//GEN-LAST:event_returnButtonMouseClicked private void nextButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_nextButtonMouseClicked saveButtonSelection(); PropertyManager.saveProps(props); - new SpeedSelector3(this.getLocationOnScreen(), this.getSize()).setVisible(true); + new SpeedSelector3(this.getLocationOnScreen(), this.getSize()); this.dispose(); }//GEN-LAST:event_nextButtonMouseClicked private void loadButtonSelection() { + Enumeration elements = buttonGroup1.getElements(); while(elements.hasMoreElements()) { AbstractButton button = elements.nextElement(); @@ -140,7 +125,7 @@ private void loadButtonSelection() { continue; if(props.getProperty(SpeedSelectorConstants.USERTYPE) == null) break; - String type = button.getText().split(":")[0]; + String type = button.getActionCommand(); if(type.equals(props.getProperty(SpeedSelectorConstants.USERTYPE))) { button.setSelected(true); break; @@ -155,7 +140,7 @@ private void saveButtonSelection() { if(button == null) continue; if(button.isSelected()) { - String type = button.getText().split(":")[0]; + String type = button.getActionCommand(); props.setProperty(SpeedSelectorConstants.USERTYPE, type); break; } @@ -166,6 +151,7 @@ private void saveButtonSelection() { private javax.swing.JRadioButton browseButton; private javax.swing.ButtonGroup buttonGroup1; private javax.swing.JRadioButton downloadButton; + private javax.swing.JLabel jLabel1; private javax.swing.JButton nextButton; private javax.swing.JLabel questionLabel; private javax.swing.JButton returnButton; diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/gui/SpeedSelector3.form index 419a22fc15..ec26972216 100644 --- a/apps/desktopgui/src/gui/SpeedSelector3.form +++ b/apps/desktopgui/src/gui/SpeedSelector3.form @@ -4,7 +4,11 @@ + + + + @@ -18,91 +22,11 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -113,6 +37,11 @@ + + + + + @@ -122,48 +51,88 @@ + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -176,6 +145,11 @@ + + + + + @@ -185,6 +159,11 @@ + + + + + @@ -197,6 +176,11 @@ + + + + + @@ -206,18 +190,148 @@ + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/gui/SpeedSelector3.java index e226f74226..8115acb2bc 100644 --- a/apps/desktopgui/src/gui/SpeedSelector3.java +++ b/apps/desktopgui/src/gui/SpeedSelector3.java @@ -9,6 +9,8 @@ package gui; import java.awt.Dimension; import java.awt.Point; import java.util.Properties; +import javax.swing.JComboBox; +import javax.swing.JTextField; import persistence.PropertyManager; import router.configuration.SpeedHandler; import router.configuration.SpeedHelper; @@ -27,7 +29,9 @@ public class SpeedSelector3 extends javax.swing.JFrame { this.setLocation(point); this.setSize(dimension); initSpeeds(); + initUsage(); this.setVisible(true); + this.requestFocus(); } /** This method is called from within the constructor to @@ -41,7 +45,7 @@ public class SpeedSelector3 extends javax.swing.JFrame { finishButton = new javax.swing.JButton(); previousButton = new javax.swing.JButton(); - jLabel1 = new javax.swing.JLabel(); + settingsInfo = new javax.swing.JLabel(); uploadLabel = new javax.swing.JLabel(); downloadLabel = new javax.swing.JLabel(); uploadBurstLabel = new javax.swing.JLabel(); @@ -52,13 +56,23 @@ public class SpeedSelector3 extends javax.swing.JFrame { uploadBurstField = new javax.swing.JTextField(); downloadField = new javax.swing.JTextField(); downloadBurstField = new javax.swing.JTextField(); - uploadMonth = new javax.swing.JLabel(); - downloadMonth = new javax.swing.JLabel(); + kbpsBurstDownload = new javax.swing.JComboBox(); + kbpsUpload = new javax.swing.JComboBox(); + kbpsBurstUpload = new javax.swing.JComboBox(); + kbpsDownload = new javax.swing.JComboBox(); + uploadGB = new javax.swing.JLabel(); + uploadMonth = new javax.swing.JTextField(); + downloadMonth = new javax.swing.JTextField(); + downloadGB = new javax.swing.JLabel(); + explanation = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class); setTitle(resourceMap.getString("Form.title")); // NOI18N + setMinimumSize(new java.awt.Dimension(670, 330)); setName("Form"); // NOI18N + setResizable(false); + getContentPane().setLayout(null); finishButton.setText(resourceMap.getString("finishButton.text")); // NOI18N finishButton.setName("finishButton"); // NOI18N @@ -67,6 +81,8 @@ public class SpeedSelector3 extends javax.swing.JFrame { finishButtonMouseClicked(evt); } }); + getContentPane().add(finishButton); + finishButton.setBounds(440, 250, 90, 29); previousButton.setText(resourceMap.getString("previousButton.text")); // NOI18N previousButton.setName("previousButton"); // NOI18N @@ -75,27 +91,43 @@ public class SpeedSelector3 extends javax.swing.JFrame { previousButtonMouseClicked(evt); } }); + getContentPane().add(previousButton); + previousButton.setBounds(336, 250, 90, 29); - jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N - jLabel1.setName("jLabel1"); // NOI18N + settingsInfo.setText(resourceMap.getString("settingsInfo.text")); // NOI18N + settingsInfo.setName("settingsInfo"); // NOI18N + getContentPane().add(settingsInfo); + settingsInfo.setBounds(20, 30, 532, 17); uploadLabel.setText(resourceMap.getString("uploadLabel.text")); // NOI18N uploadLabel.setName("uploadLabel"); // NOI18N + getContentPane().add(uploadLabel); + uploadLabel.setBounds(20, 70, 140, 30); downloadLabel.setText(resourceMap.getString("downloadLabel.text")); // NOI18N downloadLabel.setName("downloadLabel"); // NOI18N + getContentPane().add(downloadLabel); + downloadLabel.setBounds(340, 70, 160, 30); uploadBurstLabel.setText(resourceMap.getString("uploadBurstLabel.text")); // NOI18N uploadBurstLabel.setName("uploadBurstLabel"); // NOI18N + getContentPane().add(uploadBurstLabel); + uploadBurstLabel.setBounds(20, 110, 140, 30); downloadBurstLabel.setText(resourceMap.getString("downloadBurstLabel.text")); // NOI18N downloadBurstLabel.setName("downloadBurstLabel"); // NOI18N + getContentPane().add(downloadBurstLabel); + downloadBurstLabel.setBounds(340, 110, 160, 30); uploadUsageLabel.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N uploadUsageLabel.setName("uploadUsageLabel"); // NOI18N + getContentPane().add(uploadUsageLabel); + uploadUsageLabel.setBounds(20, 150, 141, 30); downloadUsageLabel.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N downloadUsageLabel.setName("downloadUsageLabel"); // NOI18N + getContentPane().add(downloadUsageLabel); + downloadUsageLabel.setBounds(340, 150, 162, 30); uploadField.setText(resourceMap.getString("uploadField.text")); // NOI18N uploadField.setMinimumSize(new java.awt.Dimension(77, 27)); @@ -105,10 +137,14 @@ public class SpeedSelector3 extends javax.swing.JFrame { speedFieldKeyReleased(evt); } }); + getContentPane().add(uploadField); + uploadField.setBounds(160, 70, 77, 27); uploadBurstField.setText(resourceMap.getString("uploadBurstField.text")); // NOI18N uploadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); uploadBurstField.setName("uploadBurstField"); // NOI18N + getContentPane().add(uploadBurstField); + uploadBurstField.setBounds(160, 110, 77, 27); downloadField.setText(resourceMap.getString("downloadField.text")); // NOI18N downloadField.setMinimumSize(new java.awt.Dimension(77, 27)); @@ -118,84 +154,89 @@ public class SpeedSelector3 extends javax.swing.JFrame { speedFieldKeyReleased(evt); } }); + getContentPane().add(downloadField); + downloadField.setBounds(500, 70, 77, 27); downloadBurstField.setText(resourceMap.getString("downloadBurstField.text")); // NOI18N downloadBurstField.setMinimumSize(new java.awt.Dimension(77, 27)); downloadBurstField.setName("downloadBurstField"); // NOI18N + getContentPane().add(downloadBurstField); + downloadBurstField.setBounds(500, 110, 77, 27); + + kbpsBurstDownload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + kbpsBurstDownload.setName("kbpsBurstDownload"); // NOI18N + kbpsBurstDownload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + kbpsBurstDownloadActionPerformed(evt); + } + }); + getContentPane().add(kbpsBurstDownload); + kbpsBurstDownload.setBounds(580, 110, 68, 27); + + kbpsUpload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + kbpsUpload.setName("kbpsUpload"); // NOI18N + kbpsUpload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + kbpsUploadActionPerformed(evt); + } + }); + getContentPane().add(kbpsUpload); + kbpsUpload.setBounds(240, 70, 68, 27); + + kbpsBurstUpload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + kbpsBurstUpload.setName("kbpsBurstUpload"); // NOI18N + kbpsBurstUpload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + kbpsBurstUploadActionPerformed(evt); + } + }); + getContentPane().add(kbpsBurstUpload); + kbpsBurstUpload.setBounds(240, 110, 68, 27); + + kbpsDownload.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" })); + kbpsDownload.setName("kbpsDownload"); // NOI18N + kbpsDownload.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + kbpsDownloadActionPerformed(evt); + } + }); + getContentPane().add(kbpsDownload); + kbpsDownload.setBounds(580, 70, 68, 27); + + uploadGB.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N + uploadGB.setName("uploadUsageLabel"); // NOI18N + getContentPane().add(uploadGB); + uploadGB.setBounds(240, 150, 45, 30); uploadMonth.setText(resourceMap.getString("uploadMonth.text")); // NOI18N uploadMonth.setName("uploadMonth"); // NOI18N + uploadMonth.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + monthKeyReleased(evt); + } + }); + getContentPane().add(uploadMonth); + uploadMonth.setBounds(160, 150, 77, 27); downloadMonth.setText(resourceMap.getString("downloadMonth.text")); // NOI18N downloadMonth.setName("downloadMonth"); // NOI18N + downloadMonth.addKeyListener(new java.awt.event.KeyAdapter() { + public void keyReleased(java.awt.event.KeyEvent evt) { + monthKeyReleased(evt); + } + }); + getContentPane().add(downloadMonth); + downloadMonth.setBounds(500, 150, 77, 27); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jLabel1) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(uploadLabel) - .addComponent(uploadBurstLabel) - .addComponent(uploadUsageLabel)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(uploadBurstField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(uploadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(uploadMonth, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(46, 46, 46) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(downloadLabel) - .addComponent(downloadBurstLabel) - .addComponent(downloadUsageLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(downloadField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(downloadMonth, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(downloadBurstField, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addGap(18, 18, 18)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(previousButton) - .addGap(18, 18, 18) - .addComponent(finishButton) - .addGap(33, 33, 33))) - .addGap(400, 400, 400)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(81, 81, 81) - .addComponent(jLabel1) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(uploadLabel) - .addComponent(downloadLabel) - .addComponent(uploadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(downloadField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(uploadBurstLabel) - .addComponent(downloadBurstLabel) - .addComponent(downloadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(uploadBurstField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(uploadUsageLabel) - .addComponent(downloadUsageLabel) - .addComponent(uploadMonth) - .addComponent(downloadMonth)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(previousButton) - .addComponent(finishButton)) - .addContainerGap()) - ); + downloadGB.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N + downloadGB.setName("downloadUsageLabel"); // NOI18N + getContentPane().add(downloadGB); + downloadGB.setBounds(580, 150, 19, 30); + + explanation.setText(resourceMap.getString("explanation.text")); // NOI18N + explanation.setName("explanation"); // NOI18N + getContentPane().add(explanation); + explanation.setBounds(20, 200, 600, 40); pack(); }// //GEN-END:initComponents @@ -203,7 +244,7 @@ public class SpeedSelector3 extends javax.swing.JFrame { private void previousButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_previousButtonMouseClicked saveSpeeds(); PropertyManager.saveProps(props); - new SpeedSelector2(this.getLocationOnScreen(), this.getSize()).setVisible(true); + new SpeedSelector2(this.getLocationOnScreen()); this.dispose(); }//GEN-LAST:event_previousButtonMouseClicked @@ -219,7 +260,7 @@ private void finishButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRS //Working in kB, not kb! SpeedHandler.setInboundBandwidth(maxDownload/8); SpeedHandler.setOutboundBandwidth(maxUpload/8); - SpeedHandler.setInboundBurstBandwidth(maxDownloadBurst); + SpeedHandler.setInboundBurstBandwidth(maxDownloadBurst/8); SpeedHandler.setOutboundBurstBandwidth(maxUploadBurst/8); this.dispose(); @@ -227,13 +268,88 @@ private void finishButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRS private void speedFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_speedFieldKeyReleased try { - initUsage(uploadField.getText(), downloadField.getText()); + String upload = ""; + if(kbpsUpload.getSelectedIndex() == KILOBIT) + upload = uploadField.getText(); + else + upload = "" + Integer.parseInt(uploadField.getText())*8; + String download = ""; + if(kbpsDownload.getSelectedIndex() == KILOBIT) + download = downloadField.getText(); + else + download = "" + Integer.parseInt(downloadField.getText())*8; + initUsage(upload, download); } catch(NumberFormatException e) { return; } }//GEN-LAST:event_speedFieldKeyReleased +private void kbpsUploadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsUploadActionPerformed + kbpsSwitchPerformed(kbpsUpload, uploadField); +}//GEN-LAST:event_kbpsUploadActionPerformed + +private void kbpsBurstUploadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsBurstUploadActionPerformed + kbpsSwitchPerformed(kbpsBurstUpload, uploadBurstField); +}//GEN-LAST:event_kbpsBurstUploadActionPerformed + +private void kbpsDownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsDownloadActionPerformed + kbpsSwitchPerformed(kbpsDownload, downloadField); +}//GEN-LAST:event_kbpsDownloadActionPerformed + +private void kbpsBurstDownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_kbpsBurstDownloadActionPerformed + kbpsSwitchPerformed(kbpsBurstDownload, downloadBurstField); +}//GEN-LAST:event_kbpsBurstDownloadActionPerformed + +private void monthKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_monthKeyReleased + try { + int uploadMonthValue = Integer.parseInt(uploadMonth.getText()); + int downloadMonthValue = Integer.parseInt(downloadMonth.getText()); + + String upload = ""; + String burstUpload = ""; + String download = ""; + String burstDownload = ""; + + if(kbpsUpload.getSelectedIndex() == KILOBIT) + upload = "" + SpeedHelper.calculateSpeed(uploadMonthValue)*8; //kbit + else + upload = "" + SpeedHelper.calculateSpeed(uploadMonthValue); //kbyte + + if(kbpsBurstUpload.getSelectedIndex() == KILOBIT) + burstUpload = "" + SpeedHelper.calculateSpeed(uploadMonthValue)*8; //kbit + else + burstUpload = "" + SpeedHelper.calculateSpeed(uploadMonthValue); //kbyte + + if(kbpsDownload.getSelectedIndex() == KILOBIT) + download = "" + SpeedHelper.calculateSpeed(downloadMonthValue)*8; //kbit + else + download = "" + SpeedHelper.calculateSpeed(downloadMonthValue); //kbyte + + if(kbpsBurstDownload.getSelectedIndex() == KILOBIT) + burstDownload = "" + SpeedHelper.calculateSpeed(downloadMonthValue)*8; //kbit + else + burstDownload = "" + SpeedHelper.calculateSpeed(downloadMonthValue); //kbyte + + initSpeeds(upload, burstUpload, download, burstDownload); + } + catch(NumberFormatException e) { + e.printStackTrace(); + return; + } +}//GEN-LAST:event_monthKeyReleased + +private void kbpsSwitchPerformed(JComboBox kbps, JTextField speed) { + int index = kbps.getSelectedIndex(); + int previous = Integer.parseInt(speed.getText()); + if(index == KILOBIT) { + speed.setText("" + previous*8); + } + else { + speed.setText("" + previous/8); + } +} + protected void initSpeeds() { String up = "" + SpeedHelper.calculateSpeed( props.getProperty(SpeedSelectorConstants.MAXUPLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); @@ -245,42 +361,79 @@ private void speedFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:eve props.getProperty(SpeedSelectorConstants.MAXDOWNLOADCAPABLE), props.getProperty(SpeedSelectorConstants.USERTYPE)); String userType = props.getProperty(SpeedSelectorConstants.USERTYPE); + initSpeeds(up, upBurst, down, downBurst); + } + + protected void initSpeeds(String up, String upBurst, String down, String downBurst) { uploadField.setText(up); uploadBurstField.setText(upBurst); downloadField.setText(down); downloadBurstField.setText(downBurst); - - initUsage(up, down); } protected void saveSpeeds() { - props.setProperty(SpeedSelectorConstants.MAXUPLOAD, uploadField.getText()); - props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, uploadBurstField.getText()); - props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, downloadField.getText()); - props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, downloadBurstField.getText()); + if(kbpsUpload.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXUPLOAD, uploadField.getText()); + else + props.setProperty(SpeedSelectorConstants.MAXUPLOAD, "" + Integer.parseInt(uploadField.getText())*8); + if(kbpsBurstUpload.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, uploadBurstField.getText()); + else + props.setProperty(SpeedSelectorConstants.MAXUPLOADBURST, "" + Integer.parseInt(uploadBurstField.getText())*8); + if(kbpsDownload.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, downloadField.getText()); + else + props.setProperty(SpeedSelectorConstants.MAXDOWNLOAD, "" + Integer.parseInt(downloadField.getText())*8); + if(kbpsBurstDownload.getSelectedIndex() == KILOBIT) + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, downloadBurstField.getText()); + else + props.setProperty(SpeedSelectorConstants.MAXDOWNLOADBURST, "" + Integer.parseInt(downloadBurstField.getText())*8); } protected void initUsage(String upload, String download) { - uploadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)/8) + " GB"); - downloadMonth.setText(SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)/8) + " GB"); + uploadMonth.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)/8)); + downloadMonth.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)/8)); + } + + protected void initUsage() { + String upload = ""; + if(kbpsUpload.getSelectedIndex() == KILOBIT) + upload = uploadField.getText(); + else + upload = "" + Integer.parseInt(uploadField.getText())/8; + String download = ""; + if(kbpsDownload.getSelectedIndex() == KILOBIT) + download = downloadField.getText(); + else + download = "" + Integer.parseInt(downloadField.getText())/8; + initUsage(upload, download); } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField downloadBurstField; private javax.swing.JLabel downloadBurstLabel; private javax.swing.JTextField downloadField; + private javax.swing.JLabel downloadGB; private javax.swing.JLabel downloadLabel; - private javax.swing.JLabel downloadMonth; + private javax.swing.JTextField downloadMonth; private javax.swing.JLabel downloadUsageLabel; + private javax.swing.JLabel explanation; private javax.swing.JButton finishButton; - private javax.swing.JLabel jLabel1; + private javax.swing.JComboBox kbpsBurstDownload; + private javax.swing.JComboBox kbpsBurstUpload; + private javax.swing.JComboBox kbpsDownload; + private javax.swing.JComboBox kbpsUpload; private javax.swing.JButton previousButton; + private javax.swing.JLabel settingsInfo; private javax.swing.JTextField uploadBurstField; private javax.swing.JLabel uploadBurstLabel; private javax.swing.JTextField uploadField; + private javax.swing.JLabel uploadGB; private javax.swing.JLabel uploadLabel; - private javax.swing.JLabel uploadMonth; + private javax.swing.JTextField uploadMonth; private javax.swing.JLabel uploadUsageLabel; // End of variables declaration//GEN-END:variables + private static final int KILOBIT = 0; + private static final int KILOBYTE = 1; } diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/gui/resources/SpeedSelector.properties index 1a476d8741..03b04286f6 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector.properties @@ -1,7 +1,6 @@ -Form.title=I2P Configuration +Form.title=I2P Speed Configuration nextButton.text=Next -uploadLabel.text=What is your maximum upload speed? +uploadLabel.text=What is your maximum upload speed? downloadLabel.text=What is your maximum download speed? -kbps1.text=kbit/second -kbps2.text=kbit/second +speedExplanation.text=Explanation about speeds... diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties index 704d9909ec..54424ed051 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties @@ -1,6 +1,9 @@ returnButton.text=Previous -Form.title=I2P Configuration +Form.title=I2P Speed Configuration questionLabel.text=Which of these descriptions fits you best? browseButton.text=Browsing: I want to use I2P to browse websites anonymously, no heavy usage. downloadButton.text=Downloading: I want to use I2P for downloads and filesharing, heavy usage. nextButton.text=Next +browseButton.actionCommand=Browsing +downloadButton.actionCommand=Downloading +jLabel1.text=Text explaining ... diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties index 49ee18ba00..9d0718f8db 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties @@ -1,16 +1,17 @@ Form.title=I2P Configuration -jLabel1.text=The profile information your entered, indicates that these are your optimal settings: previousButton.text=Previous finishButton.text=Finish uploadLabel.text=Upload Speed: uploadBurstLabel.text=Burst Upload Speed: downloadLabel.text=Download Speed: downloadBurstLabel.text=Burst Download Speed: -uploadUsageLabel.text=Monthy upload usage: -downloadUsageLabel.text=Monthy Download Usage: +uploadUsageLabel.text=GB +downloadUsageLabel.text=GB uploadField.text=jTextField1 uploadBurstField.text=jTextField2 -uploadMonth.text=jLabel8 -downloadMonth.text=jLabel9 downloadField.text=jTextField4 downloadBurstField.text=jTextField5 +uploadMonth.text=jTextField1 +downloadMonth.text=jTextField2 +settingsInfo.text=The profile information your entered, indicates that these are your optimal settings: +explanation.text=Text explaining ... diff --git a/apps/desktopgui/src/router/configuration/SpeedHelper.java b/apps/desktopgui/src/router/configuration/SpeedHelper.java index aec7825333..acad2adb14 100644 --- a/apps/desktopgui/src/router/configuration/SpeedHelper.java +++ b/apps/desktopgui/src/router/configuration/SpeedHelper.java @@ -25,4 +25,8 @@ public class SpeedHelper { public static int calculateMonthlyUsage(int kbytes) { return (int) ((((long)kbytes)*3600*24*31)/1000000); } + + public static int calculateSpeed(int gigabytes) { + return (int) (((long)gigabytes)*1000000/31/24/3600); + } } diff --git a/build.xml b/build.xml index f306a71b33..3a30ff8e7f 100644 --- a/build.xml +++ b/build.xml @@ -488,7 +488,7 @@ - + @@ -513,6 +513,6 @@ - + From 2a2d3c0fb578cde276f4868b1daba2d24204b3fb Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Fri, 10 Apr 2009 18:56:56 +0000 Subject: [PATCH 24/27] Added log viewer. Added shutdown informational messages. --- apps/desktopgui/src/gui/LogViewer.form | 101 +++++++++++ apps/desktopgui/src/gui/LogViewer.java | 163 ++++++++++++++++++ apps/desktopgui/src/gui/Tray.java | 25 ++- .../src/gui/resources/LogViewer.properties | 3 + apps/desktopgui/src/router/RouterHelper.java | 4 + 5 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 apps/desktopgui/src/gui/LogViewer.form create mode 100644 apps/desktopgui/src/gui/LogViewer.java create mode 100644 apps/desktopgui/src/gui/resources/LogViewer.properties diff --git a/apps/desktopgui/src/gui/LogViewer.form b/apps/desktopgui/src/gui/LogViewer.form new file mode 100644 index 0000000000..b53b410bac --- /dev/null +++ b/apps/desktopgui/src/gui/LogViewer.form @@ -0,0 +1,101 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/apps/desktopgui/src/gui/LogViewer.java b/apps/desktopgui/src/gui/LogViewer.java new file mode 100644 index 0000000000..2dad70c1fc --- /dev/null +++ b/apps/desktopgui/src/gui/LogViewer.java @@ -0,0 +1,163 @@ +/* + * LogViewer.java + * + * Created on 10 april 2009, 19:17 + */ + +package gui; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.WindowConstants; + +/** + * + * @author mathias + */ +public class LogViewer extends javax.swing.JFrame { + + /** Creates new form LogViewer */ + public LogViewer() { + initComponents(); + readLogText(); + this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + this.setVisible(true); + } + + private void readLogText() { + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + String s = ""; + File f = new File(LOGLOCATION); + if(f.exists()) { + try { + BufferedReader br = new BufferedReader(new FileReader(f)); + while(true) { + String line = br.readLine(); + if(line != null) + s += JTEXTNEWLINE + line; + else + break; + } + } + catch(Exception e) { + s = "An error has occurred while loading the logfiles:" + JTEXTNEWLINE + e.getMessage(); + } + } + logText.setText(s); + } + + }); + t.start(); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + textScroll = new javax.swing.JScrollPane(); + logText = new javax.swing.JTextArea(); + explanationText = new javax.swing.JLabel(); + refreshButton = new javax.swing.JButton(); + clearButton = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setName("Form"); // NOI18N + + textScroll.setName("textScroll"); // NOI18N + + logText.setColumns(20); + logText.setRows(5); + logText.setName("logText"); // NOI18N + textScroll.setViewportView(logText); + + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(LogViewer.class); + explanationText.setText(resourceMap.getString("explanationText.text")); // NOI18N + explanationText.setName("explanationText"); // NOI18N + + refreshButton.setText(resourceMap.getString("refreshButton.text")); // NOI18N + refreshButton.setName("refreshButton"); // NOI18N + refreshButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + refreshButtonActionPerformed(evt); + } + }); + + clearButton.setText(resourceMap.getString("clearButton.text")); // NOI18N + clearButton.setName("clearButton"); // NOI18N + clearButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + clearButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(12, 12, 12) + .addComponent(explanationText, javax.swing.GroupLayout.PREFERRED_SIZE, 561, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + .addComponent(textScroll, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 722, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(refreshButton) + .addGap(18, 18, 18) + .addComponent(clearButton) + .addContainerGap(587, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(explanationText, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(refreshButton) + .addComponent(clearButton)) + .addGap(14, 14, 14) + .addComponent(textScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + +private void clearButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clearButtonActionPerformed + File f = new File(LOGLOCATION); + f.delete(); + try { + f.createNewFile();//GEN-LAST:event_clearButtonActionPerformed + } catch (IOException ex) { + Logger.getLogger(LogViewer.class.getName()).log(Level.SEVERE, null, ex); + } + readLogText(); +} + +private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed + readLogText(); +}//GEN-LAST:event_refreshButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton clearButton; + private javax.swing.JLabel explanationText; + private javax.swing.JTextArea logText; + private javax.swing.JButton refreshButton; + private javax.swing.JScrollPane textScroll; + // End of variables declaration//GEN-END:variables + + private static final String LOGLOCATION = "wrapper.log"; + private static final String JTEXTNEWLINE = "\n"; +} diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/gui/Tray.java index 6ed5ea0d49..bf3cd28c87 100644 --- a/apps/desktopgui/src/gui/Tray.java +++ b/apps/desktopgui/src/gui/Tray.java @@ -24,6 +24,7 @@ import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; import router.RouterHandler; +import router.RouterHelper; /** * @@ -50,7 +51,12 @@ public class Tray { if(Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); try { - desktop.browse(new URI("http://localhost:7657")); + if(desktop.isSupported(Desktop.Action.BROWSE)) { + desktop.browse(new URI("http://localhost:7657")); + } + else { + trayIcon.displayMessage("Browser not found", "The default browser for your system was not found.", TrayIcon.MessageType.WARNING); + } } catch (URISyntaxException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); } catch(IOException ex) { @@ -103,12 +109,27 @@ public class Tray { } } + }); + MenuItem viewLog = new MenuItem("View log"); + viewLog.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + new LogViewer(); + } + }); MenuItem shutdown = new MenuItem("Shutdown I2P"); shutdown.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { RouterHandler.setStatus(RouterHandler.SHUTDOWN_GRACEFULLY); + long shutdownTime = RouterHelper.getGracefulShutdownTimeRemaining(); + System.out.println(shutdownTime); + if(shutdownTime>0) + trayIcon.displayMessage("Shutting down...", "Shutdown time remaining: " + shutdownTime/1000 + " seconds.", TrayIcon.MessageType.INFO); + else + trayIcon.displayMessage("Shutting down...", "Shutting down immediately.", TrayIcon.MessageType.INFO); } }); @@ -121,6 +142,8 @@ public class Tray { config.add(advancedConfig); popup.add(config); + popup.add(viewLog); + popup.add(shutdown); //Add tray icon diff --git a/apps/desktopgui/src/gui/resources/LogViewer.properties b/apps/desktopgui/src/gui/resources/LogViewer.properties new file mode 100644 index 0000000000..8cb0e62091 --- /dev/null +++ b/apps/desktopgui/src/gui/resources/LogViewer.properties @@ -0,0 +1,3 @@ +refreshButton.text=Refresh +clearButton.text=Clear +explanationText.text=Explanation ... diff --git a/apps/desktopgui/src/router/RouterHelper.java b/apps/desktopgui/src/router/RouterHelper.java index c5fc78e959..6d3cddd03d 100644 --- a/apps/desktopgui/src/router/RouterHelper.java +++ b/apps/desktopgui/src/router/RouterHelper.java @@ -10,4 +10,8 @@ public class RouterHelper { public static RouterContext getContext() { return (RouterContext) RouterContext.listContexts().get(0); } + + public static long getGracefulShutdownTimeRemaining() { + return RouterHelper.getContext().router().getShutdownTimeRemaining(); + } } From 384d655b1aaad83524d8123eff511ae7de816467 Mon Sep 17 00:00:00 2001 From: sponge Date: Fri, 10 Apr 2009 23:12:41 +0000 Subject: [PATCH 25/27] 2009-04-10 sponge * More BOB threadgroup fixes, plus debug dump when things go wrong. * Fixes to streaminglib, I2CP, which are related to the TG problem. * JavaDocs fixups. --- apps/BOB/src/net/i2p/BOB/DoCMDS.java | 22 +- apps/BOB/src/net/i2p/BOB/I2Plistener.java | 68 +++--- apps/BOB/src/net/i2p/BOB/I2PtoTCP.java | 18 +- apps/BOB/src/net/i2p/BOB/MUXlisten.java | 200 +++++++++++++----- apps/BOB/src/net/i2p/BOB/TCPio.java | 26 ++- apps/BOB/src/net/i2p/BOB/TCPlistener.java | 34 +-- apps/BOB/src/net/i2p/BOB/TCPtoI2P.java | 34 ++- .../i2p/i2ptunnel/I2PTunnelClientBase.java | 2 +- .../udpTunnel/I2PTunnelUDPClientBase.java | 48 ++--- .../udpTunnel/I2PTunnelUDPServerBase.java | 44 ++-- .../net/i2p/client/streaming/Connection.java | 10 +- .../client/streaming/ConnectionHandler.java | 4 +- .../net/i2p/client/streaming/PacketLocal.java | 2 +- .../net/i2p/client/I2PSessionMuxedImpl.java | 4 +- .../net/i2p/data/i2cp/I2CPMessageReader.java | 6 +- core/java/src/net/i2p/util/ConvertToHash.java | 7 +- history.txt | 5 + .../src/net/i2p/router/RouterVersion.java | 2 +- 18 files changed, 348 insertions(+), 188 deletions(-) diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java index 12d8d61560..16da28ce9c 100644 --- a/apps/BOB/src/net/i2p/BOB/DoCMDS.java +++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java @@ -748,14 +748,12 @@ public class DoCMDS implements Runnable { nickinfo = (NamedDB) database.get(Arg); if (!tunnelactive(nickinfo)) { nickinfo = null; - ns = - true; + ns = true; } } catch (Exception b) { nickinfo = null; - ns = - true; + ns = true; } try { @@ -775,10 +773,10 @@ public class DoCMDS implements Runnable { try { database.add(Arg, nickinfo); nickinfo.add(P_NICKNAME, Arg); - nickinfo.add(P_STARTING, Boolean.FALSE); - nickinfo.add(P_RUNNING, Boolean.FALSE); - nickinfo.add(P_STOPPING, Boolean.FALSE); - nickinfo.add(P_QUIET, Boolean.FALSE); + nickinfo.add(P_STARTING, new Boolean(false)); + nickinfo.add(P_RUNNING, new Boolean(false)); + nickinfo.add(P_STOPPING, new Boolean(false)); + nickinfo.add(P_QUIET, new Boolean(false)); nickinfo.add(P_INHOST, "localhost"); nickinfo.add(P_OUTHOST, "localhost"); Properties Q = new Properties(); @@ -1265,13 +1263,17 @@ public class DoCMDS implements Runnable { tunnel = new MUXlisten(database, nickinfo, _log); Thread t = new Thread(tunnel); t.start(); + try { + Thread.sleep(1000 * 10); // Slow down the startup. + } catch(InterruptedException ie) { + // ignore it + } out.println("OK tunnel starting"); } catch (I2PException e) { out.println("ERROR starting tunnel: " + e); } catch (IOException e) { out.println("ERROR starting tunnel: " + e); } - } } catch (Exception ex) { break die; @@ -1304,7 +1306,7 @@ public class DoCMDS implements Runnable { break die; } - nickinfo.add(P_STOPPING, Boolean.TRUE); + nickinfo.add(P_STOPPING, new Boolean(true)); try { wunlock(); diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java index 2dfa4bbb2c..a8115893db 100644 --- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java +++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java @@ -26,8 +26,6 @@ package net.i2p.BOB; import java.net.ConnectException; import java.net.SocketTimeoutException; import net.i2p.I2PException; -import net.i2p.client.I2PSession; -import net.i2p.client.I2PSessionException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; @@ -42,7 +40,7 @@ public class I2Plistener implements Runnable { private NamedDB info, database; private Log _log; - private int tgwatch; +// private int tgwatch; public I2PSocketManager socketManager; public I2PServerSocket serverSocket; @@ -60,7 +58,7 @@ public class I2Plistener implements Runnable { this._log = _log; this.socketManager = S; serverSocket = SS; - tgwatch = 1; +// tgwatch = 1; } private void rlock() throws Exception { @@ -84,18 +82,18 @@ public class I2Plistener implements Runnable { die: { serverSocket.setSoTimeout(50); - try { - if (info.exists("INPORT")) { - tgwatch = 2; - } - } catch (Exception e) { - try { - runlock(); - } catch (Exception e2) { - break die; - } - break die; - } +// try { +// if (info.exists("INPORT")) { +// tgwatch = 2; +// } +// } catch (Exception e) { +// try { +// runlock(); +// } catch (Exception e2) { +// break die; +// } +// break die; +// } boolean spin = true; while (spin) { @@ -137,29 +135,33 @@ die: { } } // System.out.println("I2Plistener: Close"); - try { - serverSocket.close(); - } catch (I2PException e) { + + + // Previous level does this cleanup now. + // + // try { + // serverSocket.close(); + // } catch (I2PException e) { // nop - } + //} // need to kill off the socket manager too. - I2PSession session = socketManager.getSession(); - if (session != null) { + // I2PSession session = socketManager.getSession(); + // if (session != null) { // System.out.println("I2Plistener: destroySession"); - try { - session.destroySession(); - } catch (I2PSessionException ex) { + // try { + // session.destroySession(); + // } catch (I2PSessionException ex) { // nop - } - } + // } + //} // 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) { + // 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."); } diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java index 06c3131fee..0984823b6e 100644 --- a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java +++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java @@ -23,6 +23,7 @@ */ package net.i2p.BOB; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; @@ -116,7 +117,22 @@ die: { try { Thread.sleep(10); //sleep for 10 ms } catch(InterruptedException e) { - // nop + try { + in.close(); + } catch(Exception ex) { + } + try { + out.close(); + } catch(Exception ex) { + } + try { + Iin.close(); + } catch(Exception ex) { + } + try { + Iout.close(); + } catch(Exception ex) { + } } } // System.out.println("I2PtoTCP: Going away..."); diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java index 1987fbf6b3..dc30c5445d 100644 --- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java +++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java @@ -28,14 +28,13 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; import net.i2p.I2PException; +import net.i2p.client.I2PSession; +import net.i2p.client.I2PSessionException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.client.streaming.I2PSocketManagerFactory; import net.i2p.util.Log; -import org.tanukisoftware.wrapper.WrapperManager; /** * @@ -59,8 +58,8 @@ public class MUXlisten implements Runnable { /** * Constructor Will fail if INPORT is occupied. * - * @param info - * @param database + * @param info DB entry for this tunnel + * @param database master database of tunnels * @param _log * @throws net.i2p.I2PException * @throws java.io.IOException @@ -134,11 +133,11 @@ public class MUXlisten implements Runnable { */ public void run() { I2PServerSocket SS = null; + int ticks = 1200; // Allow 120 seconds, no more. try { wlock(); try { info.add("RUNNING", new Boolean(true)); - info.add("STARTING", new Boolean(false)); } catch (Exception e) { wunlock(); return; @@ -177,12 +176,28 @@ die: q.start(); } + try { + wlock(); + try { + info.add("STARTING", new Boolean(false)); + } catch (Exception e) { + wunlock(); + break die; + } + } catch (Exception e) { + break die; + } + try { + wunlock(); + } catch (Exception e) { + break die; + } boolean spin = true; while (spin) { try { - Thread.sleep(200); //sleep for 200 ms (Two thenths second) + Thread.sleep(1000); //sleep for 1 second } catch (InterruptedException e) { - // nop + break die; } try { rlock(); @@ -220,22 +235,49 @@ die: } } // die -// try { -// Thread.sleep(500); //sleep for 500 ms (One half second) -// } catch (InterruptedException ex) { -// // nop -// } - // wait for child threads and thread groups to die + if (SS != null) { + try { + SS.close(); + } catch (I2PException ex) { + //Logger.getLogger(MUXlisten.class.getName()).log(Level.SEVERE, null, ex); + } + } + if (this.come_in) { + try { + listener.close(); + } catch (IOException e) { + } + } + + I2PSession session = socketManager.getSession(); + if (session != null) { + // System.out.println("I2Plistener: destroySession"); + try { + session.destroySession(); + } catch (I2PSessionException ex) { + // nop + } + } + try { + socketManager.destroySocketManager(); + } catch (Exception e) { + // nop + } + // Wait for child threads and thread groups to die // System.out.println("MUXlisten: waiting for children"); if (tg.activeCount() + tg.activeGroupCount() != 0) { - while (tg.activeCount() + tg.activeGroupCount() != 0) { + while ((tg.activeCount() + tg.activeGroupCount() != 0) && ticks != 0) { tg.interrupt(); // unwedge any blocking threads. + ticks--; try { Thread.sleep(100); //sleep for 100 ms (One tenth second) } catch (InterruptedException ex) { - // NOP + break quit; } } + if (tg.activeCount() + tg.activeGroupCount() != 0) { + break quit; // Uh-oh. + } } tg.destroy(); // Zap reference to the ThreadGroup so the JVM can GC it. @@ -245,20 +287,32 @@ die: break quit; } } // quit - // 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"); - } // This is here to catch when something fucks up REALLY bad. if (tg != null) { + if (SS != null) { + try { + SS.close(); + } catch (I2PException ex) { + //Logger.getLogger(MUXlisten.class.getName()).log(Level.SEVERE, null, ex); + } + } + if (this.come_in) { + try { + listener.close(); + } catch (IOException e) { + } + } + try { + socketManager.destroySocketManager(); + } catch (Exception e) { + // nop + } + ticks = 600; // 60 seconds if (tg.activeCount() + tg.activeGroupCount() != 0) { - tg.interrupt(); // unwedge any blocking threads. - while (tg.activeCount() + tg.activeGroupCount() != 0) { + while ((tg.activeCount() + tg.activeGroupCount() != 0) && ticks != 0) { + tg.interrupt(); // unwedge any blocking threads. + ticks--; try { Thread.sleep(100); //sleep for 100 ms (One tenth second) } catch (InterruptedException ex) { @@ -266,30 +320,26 @@ die: } } } - tg.destroy(); - // Zap reference to the ThreadGroup so the JVM can GC it. - tg = null; + if (tg.activeCount() + tg.activeGroupCount() == 0) { + tg.destroy(); + // Zap reference to the ThreadGroup so the JVM can GC it. + tg = null; + } else { + System.out.println("BOB: MUXlisten: Can't kill threads. Please send the following dump to sponge@mail.i2p"); + System.out.println("\n\nBOB: MUXlisten: ThreadGroup dump BEGIN"); + visit(tg, 0); + System.out.println("BOB: MUXlisten: ThreadGroup dump END\n\n"); + } } - if (SS != null) { - try { - SS.close(); - } catch (I2PException ex) { - //Logger.getLogger(MUXlisten.class.getName()).log(Level.SEVERE, null, ex); - } - } - // Lastly try to close things again. - if (this.come_in) { - try { - listener.close(); - } catch (IOException e) { - } - } - try { - socketManager.destroySocketManager(); - } catch (Exception e) { - // nop - } + // 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. try { wlock(); @@ -307,13 +357,49 @@ die: } -// private class DisconnectListener implements I2PSocketManager.DisconnectListener { -// -// public void sessionDisconnected() { -// close(); -// } -// } -// public void close() { -// socketManager.destroySocketManager(); -// } + + // Debugging... + + /** + * Find the root thread group and print them all. + * + */ + private void visitAllThreads() { + ThreadGroup root = Thread.currentThread().getThreadGroup().getParent(); + while (root.getParent() != null) { + root = root.getParent(); + } + + // Visit each thread group + visit(root, 0); + } + + /** + * Recursively visits all thread groups under `group' and dumps them. + * @param group ThreadGroup to visit + * @param level Current level + */ + private static void visit(ThreadGroup group, int level) { + // Get threads in `group' + int numThreads = group.activeCount(); + Thread[] threads = new Thread[numThreads * 2]; + numThreads = group.enumerate(threads, false); + String indent = "------------------------------------".substring(0, level) + "-> "; + // Enumerate each thread in `group' and print it. + for (int i = 0; i < numThreads; i++) { + // Get thread + Thread thread = threads[i]; + System.out.println("BOB: MUXlisten: " + indent + thread.toString()); + } + + // Get thread subgroups of `group' + int numGroups = group.activeGroupCount(); + ThreadGroup[] groups = new ThreadGroup[numGroups * 2]; + numGroups = group.enumerate(groups, false); + + // Recursively visit each subgroup + for (int i = 0; i < numGroups; i++) { + visit(groups[i], level + 1); + } + } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java index 109d8e8cb5..d4b353c549 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPio.java +++ b/apps/BOB/src/net/i2p/BOB/TCPio.java @@ -23,6 +23,7 @@ */ package net.i2p.BOB; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -40,9 +41,8 @@ public class TCPio implements Runnable { /** * Constructor * - * @param Ain - * @param Aout - * @param info + * @param Ain InputStream + * @param Aout OutputStream * * param database */ @@ -99,11 +99,11 @@ public class TCPio implements Runnable { } else if(b == 0) { Thread.yield(); // this should act like a mini sleep. if(Ain.available() == 0) { - try { +// try { // Thread.yield(); Thread.sleep(10); - } catch(InterruptedException ex) { - } +// } catch(InterruptedException ex) { +// } } } else { /* according to the specs: @@ -114,13 +114,25 @@ public class TCPio implements Runnable { * */ // System.out.println("TCPio: End Of Stream"); + Ain.close(); + Aout.close(); return; } } // System.out.println("TCPio: RUNNING = false"); } catch(Exception e) { // Eject!!! Eject!!! - // System.out.println("TCPio: Caught an exception " + e); + //System.out.println("TCPio: Caught an exception " + e); + try { + Ain.close(); + } catch (IOException ex) { +// Logger.getLogger(TCPio.class.getName()).log(Level.SEVERE, null, ex); + } + try { + Aout.close(); + } catch (IOException ex) { +// Logger.getLogger(TCPio.class.getName()).log(Level.SEVERE, null, ex); + } return; } // System.out.println("TCPio: Leaving."); diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java index 7e931768c9..78155eb787 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java +++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java @@ -27,8 +27,8 @@ import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; -import net.i2p.client.I2PSession; -import net.i2p.client.I2PSessionException; +// import net.i2p.client.I2PSession; +// import net.i2p.client.I2PSessionException; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocketManager; import net.i2p.util.Log; @@ -187,23 +187,25 @@ die: { } } } + // Previous level does this cleanup now. + // // need to kill off the socket manager too. - I2PSession session = socketManager.getSession(); - if (session != null) { - try { - session.destroySession(); - } catch (I2PSessionException ex) { + // I2PSession session = socketManager.getSession(); + // if (session != null) { + // try { + // session.destroySession(); + // } catch (I2PSessionException ex) { // nop - } - } + // } + //} //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 - } - } + //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."); } } diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java index fe1ca32788..c376e16fe1 100644 --- a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java +++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java @@ -114,11 +114,15 @@ public class TCPtoI2P implements Runnable { */ public void run() { String line, input; + InputStream Iin = null; + OutputStream Iout = null; + InputStream in = null; + OutputStream out = null; try { - InputStream in = sock.getInputStream(); - OutputStream out = sock.getOutputStream(); + in = sock.getInputStream(); + out = sock.getOutputStream(); try { line = lnRead(in); input = line.toLowerCase(); @@ -136,8 +140,8 @@ public class TCPtoI2P implements Runnable { I2P = socketManager.connect(dest); I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default // make readers/writers - InputStream Iin = I2P.getInputStream(); - OutputStream Iout = I2P.getOutputStream(); + Iin = I2P.getInputStream(); + Iout = I2P.getOutputStream(); // setup to cross the streams TCPio conn_c = new TCPio(in, Iout /*, info, database */); // app -> I2P TCPio conn_a = new TCPio(Iin, out /*, info, database */); // I2P -> app @@ -147,11 +151,11 @@ public class TCPtoI2P implements Runnable { t.start(); q.start(); while(t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread - try { +// try { Thread.sleep(10); //sleep for 10 ms - } catch(InterruptedException e) { +// } catch(InterruptedException e) { // nop - } +// } } // System.out.println("TCPtoI2P: Going away..."); @@ -171,6 +175,22 @@ public class TCPtoI2P implements Runnable { } catch(Exception e) { // bail on anything else } + try { + in.close(); + } catch(Exception e) { + } + try { + out.close(); + } catch(Exception e) { + } + try { + Iin.close(); + } catch(Exception e) { + } + try { + Iout.close(); + } catch(Exception e) { + } try { // System.out.println("TCPtoI2P: Close I2P"); I2P.close(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index f5f0df6b98..5b9aa23cbb 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -100,7 +100,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna } /** - * @param privKeyFile null to generate a transient key + * @param pkf null to generate a transient key * * @throws IllegalArgumentException if the I2CP configuration is b0rked so * badly that we cant create a socketManager diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java index c92da6ae8a..b592af5e49 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -34,7 +34,29 @@ import net.i2p.util.EventDispatcher; import net.i2p.util.I2PThread; import net.i2p.util.Log; -public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink { + /** + * Base client class that sets up an I2P Datagram client destination. + * The UDP side is not implemented here, as there are at least + * two possibilities: + * + * 1) UDP side is a "server" + * Example: Streamr Consumer + * - Configure a destination host and port + * - External application sends no data + * - Extending class must have a constructor with host and port arguments + * + * 2) UDP side is a client/server + * Example: SOCKS UDP (DNS requests?) + * - configure an inbound port and a destination host and port + * - External application sends and receives data + * - Extending class must have a constructor with host and 2 port arguments + * + * So the implementing class must create a UDPSource and/or UDPSink, + * and must call setSink(). + * + * @author zzz with portions from welterde's streamr + */ + public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements Source, Sink { private static final Log _log = new Log(I2PTunnelUDPClientBase.class); protected I2PAppContext _context; @@ -69,33 +91,11 @@ public abstract class I2PTunnelUDPClientBase extends I2PTunnelTask implements So private Source _i2pSource; private Sink _i2pSink; private Destination _otherDest; - /** - * Base client class that sets up an I2P Datagram client destination. - * The UDP side is not implemented here, as there are at least - * two possibilities: - * - * 1) UDP side is a "server" - * Example: Streamr Consumer - * - Configure a destination host and port - * - External application sends no data - * - Extending class must have a constructor with host and port arguments - * - * 2) UDP side is a client/server - * Example: SOCKS UDP (DNS requests?) - * - configure an inbound port and a destination host and port - * - External application sends and receives data - * - Extending class must have a constructor with host and 2 port arguments - * - * So the implementing class must create a UDPSource and/or UDPSink, - * and must call setSink(). - * * @throws IllegalArgumentException if the I2CP configuration is b0rked so * badly that we cant create a socketManager - * - * @author zzz with portions from welterde's streamr */ - public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis, + public I2PTunnelUDPClientBase(String destination, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) throws IllegalArgumentException { super("UDPServer", notifyThis, tunnel); _clientId = ++__clientId; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java index 8dcd66a365..2ab2687ffa 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java @@ -32,25 +32,6 @@ import net.i2p.util.EventDispatcher; import net.i2p.util.I2PThread; import net.i2p.util.Log; -public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink { - - private final static Log _log = new Log(I2PTunnelUDPServerBase.class); - - private Object lock = new Object(); - protected Object slock = new Object(); - - private static volatile long __serverId = 0; - - protected Logging l; - - private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000; - /** default timeout to 3 minutes - override if desired */ - protected long readTimeout = DEFAULT_READ_TIMEOUT; - - private I2PSession _session; - private Source _i2pSource; - private Sink _i2pSink; - /** * Base client class that sets up an I2P Datagram server destination. * The UDP side is not implemented here, as there are at least @@ -70,11 +51,34 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin * * So the implementing class must create a UDPSource and/or UDPSink, * and must call setSink(). + * + * @author zzz with portions from welterde's streamr + */ + +public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sink { + + private final static Log _log = new Log(I2PTunnelUDPServerBase.class); + + private Object lock = new Object(); + protected Object slock = new Object(); + + private static volatile long __serverId = 0; + + protected Logging l; + + private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000; + /** default timeout to 3 minutes - override if desired */ + protected long readTimeout = DEFAULT_READ_TIMEOUT; + + private I2PSession _session; + private Source _i2pSource; + private Sink _i2pSink; + + /** * * @throws IllegalArgumentException if the I2CP configuration is b0rked so * badly that we cant create a socketManager * - * @author zzz with portions from welterde's streamr */ public I2PTunnelUDPServerBase(boolean verify, File privkey, String privkeyname, Logging l, diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java index 6b99fdc00a..f73632683c 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Connection.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Connection.java @@ -187,12 +187,12 @@ public class Connection { if (_log.shouldLog(Log.DEBUG)) _log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "/" + _activeResends + "), waiting " + timeLeft); - try { _outboundPackets.wait(Math.min(timeLeft,250l)); } catch (InterruptedException ie) {} + try { _outboundPackets.wait(Math.min(timeLeft,250l)); } catch (InterruptedException ie) { if (_log.shouldLog(Log.DEBUG)) _log.debug("InterruptedException while Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends +")"); return false;} } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends + "), waiting indefinitely"); - try { _outboundPackets.wait(250); } catch (InterruptedException ie) {} //10*1000 + try { _outboundPackets.wait(250); } catch (InterruptedException ie) {if (_log.shouldLog(Log.DEBUG)) _log.debug("InterruptedException while Outbound window is full (" + _outboundPackets.size() + "/" + _activeResends + ")"); return false;} //10*1000 } } else { _context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start); @@ -810,7 +810,11 @@ public class Connection { synchronized (_connectLock) { _connectLock.wait(timeLeft); } - } catch (InterruptedException ie) {} + } catch (InterruptedException ie) { + if (_log.shouldLog(Log.DEBUG)) _log.debug("waitForConnect(): InterruptedException"); + _connectionError = "InterruptedException"; + return; + } } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java index b96eaea633..2d198ad66e 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java @@ -126,7 +126,7 @@ class ConnectionHandler { if (timeoutMs <= 0) { try { syn = _synQueue.take(); // waits forever - } catch (InterruptedException ie) {} + } catch (InterruptedException ie) { break;} } else { long remaining = expiration - _context.clock().now(); // (dont think this applies anymore for LinkedBlockingQueue) @@ -138,7 +138,7 @@ class ConnectionHandler { break; try { syn = _synQueue.poll(remaining, TimeUnit.MILLISECONDS); // waits the specified time max - } catch (InterruptedException ie) {} + } catch (InterruptedException ie) { } break; } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java index cbb89e79ec..197b927541 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java @@ -213,7 +213,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat timeRemaining = 10*1000; wait(timeRemaining); } - } catch (InterruptedException ie) {} + } catch (InterruptedException ie) { break; } } if (!writeSuccessful()) releasePayload(); diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java index b08d01c264..a3e5e57a7f 100644 --- a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java +++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java @@ -146,8 +146,8 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession { * I2PSession.PROTO_STREAMING * I2PSession.PROTO_DATAGRAM * 255 disallowed - * @param fromport 1-65535 or 0 for unset - * @param toport 1-65535 or 0 for unset + * @param fromPort 1-65535 or 0 for unset + * @param toPort 1-65535 or 0 for unset */ public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent, long expires, diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java index 13b01a67ad..79066c4c62 100644 --- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java +++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java @@ -178,7 +178,11 @@ public class I2CPMessageReader { // pause .5 secs when we're paused try { Thread.sleep(500); - } catch (InterruptedException ie) { // nop + } catch (InterruptedException ie) { + // we should break away here. + _log.warn("Breaking away stream", ie); + _listener.disconnected(I2CPMessageReader.this); + cancelRunner(); } } } diff --git a/core/java/src/net/i2p/util/ConvertToHash.java b/core/java/src/net/i2p/util/ConvertToHash.java index 0878556400..28da87d217 100644 --- a/core/java/src/net/i2p/util/ConvertToHash.java +++ b/core/java/src/net/i2p/util/ConvertToHash.java @@ -16,12 +16,15 @@ import net.i2p.data.Hash; * Base32 desthash.b32.i2p * example.i2p * - * @return null on failure - * * @author zzz */ public class ConvertToHash { + /** + * Convert any kind of destination String to a hash + * + * @return null on failure + */ public static Hash getHash(String peer) { if (peer == null) return null; diff --git a/history.txt b/history.txt index 1e88a8c7da..807629d8cc 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,8 @@ +2009-04-10 sponge + * More BOB threadgroup fixes, plus debug dump when things go wrong. + * Fixes to streaminglib, I2CP, which are related to the TG problem. + * JavaDocs fixups. + 2009-04-08 sponge * More hopeful fixups to the infamous orpahned tunnel problem. *Sigh* diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index fe47169bc4..6501cf5a77 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 14; + public final static long BUILD = 15; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); From 305fc7314f2da4cc26196d86c53f6cd6d2e93f12 Mon Sep 17 00:00:00 2001 From: mathiasdm Date: Sat, 11 Apr 2009 13:35:22 +0000 Subject: [PATCH 26/27] Added text explaining the different speeds. Tray icon colours indicates active peers (and tooltip indicates reachability). The menu now uses Swing instead of Awt, so it looks a lot better. --- .../desktopgui/resources/logo/logo_green.jpg | Bin 0 -> 1328 bytes .../desktopgui/resources/logo/logo_orange.jpg | Bin 0 -> 1302 bytes .../desktopgui/resources/logo/logo_red.jpg | Bin 0 -> 1233 bytes .../src/gui/GeneralConfiguration.form | 103 ++++++++++ .../src/gui/GeneralConfiguration.java | 106 +++++++++++ apps/desktopgui/src/gui/JPopupTrayIcon.java | 176 ++++++++++++++++++ apps/desktopgui/src/gui/SpeedSelector.form | 1 + apps/desktopgui/src/gui/SpeedSelector2.form | 7 +- apps/desktopgui/src/gui/SpeedSelector2.java | 6 +- apps/desktopgui/src/gui/SpeedSelector3.form | 2 +- apps/desktopgui/src/gui/SpeedSelector3.java | 6 +- apps/desktopgui/src/gui/Tray.java | 65 +++++-- .../gui/resources/SpeedSelector.properties | 2 +- .../gui/resources/SpeedSelector2.properties | 2 +- .../gui/resources/SpeedSelector3.properties | 2 +- .../src/router/configuration/PeerHelper.java | 165 ++++++++++++++++ 16 files changed, 614 insertions(+), 29 deletions(-) create mode 100644 apps/desktopgui/desktopgui/resources/logo/logo_green.jpg create mode 100644 apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg create mode 100644 apps/desktopgui/desktopgui/resources/logo/logo_red.jpg create mode 100644 apps/desktopgui/src/gui/GeneralConfiguration.form create mode 100644 apps/desktopgui/src/gui/GeneralConfiguration.java create mode 100644 apps/desktopgui/src/gui/JPopupTrayIcon.java create mode 100644 apps/desktopgui/src/router/configuration/PeerHelper.java diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_green.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_green.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a29b6d1c3e10d658683a91c89fcfe106e538028 GIT binary patch literal 1328 zcmex=0S2HH6B8pd3seOQD>EBA69*$FgNRs!ppe8h z$!XJp3MCjBn1LQf0<3JDU?Y?TfkrSgFfy~kq?uWmSlJkb**OFmghWIY#sA-8-~p;& z5@Z%+uxIEBDt9j`ZYrPcxi0zP%U}92zhl>aeXV-lxa;ZBZt1A3xlNtXYi&2F#nsOI zd`1;?(N6CaDbW;T1XGHY{Yjtrk*ahd2r z-NyE3d#*gp);PL_foT6=ZzF57`UoJBBhkx9=`ZF4elh5vq4R&4= zDa-t8b8haZ8K<*;ap|dC*IK#4C_{h2&Fr^12@-R?8x)*&URoLFo3XMw`y0o3netmq z8|3yy{`x)dW!C4v60g35TfOaLKJhzbrZ~H@uG0yxdY*?nu7|AO>K(XAJLct$`mhB* z7AVx4t^F6ax9*?Y;{Oc3m+P1OS>zvExBt!EtFOP#p1kkO-OqbW-4lJ|3yx%m-q&H- zzSGnDy!6z3!Jpes2&rxon{|Lo^r^|hsE5Hy0<%8J_S+k@sQb-7`ftlWo5cB_qWHi5 zXXulAbn)B#8CSmRD6X z1iykgv!8Z}|GXCg-x4R?m@HGXuO~|9>bq(CuFAWl-igq9z3-G|aCr9lSEg!9d1YTL zKkdwSS@GAe7Yv_bH*U08t+;7==VKr3a;?IjYDu~7uA8TpU&vZ|Vbzjr@57z_qpsX8 zJt=c)=bFtoPiM?@R+_(CRI{<(4-?)&*Ke&&;tUF)7!-W4}qKj%_< z+M@HXJTyCdN<11FoQtM~i?8KqYhl`25Nt4wFZGlHBfrX>g<^bydu#0E+01U|Z%UWT zY>!T=bKY{ZfIr!XY8c^lAGn1$6K36>7{`~s?HvwflBewtm literal 0 HcmV?d00001 diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_orange.jpg new file mode 100644 index 0000000000000000000000000000000000000000..137a25e7a033e632799ebb118a73c3044fedc0ee GIT binary patch literal 1302 zcmex=wyEjbSz)%qbB65R zQh%ZK$6_Dt-SW~r@A})_`=V3cynD5GE5G&8tMP}ru1&+j-wf9ktsJ3okygO}{+fzcBOcLz8)V`X0Mx`<0}1K26SCh@<|s36 zdNA|fgiWmrXEj;(Rvl$LQoF{b`|z{lJ{tv=U)ESB`cxtE=ao0v;X8};H%*b{cyjpN z#r-AoTI&N|{AbuwQvc(=#_PxT7jBDt`k*X7uUfU_{6^8}pWSbNY%SXV_0jDZ)n(7m zp4`^;<8%n;|L7 zzFH};ePLeRi4vJNCC;}`sF|HhGmd=wYqsGf!SC9&QJLG%v-e**QY>xsk)xxtBtCbO zghA>Wt%!eaN*{YFe!bndpe%6K?BEM)j@;G1TJ&OTk@oiP&gJVeQa9b3lp1sGX3o*g z0ZTje=kjEGo)6ox)9aaov!~Eo`?a=3=W`TpudrZKci*P{C|-10Rb8mdnj7JTHkQ}t z9ufBpXgT4h))1&L{Y6po7mt*4Ciiw8Hko57C#SslrIn)2t)%<6r22o_eOi3As@wWm zOqTT9rsa*sPeYs{=4IVKvE!I@b>GxA7Sj(LQoNQiW2Z`{zWn91pDS;?s^~9s{o0#w zlJQYWOG2G?@Pne5t>0AFW|l5Yi`*A7XVtg(`9a1dY@26ZT=62*#pKJ=ohkv1iW|MP z59o2U6}RlHD3;feIW;AKNq)|rg<^byyMI(vp3U4gFJbMj(|l&tKlv`ZDf38u78EX; zacw7OWk<))xNGw>I==s_%a2T*%rMorKx>2c2d>u~2@ILS#v%f)Q9PxcGw1he3%)o0 I^XLCf07gg~1^@s6 literal 0 HcmV?d00001 diff --git a/apps/desktopgui/desktopgui/resources/logo/logo_red.jpg b/apps/desktopgui/desktopgui/resources/logo/logo_red.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1da898381264bae953ddc628254788cbd4137433 GIT binary patch literal 1233 zcmex= z@Z>2|uY!zWWME`vL^g$)i3Mm7$QVUIpfQXfYhVhPSs0mE+1Q1E4iZ*0{C|sq2P7}Z zEXZKb@LWai%mvZ@ch4IivR>Z%dfly?rO(1`XSh|SZRa#*SsgO%fS1~d%bbgH^Adht zoys47xOvw>C+Qj9^9oD<-QTEuQ}pZlyeA(NjPpC&&Pwh$G1H3edvP{rliAie!BL%w zK5OTjP1$o}jaWi4!|7^oD{oHu=r>nO-^E|~cJjVi(O&Tt|8DJ{mD#hjuysNf!?bma zZ?^pC@?WdH#Z=UzfV^)YiadcU9%jr|Q4|sc~mEEkd zQaN?bD0_uzcj=__$7XF@x$g74g&SlfbTjrg3SPXgxw(Dy_8%e5c23U&-~VZuuDkYf zp>@SR-J?%RcU;-ics=dQt-IXT9@?9)l*xIl{A8goTXg5nbQh~TkE;$cipTqLGX%cU z-sy8>=Ud4HtBup-H5ZGDO`E8lF!%nWSd)n@jNhLxKR%Chv&#~zF3IZZ);+tq6@9L? zWSZQ{KOgV^pCLY_zUA0-a+mxndG4DJVBE?&Y7G!aZ$0%|5Km;%bMq3vi@B^`rW!l&Ti^v{@BI)X8UnY z_#9=m)6~bV^VES0`zHFa&z?DrlPA53Y0DPAkH)VJWq-_k^q=AMewBi(@0Ga~r`4yv z{CO|gUETYfO*-Qf=P$Fi>HXNg)#B=olA_W|y|OKGUYGlCoQ`VGD9cR>D6{cldU#>N z&QnQ7lH6A^X5RYju{fm`eKE-{O}ysOQ=EFfyS zeb@Q<&Ly(t*H7H$mtJIT@>5k?B2fDET=8R*ot90?>b-KVvpP~#mCj# zTmLf7*s(-uqMSavOy4A9NA18#+@AeGt}g|>e;?huQZTyq_~(b(%a^)MsG6l8dS_)+ z(Vf*fp=)mXe9QE7>`;-k44oa>y0p6gi`+GXy`IMTeHTu~g;<9_{cPEDLwu@nJ}Xz^ z<@UmDp}u0`8K-y3&F0$F*uE^qPv*APie)?fXN2-8y?@pwzexV={qJ&eawd9yfhUXQ zjctyq{Ek`bnI!b>KZD)->aN_(Umg8BXU>0qnoG{<{iezB*7`eMe=fcv!|4+6vTU+% epv##9(?iSM82Z%GlA4!h3me + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/apps/desktopgui/src/gui/GeneralConfiguration.java b/apps/desktopgui/src/gui/GeneralConfiguration.java new file mode 100644 index 0000000000..b9c7403813 --- /dev/null +++ b/apps/desktopgui/src/gui/GeneralConfiguration.java @@ -0,0 +1,106 @@ +/* + * GeneralConfiguration.java + * + * Created on 10 april 2009, 19:04 + */ + +package gui; + +/** + * + * @author mathias + */ +public class GeneralConfiguration extends javax.swing.JFrame { + + /** Creates new form GeneralConfiguration */ + public GeneralConfiguration() { + initComponents(); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jTabbedPane1 = new javax.swing.JTabbedPane(); + jPanel1 = new javax.swing.JPanel(); + jPanel2 = new javax.swing.JPanel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setName("Form"); // NOI18N + + jTabbedPane1.setName("jTabbedPane1"); // NOI18N + + jPanel1.setName("jPanel1"); // NOI18N + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 474, Short.MAX_VALUE) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 338, Short.MAX_VALUE) + ); + + org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(GeneralConfiguration.class); + jTabbedPane1.addTab(resourceMap.getString("jPanel1.TabConstraints.tabTitle"), jPanel1); // NOI18N + + jPanel2.setName("jPanel2"); // NOI18N + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 474, Short.MAX_VALUE) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 338, Short.MAX_VALUE) + ); + + jTabbedPane1.addTab(resourceMap.getString("jPanel2.TabConstraints.tabTitle"), jPanel2); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jTabbedPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 478, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jTabbedPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 369, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new GeneralConfiguration().setVisible(true); + } + }); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JTabbedPane jTabbedPane1; + // End of variables declaration//GEN-END:variables + +} diff --git a/apps/desktopgui/src/gui/JPopupTrayIcon.java b/apps/desktopgui/src/gui/JPopupTrayIcon.java new file mode 100644 index 0000000000..83872e4a51 --- /dev/null +++ b/apps/desktopgui/src/gui/JPopupTrayIcon.java @@ -0,0 +1,176 @@ +/* +* Created on Sep 15, 2008 5:51:33 PM +*/ + +/* + * This class is part of fishfarm project: https://fishfarm.dev.java.net/ + * It is licensed under the GPL version 2.0 with Classpath Exception. + * + * Copyright (C) 2008 Michael Bien + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +package gui; + +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Point; +import java.awt.PopupMenu; +import java.awt.TrayIcon; +import java.awt.Window; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JWindow; +import javax.swing.RootPaneContainer; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + + + +/** + * JPopupMenu compatible TrayIcon based on Alexander Potochkin's JXTrayIcon + * (http://weblogs.java.net/blog/alexfromsun/archive/2008/02/jtrayicon_updat.html) + * but uses a JWindow instead of a JDialog to workaround some bugs on linux. + * + * @author Michael Bien + */ +public class JPopupTrayIcon extends TrayIcon { + + private JPopupMenu menu; + + private Window window; + private PopupMenuListener popupListener; + + private final static boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows"); + + public JPopupTrayIcon(Image image) { + super(image); + init(); + } + + public JPopupTrayIcon(Image image, String tooltip) { + super(image, tooltip); + init(); + } + + public JPopupTrayIcon(Image image, String tooltip, PopupMenu popup) { + super(image, tooltip, popup); + init(); + } + + public JPopupTrayIcon(Image image, String tooltip, JPopupMenu popup) { + super(image, tooltip); + init(); + setJPopupMenu(popup); + } + + + private final void init() { + + + popupListener = new PopupMenuListener() { + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { +// System.out.println("popupMenuWillBecomeVisible"); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { +// System.out.println("popupMenuWillBecomeInvisible"); + if(window != null) { + window.dispose(); + window = null; + } + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { +// System.out.println("popupMenuCanceled"); + if(window != null) { + window.dispose(); + window = null; + } + } + }; + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { +// System.out.println(e.getPoint()); + showJPopupMenu(e); + } + + @Override + public void mouseReleased(MouseEvent e) { +// System.out.println(e.getPoint()); + showJPopupMenu(e); + } + }); + + } + + private final void showJPopupMenu(MouseEvent e) { + if(e.isPopupTrigger() && menu != null) { + if (window == null) { + + if(IS_WINDOWS) { + window = new JDialog((Frame)null); + ((JDialog)window).setUndecorated(true); + }else{ + window = new JWindow((Frame)null); + } + window.setAlwaysOnTop(true); + Dimension size = menu.getPreferredSize(); + + Point centerPoint = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint(); + if(e.getY() > centerPoint.getY()) + window.setLocation(e.getX(), e.getY() - size.height); + else + window.setLocation(e.getX(), e.getY()); + + window.setVisible(true); + + menu.show(((RootPaneContainer)window).getContentPane(), 0, 0); + + // popup works only for focused windows + window.toFront(); + + } + } + } + + + public final JPopupMenu getJPopupMenu() { + return menu; + } + + public final void setJPopupMenu(JPopupMenu menu) { + if (this.menu != null) { + this.menu.removePopupMenuListener(popupListener); + } + this.menu = menu; + menu.addPopupMenuListener(popupListener); + } + +} diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/gui/SpeedSelector.form index 6ccf414b5b..79d25ae965 100644 --- a/apps/desktopgui/src/gui/SpeedSelector.form +++ b/apps/desktopgui/src/gui/SpeedSelector.form @@ -22,6 +22,7 @@ + diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/gui/SpeedSelector2.form index 215d407f0f..5c305de19b 100644 --- a/apps/desktopgui/src/gui/SpeedSelector2.form +++ b/apps/desktopgui/src/gui/SpeedSelector2.form @@ -26,6 +26,7 @@ + @@ -82,7 +83,7 @@ - + @@ -97,7 +98,7 @@ - + @@ -108,7 +109,7 @@ - + diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/gui/SpeedSelector2.java index d929e4ccd2..1177b57258 100644 --- a/apps/desktopgui/src/gui/SpeedSelector2.java +++ b/apps/desktopgui/src/gui/SpeedSelector2.java @@ -85,19 +85,19 @@ public class SpeedSelector2 extends javax.swing.JFrame { browseButton.setActionCommand(resourceMap.getString("browseButton.actionCommand")); // NOI18N browseButton.setName("browseButton"); // NOI18N getContentPane().add(browseButton); - browseButton.setBounds(40, 130, 520, 40); + browseButton.setBounds(40, 120, 520, 40); buttonGroup1.add(downloadButton); downloadButton.setText(resourceMap.getString("downloadButton.text")); // NOI18N downloadButton.setActionCommand(resourceMap.getString("downloadButton.actionCommand")); // NOI18N downloadButton.setName("downloadButton"); // NOI18N getContentPane().add(downloadButton); - downloadButton.setBounds(40, 80, 499, 40); + downloadButton.setBounds(40, 70, 499, 40); jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N jLabel1.setName("jLabel1"); // NOI18N getContentPane().add(jLabel1); - jLabel1.setBounds(30, 190, 530, 50); + jLabel1.setBounds(30, 170, 530, 70); pack(); }// //GEN-END:initComponents diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/gui/SpeedSelector3.form index ec26972216..9c6ef533aa 100644 --- a/apps/desktopgui/src/gui/SpeedSelector3.form +++ b/apps/desktopgui/src/gui/SpeedSelector3.form @@ -329,7 +329,7 @@ - + diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/gui/SpeedSelector3.java index 8115acb2bc..1f523befda 100644 --- a/apps/desktopgui/src/gui/SpeedSelector3.java +++ b/apps/desktopgui/src/gui/SpeedSelector3.java @@ -122,12 +122,12 @@ public class SpeedSelector3 extends javax.swing.JFrame { uploadUsageLabel.setText(resourceMap.getString("uploadUsageLabel.text")); // NOI18N uploadUsageLabel.setName("uploadUsageLabel"); // NOI18N getContentPane().add(uploadUsageLabel); - uploadUsageLabel.setBounds(20, 150, 141, 30); + uploadUsageLabel.setBounds(20, 150, 19, 30); downloadUsageLabel.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N downloadUsageLabel.setName("downloadUsageLabel"); // NOI18N getContentPane().add(downloadUsageLabel); - downloadUsageLabel.setBounds(340, 150, 162, 30); + downloadUsageLabel.setBounds(340, 150, 19, 30); uploadField.setText(resourceMap.getString("uploadField.text")); // NOI18N uploadField.setMinimumSize(new java.awt.Dimension(77, 27)); @@ -236,7 +236,7 @@ public class SpeedSelector3 extends javax.swing.JFrame { explanation.setText(resourceMap.getString("explanation.text")); // NOI18N explanation.setName("explanation"); // NOI18N getContentPane().add(explanation); - explanation.setBounds(20, 200, 600, 40); + explanation.setBounds(20, 180, 600, 70); pack(); }// //GEN-END:initComponents diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/gui/Tray.java index bf3cd28c87..5a8da78db9 100644 --- a/apps/desktopgui/src/gui/Tray.java +++ b/apps/desktopgui/src/gui/Tray.java @@ -9,9 +9,6 @@ import desktopgui.*; import java.awt.AWTException; import java.awt.Desktop; import java.awt.Image; -import java.awt.MenuItem; -import java.awt.Menu; -import java.awt.PopupMenu; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.TrayIcon; @@ -23,8 +20,12 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; import router.RouterHandler; import router.RouterHelper; +import router.configuration.PeerHelper; /** * @@ -41,12 +42,13 @@ public class Tray { Image image = Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo.jpg"); - PopupMenu popup = new PopupMenu(); + final JPopupMenu popup = new JPopupMenu(); //Create menu items to put in the popup menu - MenuItem browserLauncher = new MenuItem("Launch browser"); + JMenuItem browserLauncher = new JMenuItem("Launch browser"); browserLauncher.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { if(Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); @@ -66,9 +68,10 @@ public class Tray { } }); - MenuItem howto = new MenuItem("How to use I2P"); + JMenuItem howto = new JMenuItem("How to use I2P"); howto.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { if(Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); @@ -84,18 +87,20 @@ public class Tray { } }); - Menu config = new Menu("Configuration"); - MenuItem speedConfig = new MenuItem("Speed"); + JMenu config = new JMenu("Configuration"); + JMenuItem speedConfig = new JMenuItem("Speed"); speedConfig.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { (new SpeedSelector()).setVisible(true); } }); - MenuItem advancedConfig = new MenuItem("Advanced Configuration"); + JMenuItem advancedConfig = new JMenuItem("Advanced Configuration"); advancedConfig.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { if(Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); @@ -110,7 +115,7 @@ public class Tray { } }); - MenuItem viewLog = new MenuItem("View log"); + JMenuItem viewLog = new JMenuItem("View log"); viewLog.addActionListener(new ActionListener() { @Override @@ -119,17 +124,21 @@ public class Tray { } }); - MenuItem shutdown = new MenuItem("Shutdown I2P"); + JMenuItem shutdown = new JMenuItem("Shutdown I2P"); shutdown.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { RouterHandler.setStatus(RouterHandler.SHUTDOWN_GRACEFULLY); long shutdownTime = RouterHelper.getGracefulShutdownTimeRemaining(); System.out.println(shutdownTime); - if(shutdownTime>0) - trayIcon.displayMessage("Shutting down...", "Shutdown time remaining: " + shutdownTime/1000 + " seconds.", TrayIcon.MessageType.INFO); - else + if(shutdownTime>0) { + trayIcon.displayMessage("Shutting down...", "Shutdown time remaining: " + shutdownTime/1000 + " seconds." + + System.getProperty("line.separator") + "Shutdown will not happen immediately, because we are still participating in the network.", TrayIcon.MessageType.INFO); + } + else { trayIcon.displayMessage("Shutting down...", "Shutting down immediately.", TrayIcon.MessageType.INFO); + } } }); @@ -147,7 +156,31 @@ public class Tray { popup.add(shutdown); //Add tray icon - trayIcon = new TrayIcon(image, "I2P: the anonymous network", popup); + trayIcon = new JPopupTrayIcon(image, "I2P: the anonymous network", popup); + PeerHelper.addReachabilityListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + trayIcon.setToolTip("I2P Network status: " + PeerHelper.getReachability()); + } + + }); + PeerHelper.addActivePeerListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent arg0) { + int activePeers = PeerHelper.getActivePeers(); + if(activePeers == 0) + trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_red.jpg")); + else if(activePeers < 10) + trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_orange.jpg")); + else + trayIcon.setImage(Toolkit.getDefaultToolkit().getImage("desktopgui/resources/logo/logo_green.jpg")); + + } + + }); + try { tray.add(trayIcon); } catch (AWTException ex) { @@ -156,6 +189,6 @@ public class Tray { } private SystemTray tray = null; - private TrayIcon trayIcon = null; + private JPopupTrayIcon trayIcon = null; } diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/gui/resources/SpeedSelector.properties index 03b04286f6..00eb6c973d 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector.properties @@ -3,4 +3,4 @@ Form.title=I2P Speed Configuration nextButton.text=Next uploadLabel.text=What is your maximum upload speed? downloadLabel.text=What is your maximum download speed? -speedExplanation.text=Explanation about speeds... +speedExplanation.text=The maximum speed is set by your provider. It can be given in kilobit (kbps) or kilobyte (kBps).
    One kilobyte equals eight kilobit. diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties index 54424ed051..909518ada2 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector2.properties @@ -6,4 +6,4 @@ downloadButton.text=Downloading: I want to use I2P for downloads and filesharing nextButton.text=Next browseButton.actionCommand=Browsing downloadButton.actionCommand=Downloading -jLabel1.text=Text explaining ... +jLabel1.text=I2P can be used for many different purposes. Here, we present two possible descriptions. If you use a lot of bandwidth in I2P (for example using downloading), please check the downloading option. If your bandwidth usage is limited, please check the browsing option. diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties index 9d0718f8db..6dca3ca516 100644 --- a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties +++ b/apps/desktopgui/src/gui/resources/SpeedSelector3.properties @@ -14,4 +14,4 @@ downloadBurstField.text=jTextField5 uploadMonth.text=jTextField1 downloadMonth.text=jTextField2 settingsInfo.text=The profile information your entered, indicates that these are your optimal settings: -explanation.text=Text explaining ... +explanation.text=We give a suggested upload and download speed. If your provider imposes a monthly bandwidth limit (usually given in gigabyte (GB)), please enter a value lower than that limit. If you run I2P only 50% of the time, you can double the bandwidth limit to use the same amount as when you are online 100% of the time. diff --git a/apps/desktopgui/src/router/configuration/PeerHelper.java b/apps/desktopgui/src/router/configuration/PeerHelper.java new file mode 100644 index 0000000000..2272456f8d --- /dev/null +++ b/apps/desktopgui/src/router/configuration/PeerHelper.java @@ -0,0 +1,165 @@ +package router.configuration; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import net.i2p.data.RouterAddress; +import net.i2p.router.CommSystemFacade; +import net.i2p.router.RouterContext; +import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.router.transport.ntcp.NTCPAddress; +import router.RouterHelper; + +/** + * Part of the code imported and adapted from the I2P Router Console (which is licensed as public domain) + */ +public class PeerHelper { + public static String getReachability() { + RouterContext context = RouterHelper.getContext(); + if (context.router().getUptime() > 60*1000 + && (!context.router().gracefulShutdownInProgress()) + && !context.clientManager().isAlive()) + return "ERROR: Client Manager I2CP Error - check logs"; // not a router problem but the user should know + if (!context.clock().getUpdatedSuccessfully()) + return "ERROR: ClockSkew"; + if (context.router().isHidden()) + return "Hidden"; + + int status = context.commSystem().getReachabilityStatus(); + switch (status) { + case CommSystemFacade.STATUS_OK: + RouterAddress ra = context.router().getRouterInfo().getTargetAddress("NTCP"); + if (ra == null || (new NTCPAddress(ra)).isPubliclyRoutable()) + return "OK"; + return "ERROR: Private TCP Address"; + case CommSystemFacade.STATUS_DIFFERENT: + return "ERROR: You are behind a symmetric NAT."; + case CommSystemFacade.STATUS_REJECT_UNSOLICITED: + if (context.router().getRouterInfo().getTargetAddress("NTCP") != null) + return "WARNING: You are behind a firewall and have Inbound TCP Enabled"; + if (((FloodfillNetworkDatabaseFacade)context.netDb()).floodfillEnabled()) + return "WARNING: You are behind a firewall and are a floodfill router"; + if (context.router().getRouterInfo().getCapabilities().indexOf('O') >= 0) + return "WARNING: You are behind a firewall and are a fast router"; + return "Firewalled"; + case CommSystemFacade.STATUS_HOSED: + return "ERROR: The UDP port is already in use. Set i2np.udp.internalPort=xxxx to a different value in the advanced config and restart"; + case CommSystemFacade.STATUS_UNKNOWN: // fallthrough + default: + ra = context.router().getRouterInfo().getTargetAddress("UDP"); + if (ra == null && context.router().getUptime() > 5*60*1000) { + if (context.getProperty(PROP_I2NP_NTCP_HOSTNAME) == null || + context.getProperty(PROP_I2NP_NTCP_PORT) == null) + return "ERROR: UDP is disabled and the inbound TCP host/port combination is not set"; + else + return "WARNING: You are behind a firewall and have UDP Disabled"; + } + return "Testing"; + } + } + + /** + * How many peers we are talking to now + * + */ + public static int getActivePeers() { + RouterContext context = RouterHelper.getContext(); + if (context == null) + return 0; + else + return context.commSystem().countActivePeers(); + } + + public static void addActivePeerListener(ActionListener listener) { + synchronized(activePeerListeners) { + activePeerListeners.add(listener); + if(activePeerTimer == null) { + activePeerTimer = new Timer(); + TimerTask t = new TimerTask() { + private int activePeers = 0; + + @Override + public void run() { + int newActivePeers = getActivePeers(); + if(!(activePeers == newActivePeers)) { + synchronized(activePeerListeners) { + for(int i=0; i reachabilityListeners = new ArrayList(); + private static Timer reachabilityTimer = null; + + private static List activePeerListeners = new ArrayList(); + private static Timer activePeerTimer = null; + + /** copied from various private components */ + public final static String PROP_I2NP_UDP_PORT = "i2np.udp.port"; + public final static String PROP_I2NP_INTERNAL_UDP_PORT = "i2np.udp.internalPort"; + public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname"; + public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port"; + public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoip"; + public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoport"; +} From 879404f7e0274836b6f25ed2ebd88f0bd67ca79b Mon Sep 17 00:00:00 2001 From: sponge Date: Sat, 11 Apr 2009 13:55:38 +0000 Subject: [PATCH 27/27] 2009-04-11 sponge * i2ptunnel janitorial work and fixes on most locks. Some locks still need work, and are marked with LINT in the comment. Just grep for "LINT" to see where the remaining places are. --- .../src/net/i2p/i2ptunnel/BufferLogger.java | 2 +- .../i2p/i2ptunnel/HTTPResponseOutputStream.java | 6 ++++++ .../java/src/net/i2p/i2ptunnel/I2PTunnel.java | 14 +++++++------- .../src/net/i2p/i2ptunnel/I2PTunnelClient.java | 10 +++++----- .../net/i2p/i2ptunnel/I2PTunnelClientBase.java | 14 +++++++------- .../i2p/i2ptunnel/I2PTunnelConnectClient.java | 5 ++--- .../net/i2p/i2ptunnel/I2PTunnelHTTPClient.java | 4 ++-- .../i2ptunnel/I2PTunnelHTTPClientRunner.java | 2 ++ .../net/i2p/i2ptunnel/I2PTunnelHTTPServer.java | 4 ++++ .../net/i2p/i2ptunnel/I2PTunnelIRCClient.java | 6 +++--- .../net/i2p/i2ptunnel/I2PTunnelIRCServer.java | 5 +---- .../src/net/i2p/i2ptunnel/I2PTunnelRunner.java | 4 +++- .../src/net/i2p/i2ptunnel/I2PTunnelServer.java | 14 ++++++-------- .../src/net/i2p/i2ptunnel/I2PTunnelTask.java | 1 + .../java/src/net/i2p/i2ptunnel/I2Ping.java | 5 +++-- .../i2p/i2ptunnel/TunnelControllerGroup.java | 2 +- .../net/i2p/i2ptunnel/socks/ReplyTracker.java | 1 - .../net/i2p/i2ptunnel/socks/SOCKS5Server.java | 2 +- .../net/i2p/i2ptunnel/socks/SOCKSServer.java | 1 - .../net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java | 5 ++--- .../src/net/i2p/i2ptunnel/streamr/Pinger.java | 2 +- .../i2p/i2ptunnel/streamr/StreamrConsumer.java | 3 ++- .../i2p/i2ptunnel/streamr/StreamrProducer.java | 3 ++- .../net/i2p/i2ptunnel/streamr/Subscriber.java | 5 ----- .../java/src/net/i2p/i2ptunnel/udp/I2PSink.java | 2 +- .../net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java | 2 +- .../udpTunnel/I2PTunnelUDPClientBase.java | 17 ++--------------- .../udpTunnel/I2PTunnelUDPServerBase.java | 13 +------------ .../src/net/i2p/i2ptunnel/web/EditBean.java | 1 - history.txt | 5 +++++ .../java/src/net/i2p/router/RouterVersion.java | 2 +- 31 files changed, 73 insertions(+), 89 deletions(-) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java index 29f954fc4f..4c3da08988 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/BufferLogger.java @@ -14,7 +14,7 @@ import net.i2p.util.Log; */ class BufferLogger implements Logging { private final static Log _log = new Log(BufferLogger.class); - private ByteArrayOutputStream _baos; + private ByteArrayOutputStream _baos; // should be final and use a factory. LINT private boolean _ignore; /** diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java index 411b2c4426..12940a81bc 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java @@ -59,13 +59,16 @@ class HTTPResponseOutputStream extends FilterOutputStream { _buf1 = new byte[1]; } + @Override public void write(int c) throws IOException { _buf1[0] = (byte)c; write(_buf1, 0, 1); } + @Override public void write(byte buf[]) throws IOException { write(buf, 0, buf.length); } + @Override public void write(byte buf[], int off, int len) throws IOException { if (_headerWritten) { out.write(buf, off, len); @@ -207,6 +210,7 @@ class HTTPResponseOutputStream extends FilterOutputStream { out.write("\r\n".getBytes()); // end of the headers } + @Override public void close() throws IOException { out.close(); } @@ -303,11 +307,13 @@ class HTTPResponseOutputStream extends FilterOutputStream { return true; } } + @Override public String toString() { return "Read: " + getTotalRead() + " expanded: " + getTotalExpanded() + " remaining: " + getRemaining() + " finished: " + getFinished(); } } + @Override public String toString() { return super.toString() + ": " + _in; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java index b11f954e7e..015f5c9c58 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java @@ -75,7 +75,7 @@ public class I2PTunnel implements Logging, EventDispatcher { private static long __tunnelId = 0; private long _tunnelId; private Properties _clientOptions; - private List _sessions; + private final List _sessions; public static final int PACKET_DELAY = 100; @@ -89,7 +89,7 @@ public class I2PTunnel implements Logging, EventDispatcher { private static final String nocli_args[] = { "-nocli", "-die"}; - private List tasks = new ArrayList(); + private final List tasks = new ArrayList(); private int next_task_id = 1; private Set listeners = new HashSet(); @@ -606,9 +606,9 @@ public class I2PTunnel implements Logging, EventDispatcher { */ public void runHttpClient(String args[], Logging l) { if (args.length >= 1 && args.length <= 3) { - int port = -1; + int clientPort = -1; try { - port = Integer.parseInt(args[0]); + clientPort = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { l.log("invalid port"); _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe); @@ -642,12 +642,12 @@ public class I2PTunnel implements Logging, EventDispatcher { I2PTunnelTask task; ownDest = !isShared; try { - task = new I2PTunnelHTTPClient(port, l, ownDest, proxy, (EventDispatcher) this, this); + task = new I2PTunnelHTTPClient(clientPort, l, ownDest, proxy, (EventDispatcher) this, this); addtask(task); notifyEvent("httpclientTaskId", Integer.valueOf(task.getId())); } catch (IllegalArgumentException iae) { - _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae); - l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]"); + _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ clientPort + "]", iae); + l.log("Invalid I2PTunnel configuration [" + host + ":" + clientPort + "]"); notifyEvent("httpclientTaskId", Integer.valueOf(-1)); } } else { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java index 502bb28d5c..12931f3a27 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClient.java @@ -44,11 +44,11 @@ public class I2PTunnelClient extends I2PTunnelClientBase { while (tok.hasMoreTokens()) { String destination = tok.nextToken(); try { - Destination dest = I2PTunnel.destFromName(destination); - if (dest == null) + Destination destN = I2PTunnel.destFromName(destination); + if (destN == null) l.log("Could not resolve " + destination); else - dests.add(dest); + dests.add(destN); } catch (DataFormatException dfe) { l.log("Bad format parsing \"" + destination + "\""); } @@ -71,10 +71,10 @@ public class I2PTunnelClient extends I2PTunnelClientBase { public long getReadTimeout() { return readTimeout; } protected void clientConnectionRun(Socket s) { - Destination dest = pickDestination(); + Destination destN = pickDestination(); I2PSocket i2ps = null; try { - i2ps = createI2PSocket(dest); + i2ps = createI2PSocket(destN); i2ps.setReadTimeout(readTimeout); new I2PTunnelRunner(s, i2ps, sockLock, null, mySockets); } catch (Exception ex) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java index 5b9aa23cbb..c4eafde506 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelClientBase.java @@ -41,8 +41,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna private static volatile long __clientId = 0; protected long _clientId; - protected Object sockLock = new Object(); // Guards sockMgr and mySockets - protected I2PSocketManager sockMgr; + protected final Object sockLock = new Object(); // Guards sockMgr and mySockets + protected I2PSocketManager sockMgr; // should be final and use a factory. LINT protected List mySockets = new ArrayList(); protected Destination dest = null; @@ -52,20 +52,20 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna private ServerSocket ss; - private Object startLock = new Object(); + private final Object startLock = new Object(); private boolean startRunning = false; - private Object closeLock = new Object(); + // private Object closeLock = new Object(); - private byte[] pubkey; + // private byte[] pubkey; private String handlerName; private String privKeyFile; - private Object conLock = new Object(); + // private Object conLock = new Object(); /** List of Socket for those accept()ed but not yet started up */ - private List _waitingSockets = new ArrayList(); + private List _waitingSockets = new ArrayList(); // should be final and use a factory. LINT /** How many connections will we allow to be in the process of being built at once? */ private int _numConnectionBuilders; /** How long will we allow sockets to sit in the _waitingSockets map before killing them? */ diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java index bf7ebf0a7a..d781866d95 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelConnectClient.java @@ -17,7 +17,6 @@ import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; -import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.util.EventDispatcher; @@ -55,7 +54,7 @@ import net.i2p.util.Log; public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runnable { private static final Log _log = new Log(I2PTunnelConnectClient.class); - private List _proxyList; + private final List _proxyList; private final static byte[] ERR_DESTINATION_UNKNOWN = ("HTTP/1.1 503 Service Unavailable\r\n"+ @@ -116,12 +115,12 @@ public class I2PTunnelConnectClient extends I2PTunnelClientBase implements Runna I2PTunnel tunnel) throws IllegalArgumentException { super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel); + _proxyList = new ArrayList(); if (waitEventValue("openBaseClientResult").equals("error")) { notifyEvent("openConnectClientResult", "error"); return; } - _proxyList = new ArrayList(); if (wwwProxy != null) { StringTokenizer tok = new StringTokenizer(wwwProxy, ","); while (tok.hasMoreTokens()) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 8e5ef9f4dc..93c8e86742 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -50,7 +50,7 @@ import net.i2p.util.Log; public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable { private static final Log _log = new Log(I2PTunnelHTTPClient.class); - private List proxyList; + private final List proxyList; private HashMap addressHelpers = new HashMap(); @@ -145,12 +145,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable I2PTunnel tunnel) throws IllegalArgumentException { super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId), tunnel); + proxyList = new ArrayList(); if (waitEventValue("openBaseClientResult").equals("error")) { notifyEvent("openHTTPClientResult", "error"); return; } - proxyList = new ArrayList(); if (wwwProxy != null) { StringTokenizer tok = new StringTokenizer(wwwProxy, ","); while (tok.hasMoreTokens()) diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java index 9ade9021f5..6afc940f7d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClientRunner.java @@ -30,11 +30,13 @@ public class I2PTunnelHTTPClientRunner extends I2PTunnelRunner { _log = I2PAppContext.getGlobalContext().logManager().getLog(I2PTunnelHTTPClientRunner.class); } + @Override protected OutputStream getSocketOut() throws IOException { OutputStream raw = super.getSocketOut(); return new HTTPResponseOutputStream(raw); } + @Override protected void close(OutputStream out, InputStream in, OutputStream i2pout, InputStream i2pin, Socket s, I2PSocket i2ps, Thread t1, Thread t2) throws InterruptedException, IOException { try { i2pin.close(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java index d6cb40a259..d43639cb7e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java @@ -59,6 +59,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { * Called by the thread pool of I2PSocket handlers * */ + @Override protected void blockingHandle(I2PSocket socket) { long afterAccept = getTunnel().getContext().clock().now(); long afterSocket = -1; @@ -247,7 +248,9 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { super(o); } + @Override protected boolean shouldCompress() { return true; } + @Override protected void finishHeaders() throws IOException { if (_log.shouldLog(Log.INFO)) _log.info("Including x-i2p-gzip as the content encoding in the response"); @@ -255,6 +258,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer { super.finishHeaders(); } + @Override protected void beginProcessing() throws IOException { if (_log.shouldLog(Log.INFO)) _log.info("Beginning compression processing"); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java index 732c222a71..8b62702198 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCClient.java @@ -51,11 +51,11 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable while (tok.hasMoreTokens()) { String destination = tok.nextToken(); try { - Destination dest = I2PTunnel.destFromName(destination); - if (dest == null) + Destination destN = I2PTunnel.destFromName(destination); + if (destN == null) l.log("Could not resolve " + destination); else - dests.add(dest); + dests.add(destN); } catch (DataFormatException dfe) { l.log("Bad format parsing \"" + destination + "\""); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java index 7e12aa30a6..2e209dbd74 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelIRCServer.java @@ -3,22 +3,18 @@ package net.i2p.i2ptunnel; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.util.Properties; -import net.i2p.I2PAppContext; import net.i2p.client.streaming.I2PSocket; import net.i2p.crypto.SHA256Generator; -import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; import net.i2p.data.Hash; import net.i2p.data.Base32; import net.i2p.util.EventDispatcher; -import net.i2p.util.I2PThread; import net.i2p.util.Log; /** @@ -83,6 +79,7 @@ public class I2PTunnelIRCServer extends I2PTunnelServer implements Runnable { this.hostname = opts.getProperty(PROP_HOSTNAME, PROP_HOSTNAME_DEFAULT); } + @Override protected void blockingHandle(I2PSocket socket) { try { // give them 15 seconds to send in the request diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java index f237b97efb..76480a9403 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelRunner.java @@ -36,7 +36,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL private Socket s; private I2PSocket i2ps; - Object slock, finishLock = new Object(); + final Object slock, finishLock = new Object(); boolean finished = false; HashMap ostreams, sockets; byte[] initialI2PData; @@ -114,6 +114,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL protected InputStream getSocketIn() throws IOException { return s.getInputStream(); } protected OutputStream getSocketOut() throws IOException { return s.getOutputStream(); } + @Override public void run() { try { InputStream in = getSocketIn(); @@ -239,6 +240,7 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL start(); } + @Override public void run() { String from = i2ps.getThisDestination().calculateHash().toBase64().substring(0,6); String to = i2ps.getPeerDestination().calculateHash().toBase64().substring(0,6); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java index fa3478c71d..76246d7b66 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelServer.java @@ -18,8 +18,6 @@ import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.I2PException; -import net.i2p.client.I2PClient; -import net.i2p.client.I2PClientFactory; import net.i2p.client.streaming.I2PServerSocket; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketManager; @@ -36,8 +34,8 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { protected I2PSocketManager sockMgr; protected I2PServerSocket i2pss; - private Object lock = new Object(); - protected Object slock = new Object(); + private final Object lock = new Object(); + protected final Object slock = new Object(); protected InetAddress remoteHost; protected int remotePort; @@ -203,17 +201,17 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable { public void run() { if (shouldUsePool()) { - I2PServerSocket i2pss = sockMgr.getServerSocket(); + I2PServerSocket i2pS_S = sockMgr.getServerSocket(); int handlers = getHandlerCount(); for (int i = 0; i < handlers; i++) { - I2PThread handler = new I2PThread(new Handler(i2pss), "Handle Server " + i); + I2PThread handler = new I2PThread(new Handler(i2pS_S), "Handle Server " + i); handler.start(); } } else { - I2PServerSocket i2pss = sockMgr.getServerSocket(); + I2PServerSocket i2pS_S = sockMgr.getServerSocket(); while (true) { try { - final I2PSocket i2ps = i2pss.accept(); + final I2PSocket i2ps = i2pS_S.accept(); if (i2ps == null) throw new I2PException("I2PServerSocket closed"); new I2PThread(new Runnable() { public void run() { blockingHandle(i2ps); } }).start(); } catch (I2PException ipe) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java index 6aa8ca18f7..4a6a0bb66e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelTask.java @@ -73,6 +73,7 @@ public abstract class I2PTunnelTask implements EventDispatcher { public void reportAbuse(I2PSession session, int severity) { } + @Override public String toString() { return name; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java index 019bc7826c..f57ecd23d1 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2Ping.java @@ -37,11 +37,11 @@ public class I2Ping extends I2PTunnelTask implements Runnable { private String command; private long timeout = PING_TIMEOUT; - private Object simulLock = new Object(); + private final Object simulLock = new Object(); private int simulPings = 0; private long lastPingTime = 0; - private Object lock = new Object(), slock = new Object(); + private final Object lock = new Object(), slock = new Object(); //public I2Ping(String cmd, Logging l, // boolean ownDest) { @@ -197,6 +197,7 @@ public class I2Ping extends I2PTunnelTask implements Runnable { start(); } + @Override public void run() { try { Destination dest = I2PTunnel.destFromName(destination); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java index 3b6e765399..16f418be9e 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelControllerGroup.java @@ -40,7 +40,7 @@ public class TunnelControllerGroup { * no more tunnels are using it) * */ - private Map _sessions; + private final Map _sessions; public static TunnelControllerGroup getInstance() { synchronized (TunnelControllerGroup.class) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java index f6a124c951..fbdf2939d1 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/ReplyTracker.java @@ -1,6 +1,5 @@ package net.i2p.i2ptunnel.socks; -import java.util.concurrent.ConcurrentHashMap; import java.util.Map; import net.i2p.data.Destination; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java index 5e52926074..cc397c414c 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKS5Server.java @@ -379,7 +379,7 @@ public class SOCKS5Server extends SOCKSServer { // This isn't really the right place for this, we can't stop the tunnel once it starts. static SOCKSUDPTunnel _tunnel; - static Object _startLock = new Object(); + static final Object _startLock = new Object(); static byte[] dummyIP = new byte[4]; /** * We got a UDP associate command. diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java index 06c3fab552..09e9284deb 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSServer.java @@ -9,7 +9,6 @@ package net.i2p.i2ptunnel.socks; import java.net.Socket; import net.i2p.client.streaming.I2PSocket; -import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.util.Log; /** diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java index 0adaa19506..0490a6f0af 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/socks/SOCKSUDPTunnel.java @@ -7,10 +7,7 @@ import java.util.Map; import net.i2p.data.Destination; import net.i2p.i2ptunnel.I2PTunnel; -import net.i2p.i2ptunnel.Logging; -import net.i2p.i2ptunnel.udp.*; import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPClientBase; -import net.i2p.util.EventDispatcher; /** * A Datagram Tunnel that can have multiple bidirectional ports on the UDP side. @@ -63,12 +60,14 @@ public class SOCKSUDPTunnel extends I2PTunnelUDPClientBase { } } + @Override public final void startRunning() { super.startRunning(); // demuxer start() doesn't do anything startall(); } + @Override public boolean close(boolean forced) { stopall(); return super.close(forced); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java index a3a7975361..29d1186c4d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Pinger.java @@ -54,6 +54,6 @@ public class Pinger implements Source, Runnable { protected Sink sink; protected Thread thread; - protected Object waitlock; + protected Object waitlock; // should be final and use a factory. LINT protected boolean running; } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java index 87ea0eefe6..9c1d584ae0 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrConsumer.java @@ -7,7 +7,6 @@ package net.i2p.i2ptunnel.streamr; import java.net.InetAddress; -import net.i2p.data.Destination; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; @@ -38,6 +37,7 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase { this.pinger.setSink(this); } + @Override public final void startRunning() { super.startRunning(); // send subscribe-message @@ -45,6 +45,7 @@ public class StreamrConsumer extends I2PTunnelUDPClientBase { l.log("Streamr client ready"); } + @Override public boolean close(boolean forced) { // send unsubscribe-message this.pinger.stop(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java index b801cb94f4..7d6b14491d 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/StreamrProducer.java @@ -9,7 +9,6 @@ package net.i2p.i2ptunnel.streamr; import java.io.File; // i2p -import net.i2p.client.I2PSession; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; @@ -45,12 +44,14 @@ public class StreamrProducer extends I2PTunnelUDPServerBase { this.server.setSink(this.multi); } + @Override public final void startRunning() { super.startRunning(); this.server.start(); l.log("Streamr server ready"); } + @Override public boolean close(boolean forced) { this.server.stop(); this.multi.stop(); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java index 97abdb8890..377292ddef 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/streamr/Subscriber.java @@ -6,17 +6,12 @@ package net.i2p.i2ptunnel.streamr; // system -import java.io.File; import java.util.Set; // i2p import net.i2p.client.I2PSession; import net.i2p.data.Destination; -import net.i2p.i2ptunnel.I2PTunnel; -import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; -import net.i2p.i2ptunnel.udpTunnel.I2PTunnelUDPServerBase; -import net.i2p.util.EventDispatcher; import net.i2p.util.ConcurrentHashSet; /** diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java index f7a1bf541e..e08596af11 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSink.java @@ -69,5 +69,5 @@ public class I2PSink implements Sink { protected boolean raw; protected I2PSession sess; protected Destination dest; - protected I2PDatagramMaker maker; + protected I2PDatagramMaker maker; // should be final and use a factory. LINT } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java index 54d5a7d97d..2da942a74a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udp/I2PSinkAnywhere.java @@ -67,5 +67,5 @@ public class I2PSinkAnywhere implements Sink { protected boolean raw; protected I2PSession sess; protected Destination dest; - protected I2PDatagramMaker maker; + protected I2PDatagramMaker maker; // should be final and use a factory. LINT } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java index b592af5e49..14945c8422 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPClientBase.java @@ -5,21 +5,9 @@ package net.i2p.i2ptunnel.udpTunnel; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.NoRouteToHostException; import java.net.ServerSocket; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; import net.i2p.I2PAppContext; -import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; @@ -31,7 +19,6 @@ import net.i2p.i2ptunnel.I2PTunnelTask; import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; import net.i2p.util.EventDispatcher; -import net.i2p.util.I2PThread; import net.i2p.util.Log; /** @@ -107,11 +94,11 @@ import net.i2p.util.Log; // create i2pclient and destination I2PClient client = I2PClientFactory.createClient(); - Destination dest; + Destination destN; byte[] key; try { ByteArrayOutputStream out = new ByteArrayOutputStream(512); - dest = client.createDestination(out); + destN = client.createDestination(out); key = out.toByteArray(); } catch(Exception exc) { throw new RuntimeException("failed to create i2p-destination", exc); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java index 2ab2687ffa..6ba8379f94 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/udpTunnel/I2PTunnelUDPServerBase.java @@ -3,33 +3,22 @@ */ package net.i2p.i2ptunnel.udpTunnel; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.util.Iterator; -import java.util.Properties; -import net.i2p.I2PAppContext; import net.i2p.I2PException; import net.i2p.client.I2PClient; import net.i2p.client.I2PClientFactory; import net.i2p.client.I2PSession; import net.i2p.client.I2PSessionException; -import net.i2p.data.Base64; import net.i2p.data.Destination; import net.i2p.i2ptunnel.I2PTunnel; import net.i2p.i2ptunnel.I2PTunnelTask; import net.i2p.i2ptunnel.Logging; import net.i2p.i2ptunnel.udp.*; import net.i2p.util.EventDispatcher; -import net.i2p.util.I2PThread; import net.i2p.util.Log; /** @@ -59,7 +48,7 @@ public class I2PTunnelUDPServerBase extends I2PTunnelTask implements Source, Sin private final static Log _log = new Log(I2PTunnelUDPServerBase.class); - private Object lock = new Object(); + private final Object lock = new Object(); protected Object slock = new Object(); private static volatile long __serverId = 0; diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 0a909c62ec..a029de725a 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -8,7 +8,6 @@ package net.i2p.i2ptunnel.web; * */ -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Properties; diff --git a/history.txt b/history.txt index 807629d8cc..5d2643a9d0 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,8 @@ +2009-04-11 sponge + * i2ptunnel janitorial work and fixes on most locks. + Some locks still need work, and are marked with LINT in the comment. + Just grep for "LINT" to see where the remaining places are. + 2009-04-10 sponge * More BOB threadgroup fixes, plus debug dump when things go wrong. * Fixes to streaminglib, I2CP, which are related to the TG problem. diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 6501cf5a77..9900aa2884 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -17,7 +17,7 @@ import net.i2p.CoreVersion; public class RouterVersion { public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 15; + public final static long BUILD = 16; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID);
  • eepsites.i2p: an anonymously hosted search engine of eepsites