Author: xor
Date: 2008-11-12 01:41:40 +0000 (Wed, 12 Nov 2008)
New Revision: 23509

Modified:
   trunk/plugins/WoT/introduction/IntroductionClient.java
Log:
Implement the IntroductionClient. It is the class to download puzzles from the 
network. All we need now is some code for inserting the solution of a puzzle 
(not very much code probably, should be also placed in IntroductionClient) and 
a captcha factory.

Modified: trunk/plugins/WoT/introduction/IntroductionClient.java
===================================================================
--- trunk/plugins/WoT/introduction/IntroductionClient.java      2008-11-12 
01:40:21 UTC (rev 23508)
+++ trunk/plugins/WoT/introduction/IntroductionClient.java      2008-11-12 
01:41:40 UTC (rev 23509)
@@ -5,6 +5,32 @@
  */
 package plugins.WoT.introduction;
 
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import plugins.WoT.Identity;
+import plugins.WoT.OwnIdentity;
+
+import com.db4o.ObjectContainer;
+import com.db4o.ObjectSet;
+import com.db4o.query.Query;
+
+import freenet.client.FetchContext;
+import freenet.client.FetchException;
+import freenet.client.FetchResult;
+import freenet.client.HighLevelSimpleClient;
+import freenet.client.InsertException;
+import freenet.client.async.BaseClientPutter;
+import freenet.client.async.ClientCallback;
+import freenet.client.async.ClientGetter;
+import freenet.keys.FreenetURI;
+import freenet.node.RequestStarter;
+import freenet.support.Logger;
+import freenet.support.io.TempBucketFactory;
+
 /**
  * This class allows the user to announce new identities:
  * It downloads puzzles from known identites and uploads solutions of the 
puzzles.
@@ -12,6 +38,231 @@
  * @author xor
  *
  */
