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