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.*;
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);
}
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);
} 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(); }
-
}
}