removed prevaylerized caching; major reduction in memory usage
[org.ibex.mail.git] / src / org / ibex / mail / target / FileBasedMailbox.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.mail.target;
6 import org.prevayler.*;
7 import org.ibex.mail.*;
8 import org.ibex.util.*;
9 import org.ibex.io.*;
10 import java.io.*;
11 import java.nio.*;
12 import java.nio.channels.*;
13 import java.net.*;
14 import java.util.*;
15 import java.text.*;
16
17 /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
18 public class FileBasedMailbox extends Mailbox.Default {
19
20     public static final long MAGIC_DATE = 0;
21     private static final char slash = File.separatorChar;
22     private static final WeakHashMap<String,FileBasedMailbox> instances = new WeakHashMap<String,FileBasedMailbox>();
23     public String toString() { return "[FileBasedMailbox " + path.getAbsolutePath() + "]"; }
24     public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); }
25
26     // FIXME: should be a File()
27     public static synchronized FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
28         try {
29             FileBasedMailbox ret = instances.get(path);
30             if (ret == null) {
31                 if (!create && !(new File(path).exists())) return null;
32                 instances.put(path, ret = new FileBasedMailbox(new File(path)));
33             }
34             return ret;
35         } catch (Exception e) {
36             Log.error(FileBasedMailbox.class, e);
37             return null;
38         }
39     }
40
41     // Instance //////////////////////////////////////////////////////////////////////////////
42
43     private File path;
44     private FileLock lock;
45     private int uidNext;
46     private int uidValidity;
47
48     // Helpers //////////////////////////////////////////////////////////////////////////////
49
50     private static void rmDashRf(File f) throws IOException {
51         if (!f.isDirectory()) { f.delete(); return; }
52         String[] children = f.list();
53         for(int i=0; i<children.length; i++) rmDashRf(new File(f.getAbsolutePath() + slash + children[i]));
54         f.delete();
55     }
56
57     private FileBasedMailbox(File path) throws MailException, IOException, ClassNotFoundException {
58         this.path = path;
59         path.mkdirs();
60
61         // acquire lock
62         File lockfile = new File(this.path.getAbsolutePath() + slash + ".lock");
63         lock = new RandomAccessFile(lockfile, "rw").getChannel().tryLock();
64         if (lock == null) throw new IOException("unable to lock FileBasedMailbox");
65         uidValidity = (int)(lockfile.lastModified() & 0xffffffff);
66         uidNext = 0;
67         String[] files = path.list();
68         for(int i=0; i<files.length; i++) {
69             try {
70                 if (files[i].indexOf('.') != -1) files[i] = files[i].substring(0, files[i].indexOf('.'));
71                 int n = Integer.parseInt(files[i]);
72                 if (n>=uidNext) uidNext = n;
73             } catch(Exception e) { /* DELIBERATE */ }
74         }
75     }
76
77
78     public Mailbox.Iterator  iterator()           { return new Iterator(); }
79     public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
80     public String[] children() {
81         Vec vec = new Vec();
82         String[] list = path.list();
83         for(int i=0; i<list.length; i++) {
84             File f = new File(path.getAbsolutePath() + slash + list[i]);
85             if (f.isDirectory() && f.getName().charAt(0) != '.') vec.addElement(list[i]);
86         }
87         return (String[])vec.copyInto(new String[vec.size()]);
88     }
89
90     public int uidValidity() { return uidValidity; }
91     public int uidNext() {  return uidNext; }
92     public synchronized void add(Message message, int flags) {
93         try {
94             String name, fullname; File target, f;
95             for(int i = uidNext; ; i++) {
96                 name = i + ".";
97                 fullname = path.getAbsolutePath() + slash + name;
98                 target = new File(fullname);
99                 f = new File(target.getCanonicalPath() + "-");
100                 if (!f.exists() && !target.exists()) break;
101                 Log.error(this, "aieeee!!!! target of add() already exists: " + target.getAbsolutePath());
102             }
103             Stream stream = new Stream(new FileOutputStream(f));
104             message.getStream().transcribe(stream);
105             stream.close();
106             f.renameTo(new File(fullname));
107             uidNext++;
108             f = new File(fullname);
109             if ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN) f.setLastModified(MAGIC_DATE);
110         } catch (IOException e) { throw new MailException.IOException(e); }
111         Log.info(this, path + " <= " + message.summary());
112     }
113
114     private class Iterator extends Mailbox.Default.Iterator {
115         int cur = -1;
116         String[] files = path.list(new FilenameFilter() { public boolean accept(File dir, String name) {
117             return name.endsWith(".");
118         } });
119         private File file() { return new File(path.getAbsolutePath() + slash + files[cur]); }
120         public boolean done() { return cur >= files.length; }
121         public boolean next() { cur++; return !done(); }
122         public boolean seen() { return false; }
123         public boolean recent() { return false; }
124         public int num() { return cur+1; }  // EUDORA insists that message numbers start at 1, not 0
125         public int uid() { return done() ? -1 : Integer.parseInt(files[cur].substring(0, files[cur].length()-1)); }
126         public void delete() { File f = file(); if (f != null && f.exists()) f.delete(); }
127         public void seen(boolean seen) { }
128         public Headers head() {
129             if (done()) return null;
130             FileInputStream fis = null;
131             try {
132                 return new Headers(new Stream(new FileInputStream(file())));
133             } catch (IOException e) { throw new MailException.IOException(e);
134             } finally { if (fis != null) try { fis.close(); } catch (Exception e) { /* DELIBERATE */ } }
135         }
136         public Message cur() {
137             FileInputStream fis = null;
138             try {
139                 return Message.newMessage(new Fountain.File(file()));
140                 //} catch (IOException e) { throw new MailException.IOException(e);
141             } catch (Message.Malformed e) { throw new MailException(e.getMessage());
142             } finally { if (fis != null) try { fis.close(); } catch (Exception e) { /* DELIBERATE */ } }
143         }
144     }
145 }