added Prevayler-based cache to FileBasedMailbox
authoradam <adam@megacz.com>
Mon, 18 Oct 2004 05:25:11 +0000 (05:25 +0000)
committeradam <adam@megacz.com>
Mon, 18 Oct 2004 05:25:11 +0000 (05:25 +0000)
darcs-hash:20041018052511-5007d-fdb82768460d8d6f71ad00d7bac551639a62b0d5.gz

src/org/ibex/mail/target/FileBasedMailbox.java

index 8bf36ad..43a21c8 100644 (file)
@@ -1,8 +1,11 @@
 package org.ibex.mail.target;
+import org.prevayler.*;
 import org.ibex.mail.*;
 import org.ibex.util.*;
 import org.ibex.io.*;
 import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
 import java.net.*;
 import java.util.*;
 import java.text.*;
@@ -14,104 +17,97 @@ import java.text.*;
 public class FileBasedMailbox extends Mailbox.Default implements Serializable {
 
     public String toString() { return "[FileBasedMailbox " + path + "]"; }
-
     private static final char slash = File.separatorChar;
-    private static final Hashtable instances = new Hashtable();
+    private static final WeakHashMap<String,FileBasedMailbox> instances = new WeakHashMap<String,FileBasedMailbox>();
+    public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path + slash + name, create); }
     public static FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
-        FileBasedMailbox ret = (FileBasedMailbox)instances.get(path);
-        if (ret != null) return ret;
-        File f = new File(path);
-        if (!create && !f.exists()) return null;
-        instances.put(path, ret = new FileBasedMailbox(path));
-        return ret;
+        try {
+            FileBasedMailbox ret = instances.get(path);
+            if (ret == null) {
+                if (!create && !new File(path).exists()) return null;
+                instances.put(path, ret = new FileBasedMailbox(path));
+            }
+            return ret;
+        } catch (Exception e) {
+            Log.error(FileBasedMailbox.class, e);
+            return null;
+        }
     }
 
-    public static final FilenameFilter filter = new FilenameFilter() {
-            public boolean accept(File f, String s) {
-                return s.indexOf('.') != -1;
-            } };
-
 
     // Instance //////////////////////////////////////////////////////////////////////////////
 
     private String path;
-    private File uidNext;
-    private FileBasedMailbox(String path) throws MailException {
+    private FileLock lock;
+    private Prevayler prevayler;
+    private LinkedList<CacheEntry> cache;
+    private int uidNext;
+
+    public static class CacheEntry implements Serializable {
+        public MIME.Headers headers;
+        public String path;
+        public boolean seen;
+        private String name() { return path.indexOf(slash) == -1 ? path : path.substring(path.lastIndexOf(slash)+1); }
+        public boolean seen() { return name().substring(name().indexOf('.')+1).indexOf('s') != -1; }
+        public int uid() { return Integer.parseInt(name().substring(0, name().indexOf('.'))); }
+        public synchronized void seen(boolean seen) {
+            String newpath = path.substring(0, path.lastIndexOf('.') + 1) + (seen ? "s" : "");
+            new File(path).renameTo(new File(newpath));
+            path = newpath;
+        }
+        public CacheEntry(File f) throws IOException {
+            path = f.getAbsolutePath();
+            headers = new MIME.Headers(new Stream(new FileInputStream(f)), true);
+        }
+    }        
+
+    private FileBasedMailbox(String path) throws MailException, IOException, ClassNotFoundException {
         new File(this.path = path).mkdirs();
-        uidNext(false);
+        new File(path + slash + ".cache").mkdirs();
+        lock = new RandomAccessFile(this.path + slash + ".cache" + slash + "lock", "rw").getChannel().tryLock();
+        if (lock == null) throw new IOException("unable to lock FileBasedMailbox");
+        prevayler = PrevaylerFactory.createPrevayler(new LinkedList<CacheEntry>(), path + slash + ".cache");
+        cache = (LinkedList<CacheEntry>)prevayler.prevalentSystem();
+        for(int i=0; i<cache.size(); i++) uidNext = Math.max(uidNext, cache.get(i).uid()+1);
+        if (cache.size() > 0) return;
+
+        Log.warn(this, "rebuilding cache for " + path);
+        String[] files = new File(path).list(new FilenameFilter() {
+                public boolean accept(File f, String s) {
+                    return s.substring(1).indexOf('.') != -1 && !s.equals("..");
+                } });
+        for(int i=0; i<files.length; i++) {
+            CacheEntry ce = new CacheEntry(new File(path + slash + files[i]));
+            prevayler.execute(new Rebuild(ce));
+            uidNext = Math.max(uidNext, ce.uid()+1);
+        }
+        prevayler.takeSnapshot();
     }
-    private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeObject(path); }
-    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
-        new File(this.path = (String)in.readObject()).mkdirs();
-        uidNext(false);
+
+    private static class Rebuild implements Transaction {
+        public final CacheEntry entry;
+        public Rebuild(CacheEntry entry) { this.entry = entry; }
+        public void executeOn(Object c, Date now) { synchronized(c) { ((LinkedList<CacheEntry>)c).add(entry); } }
     }
 
