private static final char slash = File.separatorChar;
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) {
+ public static synchronized FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
try {
FileBasedMailbox ret = instances.get(path);
if (ret == null) {
// Instance //////////////////////////////////////////////////////////////////////////////
private String path;
- private FileLock lock;
- private Prevayler prevayler;
- private LinkedList<CacheEntry> cache;
- private int uidNext;
+ private transient FileLock lock;
+ private transient Prevayler prevayler;
+ private transient ArrayList<CacheEntry> cache;
+ private transient int uidValidity;
+ private transient int uidNext;
public static class CacheEntry implements Serializable {
public MIME.Headers headers;
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 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 = PrevaylerFactory.createPrevayler(new ArrayList<CacheEntry>(), path + slash + ".cache");
+ cache = (ArrayList<CacheEntry>)prevayler.prevalentSystem();
+ for(int i=0; i<cache.size(); i++) if (cache.get(i) != null) uidNext = Math.max(uidNext, cache.get(i).uid()+1);
+ if (cache.size() == 0) {
+ 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("..");
+ } });
+ long last = System.currentTimeMillis();
+ CacheEntry[] entries = new CacheEntry[files.length];
+ for(int i=0; i<files.length; i++) {
+ if (System.currentTimeMillis() - last > 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);
+ }
+ prevayler.execute(new Rebuild(entries));
}
+ Log.warn(this, "taking snapshot for " + path);
prevayler.takeSnapshot();
}
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 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<entries.length; i++) {
+ ((ArrayList<CacheEntry>)c).add(entries[i]);
+ }
+ } }
}
public Mailbox.Iterator iterator() { return new Iterator(); }
- public int uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
+
+ public int uidValidity() { return uidValidity; }
+
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())) vec.addElement(list[i]);
+ for(int i=0; i<list.length; i++) {
+ File f = new File(path + slash + list[i]);
+ if (f.isDirectory() && f.getName().charAt(0) != '.')
+ vec.addElement(list[i]);
+ }
return (String[])vec.copyInto(new String[vec.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 entry().uid(); }
+ public int uid() { return done() ? -1 : entry().uid(); }
public Message cur() {
if (done()) return null;
try {
}
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); } });
+ final int ptr = this.cur;
+ prevayler.execute(new Transaction() {
+ public void executeOn(Object c, Date d) {
+ ((ArrayList<CacheEntry>)c).get(ptr).seen(on); } });
}
public void delete() {
if (done()) return;