fix too many open files problem
[org.ibex.mail.git] / src / org / ibex / mail / target / FileBasedMailbox.java
1 package org.ibex.mail.target;
2 import org.ibex.mail.*;
3 import org.ibex.util.*;
4 import org.ibex.io.*;
5 import java.io.*;
6 import java.net.*;
7 import java.util.*;
8 import java.text.*;
9
10 // FIXME: we can omit UIDNEXT!
11 // FIXME use directory date/time as UIDNEXT and file date/time as UID; need to 'correct' file date/time after changes
12
13 /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
14 public class FileBasedMailbox extends Mailbox.Default implements Serializable {
15
16     public String toString() { return "[FileBasedMailbox " + path + "]"; }
17
18     private static final char slash = File.separatorChar;
19     private static final Hashtable instances = new Hashtable();
20     public static FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
21         FileBasedMailbox ret = (FileBasedMailbox)instances.get(path);
22         if (ret != null) return ret;
23         File f = new File(path);
24         if (!create && !f.exists()) return null;
25         instances.put(path, ret = new FileBasedMailbox(path));
26         return ret;
27     }
28
29     public static final FilenameFilter filter = new FilenameFilter() {
30             public boolean accept(File f, String s) {
31                 return s.indexOf('.') != -1;
32             } };
33
34
35     // Instance //////////////////////////////////////////////////////////////////////////////
36
37     private String path;
38     private File uidNext;
39     private FileBasedMailbox(String path) throws MailException {
40         new File(this.path = path).mkdirs();
41         uidNext(false);
42     }
43     private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeObject(path); }
44     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
45         new File(this.path = (String)in.readObject()).mkdirs();
46         uidNext(false);
47     }
48
49     public Mailbox          slash(String name, boolean create) {
50         return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); }
51
52     public String[] children() {
53         Vec vec = new Vec();
54         String[] list = new File(path).list();
55         for(int i=0; i<list.length; i++) {
56             if (!(new File(path + slash + list[i]).isDirectory())) continue;
57             vec.addElement(list[i]);
58         }
59         String[] ret = new String[vec.size()];
60         vec.copyInto(ret);
61         return ret;
62     }
63
64     public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
65     public int              uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
66
67     public int uidNext() { return uidNext(false); }
68     public int uidNext(boolean inc) {
69         try {
70             uidNext = new File(path + slash + "UIDNEXT");
71             if (!uidNext.exists()) {
72                 File tmp = new File(uidNext + "-");
73                 PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
74                 pw.println("1");
75                 pw.flush();
76                 pw.close();
77                 tmp.renameTo(uidNext);
78                 return 1;
79             }
80             BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(uidNext)));
81             int ret = Integer.parseInt(br.readLine());
82             br.close();
83             if (inc) {
84                 File tmp = new File(uidNext + "-");
85                 PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
86                 pw.println(ret+1);
87                 pw.flush();
88                 pw.close();
89                 tmp.renameTo(uidNext);
90             }
91             return ret;
92         } catch (IOException e) { throw new MailException.IOException(e); }
93     }        
94
95     public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
96     public synchronized void add(Message message, int flags) {
97         Log.info(this, "adding message to ["+toString()+"]:\n" + message.summary());
98         try {
99             int num = new File(path).list(filter).length;
100             String name = path + slash + uidNext(true) + "." +
101                 ((flags & Mailbox.Flag.DELETED) == Mailbox.Flag.DELETED ? "x" : "") +
102                 ((flags & Mailbox.Flag.DRAFT) == Mailbox.Flag.DRAFT ? "d" : "") +
103                 ((flags & Mailbox.Flag.RECENT) == Mailbox.Flag.RECENT ? "r" : "") +
104                 ((flags & Mailbox.Flag.ANSWERED) == Mailbox.Flag.ANSWERED ? "a" : "") +
105                 ((flags & Mailbox.Flag.FLAGGED) == Mailbox.Flag.FLAGGED ? "f" : "") +
106                 ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN ? "s" : "");
107             Log.info(this, "    with chosen filename " + name);
108             File target = new File(name);
109             File f = new File(target.getCanonicalPath() + "-");
110             FileOutputStream fo = new FileOutputStream(f);
111             Stream stream = new Stream(fo);
112             stream.println("X-org.ibex.mail.headers.envelope.From: " + message.envelope.from);
113             stream.println("X-org.ibex.mail.headers.envelope.To: " + message.envelope.to);
114             message.dump(stream);
115             fo.close();
116             f.renameTo(target);
117             Log.info(this, "    done writing.");
118         } catch (IOException e) { throw new MailException.IOException(e); }
119     }
120
121     private class Iterator extends Mailbox.Default.Iterator {
122         int cur = -1;
123         private String[] names;
124         private boolean seen = false, deleted = false, draft = false, flagged = false, answered = false, recent = false;
125         public Iterator() { names = new File(path).list(filter); }
126
127         public Message head() { return cur(); }
128         public Message cur() {
129             if (cur >= names.length) return null;
130             try {
131                 File file = new File(path + File.separatorChar + names[cur]);
132                 FileInputStream fis = new FileInputStream(file);
133                 Stream stream = new Stream(fis);
134                 Address envelopeFrom = null;
135                 Address envelopeTo = null;
136                 /*
137                 for(String s = stream.readln(); s != null; s = stream.readln()) {
138                     if (s.startsWith("X-org.ibex.mail.headers.envelope.From: "))
139                         envelopeFrom = Address.parse(s.substring(38).trim());
140                     else if (s.startsWith("X-org.ibex.mail.headers.envelope.To: "))
141                         envelopeTo = Address.parse(s.substring(36).trim());
142                     else {
143                         stream.unread(s + "\r\n");
144                         break;
145                     }
146                 }
147                 */
148                 Message ret = new Message(stream, new Message.Envelope(null, null, new Date(file.lastModified())));
149                 fis.close();
150                 return ret;
151             } catch (IOException e) { throw new MailException.IOException(e);
152             } catch (Message.Malformed e) { throw new MailException(e.getMessage()); }
153         }
154         public boolean next() {
155             cur++;
156             if (cur >= names.length) return false;
157             String name = names[cur].substring(names[cur].indexOf('.') + 1);
158             if (!(new File(path + File.separatorChar + names[cur])).exists()) return next();
159             seen     = name.indexOf('s') != -1;
160             deleted  = name.indexOf('x') != -1;
161             flagged  = name.indexOf('f') != -1;
162             draft    = name.indexOf('d') != -1;
163             answered = name.indexOf('a') != -1;
164             recent   = name.indexOf('r') != -1;
165             return true;
166         }
167         public int num() { return cur; }
168         public int uid() {
169             try { return Integer.parseInt(names[cur].substring(0, names[cur].indexOf('.')));
170             } catch (NumberFormatException nfe) {
171                 Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[cur].substring(0, names[cur].length() - 1));
172                 return -1; } }
173
174         private void fixflags() {
175             String newName =
176                 names[cur].substring(0, names[cur].indexOf('.') + 1) +
177                 (seen ? "s" : "") +
178                 (deleted ? "x" : "") +
179                 (flagged ? "f" : "") +
180                 (draft ? "d" : "") +
181                 (recent ? "r" : "") +
182                 (answered ? "a" : "");
183             new File(path + File.separatorChar + names[cur]).renameTo(new File(path + File.separatorChar + newName));
184             names[cur] = newName;
185         }
186
187         public void    delete() {
188             new File(path + File.separatorChar + names[cur]).delete();
189             // FIXME remove from list?
190         }
191         public void    set(String key, String val) { throw new MailException("not supported"); }
192         public String  get(String key) { throw new MailException("not supported"); }
193
194         public boolean seen() { return seen; }
195         public boolean deleted() { return deleted; }
196         public boolean flagged() { return flagged; }
197         public boolean draft() { return draft; }
198         public boolean answered() { return answered; }
199         public boolean recent() { return recent; }
200         public void    seen(boolean on) { seen = on; fixflags(); }
201         public void    deleted(boolean on) { deleted = on; fixflags(); }
202         public void    flagged(boolean on) { flagged = on; fixflags(); }
203         public void    draft(boolean on) { draft = on; fixflags(); }
204         public void    answered(boolean on) { answered = on; fixflags(); }
205         public void    recent(boolean on) { recent = on; fixflags(); }
206
207     }
208 }