Author: dbkr
Date: 2006-07-28 17:37:32 +0000 (Fri, 28 Jul 2006)
New Revision: 9799

Added:
   trunk/apps/Freemail/src/freemail/HashSlotManager.java
   trunk/apps/Freemail/src/freemail/NIMContact.java
   trunk/apps/Freemail/src/freemail/Postman.java
   trunk/apps/Freemail/src/freemail/SlotManager.java
   trunk/apps/Freemail/src/freemail/SlotSaveCallback.java
Removed:
   trunk/apps/Freemail/src/freemail/Contact.java
Modified:
   trunk/apps/Freemail/src/freemail/AccountManager.java
   trunk/apps/Freemail/src/freemail/InboundContact.java
   trunk/apps/Freemail/src/freemail/NIMFetcher.java
   trunk/apps/Freemail/src/freemail/OutboundContact.java
   trunk/apps/Freemail/src/freemail/RTSFetcher.java
   trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java
   trunk/apps/Freemail/src/freemail/utils/PropsFile.java
Log:
It lives! (That is: implement message polling and ACK sending). One more 
protocol change planned, and some changes in the directory format, so don't 
bank on not having to wipe the directory and start again *just yet*. ;)


Modified: trunk/apps/Freemail/src/freemail/AccountManager.java
===================================================================
--- trunk/apps/Freemail/src/freemail/AccountManager.java        2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/AccountManager.java        2006-07-28 
17:37:32 UTC (rev 9799)
@@ -63,7 +63,7 @@
                        if (!nimdir.mkdir()) throw new IOException("Failed to 
create nim directory");
                }

-               File keyfile = new File(nimdir, Contact.KEYFILE);
+               File keyfile = new File(nimdir, NIMContact.KEYFILE);
                PrintWriter pw = new PrintWriter(new FileOutputStream(keyfile));

                pw.println(MessageSender.NIM_KEY_PREFIX + username + "-");

Deleted: trunk/apps/Freemail/src/freemail/Contact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/Contact.java       2006-07-27 23:59:06 UTC 
(rev 9798)
+++ trunk/apps/Freemail/src/freemail/Contact.java       2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -1,50 +0,0 @@
-package freemail;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.FileReader;
-import java.io.BufferedReader;
-import java.util.Date;
-
-import freemail.utils.DateStringFactory;
-
-public class Contact {
-       public static final String KEYFILE = "key";
-       private static final String LOGFILE_PREFIX = "log-";
-       private final File contact_dir;
-       private final File keyfile;
-
-       Contact(File dir) {
-               this.contact_dir = dir;
-               this.keyfile = new File(dir, KEYFILE);
-       }
-       
-       public String getKey() throws IOException {
-               FileReader frdr = new FileReader(this.keyfile);
-               BufferedReader br = new BufferedReader(frdr);
-               String key =  br.readLine();
-               frdr.close();
-               return key;
-       }
-       
-       public MailLog getLog(String date) {
-               return new MailLog(new File(this.contact_dir, LOGFILE_PREFIX + 
date));
-       }
-       
-       public void pruneLogs(Date keepafter) {
-               File[] files = contact_dir.listFiles();
-               
-               int i;
-               for (i = 0; i< files.length; i++) {
-                       if (!files[i].getName().startsWith(LOGFILE_PREFIX))
-                               continue;
-                       Date logdate = 
DateStringFactory.DateFromKeyString(files[i].getName().substring(LOGFILE_PREFIX.length()));
-                       if (logdate == null) {
-                               // couldn't parse the date... hmm
-                               files[i].delete();
-                       } else if (logdate.before(keepafter)) {
-                               files[i].delete();
-                       }
-               }
-       }
-}

