compiles
authoradam <adam@megacz.com>
Wed, 9 Jun 2004 22:32:44 +0000 (22:32 +0000)
committeradam <adam@megacz.com>
Wed, 9 Jun 2004 22:32:44 +0000 (22:32 +0000)
darcs-hash:20040609223244-5007d-4f739f013ab870f1499118992023862a05bec260.gz

src/org/ibex/mail/Flag.java [deleted file]
src/org/ibex/mail/MailException.java
src/org/ibex/mail/Message.java
src/org/ibex/mail/Query.java
src/org/ibex/mail/protocol/IMAP.java
src/org/ibex/mail/protocol/Incoming.java
src/org/ibex/mail/protocol/SMTP.java
src/org/ibex/mail/target/FileBasedMailbox.java
src/org/ibex/mail/target/Mailbox.java
src/org/ibex/mail/target/Transcript.java

diff --git a/src/org/ibex/mail/Flag.java b/src/org/ibex/mail/Flag.java
deleted file mode 100644 (file)
index 9707d76..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.ibex.mail;
-
-public class Flag {
-    public static final int DELETED  = 0x0001;
-    public static final int SEEN     = 0x0002;
-    public static final int FLAGGED  = 0x0004;
-    public static final int DRAFT    = 0x0010;
-    public static final int ANSWERED = 0x0020;
-    public static final int RECENT   = 0x0040;
-}
index 91883da..9f57978 100644 (file)
@@ -3,17 +3,11 @@ import java.net.*;
 import java.io.*;
 
 public class MailException extends RuntimeException {
-
     public MailException() { }
     public MailException(String s) { super(s); }
     public static class MailboxFull extends MailException { }
     public static class MetadataNotSupported extends MailException { public MetadataNotSupported(String s) { super(s); } }
     public static class Malformed extends MailException { public Malformed(String s) { super(s); } }
     public static class RelayingDenied extends MailException { }
-    public static class IOException extends MailException {
-        // FIXME: fill in stack trace
-        final java.io.IOException ioe;
-        public IOException(java.io.IOException ioe) { this.ioe = ioe; }
-    }
-
+    public static class IOException extends MailException { public IOException(java.io.IOException ioe) { initCause(ioe); } }
 }
index 6ad493b..f4644f8 100644 (file)
@@ -24,29 +24,10 @@ import java.io.*;
  */
 public class Message extends JSReflection {
 
-    // FIXME
-    public Date sent() { return null; }
-    public Date arrived() { return null; }
-
-    private static class CaseInsensitiveHash extends Hashtable {
-        public Object get(Object o) {
-            if (o instanceof String) return super.get(((String)o).toLowerCase());
-            return super.get(o);
-        }
-        public Object put(Object k, Object v) { throw new Error("you cannot write to a CaseInsensitiveHash"); }
-        void add(Object k, Object v) {
-            if (k instanceof String) super.put(((String)k).toLowerCase(), v);
-            else super.put(k, v);
-        }
-    }
-    
-    public int rfc822size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); }  // double check this
-    public int size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); }  // double check this
-
-    public final String allHeaders;   // pristine headers
-    public final Hashtable headers;   // hash of headers (not including resent's and traces)
-    public final String body;         // entire body
-    public final int lines;           // lines in the body
+    public final String allHeaders;           // pristine headers
+    public final Hashtable headers;           // hash of headers (not including resent's and traces)
+    public final String body;                 // entire body
+    public final int lines;                   // lines in the body
 
     public final Date date;
     public final Address to;
@@ -64,6 +45,7 @@ public class Message extends JSReflection {
 
     public final Date arrival;         // when the message first arrived at this machine; IMAP "internal date message attr"
     
+    // FIXME: need to be able to read back in the EnvelopeFrom / EnvelopeTo fields
     public void dump(OutputStream os) throws IOException {
         Writer w = new OutputStreamWriter(os);
         w.write(allHeaders);
@@ -113,72 +95,74 @@ public class Message extends JSReflection {
     }
 
     public static class Malformed extends MailException.Malformed { public Malformed(String s) { super(s); } }
-    public Message(Address envelopeFrom, Address[] envelopeTo, LineReader rs) throws IOException, MailException.Malformed {
-        this.envelopeFrom = envelopeFrom;
-        this.envelopeTo = envelopeTo;
-        this.arrival = new Date();
-        this.headers = new CaseInsensitiveHash();
-        String key = null;
-        StringBuffer all = new StringBuffer();
-        Date date = null;
-        Address to = null, from = null, replyto = null;
-        String subject = null, messageid = null;
-        Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec();
-        for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
-            all.append(s);
-            all.append("\r\n");
-            if (s.length() == 0 || Character.isSpace(s.charAt(0))) {
-                if (key == null) throw new Malformed("Message began with a blank line; no headers");
-                headers.put(key, headers.get(key) + s);
-                continue;
+    public Message(Address envelopeFrom, Address[] envelopeTo, LineReader rs) {
+        try {
+            this.envelopeFrom = envelopeFrom;
+            this.envelopeTo = envelopeTo;
+            this.arrival = new Date();
+            this.headers = new CaseInsensitiveHash();
+            String key = null;
+            StringBuffer all = new StringBuffer();
+            Date date = null;
+            Address to = null, from = null, replyto = null;
+            String subject = null, messageid = null;
+            Vec cc = new Vec(), bcc = new Vec(), resent = new Vec(), traces = new Vec();
+            for(String s = rs.readLine(); s != null && !s.equals(""); s = rs.readLine()) {
+                all.append(s);
+                all.append("\r\n");
+                if (s.length() == 0 || Character.isSpace(s.charAt(0))) {
+                    if (key == null) throw new Malformed("Message began with a blank line; no headers");
+                    ((CaseInsensitiveHash)headers).add(key, headers.get(key) + s);
+                    continue;
+                }
+                if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
+                key = s.substring(0, s.indexOf(':'));
+                for(int i=0; i<key.length(); i++)
+                    if (key.charAt(i) < 33 || key.charAt(i) > 126)
+                        throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\"");
+                String val = s.substring(s.indexOf(':') + 1).trim();
+                while(Character.isSpace(val.charAt(0))) val = val.substring(1);
+                if (key.startsWith("Resent-")) {
+                    if (key.startsWith("Resent-From")) resent.addElement(new Hashtable());
+                    ((Hashtable)resent.lastElement()).put(key.substring(7), val);
+                } else if (key.startsWith("Return-Path:")) {
+                    rs.pushback(s); traces.addElement(new Trace(rs));
+                } else {
+                    // just append it to the previous one; valid for Comments/Keywords
+                    if (headers.get(key) != null) val = headers.get(key) + " " + val;
+                    ((CaseInsensitiveHash)headers).add(key, val);
+                }            
             }
-            if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
-            key = s.substring(0, s.indexOf(':'));
-            for(int i=0; i<key.length(); i++)
-                if (key.charAt(i) < 33 || key.charAt(i) > 126)
-                    throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\"");
-            String val = s.substring(s.indexOf(':') + 1).trim();
-            while(Character.isSpace(val.charAt(0))) val = val.substring(1);
-            if (key.startsWith("Resent-")) {
-                if (key.startsWith("Resent-From")) resent.addElement(new Hashtable());
-                ((Hashtable)resent.lastElement()).put(key.substring(7), val);
-            } else if (key.startsWith("Return-Path:")) {
-                rs.pushback(s); traces.addElement(new Trace(rs));
-            } else {
-                // just append it to the previous one; valid for Comments/Keywords
-                if (headers.get(key) != null) val = headers.get(key) + " " + val;
-                headers.put(key, val);
-            }            
-        }
 
-        this.date      = (Date)headers.get("Date");
-        this.to        = new Address((String)headers.get("To"));  // FIXME what if null?
-        this.from      = headers.get("From") == null     ? envelopeFrom : new Address((String)headers.get("From"));
-        this.replyto   = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To"));
-        this.subject   = (String)headers.get("Subject");
-        this.messageid = (String)headers.get("Message-Id");
-        if (headers.get("Cc") != null) {
-            StringTokenizer st = new StringTokenizer((String)headers.get("Cc"));
-            this.cc = new Address[st.countTokens()];
-            for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(st.nextToken());
-        } else {
-            this.cc = new Address[0];
-        }
-        if (headers.get("Bcc") != null) {
-            StringTokenizer st = new StringTokenizer((String)headers.get("Bcc"));
-            this.bcc = new Address[st.countTokens()];
-            for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
-        } else {
-            this.bcc = new Address[0];
-        }
-        resent.copyInto(this.resent = new Hashtable[resent.size()]);
-        traces.copyInto(this.traces = new Trace[traces.size()]);
-        allHeaders = all.toString();
-        StringBuffer body = new StringBuffer();
-        int lines = 0;
-        for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; lines++; body.append(s + "\r\n"); }
-        this.lines = lines;
-        this.body = body.toString();
+            this.date      = (Date)headers.get("Date");
+            this.to        = new Address((String)headers.get("To"));  // FIXME what if null?
+            this.from      = headers.get("From") == null     ? envelopeFrom : new Address((String)headers.get("From"));
+            this.replyto   = headers.get("Reply-To") == null ? null : new Address((String)headers.get("Reply-To"));
+            this.subject   = (String)headers.get("Subject");
+            this.messageid = (String)headers.get("Message-Id");
+            if (headers.get("Cc") != null) {
+                StringTokenizer st = new StringTokenizer((String)headers.get("Cc"));
+                this.cc = new Address[st.countTokens()];
+                for(int i=0; i<this.cc.length; i++) this.cc[i] = new Address(st.nextToken());
+            } else {
+                this.cc = new Address[0];
+            }
+            if (headers.get("Bcc") != null) {
+                StringTokenizer st = new StringTokenizer((String)headers.get("Bcc"));
+                this.bcc = new Address[st.countTokens()];
+                for(int i=0; i<this.bcc.length; i++) this.bcc[i] = new Address(st.nextToken());
+            } else {
+                this.bcc = new Address[0];
+            }
+            resent.copyInto(this.resent = new Hashtable[resent.size()]);
+            traces.copyInto(this.traces = new Trace[traces.size()]);
+            allHeaders = all.toString();
+            StringBuffer body = new StringBuffer();
+            int lines = 0;
+            for(String s = rs.readLine();; s = rs.readLine()) { if (s == null) break; lines++; body.append(s + "\r\n"); }
+            this.lines = lines;
+            this.body = body.toString();
+        } catch (IOException e) { throw new MailException.IOException(e); }
     }
 
     // http://www.jwz.org/doc/mid.html
