From: adam Date: Fri, 22 Oct 2004 06:52:45 +0000 (+0000) Subject: FileBasedMailbox overhaul X-Git-Url: http://git.megacz.com/?a=commitdiff_plain;h=898c6f5d550e999cf8d68acf3aa090f743e44ef7;p=org.ibex.mail.git FileBasedMailbox overhaul darcs-hash:20041022065245-5007d-1bf708691025151a3263877c7312c518f8938eb2.gz --- diff --git a/src/org/ibex/mail/target/FileBasedMailbox.java b/src/org/ibex/mail/target/FileBasedMailbox.java index ae42112..50cce12 100644 --- a/src/org/ibex/mail/target/FileBasedMailbox.java +++ b/src/org/ibex/mail/target/FileBasedMailbox.java @@ -10,22 +10,21 @@ 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 implements Serializable { +public class FileBasedMailbox extends Mailbox.Default { - public String toString() { return "[FileBasedMailbox " + path + "]"; } + public String toString() { return "[FileBasedMailbox " + path.getAbsolutePath() + "]"; } private static final char slash = File.separatorChar; private static final WeakHashMap instances = new WeakHashMap(); - public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path + slash + name, create); } + public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); } + + // 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(path)); + if (!create && !(new File(path).exists())) return null; + instances.put(path, ret = new FileBasedMailbox(new File(path))); } return ret; } catch (Exception e) { @@ -37,103 +36,145 @@ public class FileBasedMailbox extends Mailbox.Default implements Serializable { // Instance ////////////////////////////////////////////////////////////////////////////// - private String path; - private transient FileLock lock; - private transient Prevayler prevayler; - private transient ArrayList cache; - private transient int uidValidity; - private transient 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; + 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 transient final Hashtable byuid = new Hashtable(); + public final File dir; + private int uidNext = 0; + + public Cache(File dir) { this.dir = dir; } + + public void init(Prevayler prevayler) throws IOException { + dir.mkdirs(); + Log.info(this, "initializing maildir " + dir.getAbsolutePath()); + boolean invalid = false; + for(String s : byname.keySet()) create(prevayler, s); + for(String file : dir.list()) + if (file.charAt(0)!='.' && !(new File(dir.getAbsolutePath() + slash + file).isDirectory())) + create(prevayler, file); + Log.info(this, " done initializing maildir " + dir.getAbsolutePath()); + prevayler.takeSnapshot(); } - public CacheEntry(File f) throws IOException { - path = f.getAbsolutePath(); - headers = new MIME.Headers(new Stream(new FileInputStream(f)), true); + + public synchronized void create(Prevayler prevayler, String name) throws IOException { + if (get(name) != null) return; + new Entry(this, prevayler, name); } - } - - private FileBasedMailbox(String path) throws MailException, IOException, ClassNotFoundException { - new File(this.path = path).mkdirs(); - new File(path + slash + ".cache").mkdirs(); - File uidValidityFile = new File(path + slash + ".uidValidity"); - if (!uidValidityFile.exists()) uidValidityFile.createNewFile(); - uidValidity = (int)(uidValidityFile.lastModified() & 0xffffffff); - 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 ArrayList(), path + slash + ".cache"); - cache = (ArrayList)prevayler.prevalentSystem(); - for(int i=0; i 1000 * 1) { - Log.warn(this, "rebuilding cache for " + path + ": " + Math.ceil((((float)i)/((float)files.length)) * 100)+ "%"); - last = System.currentTimeMillis(); - } - CacheEntry ce = new CacheEntry(new File(path + slash + files[i])); - entries[i] = ce; - uidNext = Math.max(uidNext, ce.uid()+1); + + public synchronized int size() { return byname.size(); } + public synchronized int uidNext(boolean increment) { return increment ? uidNext++ : uidNext; } + public synchronized Entry get(int uid) { return byuid.get(uid); } + public synchronized Entry get(String name) { return byname.get(name); } + + public static class Entry implements Serializable { + public final MIME.Headers headers; + public final String name; + private int uid; + private boolean seen; + + public Entry(Cache cache, Prevayler prevayler, String name) throws IOException { + seen = name.endsWith(".s"); + this.name = seen ? name.substring(0, name.length() - 2) : name; + headers = new MIME.Headers(new Stream(new FileInputStream(cache.dir.getAbsolutePath()+slash+name)), true); + prevayler.execute(new Transaction() { + public void executeOn(Object o, Date now) { + Cache cache = (Cache)o; + synchronized(cache) { + Entry.this.uid = cache.uidNext(true); + cache.byuid.put(Entry.this.uid, Entry.this); + cache.byname.put(Entry.this.name, Entry.this); + } } }); + } + + public int uid() { return uid; } + public boolean seen() { return seen; } + public void seen(Cache cache, boolean seen) { + String base = cache.dir.getAbsolutePath() + slash + name; + File target = new File(base + (seen?".s":"")); + if (target.exists()) return; + new File(base + (seen?"":".s")).renameTo(target); + } + public void delete(Cache cache) { + String base = cache.dir.getAbsolutePath() + slash + name; + File target = new File(base); + if (!target.exists()) target = new File(base + ".s"); + if (target.exists()) target.delete(); + } + public Message message(Cache cache) { try { + String base = cache.dir.getAbsolutePath() + slash + name; + File target = new File(base); + if (!target.exists()) target = new File(base + ".s"); + FileInputStream fis = null; + try { + fis = new FileInputStream(target); + return new Message(new Stream(fis), new Message.Envelope(null, null, new Date(target.lastModified()))); + } finally { if (fis != null) fis.close(); } + } catch (IOException e) { throw new MailException.IOException(e); + } catch (Message.Malformed e) { throw new MailException(e.getMessage()); } } - prevayler.execute(new Rebuild(entries)); } - Log.warn(this, "taking snapshot for " + path); - prevayler.takeSnapshot(); } - private static class Rebuild implements Transaction { - public final CacheEntry[] entries; - public Rebuild(CacheEntry entry) { this.entries = new CacheEntry[] { entry }; } - public Rebuild(CacheEntry[] entries) { this.entries = entries; } - public void executeOn(Object c, Date now) { - synchronized(c) { - for(int i=0; i)c).add(entries[i]); - } - } } + private static void rmDashRf(File f) throws IOException { + if (!f.isDirectory()) { f.delete(); return; } + String[] children = f.list(); + for(int i=0; i= cache.size()) return true; } return false; } + 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() { - if (done()) return null; - try { - String where = entry().path; - if (!new File(where).exists()) where = where.substring(0, where.lastIndexOf('.')+1); - File file = new File(where); - FileInputStream fis = null; - try { - fis = new FileInputStream(file); - return new Message(new Stream(fis), new Message.Envelope(null, null, new Date(file.lastModified()))); - } finally { if (fis != null) fis.close(); } - } catch (IOException e) { throw new MailException.IOException(e); - } catch (Message.Malformed e) { throw new MailException(e.getMessage()); } - } - public void seen(final boolean on) { + public Message cur() { return done() ? null : entry().message(cache); } + public void seen(final boolean seen) { if (done()) return; - final int ptr = this.cur; + final int cur = this.cur; prevayler.execute(new Transaction() { public void executeOn(Object c, Date d) { - ((ArrayList)c).get(ptr).seen(on); } }); + ((Cache)c).get(cur).seen((Cache)c, seen); } }); } public void delete() { if (done()) return; - final CacheEntry entry = entry(); + final int cur = this.cur; prevayler.execute(new Transaction() { public void executeOn(Object c, Date d) { - new File(entry.path).delete(); - if (cache.contains(entry)) { cache.remove(entry); } - }}); + ((Cache)c).get(cur).delete((Cache)c); } }); } } }