Added: trunk/apps/Freemail/src/freemail/HashSlotManager.java
===================================================================
--- trunk/apps/Freemail/src/freemail/HashSlotManager.java       2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/HashSlotManager.java       2006-07-28 
17:37:32 UTC (rev 9799)
@@ -0,0 +1,20 @@
+package freemail;
+
+import org.archive.util.Base32;
+
+import org.bouncycastle.crypto.digests.SHA256Digest;
+
+public class HashSlotManager extends SlotManager {
+       HashSlotManager(SlotSaveCallback cb, Object userdata, String slotlist) {
+               super(cb, userdata, slotlist);
+       }
+       
+       protected String incSlot(String slot) {
+               byte[] buf = Base32.decode(slot);
+               SHA256Digest sha256 = new SHA256Digest();
+               sha256.update(buf, 0, buf.length);
+               sha256.doFinal(buf, 0);
+               
+               return Base32.encode(buf);
+       }
+}

Modified: trunk/apps/Freemail/src/freemail/InboundContact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/InboundContact.java        2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/InboundContact.java        2006-07-28 
17:37:32 UTC (rev 9799)
@@ -1,12 +1,21 @@
 package freemail;

 import java.io.File;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.IOException;
+import java.io.FileNotFoundException;

 import freemail.FreenetURI;
 import freemail.utils.PropsFile;
+import freemail.fcp.HighLevelFCPClient;

