it compiles
authoradam <adam@megacz.com>
Wed, 9 Jun 2004 01:18:52 +0000 (01:18 +0000)
committeradam <adam@megacz.com>
Wed, 9 Jun 2004 01:18:52 +0000 (01:18 +0000)
darcs-hash:20040609011852-5007d-e34e7b14dc9abf2a95f22fab225a74eb34a275e6.gz

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/SMTP.java
src/org/ibex/mail/target/FileBasedMailbox.java [new file with mode: 0644]
src/org/ibex/mail/target/Mailbox.java
src/org/ibex/mail/target/Transcript.java [new file with mode: 0644]

index 4002673..91883da 100644 (file)
@@ -7,6 +7,7 @@ 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 {
index d492ed1..6ad493b 100644 (file)
@@ -24,7 +24,9 @@ import java.io.*;
  */
 public class Message extends JSReflection {
 
-    public static interface Visitor { public abstract void visit(Message m); }
+    // FIXME
+    public Date sent() { return null; }
+    public Date arrived() { return null; }
 
     private static class CaseInsensitiveHash extends Hashtable {
         public Object get(Object o) {
@@ -33,12 +35,13 @@ public class Message extends JSReflection {
         }
         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) return super.put(((String)k).toLowerCase(), v);
-            else return super.put(k, 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)
@@ -126,7 +129,7 @@ public class Message extends JSReflection {
             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.add(key, headers.get(key) + s);
+                headers.put(key, headers.get(key) + s);
                 continue;
             }
             if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s);
@@ -144,7 +147,7 @@ public class Message extends JSReflection {
             } else {
                 // just append it to the previous one; valid for Comments/Keywords
                 if (headers.get(key) != null) val = headers.get(key) + " " + val;
-                headers.add(key, val);
+                headers.put(key, val);
             }            
         }
 
index bc8495c..300397a 100644 (file)
@@ -1,5 +1,6 @@
 package org.ibex.mail;
 import java.util.*;
+import org.ibex.mail.target.*;
 
 /**
  *  [immutable] This class encapsulates a query against a mailbox.
@@ -14,38 +15,41 @@ import java.util.*;
  */
 public class Query {
 
-    public static Query not(Query q)            { return new Query(NOT, new Query[] { q },      0, 0, 0, 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); }
-    public static Query and(Query[] q)          { return new Query(AND, q,                      0, 0, 0, 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); }
-    public static Query or(Query[] q)           { return new Query(OR,  q,                      0, 0, 0, null, null, null, null); }
-    public static Query uid(int min, int max)   { return new Query(UID, null, min, max, 0, null, null, null, null); }
-    public static Query messagenum(int min, int max) { return new Query(MESSAGENUM, null, min, max, 0, null, null, null, null); }
-    public static Query sent(Date earliest, Date latest)    { return new Query(SENT, null,0,0,0,null,null, earliest, latest); }
-    public static Query arrival(Date earliest, Date latest) { return new Query(ARRIVAL, null,0,0,0,null,null, earliest, latest); }
-    public static Query header(String name, String val)     { return new Query(HEADER, null, 0, 0, 0, name, val, null, null); }
-    public static Query size(int min, int max)              { return new Query(SIZE, null, min, max, 0, null, null, null, null); }
-    public static Query flags(int flags)                    { return new Query(FLAGS, null, 0, 0, flags, null, null, null, null); }
-    public static Query body(String text)                   { return new Query(BODY, null, 0, 0, 0, null, text, null, null); }
-    public static Query body(String text)                   { return new Query(FULL, null, 0, 0, 0, null, text, 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(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); }
 
-    private Query(int type, Query[] q, int min, int max, int flags, String key, String text, Date earliest, Date latest) {
+    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;
-        this.earliest = earliest; this.latest = latest; }
+        this.earliest = earliest; this.latest = latest; this.set = set; }
 
-    public static int ALL        = 0;
-    public static int NOT        = 1;
-    public static int AND        = 2;
-    public static int OR         = 3;
-    public static int UID        = 4;
-    public static int MESSAGENUM = 5; 
-    public static int SENT       = 6;
-    public static int ARRIVAL    = 7;
-    public static int HEADER     = 8;
-    public static int SIZE       = 9;
-    public static int FLAGS      = 10;
-    public static int BODY       = 11;
-    public static int FULL       = 12;
+    public static final int ALL        = 0;
+    public static final int NOT        = 1;
+    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 final int type;
     public final Query[] q;
@@ -56,22 +60,26 @@ public class Query {
     public final String text;
     public final Date earliest;
     public final Date latest;
+    public final int[] set;
 
-    public boolean match(Mailbox mbox, Message m) {
+    public boolean match(Mailbox.Iterator it) {
         switch(type) {
             case ALL:        return true;
-            case NOT:        return !q[0].match(mbox, m);
-            case OR:         for(int i=0; i<q.length; i++) if (q[i].match(mbox, m)) return true; return false;
-            case AND:        for(int i=0; i<q.length; i++) if (!q[i].match(mbox, m)) return false; return true;
-            case UID:        return mbox.uid(m)        >= min && mbox.uid <= max;
-            case MESSAGENUM: return mbox.messagenum(m) >= min && mbox.messagenum(m) <= max;
-            case SENT:       return (latest == null || m.sent.before(latest))    &&(earliest == null || m.sent.after(earliest));
-            case ARRIVAL:    return (latest == null || m.arrival.before(latest)) &&(earliest == null || m.arrival.after(earliest));
-            case SIZE:       return m.size >= min && m.size <= max;
-            case FLAGS:      return mbox.getFlags(flags);
-            case HEADER:     return m.headers.get(key) != null && ((String)m.headers.get(key)).indexOf(text) != -1;
-            case BODY:       return m.body.indexOf(text) != -1;
-            case FULL:       return m.body.indexOf(text) != -1 || m.allHeaders.indexOf(text) != -1;
+            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 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;
             default:         throw new Error("this should not happen");
         }
     }
index 0db26a2..2806811 100644 (file)
@@ -76,46 +76,58 @@ public class IMAP extends MessageProtocol {
         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(int[] set, Mailbox target) { for(int i=0; i<set.length; i++) target.add(selected.get(set[i])); }
+        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"); }
-        public void delete(Mailbox m) { if (!m.getName().toLowerCase().equals("inbox")) m.destroy(); 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"); }
-        public void create(String mailbox){if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);}
+
+        // FIXME
+        public void create(String mailbox){/*if(!mailbox.endsWith(".")&&!mailbox.equalsIgnoreCase("inbox"))getMailbox(mailbox,true);*/}
+
+        // 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);
+            else*/ from.rename(to);
         }
 
-        public void status(Mailbox m, Token[] attrs) {
-            int[] list = m.list();
-            int recent = 0, unseen = 0;
-            for(int i=0; i<list.length; i++) { if (!m.seen(i)) unseen++; if (m.recent(i)) recent++; }
+        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++;
+            }
             String response = "";
             for(int i=0; i<attrs.length; i++) {
                 String s = attrs[i].atom();
-                if (s.equals("MESSAGES"))    response += "MESSAGES "    + list.length;
-                if (s.equals("RECENT"))      response += "RECENT "      + recent;
-                if (s.equals("UIDNEXT"))     response += "UNSEEN "      + unseen;
-                if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + m.uidvalidity;
-                if (s.equals("UNSEEN"))      response += "UIDNEXT "     + m.uidnext;
+                if (s.equals("MESSAGES"))    response += "MESSAGES "    + count2;
+                if (s.equals("RECENT"))      response += "RECENT "      + count0;
+                if (s.equals("UIDNEXT"))     response += "UNSEEN "      + count1;
+                if (s.equals("UIDVALIDITY")) response += "UIDVALIDITY " + m.uidValidity();
+                if (s.equals("UNSEEN"))      response += "UIDNEXT "     + m.uidNext();
             }
             star("STATUS " + m.getName() + " (" + response + ")");
         }
 
         public void select(String mailbox, boolean examineOnly) {
             selected = getMailbox(mailbox, false);
-            star(selected.list().length + " EXISTS");
+            star(selected.count(Query.all()) + " EXISTS");
             int recent = 0;
-            int[] list = selected.list(); for(int i=0; i<list.length; i++) if (selected.recent(list[i])) recent++;
+            for(Mailbox.Iterator it = selected.iterator(); it.next(); ) { if (it.recent()) recent++; }
             star(recent + " RECENT");
             //star("OK [UNSEEN 12] Message 12 is first unseen");    FEATURE
-            star("OK [UIDVALIDITY " + selected.uidvalidity + "] UIDs valid");
+            star("OK [UIDVALIDITY " + selected.uidValidity() + "] UIDs valid");
             star("FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)");
             // FEATURE: READ-WRITE / READ-ONLY
             ok((examineOnly ? "EXAMINE" : "SELECT") + " completed");
@@ -128,15 +140,12 @@ public class IMAP extends MessageProtocol {
             ok("CLOSE completed");
         }
 
-        public void expunge(boolean examineOnly, boolean silent) {
+        public void expunge(final boolean examineOnly, final boolean silent) {
             if (selected == null) throw new Exn.Bad("no mailbox selected");
-            int[] messages = selected.list();
-            for(int i=0; i<messages.length; i++) {
-                Message m = selected.get(messages[i]);
-                if (selected.deleted(messages[i])) {
-                    if (!silent) star(selected.uid(m) + " EXPUNGE");
-                    if (!examineOnly) selected.delete(m);
-                }
+            for(Mailbox.Iterator it = selected.iterator(); it.next();) {
+                if (!it.deleted()) return;
+                if (!silent) star(it.uid() + " EXPUNGE");
+                if (!examineOnly) it.delete();
             }
             if (!silent) ok("EXPUNGE completed");
         }
@@ -156,7 +165,7 @@ public class IMAP extends MessageProtocol {
             }
         }
 
-        public void fetch(int[] set, Token t) {
+        public void fetch(Query q, Token t) {
             Token[] tl = null;
             int start = -1, end = -1;
             boolean peek = false, fast = false, all = false, full = false;
@@ -166,8 +175,8 @@ public class IMAP extends MessageProtocol {
             else if (t.atom().equals("FAST")) fast = true;
             // FEATURE: range requests
             StringBuffer reply = new StringBuffer();
-            for(int j=0; j<set.length; j++) {
-                Message m = selected.get(set[j]);
+            for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
+                Message m = it.cur();
                 for (int i=0; i<tl.length; i++) {
                     String s = tl[i].atom();
                     if (s.startsWith("BODY.PEEK"))                 { peek = true; s = "BODY" + s.substring(9); }
@@ -183,14 +192,14 @@ public class IMAP extends MessageProtocol {
                         start = Integer.parseInt(range);
                     }
                     if (s.equals("ENVELOPE") || all || full)          reply.append("ENVELOPE " + envelope(m) + " ");
-                    if (s.equals("FLAGS") || full || all || fast)  reply.append("FLAGS (" + flags(selected, set[j]) + ") ");
+                    if (s.equals("FLAGS") || full || all || fast)  reply.append("FLAGS (" + flags(it) + ") ");
                     if (s.equals("INTERNALDATE") || full || all || fast) reply.append("INTERNALDATE "+quotify(m.arrival)+" ");
                     if (s.equals("RFC822.SIZE") || full || all || fast) reply.append("RFC822.SIZE " + m.rfc822size() + " ");
                     if (s.equals("RFC822.HEADER") || s.equals("BODY[HEADER]"))
                         { reply.append("BODY[HEADER] {" + m.allHeaders.length() + "}\r\n"); reply.append(m.allHeaders); }
                     if (s.equals("RFC822")||s.equals("RFC822.TEXT")||s.equals("BODY")||s.equals("BODY[]")||s.equals("TEXT")||full)
                         { reply.append("BODY[TEXT] {" + m.body.length() + "}\r\n"); reply.append(m.body); }
-                    if (s.equals("UID"))                        reply.append("UID " + selected.uid(set[j]));
+                    if (s.equals("UID"))                        reply.append("UID " + it.uid());
                     if (s.equals("MIME"))                       throw new Exn.No("FETCH BODY.MIME not supported");
                     if (s.startsWith("BODY[HEADER.FIELDS"))     throw new Exn.No("partial headers not supported");
                     if (s.startsWith("BODY[HEADER.FIELDS.NOT")) throw new Exn.No("partial headers not supported");
@@ -198,32 +207,26 @@ public class IMAP extends MessageProtocol {
                         reply.append("(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\") NIL NIL \"7BIT\" " +
                                      m.rfc822size()+" "+ m.lines +")");
                 }
-                star(set[j] + " FETCH (" + reply.toString() + ")");
+                star(it.num() + " FETCH (" + reply.toString() + ")");
                 // FEATURE set seen flag if not BODY.PEEK
             }
         }
 
-        public void store(int[] messages, String what, Token[] flags) {
-            for(int i=0; i<messages.length; i++) {
-                Message m = selected.get(messages[i]);
+        public void store(Query q, String what, Token[] flags) {
+            for(Mailbox.Iterator it = selected.iterator(q); it.next(); ) {
+                Message m = it.cur();
                 if (what.charAt(0) == 'F') {
-                    selected.setDeleted(messages[i], false);
-                    selected.setSeen(messages[i], false);
-                    selected.setFlagged(messages[i], false);
-                    selected.setDraft(messages[i], false);
-                    selected.setAnswered(messages[i], false);
-                    selected.setRecent(messages[i], false);
-                }
+                    it.deleted(false); it.seen(false); it.flagged(false); it.draft(false); it.answered(false); it.recent(false); }
                 for(int j=0; j<flags.length; j++) {
                     String flag = flags[j].flag();
-                    if (flag.equals("Deleted"))  selected.setDeleted(messages[i], what.charAt(0) != '-');
-                    if (flag.equals("Seen"))     selected.setSeen(messages[i], what.charAt(0) != '-');
-                    if (flag.equals("Flagged"))  selected.setFlagged(messages[i], what.charAt(0) != '-');
-                    if (flag.equals("Draft"))    selected.setDraft(messages[i], what.charAt(0) != '-');
-                    if (flag.equals("Answered")) selected.setAnswered(messages[i], what.charAt(0) != '-');
-                    if (flag.equals("Recent"))   selected.setRecent(messages[i],  what.charAt(0) != '-');
+                    if (flag.equals("Deleted"))  it.deleted(what.charAt(0) != '-');
+                    if (flag.equals("Seen"))     it.seen(what.charAt(0) != '-');
+                    if (flag.equals("Flagged"))  it.flagged(what.charAt(0) != '-');
+                    if (flag.equals("Draft"))    it.draft(what.charAt(0) != '-');
+                    if (flag.equals("Answered")) it.answered(what.charAt(0) != '-');
+                    if (flag.equals("Recent"))   it.recent( what.charAt(0) != '-');
                 }
-                selected.add(m);  // re-add
+                selected.add(m);  // re-add (?)
             }
         }
 
@@ -247,12 +250,12 @@ public class IMAP extends MessageProtocol {
                 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(set(), mailbox());
+                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(set(), atom(), l());
-                else if (command.equals("FETCH"))        fetch(set(), token());
+                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 + "\"");
             }
@@ -271,14 +274,14 @@ public class IMAP extends MessageProtocol {
             ret.append(")");
             return ret.toString();
         }
-        static String flags(Mailbox s, int i) {
+        static String flags(Mailbox.Iterator it) {
             return 
-                (s.deleted(i)  ? "\\Deleted "  : "") +
-                (s.seen(i)     ? "\\Seen "     : "") +
-                (s.flagged(i)  ? "\\Flagged "  : "") +
-                (s.draft(i)    ? "\\Draft "    : "") +
-                (s.answered(i) ? "\\Answered " : "") +
-                (s.recent(i)   ? "\\Recent "   : "");
+                (it.deleted()  ? "\\Deleted "  : "") +
+                (it.seen()     ? "\\Seen "     : "") +
+                (it.flagged()  ? "\\Flagged "  : "") +
+                (it.draft()    ? "\\Draft "    : "") +
+                (it.answered() ? "\\Answered " : "") +
+                (it.recent()   ? "\\Recent "   : "");
         }
         static String envelope(Message m) {
             return
@@ -317,7 +320,7 @@ public class IMAP extends MessageProtocol {
             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.Flag.RECENT, Query.not(Query.Flag.SEEN));
+            else if (s.equals("NEW"))        q = Query.and(Query.flags(Flag.RECENT), Query.not(Query.flags(Flag.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());
@@ -325,17 +328,18 @@ public class IMAP extends MessageProtocol {
             else if (s.equals("FROM"))       q = Query.header("from", astring());
             else if (s.equals("TO"))         q = Query.header("to", astring());
             else if (s.equals("SUBJECT"))    q = Query.header("subject", astring());
-            else if (s.equals("LARGER"))     q = Query.size(n(), true);
-            else if (s.equals("SMALLER"))    q = Query.size(n(), false);
-            else if (s.equals("BODY"))       q = Query.body(astring(), true, false);
-            else if (s.equals("TEXT"))       q = Query.fullText(astring(), true, true);
+            else if (s.equals("LARGER"))     q = Query.size(n(), Integer.MAX_VALUE);
+            else if (s.equals("SMALLER"))    q = Query.size(Integer.MIN_VALUE, n());
+            else if (s.equals("BODY"))       q = Query.body(astring());
+            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("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());
+            //else if (s.equals("UID"))        q = Query.uid(set());
+            // FIXME
             return q;
         }
 
index 63563e0..2350dfd 100644 (file)
@@ -111,9 +111,9 @@ public class SMTP extends MessageProtocol {
         static void runq() {
             try {
                 Log.setThreadAnnotation("[outgoing smtp] ");
-                int[] outgoing = Mailbox.root.slash("outgoing").list();
-                Log.info(SMTP.Outgoing.class, "outgoing thread started; " + outgoing.length + " messages to send");
-                for(int i=0; i<outgoing.length; i++) queue.append(Mailbox.root.slash("outgoing").get(outgoing[i]));
+                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) {
                     int num = queue.size();
                     for(int i=0; i<num; i++) {
diff --git a/src/org/ibex/mail/target/FileBasedMailbox.java b/src/org/ibex/mail/target/FileBasedMailbox.java
new file mode 100644 (file)
index 0000000..7e3f3a0
--- /dev/null
@@ -0,0 +1,114 @@
+package org.ibex.mail.target;
+import org.ibex.mail.*;
+import org.ibex.util.*;
+import org.ibex.mail.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.text.*;
+
+// FIXME: uids and messagenums are all messed up
+
+public class FileBasedMailbox extends Mailbox {
+
+    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); }
+    }
+
+    protected synchronized int flags(Message m) {
+        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;
+            }
+            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 Mailbox.Iterator iterator() { return new FileBasedMailbox.Iterator(); }
+    private class Iterator extends Mailbox.Iterator {
+        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() { }
+
+        /*
+            try {
+                //= Integer.parseInt(names[i].substring(0, names[i].length() - 1));
+                // FIXME
+            } 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;
+            }
+        }
+        */
+    }
+
+    private Hashtable uidMap = new Hashtable();
+    private Hashtable numMap = new Hashtable();
+    private int numNext = 0;
+
+    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); }
+    }
+
+}
index 5ff7a8b..86bd83f 100644 (file)
@@ -11,49 +11,87 @@ public abstract class Mailbox extends Target {
 
     private static final String STORAGE_ROOT =
         System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail");
-    public static FileBased root = null;
+    public static FileBasedMailbox root = null;
     public static Transcript transcript = null;
     static {
         try {
-            root = new FileBased(STORAGE_ROOT + File.separatorChar);
+            root = new FileBasedMailbox(STORAGE_ROOT + File.separatorChar);
             transcript = new Transcript(STORAGE_ROOT + File.separatorChar + "transcript");
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
-    // metadata
-    public void   set(Message m, String key, String val) { throw new MailException.MetadataNotSupported(); }
-    public String get(Message m, String key)             { throw new MailException.MetadataNotSupported(); }
-
-    // flags
-    public final boolean getFlag(Message m, int flags)   { return (flags(m) & flag) == flags; }
-    public final void    setFlag(Message m, int flag)   { flags(m, flags | flag); }
-    public final void    clearFlag(Message m, int flag) { flags(m, flags & ~flag); }
-    protected abstract void flags(Message m, int newFlags);  // set the flags for a given message
-    protected abstract int  flags(Message m);                // get the flags for a given message
-    public    abstract int  uid(Message m);                  // get the uid for a given message              (see IMAP RFC)
-    public    abstract int  num(Message m);                  // get the "message number" for a given message (see IMAP RFC)
-    public    abstract int  uidNext();                       // get the next uid to be assigned 
-    public    abstract int  uidValidity();                   // get the uid validity identifier              (see IMAP RFC)
-
-    // messages
-    public abstract void visit(Message.Visitor v) throws MailException;   // only abstract method
+    // 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 { CounterVisitor cv = new CounterVisitor(this,q); visit(cv); return cv.count; }
-    public void query(Query q, final Message.Visitor v) throws MailException {
-        visit(new Message.Visitor() { public void visit(Message m) { if (q.match(Mailbox.this,m)) v.visit(Mailbox.this,m); } }); } 
+    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(); }
+    }
+
+    public static abstract class Iterator {
+        public abstract Message cur();
+        public abstract boolean next();
+
+        // 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); }
 
-    private static class CounterVisitor implements Message.Visitor {
-        public int count = 0;
-        public void visit(Message m) { if (q.match(Mailbox.this, m)) count++; }
+        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 final void    accept(Message m) throws MailException { add(m); }
diff --git a/src/org/ibex/mail/target/Transcript.java b/src/org/ibex/mail/target/Transcript.java
new file mode 100644 (file)
index 0000000..5f77980
--- /dev/null
@@ -0,0 +1,47 @@
+package org.ibex.mail.target;
+import org.ibex.mail.*;
+import org.ibex.util.*;
+import org.ibex.mail.*;
+import java.io.*;
+import java.net.*;
+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 {
+    private String path;
+    public Transcript(String path) throws MailException { 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 {
+        try {
+            File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
+            today.mkdirs();
+                
+            String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
+            synchronized (Transcript.class) {
+                if (lastTime != null && lastTime.equals(time)) {
+                    time += "." + (++lastCounter);
+                } else {
+                    lastTime = time;
+                }
+            }
+                
+            File target = new File(today.getPath() + File.separatorChar + time + ".txt");
+            OutputStream os = new FileOutputStream(target);
+            message.dump(os);
+            os.close();
+            return -1;
+        } catch (IOException e) { throw new MailException.IOException(e); }
+    }
+}
+