-    public Mailbox          slash(String name, boolean create) {
-        return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); }
+    public Mailbox.Iterator  iterator()           { return new Iterator(); }
+    public int               uidValidity()        { return (int)(new File(path).lastModified() & 0xffffffL); }
+    public int               uidNext()            { return uidNext(false); }
+    public int               uidNext(boolean inc) { return inc ? uidNext++ : uidNext; }
+    public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
 
     public String[] children() {
         Vec vec = new Vec();
         String[] list = new File(path).list();
-        for(int i=0; i<list.length; i++) {
-            if (!(new File(path + slash + list[i]).isDirectory())) continue;
-            vec.addElement(list[i]);
-        }
-        String[] ret = new String[vec.size()];
-        vec.copyInto(ret);
-        return ret;
+        for(int i=0; i<list.length; i++) if ((new File(path + slash + list[i]).isDirectory())) vec.addElement(list[i]);
+        return (String[])vec.copyInto(new String[vec.size()]);
     }
 
-    public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
-    public int              uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
-
-    public int uidNext() { return uidNext(false); }
-    public int uidNext(boolean inc) {
-        try {
-            uidNext = new File(path + slash + "UIDNEXT");
-            if (!uidNext.exists()) {
-                File tmp = new File(uidNext + "-");
-                PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
-                pw.println("1");
-                pw.flush();
-                pw.close();
-                tmp.renameTo(uidNext);
-                return 1;
-            }
-            FileInputStream fis = null;
-            int ret = -1;
-            try {
-                fis = new FileInputStream(uidNext);
-                BufferedReader br = new BufferedReader(new InputStreamReader(fis));
-                ret = Integer.parseInt(br.readLine());
-                if (inc) {
-                    File tmp = new File(uidNext + "-");
-                    FileOutputStream fos = null;
-                    try {
-                        fos = new FileOutputStream(tmp);
-                        PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos));
-                        pw.println(ret+1);
-                        pw.flush();
-                    } finally { if (fos != null) fos.close(); }
-                    tmp.renameTo(uidNext);
-                }
-            } finally { if (fis != null) fis.close(); }
-            return ret;
-        } catch (IOException e) { throw new MailException.IOException(e); }
-    }        
-
-    public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
     public synchronized void add(Message message, int flags) {
         Log.info(path, message.summary());
         try {
-            int num = new File(path).list(filter).length;
-           String name = path + slash + uidNext(true) + "." +
-                ((flags & Mailbox.Flag.DELETED) == Mailbox.Flag.DELETED ? "x" : "") +
-                ((flags & Mailbox.Flag.DRAFT) == Mailbox.Flag.DRAFT ? "d" : "") +
-                ((flags & Mailbox.Flag.RECENT) == Mailbox.Flag.RECENT ? "r" : "") +
-                ((flags & Mailbox.Flag.ANSWERED) == Mailbox.Flag.ANSWERED ? "a" : "") +
-                ((flags & Mailbox.Flag.FLAGGED) == Mailbox.Flag.FLAGGED ? "f" : "") +
+           final String name = path + slash + uidNext(true) + "." +
                 ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN ? "s" : "");
-            //Log.info(this, "    with chosen filename " + name);
             File target = new File(name);
             File f = new File(target.getCanonicalPath() + "-");
             FileOutputStream fo = new FileOutputStream(f);
@@ -122,22 +118,25 @@ public class FileBasedMailbox extends Mailbox.Default implements Serializable {
             }
             message.dump(stream);
             fo.close();
-            f.renameTo(target);
-            //Log.info(this, "    done writing.");
+            f.renameTo(new File(name));
+            CacheEntry entry = new CacheEntry(new File(name));
+            prevayler.execute(new Rebuild(entry));
         } catch (IOException e) { throw new MailException.IOException(e); }
     }
 