-public class InboundContact {
+public class InboundContact extends Postman implements SlotSaveCallback {
        private static final String IBCT_PROPSFILE = "props";
+       // how many slots should we poll past the last occupied one?
+       private static final int POLL_AHEAD = 6;
        private File ibct_dir;
        private PropsFile ibct_props;

@@ -14,7 +23,7 @@
                this(contact_dir, mailsite.getKeyBody());
        }

-       private InboundContact(File contact_dir, String keybody) {
+       public InboundContact(File contact_dir, String keybody) {
                this.ibct_dir = new File(contact_dir, keybody);

                if (!this.ibct_dir.exists()) {
@@ -31,4 +40,136 @@
        public String getProp(String key) {
                return this.ibct_props.get(key);
        }
+       
+       public void fetch(MessageBank mb) {
+               HighLevelFCPClient fcpcli = new HighLevelFCPClient();
+               
+               String slots = this.ibct_props.get("slots");
+               if (slots == null) {
+                       System.out.println("Contact "+this.ibct_dir.getName()+" 
is corrupt - account file has no 'slots' entry!");
+                       // TODO: probably delete the contact. it's useless now.
+                       return;
+               }
+               
+               HashSlotManager sm = new HashSlotManager(this, null, slots);
+               sm.setPollAhead(POLL_AHEAD);
+               
+               String basekey = this.ibct_props.get("commssk");
+               if (basekey == null) {
+                       System.out.println("Contact "+this.ibct_dir.getName()+" 
is corrupt - account file has no 'commssk' entry!");
+                       // TODO: probably delete the contact. it's useless now.
+                       return;
+               }
+               String slot;
+               while ( (slot = sm.getNextSlot()) != null) {
+                       String key = basekey+slot;
+                       
+                       System.out.println("Attempting to fetch mail on key 
"+key);
+                       File msg = fcpcli.fetch(key);
+                       if (msg == null) {
+                               System.out.println("No mail there.");
+                               continue;
+                       }
+                       System.out.println("Found a message!");
+                       
+                       // parse the Freemail header(s) out.
+                       PropsFile msgprops = new PropsFile(msg, true);
+                       String s_id = msgprops.get("id");
+                       if (s_id == null) {
+                               System.out.println("Got a message with an 
invalid header. Discarding.");
+                               sm.slotUsed();
+                               msgprops.closeReader();
+                               msg.delete();
+                               continue;
+                       }
+                       
+                       int id;
+                       try {
+                               id = Integer.parseInt(s_id);
+                       } catch (NumberFormatException nfe) {
+                               System.out.println("Got a message with an 
invalid (non-integer) id. Discarding.");
+                               sm.slotUsed();
+                               msgprops.closeReader();
+                               msg.delete();
+                               continue;
+                       }
+                       
+                       MessageLog msglog = new MessageLog(this.ibct_dir);
+                       boolean isDupe;
+                       try {
+                               isDupe = msglog.isPresent(id);
+                       } catch (IOException ioe) {
+                               System.out.println("Couldn't read logfile, so 
don't know whether received message is a duplicate or not. Leaving in the queue 
to try later.");
+                               msgprops.closeReader();
+                               msg.delete();
+                               continue;
+                       }
+                       if (isDupe) {
+                               System.out.println("Got a message, but we've 
already logged that message ID as received. Discarding.");
+                               sm.slotUsed();
+                               msgprops.closeReader();
+                               msg.delete();
+                               continue;
+                       }
+                       
+                       BufferedReader br = msgprops.getReader();
+                       if (br == null) {
+                               System.out.println("Got an invalid message. 
Discarding.");
+                               sm.slotUsed();
+                               msgprops.closeReader();
+                               msg.delete();
+                               continue;
+                       }
+                       
+                       try {
+                               this.storeMessage(br, mb);
+                               msg.delete();
+                       } catch (IOException ioe) {
+                               msg.delete();
+                               continue;
+                       }
+                       System.out.println("You've got mail!");
+                       sm.slotUsed();
+               }
+       }
+       
+       public void saveSlots(String s, Object userdata) {
+               System.out.println("putting "+s+" into slots");
+               this.ibct_props.put("slots", s);
+       }
+       
+       private class MessageLog {
+               private static final String LOGFILE = "log";
+               private final File logfile;
+               
+               public MessageLog(File ibctdir) {
+                       this.logfile = new File(ibctdir, LOGFILE);
+               }
+               
+               public boolean isPresent(int targetid) throws IOException {
+                       BufferedReader br;
+                       try {
+                               br = new BufferedReader(new 
FileReader(this.logfile));
+                       } catch (FileNotFoundException fnfe) {
+                               return false;
+                       }
+                       
+                       String line;
+                       while ( (line = br.readLine()) != null) {
+                               int curid = Integer.parseInt(line);
+                               if (curid == targetid) return true;
+                       }
+                       
+                       br.close();
+                       return false;
+               }
+               
+               public void add(int id) throws IOException {
+                       FileOutputStream fos = new 
FileOutputStream(this.logfile, true);
+                       
+                       PrintStream ps = new PrintStream(fos);
+                       ps.println(id);
+                       ps.close();
+               }
+       }
 }

Copied: trunk/apps/Freemail/src/freemail/NIMContact.java (from rev 9671, 
trunk/apps/Freemail/src/freemail/Contact.java)
===================================================================
--- trunk/apps/Freemail/src/freemail/Contact.java       2006-07-20 13:34:13 UTC 
(rev 9671)
+++ trunk/apps/Freemail/src/freemail/NIMContact.java    2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -0,0 +1,50 @@
+package freemail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.util.Date;
+
+import freemail.utils.DateStringFactory;
+
+public class NIMContact {
+       public static final String KEYFILE = "key";
+       private static final String LOGFILE_PREFIX = "log-";
+       private final File contact_dir;
+       private final File keyfile;
+
+       NIMContact(File dir) {
+               this.contact_dir = dir;
+               this.keyfile = new File(dir, KEYFILE);
+       }
+       
+       public String getKey() throws IOException {
+               FileReader frdr = new FileReader(this.keyfile);
+               BufferedReader br = new BufferedReader(frdr);
+               String key =  br.readLine();
+               frdr.close();
+               return key;
+       }
+       
+       public MailLog getLog(String date) {
+               return new MailLog(new File(this.contact_dir, LOGFILE_PREFIX + 
date));
+       }
+       
+       public void pruneLogs(Date keepafter) {
+               File[] files = contact_dir.listFiles();
+               
+               int i;
+               for (i = 0; i< files.length; i++) {
+                       if (!files[i].getName().startsWith(LOGFILE_PREFIX))
+                               continue;
+                       Date logdate = 
DateStringFactory.DateFromKeyString(files[i].getName().substring(LOGFILE_PREFIX.length()));
+                       if (logdate == null) {
+                               // couldn't parse the date... hmm
+                               files[i].delete();
+                       } else if (logdate.before(keepafter)) {
+                               files[i].delete();
+                       }
+               }
+       }
+}

Modified: trunk/apps/Freemail/src/freemail/NIMFetcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/NIMFetcher.java    2006-07-27 23:59:06 UTC 
(rev 9798)
+++ trunk/apps/Freemail/src/freemail/NIMFetcher.java    2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -4,31 +4,27 @@
 import freemail.utils.DateStringFactory;

 import java.io.File;
-import java.io.IOException;
-import java.io.BufferedReader;
-import java.io.PrintStream;
 import java.io.FileReader;
-import java.text.SimpleDateFormat;
+import java.io.BufferedReader;
+import java.io.IOException;
 import java.util.Date;
 import java.util.Calendar;
 import java.util.TimeZone;

-public class NIMFetcher {
+public class NIMFetcher extends Postman {
        private final MessageBank mb;
        private File contact_dir;
-       private final SimpleDateFormat sdf;
        private static final int POLL_AHEAD = 3;
        private static int PASSES_PER_DAY = 3;
        private static int MAX_DAYS_BACK = 30;

        NIMFetcher(MessageBank m, File ctdir) {
                this.mb = m;
-               this.sdf = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z");
                this.contact_dir = ctdir;
        }

        public void fetch() {
-               Contact contact = new Contact(this.contact_dir);
+               NIMContact contact = new NIMContact(this.contact_dir);

                int i;
                for (i = 1 - MAX_DAYS_BACK; i <= 0; i++) {
@@ -51,7 +47,7 @@
                contact.pruneLogs(cal.getTime());
        }

-       private void fetch_day(Contact contact, MailLog log, String date) {
+       private void fetch_day(NIMContact contact, MailLog log, String date) {
                HighLevelFCPClient fcpcli;
                fcpcli = new HighLevelFCPClient();

@@ -73,7 +69,8 @@
                        if (result != null) {
                                System.out.println(keybase+i+": got message!");
                                try {
-                                       this.storeMessage(result);
+                                       this.storeMessage(new 
BufferedReader(new FileReader(result)), this.mb);
+                                       result.delete();
                                        log.addMessage(i, "received");
                                } catch (IOException ioe) {
                                        continue;
@@ -83,27 +80,4 @@
                        }
                }
        }
-       
-       private void storeMessage(File file) throws IOException {
-               MailMessage newmsg = this.mb.createMessage();
-               
-               // add our own headers first
-               // recieved and date
-               newmsg.addHeader("Received", "(Freemail); "+this.sdf.format(new 
Date()));
-               
-               BufferedReader rdr = new BufferedReader(new FileReader(file));
-               
-               newmsg.readHeaders(rdr);
-               
-               PrintStream ps = newmsg.writeHeadersAndGetStream();
-               
-               String line;
-               while ( (line = rdr.readLine()) != null) {
-                       ps.println(line);
-               }
-               
-               newmsg.commit();
-               rdr.close();
-               file.delete();
-       }
 }

Modified: trunk/apps/Freemail/src/freemail/OutboundContact.java
===================================================================
--- trunk/apps/Freemail/src/freemail/OutboundContact.java       2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/OutboundContact.java       2006-07-28 
17:37:32 UTC (rev 9799)
@@ -117,6 +117,8 @@
                                System.out.println("Sucessfully received CTS 
for "+this.address.getMailsiteKey());
                                cts.delete();
                                this.contactfile.put("status", "cts-received");
+                               // delete inital slot for forward secrecy
+                               this.contactfile.remove("initialslot");
                        }
                } else {
                        this.init();
@@ -250,7 +252,6 @@

                String initialslot = this.getInitialSlot();

-               this.contactfile.put("nextslot", initialslot);
                rtsmessage.append("initialslot="+initialslot+"\r\n");

                rtsmessage.append("messagetype=rts\r\n");
@@ -362,8 +363,11 @@

        private String popNextSlot() {
                String slot = this.contactfile.get("nextslot");
+               if (slot == null) {
+                       slot = this.contactfile.get("initialslot");
+               }
                SHA256Digest sha256 = new SHA256Digest();
-               sha256.update(slot.getBytes(), 0, Base32.decode(slot).length);
+               sha256.update(Base32.decode(slot), 0, 
Base32.decode(slot).length);
                byte[] nextslot = new byte[sha256.getDigestSize()];
                sha256.doFinal(nextslot, 0);
                this.contactfile.put("nextslot", Base32.encode(nextslot));
@@ -390,9 +394,13 @@
                // create a new file that contains the complete Freemail
                // message, with Freemail headers
                QueuedMessage qm = new QueuedMessage(uid);
+               
+               File msg;
                FileOutputStream fos;
                try {
-                       fos = qm.getOutputStream();
+                       msg = File.createTempFile("ogm", "msg", 
Freemail.getTempDir());
+                       
+                       fos = new FileOutputStream(msg);
                } catch (IOException ioe) {
                        System.out.println("IO Error encountered whilst trying 
to send message: "+ioe.getMessage()+" Will try again soon");
                        return false;
@@ -414,6 +422,7 @@
                } catch (IOException ioe) {
                        System.out.println("IO Error encountered whilst trying 
to send message: "+ioe.getMessage()+" Will try again soon");
                        qm.delete();
+                       msg.delete();
                        return false;
                }

@@ -421,7 +430,7 @@

                qm.slot = slot;

-               if (qm.saveProps()) {
+               if (qm.setMessageFile(msg) && qm.saveProps()) {
                        body.delete();
                        return true;
                }
@@ -506,6 +515,8 @@
                                msgs[i].delete();
                                // treat the ACK as a CTS too
                                this.contactfile.put("status", "cts-received");
+                               // delete inital slot for forward secrecy
+                               this.contactfile.remove("initialslot");
                        } else {
                                if (System.currentTimeMillis() > 
msgs[i].first_send_time + FAIL_DELAY) {
                                        // give up and bounce the message
@@ -584,8 +595,8 @@
                        return new FileInputStream(this.file);
                }

-               public FileOutputStream getOutputStream() throws 
FileNotFoundException {
-                       return new FileOutputStream(this.file);
+               public boolean setMessageFile(File newfile) {
+                       return newfile.renameTo(this.file);
                }

                public boolean saveProps() {

Added: trunk/apps/Freemail/src/freemail/Postman.java
===================================================================
--- trunk/apps/Freemail/src/freemail/Postman.java       2006-07-27 23:59:06 UTC 
(rev 9798)
+++ trunk/apps/Freemail/src/freemail/Postman.java       2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -0,0 +1,35 @@
+package freemail;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.io.File;
+import java.io.BufferedReader;
+import java.io.PrintStream;
+import java.io.IOException;
+
+/** A postman is any class that delivers mail to an inbox. Simple,
+ *  if not politically correct.
+ */
+public abstract class Postman {
+       protected void storeMessage(BufferedReader brdr, MessageBank mb) throws 
IOException {
+               MailMessage newmsg = mb.createMessage();
+               
+               SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy 
HH:mm:ss Z");
+               
+               // add our own headers first
+               // recieved and date
+               newmsg.addHeader("Received", "(Freemail); "+sdf.format(new 
Date()));
+               
+               newmsg.readHeaders(brdr);
+               
+               PrintStream ps = newmsg.writeHeadersAndGetStream();
+               
+               String line;
+               while ( (line = brdr.readLine()) != null) {
+                       ps.println(line);
+               }
+               
+               newmsg.commit();
+               brdr.close();
+       }
+}

Modified: trunk/apps/Freemail/src/freemail/RTSFetcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/RTSFetcher.java    2006-07-27 23:59:06 UTC 
(rev 9798)
+++ trunk/apps/Freemail/src/freemail/RTSFetcher.java    2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -32,7 +32,7 @@
        private static final int POLL_AHEAD = 3;
        private static final int PASSES_PER_DAY = 3;
        private static final int MAX_DAYS_BACK = 30;
-       private static final String LOGFILE = "rtslog";
+       public static final String LOGFILE = "rtslog";
        private static final int RTS_MAX_SIZE = 2 * 1024 * 1024;
        private static final String RTS_UNPROC_PREFIX = "unprocessed_rts";
        private static final int RTS_MAX_ATTEMPTS = 15;
@@ -330,6 +330,7 @@
                String ackssk = rtsprops.get("ackssk");
                if (!ackssk.endsWith("/")) ackssk += "/";
                ibct.setProp("ackssk", ackssk);
+               ibct.setProp("slots", rtsprops.get("initialslot"));

                // insert the cts at some point
                AckProcrastinator.put(ackssk+"cts");

Modified: trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java
===================================================================
--- trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java  2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/SingleAccountWatcher.java  2006-07-28 
17:37:32 UTC (rev 9799)
@@ -17,18 +17,19 @@
        private long mailsite_last_upload;
        private final PropsFile accprops;
        private final File obctdir;
+       private final File ibctdir;
        private final File accdir;

        SingleAccountWatcher(File accdir) {
                this.accdir = accdir;
                this.accprops = AccountManager.getAccountFile(accdir);
                File contacts_dir = new File(accdir, CONTACTS_DIR);
-               File inbound_dir = new File(contacts_dir, INBOUND_DIR);
+               this.ibctdir = new File(contacts_dir, INBOUND_DIR);
                this.obctdir = new File(contacts_dir, OUTBOUND_DIR);
                this.mailsite_last_upload = 0;

-               if (!inbound_dir.exists()) {
-                       inbound_dir.mkdir();
+               if (!this.ibctdir.exists()) {
+                       this.ibctdir.mkdir();
                }

                this.mb = new MessageBank(accdir.getName());
@@ -41,7 +42,7 @@
                }


-               this.rtsf = new 
RTSFetcher("KSK@"+this.accprops.get("rtskey")+"-", inbound_dir, accdir);
+               this.rtsf = new 
RTSFetcher("KSK@"+this.accprops.get("rtskey")+"-", this.ibctdir, accdir);

                //this.mf = new MailFetcher(this.mb, inbound_dir, 
Freemail.getFCPConnection());

@@ -77,7 +78,18 @@

                        this.rtsf.poll();

-                       //mf.fetch_from_all();
+                       // poll for incoming message from all inbound contacts
+                       File[] ibcontacts = this.ibctdir.listFiles();
+                       if (ibcontacts != null) {
+                               int i;
+                               for (i = 0; i < ibcontacts.length; i++) {
+                                       if 
(ibcontacts[i].getName().equals(RTSFetcher.LOGFILE)) continue;
+                                       
+                                       InboundContact ibct = new 
InboundContact(this.ibctdir, ibcontacts[i].getName());
+                                       
+                                       ibct.fetch(this.mb);
+                               }
+                       }

                        long runtime = System.currentTimeMillis() - start;


Added: trunk/apps/Freemail/src/freemail/SlotManager.java
===================================================================
--- trunk/apps/Freemail/src/freemail/SlotManager.java   2006-07-27 23:59:06 UTC 
(rev 9798)
+++ trunk/apps/Freemail/src/freemail/SlotManager.java   2006-07-28 17:37:32 UTC 
(rev 9799)
@@ -0,0 +1,152 @@
+package freemail;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+/** Manages sequences of slots which are polled for messages, keeping track of 
which
+ *  ones still need to be checked, which ones are used and which have expired.
+ */
+public abstract class SlotManager {
+       // how long we should keep checking a slot for which a successibe slot 
has
+       // had a message retrieved on
+       private static final long SLOT_LIFETIME = 7 * 24 * 60 * 60 * 1000;
+       private static final int DEFAULT_POLL_AHEAD = 3;
+
+       // 'slots' contains all unused slots, in order for which there is a
+       // higher slot that is used. If there are no such slots, it contains the
+       // first free slot
+       private Vector slots;
+       private int nextSlotNum;
+       private final SlotSaveCallback cb;
+       private final Object userdata;
+       private int pollAhead;
+       
+       protected SlotManager(SlotSaveCallback cb, Object userdata, String 
slotlist) {
+               this.slots = new Vector();
+               this.cb = cb;
+               this.userdata = userdata;
+               this.nextSlotNum = 0;
+               this.pollAhead = DEFAULT_POLL_AHEAD;
+               
+               String parts[] = slotlist.split(",");
+               int i;
+               for (i = 0; i < parts.length; i++) {
+                       String[] parts2 = parts[i].split("=", 2);
+                       Slot s = new Slot();
+                       s.slot = parts2[0];
+                       if (parts2.length > 1)
+                               s.time_added = Long.parseLong(parts2[1]);
+                       else
+                               s.time_added = -1;
+                       
+                       this.slots.add(s);
+               }
+       }
+       
+       /** Set the number of slots to poll after the last free one
+        */
+       public void setPollAhead(int pa) {
+               this.pollAhead = pa;
+       }
+       
+       /** Mark the last given slot as used
+        */
+       public synchronized void slotUsed() {
+               if (this.nextSlotNum <= this.slots.size()) {
+                       // it's one in the list. delete it and move the next
+                       // pointer down to point to the same one
+                       // (If nextSlotNum is 0, this should rightfully throw
+                       // an ArrayIndexOutOfBoundsException
+                       this.nextSlotNum--;
+                       Slot s = (Slot)this.slots.remove(this.nextSlotNum);
+                       // additionally, if it was the last one, we need to push
+                       // the next slot onto the end
+                       if (this.nextSlotNum == this.slots.size()) {
+                               s.slot = this.incSlot(s.slot);
+                               // time added is -1 since no subsequent slots
+                               // have been used
+                               s.time_added = -1;
+                               this.slots.add(s);
+                       }
+               } else {
+                       // add all the slots before the used one that aren't 
already
+                       // in the list
+                       int i;
+                       Slot s = (Slot)this.slots.lastElement();
+                       int slots_start_size = this.slots.size();
+                       for (i = slots_start_size; i < this.nextSlotNum - 1; 
i++) {
+                               s.slot = this.incSlot(s.slot);
+                               s.time_added = System.currentTimeMillis();
+                               this.slots.add(s);
+                       }
+                       // increment to get the used slot...
+                       s.slot = this.incSlot(s.slot);
+                       // and again to get the one that nextSlotNum is 
pointing at
+                       s.slot = this.incSlot(s.slot);
+                       // ...and add that
+                       s.time_added = System.currentTimeMillis();
+                       this.slots.add(s);
+               }
+               this.saveSlots();
+       }
+       
+       private void saveSlots() {
+               StringBuffer buf = new StringBuffer();
+               
+               Enumeration e = this.slots.elements();
+               boolean first = true;
+               while (e.hasMoreElements()) {
+                       if (!first) buf.append(",");
+                       first = false;
+                       Slot s = (Slot)e.nextElement();
+                       buf.append(s.slot);
+                       if (s.time_added > 0)
+                               
buf.append(",").append(Long.toString(s.time_added));
+               }
+               this.cb.saveSlots(buf.toString(), this.userdata);
+       }
+       
+       /** Method provided by subclasses to return the next slot given any slot
+        */
+       protected abstract String incSlot(String slot);
+       
+       public synchronized String getNextSlot() {
+               String retval = null;
+               
+               boolean tryAgain = true;
+               while (tryAgain) {
+                       tryAgain = false;
+                       if (this.nextSlotNum >= this.slots.size() + 
this.pollAhead) {
+                               // you've reached the end
+                               retval = null;
+                       } else if (this.nextSlotNum >= this.slots.size()) {
+                               // we're into the unused slots. make one up.
+                               Slot s = (Slot)this.slots.lastElement();
+                               int i;
+                               for (i = this.slots.size(); i <= 
this.nextSlotNum; i++) {
+                                       s.slot = this.incSlot(s.slot);
+                               }
+                               retval = s.slot;
+                       } else {
+                               // we're looking at an unused slot
+                               Slot s = (Slot) 
this.slots.get(this.nextSlotNum);
+                               // is this one too old?
+                               if (s.time_added > 0 && s.time_added < 
System.currentTimeMillis() - SLOT_LIFETIME && this.nextSlotNum != 
this.slots.size() - 1) {
+                                       // this slot is too old. Forget it.
+                                       this.slots.remove(this.nextSlotNum);
+                                       tryAgain = true;
+                               } else {
+                                       retval = s.slot;
+                               }
+                       }
+               }
+               
+               this.nextSlotNum++;
+               return retval;
+       }
+       
+       private class Slot {
+               String slot;
+               long time_added;
+       }
+}

Added: trunk/apps/Freemail/src/freemail/SlotSaveCallback.java
===================================================================
--- trunk/apps/Freemail/src/freemail/SlotSaveCallback.java      2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/SlotSaveCallback.java      2006-07-28 
17:37:32 UTC (rev 9799)
@@ -0,0 +1,5 @@
+package freemail;
+
+public interface SlotSaveCallback {
+       public void saveSlots(String slots, Object userdata);
+}

Modified: trunk/apps/Freemail/src/freemail/utils/PropsFile.java
===================================================================
--- trunk/apps/Freemail/src/freemail/utils/PropsFile.java       2006-07-27 
23:59:06 UTC (rev 9798)
+++ trunk/apps/Freemail/src/freemail/utils/PropsFile.java       2006-07-28 
17:37:32 UTC (rev 9799)
@@ -14,34 +14,59 @@
 public class PropsFile {
        private final File file;
        private HashMap data;
+       private BufferedReader bufrdr;

-       public PropsFile(File f) {
+       /** Pass true into stopAtBlank to cause the reader to stop upon 
encountering
+        * a blank line. It's the the caller's responsibility to get
+        * (using the getReader() method) the stream and close it properly.
+        */
+       public PropsFile(File f, boolean stopAtBlank) {
                this.file = f;
                this.data = null;

                if (f.exists()) {
                        try {
-                               this.read();
+                               this.bufrdr = this.read(stopAtBlank);
                        } catch (IOException ioe) {
                        }
                }
        }

-       private void read() throws IOException {
+       public PropsFile(File f) {
+               this(f, false);
+       }
+       
+       private BufferedReader read(boolean stopAtBlank) throws IOException {
                this.data = new HashMap();

                BufferedReader br = new BufferedReader(new 
FileReader(this.file));

                String line = null;
                while ( (line = br.readLine()) != null) {
+                       if (stopAtBlank && line.length() == 0) {
+                               return br;
+                       }
                        String[] parts = line.split("=", 2);
                        if (parts.length < 2) continue;
                        this.data.put(parts[0], parts[1]);
                }

                br.close();
+               return null;
        }

+       public BufferedReader getReader() {
+               return this.bufrdr;
+       }
+       
+       public void closeReader() {
+               if (this.bufrdr == null) return;
+               try {
+                       this.bufrdr.close();
+               } catch (IOException ioe) {
+               }
+       }
+       
        private void write() throws IOException {
                PrintWriter pw = new PrintWriter(new 
FileOutputStream(this.file));



Reply via email to