@@ -195,6 +179,8 @@ public class Message extends JSReflection {
         return ret.toString();
     }
 
+    public int rfc822size() { return allHeaders.length() + 2 /* CRLF */ + body.length(); }  // FIXME: double check this
+
     public String summary() {
         return
             "          Subject: " + subject + "\n" +
@@ -205,4 +191,11 @@ public class Message extends JSReflection {
 
     //  use null-sender for error messages (don't send errors to the null addr)
     public Message bounce(String reason) { throw new RuntimeException("bounce not implemented"); }  // FIXME!
+
+    private static class CaseInsensitiveHash extends Hashtable {
+        public Object get(Object o) { return (o instanceof String) ? super.get(((String)o).toLowerCase()) : super.get(o); }
+        public Object put(Object k, Object v) { throw new Error("you cannot write to a CaseInsensitiveHash"); }
+        void add(Object k, Object v) { if (k instanceof String) super.put(((String)k).toLowerCase(), v); else super.put(k, v); }
+    }
+
 }
index 300397a..8dcd389 100644 (file)
@@ -15,22 +15,28 @@ import org.ibex.mail.target.*;
  */
 public class Query {
 
-    public static Query not(Query q)      { return new Query(NOT, new Query[] { q },      0, 0, 0, null, null, null, null, null); }
-    public static Query and(Query q1, Query q2) { return new Query(AND, new Query[] {q1,q2},0,0,0,null, null, null, null, null); }
-    public static Query and(Query[] q)   { return new Query(AND, q,             0, 0, 0, null, null, null, null, null ); }
-    public static Query or(Query q1, Query q2)  { return new Query(OR,new Query[] {q1,q2},0, 0, 0, null, null, null, null, null); }
-    public static Query or(Query[] q)    { return new Query(OR,  q,                      0, 0, 0, null, null, null, null, null); }
-    public static Query uid(int min, int max)  { return new Query(UID, null, min, max, 0, null, null, null, null, null); }
-    public static Query messagenum(int min, int max) { return new Query(MESSAGENUM,null,min,max,0,null,null,null,null, null); }
-    public static Query sent(Date earliest, Date latest) { return new Query(SENT,null,0,0,0,null,null,earliest,latest,null); }
-    public static Query arrival(Date earliest, Date latest){return new Query(ARRIVAL,null,0,0,0,null,null, earliest,latest,null);}
-    public static Query header(String name, String val)  { return new Query(HEADER, null, 0, 0, 0, name, val, null, null, null);}
-    public static Query size(int min, int max)       { return new Query(SIZE, null, min, max, 0, null, null, null, null, null);}
-    public static Query flags(int flags)             { return new Query(FLAGS, null, 0, 0, flags, null, null, null, null, null);}
-    public static Query body(String text)            { return new Query(BODY, null, 0, 0, 0, null, text, null, null, null);}
-    public static Query full(String text)            { return new Query(FULL, null, 0, 0, 0, null, text, null, null, null);}
-    public static Query set(int[] set)               { return new Query(SET,  null, 0 ,0 ,0, null, null, null, null, set);}
-    public static Query all() { return new Query(ALL, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query not(Query q)                { return new Query(NOT, new Query[] { q },0,0,0,null,null,null,null,null); }
+    public static Query and(Query q1, Query q2)     { return new Query(AND,new Query[]{q1,q2},0,0,0,null,null,null,null,null); }
+    public static Query and(Query[] q)              { return new Query(AND, q,0,0,0,null, null, null, null, null ); }
+    public static Query or(Query q1, Query q2)      { return new Query(OR,new Query[] {q1,q2},0,0,0,null,null, null, null, null); }
+    public static Query or(Query[] q)               { return new Query(OR, q, 0, 0, 0, null, null, null, null, null); }
+    public static Query uid(int min, int max)       { return new Query(UID, null, min, max, 0, null, null, null, null, null); }
+    public static Query messagenum(int min,int max) { return new Query(NUM,null,min,max,0,null,null,null,null, null); }
+    public static Query sent(Date e, Date l)        { return new Query(SENT,null,0,0,0,null,null,e,l,null); }
+    public static Query arrival(Date e, Date l)     { return new Query(ARRIVAL,null,0,0,0,null,null, e,l,null);}
+    public static Query header(String k, String v)  { return new Query(HEADER, null, 0, 0, 0, k, v, null, null, null);}
+    public static Query size(int min, int max)      { return new Query(SIZE, null, min, max, 0, null, null, null, null, null);}
+    public static Query body(String text)           { return new Query(BODY, null, 0, 0, 0, null, text, null, null, null);}
+    public static Query full(String text)           { return new Query(FULL, null, 0, 0, 0, null, text, null, null, null);}
+    public static Query uid(int[] set)              { return new Query(UID,  null, 0 ,0 ,0, null, null, null, null, set);}
+    public static Query num(int[] set)              { return new Query(NUM,  null, 0 ,0 ,0, null, null, null, null, set);}
+    public static Query all()                       { return new Query(ALL, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query deleted()                   { return new Query(DELETED, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query seen()                      { return new Query(SEEN, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query flagged()                   { return new Query(FLAGGED, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query draft()                     { return new Query(DRAFT, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query answered()                  { return new Query(ANSWERED, null, 0, 0, 0, null, null, null, null, null); }
+    public static Query recent()                    { return new Query(RECENT, null, 0, 0, 0, null, null, null, null, null); }
 
     private Query(int type, Query[] q,int min,int max, int flags, String key, String text, Date earliest, Date latest, int[] set) {
         this.type = type; this.q = q; this.min = min; this.max = max; this.flags = flags; this.key = key; this.text = text;
@@ -41,15 +47,19 @@ public class Query {
     public static final int AND        = 2;
     public static final int OR         = 3;
     public static final int UID        = 4;
-    public static final int MESSAGENUM = 5; 
-    public static final int SENT       = 6;
-    public static final int ARRIVAL    = 7;
-    public static final int HEADER     = 8;
-    public static final int SIZE       = 9;
-    public static final int FLAGS      = 10;
-    public static final int BODY       = 11;
-    public static final int FULL       = 12;
-    public static final int SET        = 13;
+    public static final int SENT       = 5;
+    public static final int ARRIVAL    = 6;
+    public static final int HEADER     = 7;
+    public static final int SIZE       = 8;
+    public static final int BODY       = 9;
+    public static final int FULL       = 10;
+    public static final int NUM        = 11;
+    public static final int DELETED    = 12;
+    public static final int SEEN       = 13;
+    public static final int FLAGGED    = 14;
+    public static final int DRAFT      = 15;
+    public static final int ANSWERED   = 16;
+    public static final int RECENT     = 17;
 
     public final int type;
     public final Query[] q;
@@ -68,20 +78,25 @@ public class Query {
             case NOT:        return !q[0].match(it);
             case OR:         for(int i=0; i<q.length; i++) if (q[i].match(it)) return true; return false;
             case AND:        for(int i=0; i<q.length; i++) if (!q[i].match(it)) return false; return true;
-            case UID:        return it.uid()        >= min && it.uid() <= max;
-            case MESSAGENUM: return it.num()       >= min && it.num() <= max;
-            case SENT:       return (latest == null || it.cur().sent().before(latest))
-                                 &&(earliest == null || it.cur().sent().after(earliest));
-            case ARRIVAL:    return (latest == null || it.cur().arrived().before(latest))
-                                 &&(earliest == null || it.cur().arrived().after(earliest));
-            case SIZE:       return it.cur().size() >= min && it.cur().size() <= max;
-            case FLAGS:      return it.getFlag(flags);
+            case UID:        if (set != null){for(int i=0; i<set.length; i++) if (set[i] == it.uid()) return true; return false; }
+                             else return it.uid() >= min && it.uid() <= max;
+            case NUM:        if (set != null){for(int i=0; i<set.length; i++) if (set[i] == it.uid()) return true; return false; }
+                             else return it.uid() >= min && it.uid() <= max;
+            case SENT:       return (latest==null||it.cur().date.before(latest)) && 
+                                    (earliest==null||it.cur().date.after(earliest));
+            case ARRIVAL:    return (latest == null || it.cur().arrival.before(latest)) &&
+                                    (earliest == null || it.cur().arrival.after(earliest));
+            case SIZE:       return it.cur().rfc822size() >= min && it.cur().rfc822size() <= max;
             case HEADER:     return it.cur().headers.get(key) != null && ((String)it.cur().headers.get(key)).indexOf(text) != -1;
             case BODY:       return it.cur().body.indexOf(text) != -1;
             case FULL:       return it.cur().body.indexOf(text) != -1 || it.cur().allHeaders.indexOf(text) != -1;
-            case SET:        for(int i=0; i<set.length; i++) if (set[i] == it.num()) return true; return false;
+            case DELETED:    return it.deleted();
+            case SEEN:       return it.seen();
+            case FLAGGED:    return it.flagged();
+            case DRAFT:      return it.draft();
+            case ANSWERED:   return it.answered();
+            case RECENT:     return it.recent();
             default:         throw new Error("this should not happen");
         }
     }
-
 }
index 2806811..a2a7b76 100644 (file)
@@ -12,6 +12,8 @@ import java.io.*;
 // FEATURE: support [charset]
 public class IMAP extends MessageProtocol {
 
+    public static final char imapSeparator = '.';
+
     public static void main(String[] args) throws Exception {
         ServerSocket ss = new ServerSocket(143);
         while(true) {
@@ -21,7 +23,10 @@ public class IMAP extends MessageProtocol {
             new Thread() {
                 public void run() {
                     try {
-                        new Listener(s, "megacz.com").handleRequest();
+                        Mailbox root =
+                            FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT + File.separatorChar + "imap", true);
+                        Mailbox inbox = root.slash("users", true).slash("megacz", true);
+                        new Listener(s, "megacz.com", root, inbox).handleRequest();
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
@@ -38,26 +43,25 @@ public class IMAP extends MessageProtocol {
 
     private static class Listener extends Incoming {
         Mailbox selected = null;
-        Mailbox root = null;
-
+        Mailbox root;
+        Mailbox inbox;
         Socket conn;
         String vhost;
         PrintWriter pw;
         InputStream is;
         PushbackReader r;
         public void init() { }
-        public Listener(Socket conn, String vhost) throws IOException {
+        public Listener(Socket conn, String vhost, Mailbox root, Mailbox inbox) throws IOException {
             this.vhost = vhost;
             this.conn = conn;
             this.selected = null;
+            this.root = root;
+            this.inbox = inbox;
             this.pw = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
             this.is = conn.getInputStream();
             this.r = new PushbackReader(new InputStreamReader(is));
         }
 
-        // ugly: not concurrent
-        String tag;
-        private void ok(String s) { pw.println(tag + " OK " + s); }
         private void star(String s) { pw.println("* " + s); }
 
         // FIXME should throw a No if mailbox not found and create==false
@@ -65,49 +69,35 @@ public class IMAP extends MessageProtocol {
             Mailbox m = root;
             while(name.length() > 0) {
                 int end = name.length();
-                if (name.indexOf('.') != -1) end = name.indexOf('.');
+                if (name.indexOf(imapSeparator) != -1) end = name.indexOf(imapSeparator);
                 m = m.slash(name.substring(0, end), create);
                 name = name.substring(end);
             }
             return m;
         }
 
-        public void lsub(Mailbox m, String s) { star("LIST () \".\" INBOX"); ok("LSUB completed"); } // FIXME
-        public void list(Mailbox m, String s) { star("LIST () \".\" INBOX"); ok("LIST completed"); } // FIXME
-
         private boolean auth(String user, String pass) { /* FEATURE */ return user.equals("megacz") && pass.equals(""); }
-        public void copy(final Query q, final Mailbox target) {
-            for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) { target.add(it.cur()); } }
-        public void login(String user, String password) {if (!auth(user,password))throw new Exn.No("Liar, liar, pants on fire."); }
-        public void capability() { star("CAPABILITY IMAP4rev1"); ok("Completed"); }
-        public void noop() { ok("Completed"); }
-        public void logout() { star("BYE LOGOUT received"); ok("Completed"); }
-
-        // FIXME
-        public void delete(Mailbox m) { /*if (!m.getName().toLowerCase().equals("inbox")) m.destroy(); ok("Completed");*/ }
-
-        public void subscribe(String[] args) { ok("SUBSCRIBE ignored"); }
-        public void unsubscribe(String[] args) { ok("UNSUBSCRIBE ignored"); }
-        public void check() { ok("CHECK ignored"); }
 
-        // FIXME
-        public void create(String mailbox){/*if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);*/}
+        public void lsub(Mailbox m, String s) { star("LIST () \".\" INBOX"); } // FEATURE
+        public void list(Mailbox m, String s) { star("LIST () \".\" INBOX"); } // FEATURE
+        public void copy(final Query q, Mailbox to) { for(Mailbox.Iterator it=selected.iterator(q);it.next();) to.add(it.cur()); }
+        public void login(String user, String pass) { if (!auth(user, pass)) throw new Exn.No("Liar, liar, pants on fire."); }
+        public void capability() { star("CAPABILITY IMAP4rev1"); }
+        public void logout() { star("BYE LOGOUT received"); }
+        public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(); }
+        public void create(String mailbox){if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);}
+        public void close(boolean examineOnly) { expunge(false, true); selected = null; }
+        public void check() { }
 
-        // FIXME
         public void rename(Mailbox from, String to) {
-            /*
-            if (from.getName().equalsIgnoreCase("inbox")) from.moveAllMessagesTo(getMailbox(to, true));
-            else if (to.equalsIgnoreCase("inbox"))      { from.moveAllMessagesTo(getMailbox(to, true)); from.destroy(); }
-            else*/ from.rename(to);
+            if (from.equals(inbox))                from.copy(Query.all(), getMailbox(to, true));
+            else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), getMailbox(to, true)); from.destroy(); }
+            else from.rename(to);
         }
 
         public void status(final Mailbox m, Token[] attrs) {
             int count0 = 0, count1 = 0, count2 = 0;
-            for(Mailbox.Iterator it = m.iterator(); it.next(); ) {
-                if (!it.seen()) count0++;
-                if (it.recent()) count1++;
-                count2++;
-            }
+            for(Mailbox.Iterator it = m.iterator(); it.next(); ) { if (!it.seen()) count0++; if (it.recent()) count1++; count2++; }
             String response = "";
             for(int i=0; i<attrs.length; i++) {
                 String s = attrs[i].atom();
@@ -117,27 +107,17 @@ public class IMAP extends MessageProtocol {
                 if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + m.uidValidity();
                 if (s.equals("UNSEEN"))      response += "UIDNEXT "     + m.uidNext();
             }
-            star("STATUS " + m.getName() + " (" + response + ")");
+            star("STATUS " + /* FIXME m.getName() +*/ " (" + response + ")");
         }
 
         public void select(String mailbox, boolean examineOnly) {
             selected = getMailbox(mailbox, false);
             star(selected.count(Query.all()) + " EXISTS");
-            int recent = 0;
-            for(Mailbox.Iterator it = selected.iterator(); it.next(); ) { if (it.recent()) recent++; }
-            star(recent + " RECENT");
+            star(selected.count(Query.recent()) + " RECENT");
             //star("OK [UNSEEN 12] Message 12 is first unseen");    FEATURE
             star("OK [UIDVALIDITY " + selected.uidValidity() + "] UIDs valid");
             star("FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)");
             // FEATURE: READ-WRITE / READ-ONLY
-            ok((examineOnly ? "EXAMINE" : "SELECT") + " completed");
-        }
-
-        public void close(boolean examineOnly) {
-            if (selected == null) throw new Exn.Bad("no mailbox selected");
-            expunge(false, true);
-            selected = null;
-            ok("CLOSE completed");
         }
 
         public void expunge(final boolean examineOnly, final boolean silent) {
@@ -147,7 +127,6 @@ public class IMAP extends MessageProtocol {
                 if (!silent) star(it.uid() + " EXPUNGE");
                 if (!examineOnly) it.delete();
             }
-            if (!silent) ok("EXPUNGE completed");
         }
 
         public void append(Mailbox m, Token t) {
@@ -156,13 +135,8 @@ public class IMAP extends MessageProtocol {
             if (t.type == t.LIST)   { flags = t.l();          t = token(); }
             if (t.type == t.QUOTED) { arrival = t.datetime(); t = token(); }
             String literal = q();
-            try {
-                Message message = new Message(null, null, new LineReader(new StringReader(literal)));
-                if (flags != null) { /* FEATURE */ }
-                selected.add(message);
-            } catch (IOException e) {
-                throw new MailException.IOException(e);
-            }
+            selected.add(new Message(null, null, new LineReader(new StringReader(literal))));
+            if (flags != null) { /* FEATURE */ }
         }
 
         public void fetch(Query q, Token t) {
@@ -173,7 +147,6 @@ public class IMAP extends MessageProtocol {
             else if (t.atom().equals("FULL")) full = true;
             else if (t.atom().equals("ALL")) all = true;
             else if (t.atom().equals("FAST")) fast = true;
-            // FEATURE: range requests
             StringBuffer reply = new StringBuffer();
             for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
                 Message m = it.cur();
@@ -184,10 +157,10 @@ public class IMAP extends MessageProtocol {
                     if (s.indexOf('<') != -1) {
                         String range = s.substring(s.indexOf('<') + 1, s.indexOf('>'));
                         s = s.substring(0, s.indexOf('<'));
-                        if (range.indexOf('.') == -1) end = Integer.MAX_VALUE;
+                        if (range.indexOf(imapSeparator) == -1) end = Integer.MAX_VALUE;
                         else {
-                            end = Integer.parseInt(range.substring(range.indexOf('.') + 1));
-                            range = range.substring(0, range.indexOf('.'));
+                            end = Integer.parseInt(range.substring(range.indexOf(imapSeparator) + 1));
+                            range = range.substring(0, range.indexOf(imapSeparator));
                         }
                         start = Integer.parseInt(range);
                     }
@@ -208,10 +181,12 @@ public class IMAP extends MessageProtocol {
                                      m.rfc822size()+" "+ m.lines +")");
                 }
                 star(it.num() + " FETCH (" + reply.toString() + ")");
+                // FEATURE: range requests
                 // FEATURE set seen flag if not BODY.PEEK
             }
         }
 
+        // FEATURE: hoist the parsing here
         public void store(Query q, String what, Token[] flags) {
             for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
                 Message m = it.cur();
@@ -234,33 +209,43 @@ public class IMAP extends MessageProtocol {
             LineReader lr = new LineReader(r);
             pw.println("* OK " + vhost + " " + IMAP.class.getName() + " IMAP4 v0.1 server ready");
             while(true) {
-                boolean uid = false;
-                String s = lr.readLine();
-                if (s.indexOf(' ') == -1) { pw.println("* BAD Invalid tag"); continue; }
-                tag = atom();
-                String command = atom();
-                if (command.equals("UID"))             { uid = true; command = atom(); }
-                if (command.equals("AUTHENTICATE"))    { login(astring(), astring()); }
-                else if (command.equals("LIST"))         list(mailbox(), mailboxPattern()); 
-                else if (command.equals("LSUB"))         lsub(mailbox(), mailboxPattern()); 
-                else if (command.equals("CAPABILITY")) { capability(); }
-                else if (command.equals("LOGIN"))        login(astring(), astring());
-                else if (command.equals("LOGOUT"))     { logout(); conn.close(); return false; }
-                else if (command.equals("RENAME"))       rename(mailbox(), atom());
-                else if (command.equals("APPEND"))       append(mailbox(), token()); 
-                else if (command.equals("EXAMINE"))      select(astring(), true);
-                else if (command.equals("SELECT"))       select(astring(), false);
-                else if (command.equals("COPY"))         copy(Query.set(set()), mailbox());
-                else if (command.equals("DELETE"))       delete(mailbox());
-                else if (command.equals("CHECK"))        check();
-                else if (command.equals("CREATE"))       create(astring());
-                else if (command.equals("STORE"))        store(Query.set(set()), atom(), l());
-                else if (command.equals("FETCH"))        fetch(Query.set(set()), token());
-                else if (command.equals("STATUS"))       status(mailbox(), l());
-                else                                     throw new Exn.Bad("unrecognized command \"" + command + "\"");
+                String tag = null;
+                try {
+                    boolean uid = false;
+                    String s = lr.readLine();
+                    if (s.indexOf(' ') == -1) throw new Exn.Bad("BAD Invalid tag");
+                    tag = atom();
+                    String command = atom();
+                    if (command.equals("UID"))             { uid = true; command = atom(); }
+                    if (command.equals("AUTHENTICATE"))    { login(astring(), astring()); }
+                    else if (command.equals("LIST"))         list(mailbox(), mailboxPattern()); 
+                    else if (command.equals("LSUB"))         lsub(mailbox(), mailboxPattern()); 
+                    else if (command.equals("CAPABILITY")) { capability(); }
+                    else if (command.equals("LOGIN"))        login(astring(), astring());
+                    else if (command.equals("LOGOUT"))     { logout(); conn.close(); return false; }
+                    else if (command.equals("RENAME"))       rename(mailbox(), atom());
+                    else if (command.equals("APPEND"))       append(mailbox(), token()); 
+                    else if (command.equals("EXAMINE"))      select(astring(), true);
+                    else if (command.equals("SELECT"))       select(astring(), false);
+                    else if (command.equals("COPY"))         copy(Query.num(set()), mailbox());
+                    else if (command.equals("DELETE"))       delete(mailbox());
+                    else if (command.equals("CHECK"))        check();
+                    else if (command.equals("CREATE"))       create(astring());
+                    else if (command.equals("STORE"))        store(Query.num(set()), atom(), l());
+                    else if (command.equals("FETCH"))        fetch(Query.num(set()), token());
+                    else if (command.equals("STATUS"))       status(mailbox(), l());
+                    else                                     throw new Exn.Bad("unrecognized command \"" + command + "\"");
+                    pw.println(tag + " OK " + command + " Completed.");
+                } catch (Exn.Bad b) { pw.println(tag + " Bad " + b.toString());
+                } catch (Exn.No n) {  pw.println(tag + " OK " + n.toString());
+                }
+                pw.flush();
             }
         }
 
+        
+        // Unparsing (printing) logic //////////////////////////////////////////////////////////////////////////////
+
         static String quotify(String s){return s==null?"NIL":"\""+s.replaceAll("\\\\","\\\\").replaceAll("\"","\\\\\"")+"\"";}
         static String quotify(Date d) { return new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss +zzzz").format(d); }
         static String address(Address a) {return"("+quotify(a.description)+" NIL "+quotify(a.user)+" "+quotify(a.host)+")"; }
@@ -298,6 +283,10 @@ public class IMAP extends MessageProtocol {
                 ")";
         }
 
+
+
+        // Parsing Logic //////////////////////////////////////////////////////////////////////////////
+
         Query query() {
             String s = null;
             boolean not = false;
@@ -305,7 +294,7 @@ public class IMAP extends MessageProtocol {
             while(true) {
                 Token t = token();
                 if (t.type == t.LIST) throw new Exn.No("nested queries not yet supported");
-                else if (t.type == t.SET) return Query.set(t.set());
+                else if (t.type == t.SET) return Query.num(t.set());
                 s = t.atom();
                 if (s.equals("NOT")) { not = true; continue; }
                 if (s.equals("OR"))    return Query.or(query(), query());
@@ -313,14 +302,14 @@ public class IMAP extends MessageProtocol {
                 break;
             }
             if (s.startsWith("UN"))        { not = true; s = s.substring(2); }
-            if (s.equals("ANSWERED"))        q = Query.flags(Flag.ANSWERED);
-            else if (s.equals("DELETED"))    q = Query.flags(Flag.DELETED);
-            else if (s.equals("DRAFT"))      q = Query.flags(Flag.DRAFT);
-            else if (s.equals("FLAGGED"))    q = Query.flags(Flag.FLAGGED);
-            else if (s.equals("RECENT"))     q = Query.flags(Flag.RECENT);
-            else if (s.equals("SEEN"))       q = Query.flags(Flag.SEEN);
-            else if (s.equals("OLD"))      { not = true; q = Query.flags(Flag.RECENT); }
-            else if (s.equals("NEW"))        q = Query.and(Query.flags(Flag.RECENT), Query.not(Query.flags(Flag.SEEN)));
+            if (s.equals("ANSWERED"))        q = Query.answered();
+            else if (s.equals("DELETED"))    q = Query.deleted();
+            else if (s.equals("DRAFT"))      q = Query.draft();
+            else if (s.equals("FLAGGED"))    q = Query.flagged();
+            else if (s.equals("RECENT"))     q = Query.recent();
+            else if (s.equals("SEEN"))       q = Query.seen();
+            else if (s.equals("OLD"))      { not = true; q = Query.recent(); }
+            else if (s.equals("NEW"))        q = Query.and(Query.recent(), Query.not(Query.seen()));
             else if (s.equals("KEYWORD"))    q = Query.header("keyword", flag());
             else if (s.equals("HEADER"))     q = Query.header(astring(), astring());
             else if (s.equals("BCC"))        q = Query.header("bcc", astring());
@@ -334,12 +323,11 @@ public class IMAP extends MessageProtocol {
             else if (s.equals("TEXT"))       q = Query.full(astring());
             else if (s.equals("BEFORE"))     q = Query.arrival(new Date(0), date());
             else if (s.equals("SINCE"))      q = Query.arrival(date(), new Date(Long.MAX_VALUE));
-            else if (s.equals("ON"))         q = null; // FIXME
+            else if (s.equals("ON"))       { Date d = date(); q = Query.arrival(d, new Date(d.getTime() + 24 * 60 * 60)); }
             else if (s.equals("SENTBEFORE")) q = Query.sent(new Date(0), date());
             else if (s.equals("SENTSINCE"))  q = Query.sent(date(), new Date(Long.MAX_VALUE));
-            else if (s.equals("SENTON"))     q = null; // FIXME
-            //else if (s.equals("UID"))        q = Query.uid(set());
-            // FIXME
+            else if (s.equals("SENTON"))   { Date d = date(); q = Query.sent(d, new Date(d.getTime() + 24 * 60 * 60)); }
+            else if (s.equals("UID"))        q = Query.uid(set());
             return q;
         }
 
@@ -360,11 +348,6 @@ public class IMAP extends MessageProtocol {
             public Token(Token[] list) { l = list; s = null; type = LIST; n = 0; }
             public Token(int number) { n = number; l = null; s = null; type = NUMBER; }
 
-            // assumes token is a flag list or a flag
-            public void setFlags(Message m) { }
-            public void addFlags(Message m) { }
-            public void deleteFlags(Message m) { }
-
             public String mailboxPattern() throws Exn.Bad {
                 if (type == ATOM) return s;
                 if (type == QUOTED) return s;
@@ -514,6 +497,7 @@ public class IMAP extends MessageProtocol {
             }
         }
 
+        // FEATURE: inline these?
         public String mailboxPattern() throws Exn.Bad { return token().mailboxPattern(); }
         public String flag() throws Exn.Bad { return token().flag(); }
         public int n() throws Exn.Bad { return token().n(); }
index 0f3adcb..fe37582 100644 (file)
@@ -5,7 +5,7 @@ import java.io.*;
 
 public class Incoming {
     protected void accept(Message m) throws IOException, MailException {
-        Mailbox.transcript.add(m);     // currently, we write all inbound messages to the transcript
+        Transcript.transcript.accept(m);     // currently, we write all inbound messages to the transcript
         Target.root.accept(m);
     }
 }
index 2350dfd..8261247 100644 (file)
@@ -11,6 +11,9 @@ import javax.naming.directory.*;
 
 public class SMTP extends MessageProtocol {
 
+    // FIXME
+    private static final Mailbox outgoing = null;
+
     static { new Thread() { public void run() { Outgoing.runq(); } }.start(); }
     public static String convdir = null;
     public static void main(String[] s) throws Exception {
@@ -48,7 +51,7 @@ public class SMTP extends MessageProtocol {
                 return;
             }
             synchronized(Outgoing.class) {
-                Mailbox.root.slash("outgoing").add(m);
+                outgoing.add(m);
                 queue.append(m);
                 Outgoing.class.notify();
             }
@@ -96,7 +99,8 @@ public class SMTP extends MessageProtocol {
                 w.print(".\r\n");
                 check(r.readLine());
                 Log.info(SMTP.Outgoing.class, "message accepted by " + mx);
-                Mailbox.root.slash("outgoing").delete(m);
+                // FIXME!
+                //outgoing.delete(m);
                 s.close();
                 return true;
             } catch (Exception e) {
@@ -111,7 +115,6 @@ public class SMTP extends MessageProtocol {
         static void runq() {
             try {
                 Log.setThreadAnnotation("[outgoing smtp] ");
-                Mailbox outgoing = Mailbox.root.slash("outgoing");
                 Log.info(SMTP.Outgoing.class, "outgoing thread started; " + outgoing.count(Query.all()) + " messages to send");
                 for(Mailbox.Iterator it = outgoing.iterator(); it.cur() != null; it.next()) queue.append(it.cur());
                 while(true) {
index 7e3f3a0..fb5e9aa 100644 (file)
@@ -7,108 +7,141 @@ import java.net.*;
 import java.util.*;
 import java.text.*;
 
-// FIXME: uids and messagenums are all messed up
+/** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
+public class FileBasedMailbox extends Mailbox.Default {
 
-public class FileBasedMailbox extends Mailbox {
+    private static final char slash = File.separatorChar;
+    private static final Hashtable instances = new Hashtable();
+    public static FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
+        FileBasedMailbox ret = (FileBasedMailbox)instances.get(path);
+        if (ret != null) return ret;
+        File f = new File(path);
+        if (!create && !f.exists()) return null;
+        instances.put(path, ret = new FileBasedMailbox(path));
+        return ret;
+    }
 
-    private String path;
-    public FileBasedMailbox(String path) throws MailException { new File(this.path = path).mkdirs(); }
-    public Mailbox slash(String name, boolean create) throws MailException { return new FileBasedMailbox(path + "/" + name); }
 
-    // FIXME
-    public String getName() { return "FOO"; }
-    
-    protected synchronized void flags(Message m, int newFlags) throws MailException {
-        try {
-            File f = new File(messageToFile(m).getCanonicalPath() + ".flags");
-            PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(f)));
-            if ((newFlags & Flag.DELETED) != 0)  pw.print("\\Deleted ");
-            if ((newFlags & Flag.SEEN) != 0)     pw.print("\\Seen ");
-            if ((newFlags & Flag.FLAGGED) != 0)  pw.print("\\Flagged ");
-            if ((newFlags & Flag.DRAFT) != 0)    pw.print("\\Draft ");
-            if ((newFlags & Flag.ANSWERED) != 0) pw.print("\\Answered ");
-            if ((newFlags & Flag.RECENT) != 0)   pw.print("\\Recent ");
-            pw.close();
-        } catch (IOException e) { throw new MailException.IOException(e); }
+    public static final FilenameFilter filter = new FilenameFilter() {
+            public boolean accept(File f, String s) {
+                return s.indexOf('.') != -1;
+            } };
+
+    // Instance //////////////////////////////////////////////////////////////////////////////
+
+    private String path;
+    private File uidNext;
+    private FileBasedMailbox(String path) throws MailException {
+        new File(this.path = path).mkdirs();
+        uidNext(false);
     }
 
-    protected synchronized int flags(Message m) {
+    public Mailbox          slash(String name, boolean create) {
+        return FileBasedMailbox.getFileBasedMailbox(path + slash + name, create); }
+    public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
+    public int              uidValidity() { return (int)(new File(path).lastModified() & 0xffffffL); }
+
+    public int uidNext() { return uidNext(false); }
+    public int uidNext(boolean inc) {
         try {
-            File f = new File(messageToFile(m).getCanonicalPath() + ".flags");
-            if (!f.exists()) return 0;
-            String s = new BufferedReader(new InputStreamReader(new FileInputStream(f))).readLine();
-            StringTokenizer st = new StringTokenizer(s);
-            int ret = 0;
-            while(st.hasMoreTokens()) {
-                String s2 = st.nextToken();
-                if (s2.equals("\\Deleted")) ret |= Flag.DELETED;
-                if (s2.equals("\\Seen")) ret |= Flag.SEEN;
-                if (s2.equals("\\Flagged")) ret |= Flag.FLAGGED;
-                if (s2.equals("\\Draft")) ret |= Flag.DRAFT;
-                if (s2.equals("\\Answered")) ret |= Flag.ANSWERED;
-                if (s2.equals("\\Recent")) ret |= Flag.RECENT;
+            uidNext = new File(path + slash + "UIDNEXT");
+            if (!uidNext.exists()) {
+                File tmp = new File(uidNext + "-");
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
+                pw.println("1");
+                pw.flush();
+                pw.close();
+                tmp.renameTo(uidNext);
+                return 1;
+            }
+            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(uidNext)));
+            int ret = Integer.parseInt(br.readLine());
+            if (inc) {
+                File tmp = new File(uidNext + "-");
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(tmp)));
+                pw.println(ret+1);
+                pw.flush();
+                pw.close();
+                tmp.renameTo(uidNext);
             }
             return ret;
         } catch (IOException e) { throw new MailException.IOException(e); }
-    }
-
-    private int uidNext = 0;
-    public int uidNext() { return uidNext; }
-    public int uidValidity() { return 1; }
-    public int uid(Message m) { Integer i = (Integer)uidMap.get(m); if (i == null) return -1; return i.intValue(); }
-    public int num(Message m) { Integer i = (Integer)numMap.get(m); if (i == null) return -1; return i.intValue(); }
-    private File messageToFile(Message message) { return new File(path + File.separatorChar +  num(message) + "."); }
+    }        
 
-    public int delete(Message message) throws MailException {
-        messageToFile(message).delete();
-        uidMap.remove(message);
-        numMap.remove(message);
-        return -1;
+    public synchronized int add(Message message) {
+        try {
+            int num = new File(path).list(filter).length;
+            File target = new File(path + slash + uidNext(true) + ".");
+            File f = new File(target.getCanonicalPath() + "-");
+            FileOutputStream fo = new FileOutputStream(f);
+            message.dump(fo);
+            fo.close();
+            f.renameTo(target);
+            return num;
+        } catch (IOException e) { throw new MailException.IOException(e); }
     }
 
-    public Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
-    private class Iterator extends Mailbox.Iterator {
+    private class Iterator implements Mailbox.Default.Iterator {
+        int cur = -1;
         private String[] names;
-        public Iterator() { names = new File(path).list(); }
-        public Message cur() { return null; }
-        public boolean next() { return false; }
-        protected  int  flags() { return 0; }
-        protected  void flags(int newFlags) { }
-        public  int  uid() { return -1; }
-        public  int  num() { return -1; }
-        public  void delete() { }
+        private boolean seen = false, deleted = false, draft = false, flagged = false, answered = false, recent = false;
+        public Iterator() { names = new File(path).list(filter); }
 
-        /*
+        public Message cur() {
             try {
-                //= Integer.parseInt(names[i].substring(0, names[i].length() - 1));
-                // FIXME
+                File file = new File(path + File.separatorChar + names[cur]);
+                return new Message(/* FIXME */ null, /* FIXME */ new Address[] { },
+                                   new LineReader(new InputStreamReader(new FileInputStream(file))));
+            } catch (IOException e) { throw new MailException.IOException(e); }
+        }
+        public boolean next() {
+            cur++;
+            if (cur >= names.length) return false;
+            String name = names[cur].substring(names[cur].indexOf('.') + 1);
+            seen     = name.indexOf('s') != -1;
+            deleted  = name.indexOf('x') != -1;
+            flagged  = name.indexOf('f') != -1;
+            draft    = name.indexOf('d') != -1;
+            answered = name.indexOf('a') != -1;
+            recent   = name.indexOf('r') != -1;
+            return true;
+        }
+        public int num() { return cur; }
+        public int uid() {
+            try { return Integer.parseInt(names[cur].substring(0, names[cur].indexOf('.')));
             } catch (NumberFormatException nfe) {
-                Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[i].substring(0, names[i].length() - 1));
-                j--;
-                int[] newret = new int[ret.length - 1];
-                System.arraycopy(ret, 0, newret, 0, newret.length);
-                ret = newret;
-            }
+                Log.warn(FileBasedMailbox.class, "NumberFormatException: " + names[cur].substring(0, names[cur].length() - 1));
+                return -1; } }
+
+        private void fixflags() {
+            String newName =
+                names[cur].substring(0, names[cur].indexOf('.') + 1) +
+                (seen ? "s" : "") +
+                (deleted ? "x" : "") +
+                (flagged ? "f" : "") +
+                (draft ? "d" : "") +
+                (recent ? "r" : "") +
+                (answered ? "a" : "");
+            new File(names[cur]).renameTo(new File(path + File.separatorChar + newName));
+            names[cur] = newName;
         }
-        */
-    }
 
-    private Hashtable uidMap = new Hashtable();
-    private Hashtable numMap = new Hashtable();
-    private int numNext = 0;
+        public void    delete() { new File(names[cur]).delete(); }
+        public void    set(String key, String val) { throw new MailException("not supported"); }
+        public String  get(String key) { throw new MailException("not supported"); }
 
-    public synchronized int add(Message message) throws MailException {
-        try {
-            File target = messageToFile(message);
-            File f = new File(target.getCanonicalPath() + ".-");
-            FileOutputStream fo = new FileOutputStream(f);
-            message.dump(fo);
-            fo.close();
-            f.renameTo(target);
-            uidMap.put(new Integer(uidNext++), message);
-            numMap.put(new Integer(numNext++), message);
-            return numNext - 1;
-        } catch (IOException e) { throw new MailException.IOException(e); }
-    }
+        public boolean seen() { return seen; }
+        public boolean deleted() { return deleted; }
+        public boolean flagged() { return flagged; }
+        public boolean draft() { return draft; }
+        public boolean answered() { return answered; }
+        public boolean recent() { return recent; }
+        public void    seen(boolean on) { seen = on; fixflags(); }
+        public void    deleted(boolean on) { deleted = on; fixflags(); }
+        public void    flagged(boolean on) { flagged = on; fixflags(); }
+        public void    draft(boolean on) { draft = on; fixflags(); }
+        public void    answered(boolean on) { answered = on; fixflags(); }
+        public void    recent(boolean on) { recent = on; fixflags(); }
 
+    }
 }
index 86bd83f..5e3e67f 100644 (file)
@@ -9,90 +9,98 @@ import java.text.*;
 
 public abstract class Mailbox extends Target {
 
-    private static final String STORAGE_ROOT =
+    public static final String STORAGE_ROOT =
         System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
-    public static FileBasedMailbox root = null;
-    public static Transcript transcript = null;
-    static {
-        try {
-            root = new FileBasedMailbox(STORAGE_ROOT + File.separatorChar);
-            transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
-        } catch (Exception e) {
-            e.printStackTrace();
+
+
+    // Required Methods //////////////////////////////////////////////////////////////////////////////
+
+    public abstract Mailbox.Iterator iterator(Query q);
+    public abstract int              add(Message message);
+    public abstract void             move(Query q, Mailbox dest);
+    public abstract void             copy(Query q, Mailbox dest);
+    public abstract int              count(Query q);
+    public abstract int              uidNext();
+    public abstract int              uidValidity();
+    public abstract void             rename(String newName);
+    public abstract void             destroy();
+    public abstract Mailbox          slash(String name, boolean create);
+
+
+    // Thunks ////////////////////////////////////////////////////////////////////////////
+
+    public final    void             accept(Message m) { add(m); }
+    public          Mailbox.Iterator iterator() { return iterator(Query.all()); }
+
+
+    // Default Implementation //////////////////////////////////////////////////////////////////////////////
+
+    /** default, inefficient implementation of Mailbox; only requires a few methods to be implemented */
+    public static abstract class Default extends Mailbox {
+        public Mailbox.Iterator iterator(Query q) { return new Mailbox.Iterator.QueryIterator(q, this); }
+        public void copy(Query q, Mailbox dest) { for(Mailbox.Iterator it = iterator(q); it.next();) dest.add(it.cur()); }
+        public int count(Query q) { int count = 0; for(Mailbox.Iterator it = iterator(q); it.next();) count++; return count; }
+        public void rename(String newName) { throw new MailException("not supported"); }
+        public void  destroy() { throw new MailException("not supported"); }
+        public Mailbox slash(String name, boolean create) { throw new MailException("not supported"); }
+        public void move(Query q, Mailbox dest) {
+            for(Mailbox.Iterator it = iterator(q);it.next();) { dest.add(it.cur()); it.delete(); }
         }
     }
 
-    // iterator
-    public abstract Mailbox.Iterator iterator();   // only abstract method
-    public          Mailbox.Iterator iterator(Query q)    throws MailException { return new QueryIterator(q); }
-    public int  add(Message message)                      throws MailException { throw new MailException("not implemented"); }
-    public int  delete(Message message)                   throws MailException { throw new MailException("not implemented"); }
-    public void move(Query q, Mailbox dest, boolean copy) throws MailException { throw new MailException("not implemented"); }
-    public int  count(Query q) throws MailException {
-        int i=0;
-        for(Iterator it = iterator(); it.next(); ) if (q.match(it)) i++; 
-        return i;
-    }
 
-    // submailboxes
-    public void    rename(String newName) throws MailException { throw new MailException("you cannot rename this mailbox"); }
-    public void    destroy() throws MailException { throw new MailException("you cannot destroy this mailbox"); }
-    public Mailbox slash(String name, boolean create) throws MailException { throw new MailException("no submailboxes"); }
-    public Mailbox slash(String name) throws MailException { return slash(name, false); }
-
-    public abstract int  uidNext();
-    public abstract int  uidValidity();
-
-    // FIXME
-    public abstract String getName(); 
-
-    private class QueryIterator extends Iterator {
-        Query q;
-        Mailbox.Iterator i;
-        public    QueryIterator(Query q)       { this.q = q; this.i = Mailbox.this.iterator(); }
-        public    Message  cur()               { return i.cur(); }
-        public    boolean  next()              { do { if (!i.next()) return false; } while(!q.match(i)); return true; }
-        protected int      flags()             { return i.flags(); }
-        protected void     flags(int newFlags) { i.flags(newFlags); }
-        public    int      uid()               { return i.uid(); }
-        public    int      num()               { return i.num(); }
-        public    void     set(String key, String val) { i.set(key, val); }
-        public    String   get(String key)           { return i.get(key); }
-        public    void     delete() { i.delete(); }
-    }
+    // Iterator Definition //////////////////////////////////////////////////////////////////////////////
 
-    public static abstract class Iterator {
+    public static interface Iterator {
         public abstract Message cur();
         public abstract boolean next();
+        public abstract int     uid();
+        public abstract int     num();
+        public abstract void    delete();
+        public abstract void    set(String key, String val);
+        public abstract String  get(String key);
+        public abstract boolean seen();
+        public abstract boolean deleted();
+        public abstract boolean flagged();
+        public abstract boolean draft();
+        public abstract boolean answered();
+        public abstract boolean recent();
+        public abstract void    seen(boolean on);
+        public abstract void    deleted(boolean on);
+        public abstract void    flagged(boolean on);
+        public abstract void    draft(boolean on);
+        public abstract void    answered(boolean on);
+        public abstract void    recent(boolean on);
 
-        // minimal implementation
-        protected abstract int  flags();
-        protected abstract void flags(int newFlags);
-        public abstract int  uid();
-        public abstract int  num();
-        public abstract void delete();
-
-        public void set(String key, String val)            { throw new MailException.MetadataNotSupported(""); }
-        public String get(String key)                      { throw new MailException.MetadataNotSupported(""); }
-
-        public final boolean getFlag(int flag)             { return ((flags() & flag) == flag); }
-        public final void    setFlag(int flag)             { flags(flags() | flag); }
-        public final void    clearFlag(int flag)           { flags(flags() & ~flag); }
-
-        public final boolean seen()                        { return getFlag(Flag.SEEN); }
-        public final boolean deleted()                     { return getFlag(Flag.DELETED); }
-        public final boolean flagged()                     { return getFlag(Flag.FLAGGED); }
-        public final boolean draft()                       { return getFlag(Flag.DRAFT); }
-        public final boolean answered()                    { return getFlag(Flag.ANSWERED); }
-        public final boolean recent()                      { return getFlag(Flag.RECENT); }
-
-        public final void seen(boolean on)                 { setFlag(Flag.SEEN); }
-        public final void deleted(boolean on)              { setFlag(Flag.DELETED); }
-        public final void flagged(boolean on)              { setFlag(Flag.FLAGGED); }
-        public final void draft(boolean on)                { setFlag(Flag.DRAFT); }
-        public final void answered(boolean on)             { setFlag(Flag.ANSWERED); }
-        public final void recent(boolean on)               { setFlag(Flag.RECENT); }
-    }
+        public static class Wrapper implements Iterator {
+            private Iterator it;
+            public Wrapper(Iterator it) { this.it = it; }
+            public Message cur() { return it.cur(); }
+            public boolean next() { return it.next(); }
+            public int     uid() { return it.uid(); }
+            public int     num() { return it.num(); }
+            public void    set(String key, String val) { it.set(key, val); }
+            public String  get(String key) { return it.get(key); }
+            public void    delete() { it.delete(); }
+            public boolean seen() { return it.seen(); }
+            public boolean deleted() { return it.deleted(); }
+            public boolean flagged() { return it.flagged(); }
+            public boolean draft() { return it.draft(); }
+            public boolean answered() { return it.answered(); }
+            public boolean recent() { return it.recent(); }
+            public void    seen(boolean on) { it.seen(on); }
+            public void    deleted(boolean on) { it.deleted(on); }
+            public void    flagged(boolean on) { it.flagged(on); }
+            public void    draft(boolean on) { it.draft(on); }
+            public void    answered(boolean on) { it.answered(on); }
+            public void    recent(boolean on) { it.recent(on); }
+        }
 
-    public final void    accept(Message m) throws MailException { add(m); }
+        class QueryIterator extends Mailbox.Iterator.Wrapper {
+            Query q;
+            public QueryIterator(Query q, Mailbox m) { super(m.iterator()); this.q = q; }
+            public boolean next() { do { if (!super.next()) return false; } while(!q.match(this)); return true; }
+        }
+    }
+    
 }
index 5f77980..fbb485c 100644 (file)
@@ -8,21 +8,16 @@ import java.util.*;
 import java.text.*;
 
 /** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
-public class Transcript extends Mailbox {
+public class Transcript extends Target {
+
+    public static final Transcript transcript = new Transcript(Mailbox.STORAGE_ROOT + File.separatorChar + "transcript");
+
     private String path;
-    public Transcript(String path) throws MailException { new File(this.path = path).mkdirs(); }
+    public Transcript(String path) { new File(this.path = path).mkdirs(); }
     private static String lastTime = null;
     private static int lastCounter = 0;
 
-    // FIXME
-    public String getName() { return "Transcript"; }
-
-    public Mailbox.Iterator iterator() { return null; }
-    public int uidValidity() { return 0; }
-    public int uidNext() { return 0; }
-    
-    /** returns a message identifier */
-    public synchronized int add(Message message) throws MailException {
+    public synchronized void accept(Message message) {
         try {
             File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
             today.mkdirs();
@@ -33,14 +28,15 @@ public class Transcript extends Mailbox {
                     time += "." + (++lastCounter);
                 } else {
                     lastTime = time;
+                    lastCounter = 0;
                 }
             }
                 
             File target = new File(today.getPath() + File.separatorChar + time + ".txt");
             OutputStream os = new FileOutputStream(target);
             message.dump(os);
+            os.flush();
             os.close();
-            return -1;
         } catch (IOException e) { throw new MailException.IOException(e); }
     }
 }