-    private class Iterator extends Mailbox.Default.Iterator {
+    private class Iterator extends Mailbox.Default.Iterator implements Serializable {
         int cur = -1;
-        private String[] names;
-        private boolean seen = false, deleted = false, draft = false, flagged = false, answered = false, recent = false;
-        public Iterator() { names = new File(path).list(filter); }
-
-        public Message head() { return cur(); }
+        private CacheEntry entry() { synchronized(cache) { return cache.get(cur); } }
+        public MIME.Headers head() { return done() ? null : entry().headers; }
+        public boolean done() { synchronized(cache) { if (cur >= cache.size()) return true; } return false; }
+        public boolean next() { cur++; return !done(); }
+        public boolean seen() { return done() ? false : entry().seen(); }
+        public int num() { return cur+1; }  // EUDORA insists that message numbers start at 1, not 0
+        public int uid() { return entry().uid(); }
         public Message cur() {
-           if (cur >= names.length) return null;
+            if (done()) return null;
             try {
-                File file = new File(path + File.separatorChar + names[cur]);
+                File file = new File(entry().path);
                 FileInputStream fis = null;
                 try {
                     fis = new FileInputStream(file);
@@ -146,62 +145,19 @@ public class FileBasedMailbox extends Mailbox.Default implements Serializable {
             } catch (IOException e) { throw new MailException.IOException(e);
             } catch (Message.Malformed e) { throw new MailException(e.getMessage()); }
         }
-        public boolean next() {
-            cur++;
-            if (cur >= names.length) return false;
-            String name = names[cur].substring(names[cur].indexOf('.') + 1);
-           if (!(new File(path + File.separatorChar + names[cur])).exists()) return next();
-            seen     = name.indexOf('s') != -1;
-            deleted  = name.indexOf('x') != -1;
-            flagged  = name.indexOf('f') != -1;
-            draft    = name.indexOf('d') != -1;
-            answered = name.indexOf('a') != -1;
-            //recent   = name.indexOf('r') != -1;
-            recent = true;
-            return true;
+        public void seen(final boolean on) {
+            if (done()) return;
+            final CacheEntry entry = entry();
+            prevayler.execute(new Transaction() { public void executeOn(Object c, Date d) { entry.seen(on); } });
         }
-
-        // EUDORA insists that message numbers start at 1, not 0
-        public int num() { return cur+1; }
-
-        public int uid() {
-            try { return Integer.parseInt(names[cur].substring(0, names[cur].indexOf('.')));
-            } catch (NumberFormatException nfe) {
-                Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[cur].substring(0, names[cur].length() - 1));
-                return -1; } }
-
-        private void fixflags() {
-            String newName =
-                names[cur].substring(0, names[cur].indexOf('.') + 1) +
-                (seen ? "s" : "") +
-                (deleted ? "x" : "") +
-                (flagged ? "f" : "") +
-                (draft ? "d" : "") +
-                (recent ? "r" : "") +
-                (answered ? "a" : "");
-            new File(path + File.separatorChar + names[cur]).renameTo(new File(path + File.separatorChar + newName));
-            names[cur] = newName;
-        }
-
-        public void    delete() {
-           new File(path + File.separatorChar + names[cur]).delete();
-           // FIXME remove from list?
+        public void delete() {
+            if (done()) return;
+            final CacheEntry entry = entry();
+            prevayler.execute(new Transaction() {
+                    public void executeOn(Object c, Date d) {
+                            new File(entry.path).delete();
+                            cache.remove(entry);
+                    }});
        }
-        public void    set(String key, String val) { throw new MailException("not supported"); }
-        public String  get(String key) { throw new MailException("not supported"); }
-
-        public boolean seen() { return seen; }
-        public boolean deleted() { return deleted; }
-        public boolean flagged() { return flagged; }
-        public boolean draft() { return draft; }
-        public boolean answered() { return answered; }
-        public boolean recent() { return recent; }
-        public void    seen(boolean on) { seen = on; fixflags(); }
-        public void    deleted(boolean on) { deleted = on; fixflags(); }
-        public void    flagged(boolean on) { flagged = on; fixflags(); }
-        public void    draft(boolean on) { draft = on; fixflags(); }
-        public void    answered(boolean on) { answered = on; fixflags(); }
-        public void    recent(boolean on) { recent = on; fixflags(); }
-
     }
 }