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