-public class IntroductionClient {
+public class IntroductionClient implements Runnable, ClientCallback  {
+       
+       private static final long THREAD_PERIOD = 30 * 60 * 1000; /* FIXME: 
tweak before release: */ 
+       
+       public static final byte PUZZLE_DOWNLOAD_BACKWARDS_DAYS = 
IntroductionServer.PUZZLE_INVALID_AFTER_DAYS - 1;
 
+       public static final int PUZZLE_REQUEST_COUNT = 16;
+       
+       public static final int PUZZLE_POOL_SIZE = 128;
+       
+       /* FIXME: Display a random puzzle of an identity in the UI instead of 
always the first one! Otherwise we should really have each identity only
+        * insert one puzzle per day. But it might be a good idea to allow many 
puzzles per identity: Our seed identity could be configured to
+        * insert a very large amount of puzzles and therefore help the WoT 
while it is small */
+       public static final int MAX_PUZZLES_PER_IDENTITY = 
IntroductionServer.PUZZLE_COUNT;
+
+       private static final int MINIMUM_SCORE_FOR_PUZZLE_DOWNLOAD = 10; /* 
FIXME: tweak before release */
+
+       private Thread mThread;
+       
+       /** Used to tell the introduction server thread if it should stop */
+       private boolean isRunning;
+       
+       /** A reference to the database */
+       private ObjectContainer db;
+
+       /** A reference the HighLevelSimpleClient used to perform inserts */
+       private HighLevelSimpleClient mClient;
+       
+       /** The TempBucketFactory used to create buckets from puzzles before 
insert */
+       private final TempBucketFactory mTBF;
+       
+       /** All current requests */
+       /* FIXME FIXME FIXME: Use LRUQueue instead. ArrayBlockingQueue does not 
use a Hashset for contains()! */
+       private final ArrayBlockingQueue<Identity> mIdentities = new 
ArrayBlockingQueue<Identity>(PUZZLE_POOL_SIZE); /* FIXME: figure out whether my 
assumption that this is just the right size is correct */
+       private final HashSet<ClientGetter> mRequests = new 
HashSet<ClientGetter>(PUZZLE_REQUEST_COUNT * 2); /* TODO: profile & tweak */
+
+       /**
+        * Creates an IntroductionServer
+        * 
+        * @param db
+        *            A reference to the database
+        * @param client
+        *            A reference to an [EMAIL PROTECTED] 
HighLevelSimpleClient} to perform
+        *            inserts
+        * @param tbf
+        *            Needed to create buckets from Identities before insert
+        */
+       public IntroductionClient(ObjectContainer myDB, HighLevelSimpleClient 
myClient, TempBucketFactory myTBF) {
+               isRunning = true;
+               db = myDB;
+               mClient = myClient;
+               mTBF = myTBF;
+       }
+
+       public void run() {
+               Logger.debug(this, "Introduction client thread started.");
+               
+               mThread = Thread.currentThread();
+               try {
+                       Thread.sleep((long) (1*60*1000 * (0.5f + 
Math.random()))); // Let the node start up
+               }
+               catch (InterruptedException e)
+               {
+                       mThread.interrupt();
+               }
+               
+               while(isRunning) {
+                       Logger.debug(this, "Introduction client loop 
running...");
+                       
+                       IntroductionPuzzle.deleteExpiredPuzzles(db);
+                       downloadPuzzles();
+                       
+                       try {
+                               Thread.sleep((long) (THREAD_PERIOD * (0.5f + 
Math.random())));
+                       }
+                       catch (InterruptedException e)
+                       {
+                               mThread.interrupt();
+                               Logger.debug(this, "Introduction client loop 
interrupted.");
+                       }
+                       Logger.debug(this, "Introduction client loop 
finished.");
+               }
+               
+               cancelRequests();
+               Logger.debug(this, "Introduction client thread finished.");
+       }
+       
+       public synchronized void terminate() {
+               Logger.debug(this, "Stopping the introduction client...");
+               isRunning = false;
+               mThread.interrupt();
+               try {
+                       mThread.join();
+               }
+               catch(InterruptedException e)
+               {
+                       Thread.currentThread().interrupt();
+               }
+               Logger.debug(this, "Stopped the introduction client.");
+       }
+       
+       private synchronized void cancelRequests() {
+               Iterator<ClientGetter> i = mRequests.iterator();
+               int counter = 0;
+               Logger.debug(this, "Trying to stop all requests"); 
+               while (i.hasNext()) { i.next().cancel(); ++counter; }
+               mIdentities.clear();
+               Logger.debug(this, "Stopped " + counter + " current requests");
+       }
+       
+       private synchronized void downloadPuzzles() {
+               Query q = db.query();
+               q.constrain(Identity.class);
+               q.constrain(OwnIdentity.class).not();
+               q.descend("lastChange").constrain(new 
Date(System.currentTimeMillis() - 1 * 24 * 60 * 60 * 1000)).greater();
+               q.descend("lastChange").orderDescending(); /* This should 
choose identities in a sufficiently random order */
+               ObjectSet<Identity> allIds = q.execute();
+               ArrayList<Identity> ids = new 
ArrayList<Identity>(PUZZLE_POOL_SIZE);
+               
+               int counter = 0;
+               for(Identity i : allIds) {
+                       /* TODO: Create a "boolean providesIntroduction" in 
Identity to use a database query instead of this */ 
+                       
if(i.hasContext(IntroductionPuzzle.INTRODUCTION_CONTEXT) && 
!mIdentities.contains(i)
+                                       && i.getBestScore(db) > 
MINIMUM_SCORE_FOR_PUZZLE_DOWNLOAD)  {
+                               ids.add(i);
+                               ++counter;
+                       }
+
+                       if(counter == PUZZLE_REQUEST_COUNT)
+                               break;
+               }
+               
+               if(counter == 0) {
+                       ids.clear();  /* We probably have less updated 
identities today than the size of the LRUQueue, empty it */
+
+                       for(Identity i : allIds) {
+                               /* TODO: Create a "boolean 
providesIntroduction" in Identity to use a database query instead of this */ 
+                               
if(i.hasContext(IntroductionPuzzle.INTRODUCTION_CONTEXT) && i.getBestScore(db) 
> MINIMUM_SCORE_FOR_PUZZLE_DOWNLOAD)  {
+                                       ids.add(i);
+                                       ++counter;
+                               }
+
+                               if(counter == PUZZLE_REQUEST_COUNT)
+                                       break;
+                       }
+               }
+
+               
+               /* I suppose its a good idea to restart downloading the puzzles 
from the latest updated identities every time the thread iterates
+                * This prevents denial of service because people will usually 
get very new puzzles. */
+               cancelRequests();
+               
+               for(Identity i : ids) {
+                       try {
+                               downloadPuzzle(i, 0);
+                       } catch (Exception e) {
+                               Logger.error(this, "Starting puzzle download 
failed.", e);
+                       }
+               }
+               
+       }
+               
+       private synchronized void downloadPuzzle(Identity identity, int index) 
throws FetchException {
+               FreenetURI uri = 
IntroductionPuzzle.generateRequestURI(identity, new Date(), index);
+               
+               FetchContext fetchContext = mClient.getFetchContext();
+               fetchContext.maxSplitfileBlockRetries = -1; // retry forever
+               fetchContext.maxNonSplitfileRetries = -1; // retry forever
+               ClientGetter g = mClient.fetch(uri, -1, this, this, 
fetchContext);
+               g.setPriorityClass(RequestStarter.UPDATE_PRIORITY_CLASS); /* 
FIXME: decide which one to use */
+               mRequests.add(g);
+               if(!mIdentities.contains(identity)) {
+                       mIdentities.poll();
+                       try {
+                       mIdentities.put(identity);
+                       } catch(InterruptedException e) {}
+               }
+               Logger.debug(this, "Trying to fetch puzzle from " + 
uri.toString());
+       }
+
+
+       /**
+        * Called when the node can't fetch a file OR when there is a newer 
edition.
+        * In our case, called when there is no puzzle available.
+        */
+       public synchronized void onFailure(FetchException e, ClientGetter 
state) {
+               Logger.normal(this, "Downloading puzzle " + state.getURI() + " 
failed.", e);
+
+               mRequests.remove(state);
+       }
+
+       /**
+        * Called when a puzzle is successfully fetched.
+        */
+       public synchronized void onSuccess(FetchResult result, ClientGetter 
state) {
+               Logger.debug(this, "Fetched puzzle: " + state.getURI());
+
+               try {
+                       IntroductionPuzzle p = 
IntroductionPuzzle.importFromXML(db, result.asBucket().getInputStream(), 
state.getURI());
+                       IntroductionPuzzle.deleteOldestPuzzles(db, 
PUZZLE_POOL_SIZE);
+                       state.cancel(); /* FIXME: is this necessary */ 
+                       mRequests.remove(state);
+                       if(p.getIndex() < MAX_PUZZLES_PER_IDENTITY) {
+                               downloadPuzzle(p.getInserter(), p.getIndex() + 
1);
+                       }
+               } catch (Exception e) { 
+                       Logger.error(this, "Parsing failed for "+ 
state.getURI(), e);
+               }
+       }
+       
+       /* Not needed functions from the ClientCallback inteface */
+       
+       // Only called by inserts
+       public void onSuccess(BaseClientPutter state) {}
+       
+       // Only called by inserts
+       public void onFailure(InsertException e, BaseClientPutter state) {}
+
+       // Only called by inserts
+       public void onFetchable(BaseClientPutter state) {}
+
+       // Only called by inserts
+       public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) {}
+
+       /** Called when freenet.async thinks that the request should be 
serialized to
+        * disk, if it is a persistent request. */
+       public void onMajorProgress() {}
 }

_______________________________________________
cvs mailing list
[email protected]
http://emu.freenetproject.org/cgi-bin/mailman/listinfo/cvs

Reply via email to