maxuid support and clarified IMAP distinction between by-UID and by-Num
authoradam <adam@megacz.com>
Fri, 7 Jan 2005 18:15:44 +0000 (18:15 +0000)
committeradam <adam@megacz.com>
Fri, 7 Jan 2005 18:15:44 +0000 (18:15 +0000)
darcs-hash:20050107181544-5007d-a3cab9591f5390af8bd844f30f398c28da4fffdc.gz

src/org/ibex/mail/protocol/IMAP.java

index 40ea210..4867c41 100644 (file)
@@ -68,6 +68,8 @@ public class IMAP {
         public int       unseen(String mailbox);
         public int       recent(String mailbox);
         public int       count(String mailbox);
+        public int       count();
+        public int       maxuid();
         public int       uidNext(String mailbox);
         public int       uidValidity(String mailbox);
         public int[]     search(Query q, boolean uid);
@@ -174,9 +176,17 @@ public class IMAP {
         public void expunge(Mailbox.Iterator it) { client.expunge(it.uid()); it.delete(); }
         public void subscribe(String mailbox) { }
         public void unsubscribe(String mailbox) { }
+        public int maxuid() {
+            int ret = 0;
+            Mailbox mb = selected();
+            if (mb == null) return 0;
+            for(Mailbox.Iterator it = mb.iterator(); it.next(); ) ret = it.uid();
+            return ret;
+        }
         public int unseen(String mailbox)      { return mailbox(mailbox, false).count(Query.not(Query.seen())); }
         public int recent(String mailbox)      { return mailbox(mailbox, false).count(Query.recent()); }
         public int count(String mailbox)       { return mailbox(mailbox, false).count(Query.all()); }
+        public int count()                     { return selected().count(Query.all()); }
         public int uidNext(String mailbox)     { return mailbox(mailbox, false).uidNext(); }
         public int uidValidity(String mailbox) { return mailbox(mailbox, false).uidValidity(); }
         public void select(String mailbox, boolean examineOnly) {
@@ -199,12 +209,12 @@ public class IMAP {
         private void doFlags(Query q, int flags, boolean uid, int style, boolean silent) {
             for(Mailbox.Iterator it = selected().iterator(q);it.next();) {
                 boolean recent = it.recent();
+                try { throw new Exception("flags " + flags); } catch (Exception e) { Log.error(this, e); }
                 if (style == -1)     it.removeFlags(flags);
                 else if (style == 0) it.setFlags(flags);
                 else if (style == 1) it.addFlags(flags);
                 it.recent(recent);
-               // FIXME
-                //if (!silent) client.fetch(it.num(), it.flags(), -1, null, it.uid());
+                if (!silent) client.fetch(it.num(), it.flags(), -1, null, it.uid());
             }
         }            
         public void rename(String from0, String to) {
@@ -239,7 +249,7 @@ public class IMAP {
         Parser.Token token() { return parser.token(); }
         void println(String s) { conn.println(s); }
         void newline() { parser.newline(); }
-        Query query() { return parser.query(); }
+        Query query(int max) { return parser.query(max, maxn(true)); }
 
         public void login(String u, String p) {
             Object ret;
@@ -256,6 +266,8 @@ public class IMAP {
             }
         }
 
+        private int maxn(boolean uid) { return uid ? api.maxuid() : api.count(); }
+
         public void handleRequest(Connection conn) {
             this.conn = conn;
             parser = new Parser(conn);
@@ -278,7 +290,7 @@ public class IMAP {
                     case SUBSCRIBE:    api.subscribe(token().astring()); break;
                     case UNSUBSCRIBE:  api.unsubscribe(token().astring()); break;
                     case RENAME:       api.rename(token().astring(), token().astring()); break;
-                    case COPY:         selected(); api.copy(Query.set(uid, token().set()), token().astring()); break;
+                    case COPY:         selected(); api.copy(Query.set(uid, token().set(maxn(uid))), token().astring()); break;
                     case DELETE:       api.delete(token().atom()); break;
                     case CHECK:        selected(); api.check(); break;
                     case NOOP:         api.noop(); break;
@@ -286,7 +298,7 @@ public class IMAP {
                     case EXPUNGE:      selected(); api.expunge(); break;
                     case UNSELECT:     selected(); api.unselect(); selected = false; break;
                     case CREATE:       api.create(token().astring()); break;
-                    case FETCH:        selected(); fetch(Query.set(lastuid=uid, token().set()),
+                    case FETCH:        selected(); fetch(Query.set(lastuid=uid, token().set(maxn(uid))),
                                                         lastfetch=token().lx(), 0, 0, 0, uid, 0); break;
                     case SEARCH:       selected(); println("* SEARCH " + Printer.join(api.search(query(), uid))); break;
                     case EXAMINE:
@@ -328,7 +340,7 @@ public class IMAP {
                         break; }
                     case STORE: {
                         selected();
-                        Query q = uid ? Query.uid(token().set()) : Query.num(token().set());
+                        Query q = uid ? Query.uid(token().set(maxn(uid))) : Query.num(token().set(maxn(uid)));
                         String s = token().atom().toUpperCase();
                         int flags = token().flags();
                         if (s.equals("FLAGS"))              api.setFlags(q,    flags, uid, false);
@@ -368,8 +380,8 @@ public class IMAP {
          *      - emit a fetch reply for the parsed spec with respect to message m
          */
         private void fetch(Object o, Parser.Token[] t, int num, int flags, int size, boolean uid, int muid) {
-            Query q   = o instanceof Query ? (Query)o : null;
-            Message m = o instanceof Message ? (Message)o : null;
+            Query q   = o == null ? null : o instanceof Query ? (Query)o : null;
+            Message m = o == null ? null : o instanceof Message ? (Message)o : null;
             boolean e = q == null;
 
             lastfetch = t;
@@ -377,7 +389,7 @@ public class IMAP {
             String[] headers = null;
             int start = -1, end = -1;
             StringBuffer r = new StringBuffer();
-            if (e) { r.append(uid ? muid : num); r.append(" FETCH ("); }
+            if (e) { r.append(num); r.append(" FETCH ("); }
             int initlen = r.length();
             if (uid) {
                 boolean good = false;
@@ -513,49 +525,53 @@ public class IMAP {
         private Stream stream;
         public Parser(Stream from) { this.stream = from; }
         public Token token(String s) { return new Token(s); }
-        protected Query query() {
+        protected Query query(int max, int maxuid) {
             String s = null;
             boolean not = false;
             Query q = null;
+            Query ret = null;
             while(true) {
                 Parser.Token t = token(false);
                 if (t == null) break;
                 if (t.type == t.LIST) throw new Server.No("nested queries not yet supported FIXME");
-                else if (t.type == t.SET) return Query.num(t.set());
+                else if (t.type == t.SET) return Query.num(t.set(max));
                 s = t.atom().toUpperCase();
-                if (s.equals("NOT")) { not = true; continue; }
-                if (s.equals("OR"))    return Query.or(query(), query());    // FIXME parse rest of list
-                if (s.equals("AND"))   return Query.and(query(), query());
+                if (s.equals("NOT"))   return Query.not(query(max, maxuid));
+                if (s.equals("OR"))    return Query.or(query(max, maxuid), query(max, maxuid));    // FIXME parse rest of list
+                if (s.equals("AND"))   return Query.and(query(max, maxuid), query(max, maxuid));
+
+                if (s.startsWith("UN"))        { not = true; s = s.substring(2); }
+                if (s.equals("ANSWERED"))        q = Query.answered();
+                else if (s.equals("DELETED"))    q = Query.deleted();
+                else if (s.equals("ALL"))        q = Query.all();
+                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", token().flag());
+                else if (s.equals("HEADER"))     q = Query.header(token().astring(), token().astring());
+                else if (s.equals("BCC"))        q = Query.header("bcc", token().astring());
+                else if (s.equals("CC"))         q = Query.header("cc", token().astring());
+                else if (s.equals("FROM"))       q = Query.header("from", token().astring());
+                else if (s.equals("TO"))         q = Query.header("to", token().astring());
+                else if (s.equals("SUBJECT"))    q = Query.header("subject", token().astring());
+                else if (s.equals("LARGER"))     q = Query.size(token().n(), Integer.MAX_VALUE);
+                else if (s.equals("SMALLER"))    q = Query.size(Integer.MIN_VALUE, token().n());
+                else if (s.equals("BODY"))       q = Query.body(token().astring());
+                else if (s.equals("TEXT"))       q = Query.full(token().astring());
+                else if (s.equals("BEFORE"))     q = Query.arrival(new Date(0), token().date());
+                else if (s.equals("SINCE"))      q = Query.arrival(token().date(), new Date(Long.MAX_VALUE));
+                else if (s.equals("ON"))       { Date d = token().date(); q = Query.arrival(d, new Date(d.getTime() + 24 * 60 * 60)); }
+                else if (s.equals("SENTBEFORE")) q = Query.sent(new Date(0), token().date());
+                else if (s.equals("SENTSINCE"))  q = Query.sent(token().date(), new Date(Long.MAX_VALUE));
+                else if (s.equals("SENTON"))   { Date d = token().date(); q = Query.sent(d, new Date(d.getTime() + 24 * 60 * 60)); }
+                else if (s.equals("UID"))        q = Query.uid(token().set(max));
+                q = not ? Query.not(q) : q;
+                ret = ret == null ? q : Query.and(ret, q);
             }
-            if (s.startsWith("UN"))        { not = true; s = s.substring(2); }
-            if (s.equals("ANSWERED"))        q = Query.answered();
-            else if (s.equals("DELETED"))    q = Query.deleted();
-            else if (s.equals("ALL"))        q = Query.all();
-            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", token().flag());
-            else if (s.equals("HEADER"))     q = Query.header(token().astring(), token().astring());
-            else if (s.equals("BCC"))        q = Query.header("bcc", token().astring());
-            else if (s.equals("CC"))         q = Query.header("cc", token().astring());
-            else if (s.equals("FROM"))       q = Query.header("from", token().astring());
-            else if (s.equals("TO"))         q = Query.header("to", token().astring());
-            else if (s.equals("SUBJECT"))    q = Query.header("subject", token().astring());
-            else if (s.equals("LARGER"))     q = Query.size(token().n(), Integer.MAX_VALUE);
-            else if (s.equals("SMALLER"))    q = Query.size(Integer.MIN_VALUE, token().n());
-            else if (s.equals("BODY"))       q = Query.body(token().astring());
-            else if (s.equals("TEXT"))       q = Query.full(token().astring());
-            else if (s.equals("BEFORE"))     q = Query.arrival(new Date(0), token().date());
-            else if (s.equals("SINCE"))      q = Query.arrival(token().date(), new Date(Long.MAX_VALUE));
-            else if (s.equals("ON"))       { Date d = token().date(); q = Query.arrival(d, new Date(d.getTime() + 24 * 60 * 60)); }
-            else if (s.equals("SENTBEFORE")) q = Query.sent(new Date(0), token().date());
-            else if (s.equals("SENTSINCE"))  q = Query.sent(token().date(), new Date(Long.MAX_VALUE));
-            else if (s.equals("SENTON"))   { Date d = token().date(); q = Query.sent(d, new Date(d.getTime() + 24 * 60 * 60)); }
-            else if (s.equals("UID"))        q = Query.uid(token().set());
-            return not ? Query.not(q) : q;
+            return ret;
         }
 
         private static void bad(String s) { throw new Server.Bad(s); }
@@ -615,30 +631,27 @@ public class IMAP {
                 }
                 return ret;
             }
-            public int[] set() {
+            public int[] set(int largest) {
                 if (type != ATOM) bad("expected a messageid set");
                 Vec.Int ids = new Vec.Int();
                 StringTokenizer st = new StringTokenizer(s, ",");
                 while(st.hasMoreTokens()) {
                     String s = st.nextToken();
                     if (s.indexOf(':') == -1) {
-                       if (s.equals("*")) {
-                           ids.addElement(0);
-                           ids.addElement(Integer.MAX_VALUE);
-                       } else {
-                           ids.addElement(Integer.parseInt(s));
-                           ids.addElement(Integer.parseInt(s));
-                       }
-                       continue; }
+                        if (s.equals("*")) {
+                            ids.addElement(largest);
+                            ids.addElement(largest);
+                        } else {
+                            ids.addElement(Integer.parseInt(s));
+                            ids.addElement(Integer.parseInt(s));
+                        }
+                        continue; }
                     int start = Integer.parseInt(s.substring(0, s.indexOf(':')));
                     String end_s = s.substring(s.indexOf(':')+1);
-                    if (end_s.equals("*")) { ids.addElement(start); ids.addElement(Integer.MAX_VALUE); }
-                    else {
-                        int end = Integer.parseInt(end_s);
-                        for(int j=Math.min(start,end); j<=Math.max(start,end); j++) {
-                            ids.addElement(j);
-                            ids.addElement(j);
-                       }
+                    int end = end_s.equals("*") ? largest : Integer.parseInt(end_s);
+                    for(int j=Math.min(start,end); j<=Math.max(start,end); j++) {
+                        ids.addElement(j);
+                        ids.addElement(j);
                     }
                 }
                 return ids.dump();
@@ -783,7 +796,7 @@ public class IMAP {
         public static String qq(String s) {
             StringBuffer ret = new StringBuffer();
             ret.append('{');
-            ret.append(s.length());
+            ret.append(s.getBytes().length);
             ret.append('}');
             ret.append('\r');
             ret.append('\n');