X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=src%2Forg%2Fibex%2Fmail%2Ftarget%2FFileBasedMailbox.java;h=bf5d370ecd344a00e0ae39084015b228a6b40761;hb=8bed6f7a1b4b9336c71c89711df9def5c27d22c5;hp=a934fac088861027b8a9bf3482cba81fb30bbde8;hpb=2dc579e8aa98e6823094d326d6447aaa647478e1;p=org.ibex.mail.git diff --git a/src/org/ibex/mail/target/FileBasedMailbox.java b/src/org/ibex/mail/target/FileBasedMailbox.java index a934fac..bf5d370 100644 --- a/src/org/ibex/mail/target/FileBasedMailbox.java +++ b/src/org/ibex/mail/target/FileBasedMailbox.java @@ -1,203 +1,247 @@ 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.*; -// FIXME: we can omit UIDNEXT! -// FIXME use directory date/time as UIDNEXT and file date/time as UID; need to 'correct' file date/time after changes - /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */ public class FileBasedMailbox extends Mailbox.Default { - public String toString() { return "[FileBasedMailbox " + path + "]"; } + public static final long MAGIC_DATE = 0; + public String toString() { return "[FileBasedMailbox " + path.getAbsolutePath() + "]"; } private static final char slash = File.separatorChar; - private static final Hashtable instances = new Hashtable(); - 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; - } + private static final WeakHashMap instances = new WeakHashMap(); + public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); } - public static final FilenameFilter filter = new FilenameFilter() { - public boolean accept(File f, String s) { - return s.indexOf('.') != -1; - } }; + // FIXME: should be a File() + public static synchronized FileBasedMailbox getFileBasedMailbox(String path, boolean create) { + try { + FileBasedMailbox ret = instances.get(path); + if (ret == null) { + if (!create && !(new File(path).exists())) return null; + instances.put(path, ret = new FileBasedMailbox(new File(path))); + } + return ret; + } catch (Exception e) { + Log.error(FileBasedMailbox.class, e); + return null; + } + } // Instance ////////////////////////////////////////////////////////////////////////////// - private String path; - private File uidNext; - private FileBasedMailbox(String path) throws MailException { - new File(this.path = path).mkdirs(); - uidNext(false); - } + private File path; + private FileLock lock; + private Prevayler prevayler; + private Cache cache; + + public static class Cache implements Serializable { + public final int uidValidity = new Random().nextInt(); + private final Hashtable byname = new Hashtable(); + private final Hashtable byuid = new Hashtable(); + private final ArrayList linear = new ArrayList(); + public final File dir; + private int uidNext = 0; + + public Cache(File dir) { this.dir = dir; } + + public void init(final Prevayler prevayler) throws IOException { + dir.mkdirs(); + Log.info(this, "initializing maildir " + dir.getParent()); + boolean invalid = false; + ArrayList kill = new ArrayList(); + for(String s : byname.keySet()) { + String name = dir.getParent() + slash + s; + if (!new File(name).exists() && !new File(name+"s").exists()) { + Log.error(this, "dropping message " + name); + kill.add(new Drop(byname.get(s).uid())); + } + } + for(Transaction t : kill) prevayler.execute(t); + for(String file : new File(dir.getParent()).list()) + if (file.charAt(0)!='.' && !(new File(dir.getParent() + slash + file).isDirectory())) + if (get(file) == null) new Entry(this, prevayler, file); + Log.info(this, " done initializing maildir " + dir.getParent()); + new Thread() { public void run() { + try { prevayler.takeSnapshot(); } catch (Exception e) { Log.error(this, e); } + } }.start(); + } - public Mailbox slash(String name, boolean create) { - return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); } + public static class Drop implements Transaction { + int uid; + public Drop(int uid) { this.uid = uid; } + public void executeOn(Object o, Date now) { + Cache c = (Cache)o; + Entry e = c.get(uid); + c.byname.remove(e.name); + c.linear.remove(e); + c.byuid.remove(uid); + } + } + + public synchronized int size() { return linear.size(); } + public synchronized int uidNext(boolean increment) { return increment ? uidNext++ : uidNext; } + public synchronized Entry get(int uid) { return byuid.get(uid); } + public synchronized Entry getLinear(int num) { return linear.get(num); } + public synchronized Entry get(String name) { + if (name.endsWith("s")) name = name.substring(0, name.length() - 1); + return byname.get(name); + } - public String[] children() { - Vec vec = new Vec(); - String[] list = new File(path).list(); - for(int i=0; i= names.length) return null; - try { - File file = new File(path + File.separatorChar + names[cur]); - FileInputStream fis = new FileInputStream(file); - Stream stream = new Stream(fis); - Address envelopeFrom = null; - Address envelopeTo = null; - /* - for(String s = stream.readln(); s != null; s = stream.readln()) { - if (s.startsWith("X-org.ibex.mail.headers.envelope.From: ")) - envelopeFrom = Address.parse(s.substring(38).trim()); - else if (s.startsWith("X-org.ibex.mail.headers.envelope.To: ")) - envelopeTo = Address.parse(s.substring(36).trim()); - else { - stream.unread(s + "\r\n"); - break; - } - } - */ - Message ret = new Message(stream, new Message.Envelope(null, null, new Date(file.lastModified()))); - fis.close(); - return ret; - } 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; - return true; - } - public int num() { return cur; } - 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 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(); } + private Cache.Entry entry() { return cache.getLinear(cur); } + public MIME.Headers head() { return done() ? null : entry().headers(); } + public boolean done() { return cur >= cache.size(); } + 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 done() ? -1 : entry().uid(); } + public Message cur() { return done() ? null : entry().message(cache); } + public void seen(boolean seen) { prevayler.execute(new Seen(uid(), seen)); } + public void delete() { if (!done()) prevayler.execute(new Delete(uid())); } + } + private static class Delete implements Transaction { + private int uid; + public Delete(int uid) { this.uid = uid; } + public void executeOn(Object c, Date d) { ((Cache)c).get(uid).delete((Cache)c); } + } + private static class Seen implements Transaction { + private int uid; + private boolean seen; + public Seen(int uid, boolean seen) { this.uid = uid; this.seen = seen; } + public void executeOn(Object c, Date d) { ((Cache)c).get(uid).seen((Cache)c, seen); } } }