1 package org.ibex.mail.target;
2 import org.ibex.mail.*;
3 import org.ibex.util.*;
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
13 /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
14 public class FileBasedMailbox extends Mailbox.Default implements Serializable {
16 public String toString() { return "[FileBasedMailbox " + path + "]"; }
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));
29 public static final FilenameFilter filter = new FilenameFilter() {
30 public boolean accept(File f, String s) {
31 return s.indexOf('.') != -1;
35 // Instance //////////////////////////////////////////////////////////////////////////////
39 private FileBasedMailbox(String path) throws MailException {
40 new File(this.path = path).mkdirs();
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();
49 public Mailbox slash(String name, boolean create) {
50 return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); }
52 public String[] children() {
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]);
59 String[] ret = new String[vec.size()];
64 public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
65 public int uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
67 public int uidNext() { return uidNext(false); }
68 public int uidNext(boolean inc) {
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)));
77 tmp.renameTo(uidNext);
80 FileInputStream fis = null;
83 fis = new FileInputStream(uidNext);
84 BufferedReader br = new BufferedReader(new InputStreamReader(fis));
85 ret = Integer.parseInt(br.readLine());
87 File tmp = new File(uidNext + "-");
88 FileOutputStream fos = null;
90 fos = new FileOutputStream(tmp);
91 PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos));
94 } finally { if (fos != null) fos.close(); }
95 tmp.renameTo(uidNext);
97 } finally { if (fis != null) fis.close(); }
99 } catch (IOException e) { throw new MailException.IOException(e); }
102 public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
103 public synchronized void add(Message message, int flags) {
104 Log.info(path, message.summary());
106 int num = new File(path).list(filter).length;
107 String name = path + slash + uidNext(true) + "." +
108 ((flags & Mailbox.Flag.DELETED) == Mailbox.Flag.DELETED ? "x" : "") +
109 ((flags & Mailbox.Flag.DRAFT) == Mailbox.Flag.DRAFT ? "d" : "") +
110 ((flags & Mailbox.Flag.RECENT) == Mailbox.Flag.RECENT ? "r" : "") +
111 ((flags & Mailbox.Flag.ANSWERED) == Mailbox.Flag.ANSWERED ? "a" : "") +
112 ((flags & Mailbox.Flag.FLAGGED) == Mailbox.Flag.FLAGGED ? "f" : "") +
113 ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN ? "s" : "");
114 //Log.info(this, " with chosen filename " + name);
115 File target = new File(name);
116 File f = new File(target.getCanonicalPath() + "-");
117 FileOutputStream fo = new FileOutputStream(f);
118 Stream stream = new Stream(fo);
119 if (message.envelope != null) {
120 stream.println("X-org.ibex.mail.headers.envelope.From: " + message.envelope.from);
121 stream.println("X-org.ibex.mail.headers.envelope.To: " + message.envelope.to);
123 message.dump(stream);
126 //Log.info(this, " done writing.");
127 } catch (IOException e) { throw new MailException.IOException(e); }
130 private class Iterator extends Mailbox.Default.Iterator {
132 private String[] names;
133 private boolean seen = false, deleted = false, draft = false, flagged = false, answered = false, recent = false;
134 public Iterator() { names = new File(path).list(filter); }
136 public Message head() { return cur(); }
137 public Message cur() {
138 if (cur >= names.length) return null;
140 File file = new File(path + File.separatorChar + names[cur]);
141 FileInputStream fis = null;
143 fis = new FileInputStream(file);
144 return new Message(new Stream(fis), new Message.Envelope(null, null, new Date(file.lastModified())));
145 } finally { if (fis != null) fis.close(); }
146 } catch (IOException e) { throw new MailException.IOException(e);
147 } catch (Message.Malformed e) { throw new MailException(e.getMessage()); }
149 public boolean next() {
151 if (cur >= names.length) return false;
152 String name = names[cur].substring(names[cur].indexOf('.') + 1);
153 if (!(new File(path + File.separatorChar + names[cur])).exists()) return next();
154 seen = name.indexOf('s') != -1;
155 deleted = name.indexOf('x') != -1;
156 flagged = name.indexOf('f') != -1;
157 draft = name.indexOf('d') != -1;
158 answered = name.indexOf('a') != -1;
159 //recent = name.indexOf('r') != -1;
164 // EUDORA insists that message numbers start at 1, not 0
165 public int num() { return cur+1; }
168 try { return Integer.parseInt(names[cur].substring(0, names[cur].indexOf('.')));
169 } catch (NumberFormatException nfe) {
170 Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[cur].substring(0, names[cur].length() - 1));
173 private void fixflags() {
175 names[cur].substring(0, names[cur].indexOf('.') + 1) +
177 (deleted ? "x" : "") +
178 (flagged ? "f" : "") +
180 (recent ? "r" : "") +
181 (answered ? "a" : "");
182 new File(path + File.separatorChar + names[cur]).renameTo(new File(path + File.separatorChar + newName));
183 names[cur] = newName;
186 public void delete() {
187 new File(path + File.separatorChar + names[cur]).delete();
188 // FIXME remove from list?
190 public void set(String key, String val) { throw new MailException("not supported"); }
191 public String get(String key) { throw new MailException("not supported"); }
193 public boolean seen() { return seen; }
194 public boolean deleted() { return deleted; }
195 public boolean flagged() { return flagged; }
196 public boolean draft() { return draft; }
197 public boolean answered() { return answered; }
198 public boolean recent() { return recent; }
199 public void seen(boolean on) { seen = on; fixflags(); }
200 public void deleted(boolean on) { deleted = on; fixflags(); }
201 public void flagged(boolean on) { flagged = on; fixflags(); }
202 public void draft(boolean on) { draft = on; fixflags(); }
203 public void answered(boolean on) { answered = on; fixflags(); }
204 public void recent(boolean on) { recent = on; fixflags(); }