change num() to imapNumber() and nntpNumber(), add comments about semantics
authoradam <adam@megacz.com>
Wed, 28 Feb 2007 10:18:51 +0000 (10:18 +0000)
committeradam <adam@megacz.com>
Wed, 28 Feb 2007 10:18:51 +0000 (10:18 +0000)
darcs-hash:20070228101851-5007d-62915ed43ed5da012a1fb1f984b933b9484605b9.gz

src/org/ibex/mail/Query.java
src/org/ibex/mail/protocol/GMail.java
src/org/ibex/mail/protocol/IMAP.java
src/org/ibex/mail/protocol/NNTP.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/MailmanArchives.java
src/org/ibex/mail/target/MessageArrayMailbox.java
src/org/ibex/mail/target/SqliteJdbcMailbox.java

index d308da8..14f3e09 100644 (file)
@@ -26,7 +26,8 @@ public class Query {
     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 imapNumber(int min,int max) { return new Query(IMAPNUM,null,min,max,0,null,null,null,null, null); }
+    public static Query nntpNumber(int min,int max) { return new Query(NNTPNUM,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);}
@@ -34,7 +35,7 @@ public class Query {
     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 imapNumber(int[] set)       { return new Query(IMAPNUM,  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); }
@@ -42,7 +43,7 @@ public class Query {
     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); }
-    public static Query set(boolean uid, int[] set) { return uid ? uid(set) : num(set); }
+    //public static Query set(boolean uid, int[] set) { return uid ? uid(set) : imapNumber(set); }
 
     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;
@@ -59,13 +60,14 @@ public class Query {
     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 IMAPNUM    = 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 static final int NNTPNUM    = 18;
 
     public final int type;
     public final Query[] q;
@@ -89,10 +91,14 @@ public class Query {
                                     if (set[i] <= it.uid() && set[i+1] >= 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+=2) if (set[i] <= it.num() && set[i+1] >= it.num()) return true;
+            case IMAPNUM:    if (set != null) {
+                                 for(int i=0; i<set.length; i+=2) if (set[i] <= it.imapNumber() && set[i+1] >= it.imapNumber()) return true;
                                  return false; }
-                             else return it.num() >= min && it.num() <= max;
+                             else return it.imapNumber() >= min && it.imapNumber() <= max;
+            case NNTPNUM:    if (set != null) {
+                                 for(int i=0; i<set.length; i+=2) if (set[i] <= it.nntpNumber() && set[i+1] >= it.nntpNumber()) return true;
+                                 return false; }
+                             else return it.imapNumber() >= min && it.imapNumber() <= 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)) &&
index aadfa41..c43df9f 100644 (file)
@@ -132,7 +132,8 @@ public class GMail extends Account {
                         public Headers head() { return m.headers; }
                         public boolean next() { return false; }
                         public int     uid() { return num; }
-                        public int     num() { return num; }
+                        public int     imapNumber() { return num; }
+                        public int     nntpNumber() { throw new RuntimeException("not supported"); }
                         public void    delete() { }
                         public void    set(String key, String val) { }
                         public String  get(String key) { return null; }
index f6c084f..5b98105 100644 (file)
@@ -207,7 +207,7 @@ public class IMAP {
         public int[] search(Query q, boolean uid) {
             Vec.Int vec = new Vec.Int();
             for(Mailbox.Iterator it = selected().iterator(q); it.next();) {
-                vec.addElement(uid ? it.uid() : it.num());
+                vec.addElement(uid ? it.uid() : it.imapNumber());
                 it.recent(false);
             }
             return vec.dump();
@@ -225,7 +225,7 @@ public class IMAP {
                 else if (style == 0) it.setFlags(flags);
                 else if (style == 1) it.addFlags(flags);
                 it.recent(recent);
-                if (!silent) client.fetch(it.num(), it.flags(), -1, null, it.uid());
+                if (!silent) client.fetch(it.imapNumber(), it.flags(), -1, null, it.uid());
             }
         }            
         public void rename(String from0, String to) {
@@ -239,7 +239,7 @@ public class IMAP {
                 Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 |
                                             RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null;
                 int size = message == null ? 0 : message.getLength();
-                client.fetch(it.num(), it.flags(), size, message, it.uid());
+                client.fetch(it.imapNumber(), it.flags(), size, message, it.uid());
                 it.recent(false);
             }
         }
@@ -301,7 +301,6 @@ 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(maxn(uid))), token().astring()); break;
                     case DELETE:       api.delete(token().atom()); break;
                     case CHECK:        selected(); api.check(); break;
                     case NOOP:         api.noop(); break;
@@ -309,8 +308,13 @@ 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(maxn(uid))),
+                    case FETCH:        selected(); fetch(((lastuid=uid)
+                                                          ? Query.uid(token().set(maxn(uid)))
+                                                          : Query.imapNumber(token().set(maxn(uid)))),
                                                         lastfetch=token().lx(), 0, 0, 0, uid, 0); break;
+                    case COPY:         selected(); api.copy(uid
+                                                            ? Query.uid(token().set(maxn(uid)))
+                                                            : Query.imapNumber(token().set(maxn(uid))), token().astring()); break;
                     case SEARCH: {
                         selected();
                         int[] result = api.search(query(maxn(uid)), uid);
@@ -357,7 +361,7 @@ public class IMAP {
                         break; }
                     case STORE: {
                         selected();
-                        Query q = uid ? Query.uid(token().set(maxn(uid))) : Query.num(token().set(maxn(uid)));
+                        Query q = uid ? Query.uid(token().set(maxn(uid))) : Query.imapNumber(token().set(maxn(uid)));
                         String s = token().atom().toUpperCase();
                         int flags = token().flags();
                         if (s.equals("FLAGS"))              api.setFlags(q,    flags, uid, false);
@@ -460,7 +464,7 @@ public class IMAP {
                 } else if (!(s.equals("BODY.PEEK") || s.equals("BODY"))) { throw new Server.No("unknown fetch argument: " + s);
                 } else {
                     if (s.equalsIgnoreCase("BODY.PEEK"))   spec |= PEEK;
-                    //else if (e) api.addFlags(Query.num(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
+                    //else if (e) api.addFlags(Query.imapNumber(new int[] { num, num }), Mailbox.Flag.SEEN, false, false);
                     if (i >= t.length - 1 || t[i+1].type != Parser.Token.LIST) {
                        spec |= BODYSTRUCTURE;
                        if (e) { r.append(" "); r.append(Printer.bodystructure(m)); } continue;
@@ -563,7 +567,7 @@ public class IMAP {
                 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(max));
+                else if (t.type == t.SET) return Query.imapNumber(t.set(max));
                 s = t.atom().toUpperCase();
                 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
index c4155fd..284cf04 100644 (file)
@@ -78,13 +78,13 @@ public class NNTP {
         public Article  next()                  { return article(ptr++, false, false); }
         public Article  last()                  { return article(ptr--, false, false); }
         public Article  article(String i, boolean h, boolean b) { return article(Query.header("message-id",i),h,b); }
-        public Article  article(int    n, boolean h, boolean b) { ptr = n; return article(Query.messagenum(n,n),h,b); }
+        public Article  article(int    n, boolean h, boolean b) { ptr = n; return article(Query.nntpNumber(n,n),h,b); }
         private Article article(Query q,  boolean head, boolean body) {
             Mailbox.Iterator it = current.iterator(q);
             if (!it.next()) return null;
             try {
                 Message m = body ? it.cur() : Message.newMessage(new Fountain.StringFountain(it.head() + "\r\n"));
-                return new Article(it.num(), m);
+                return new Article(it.nntpNumber(), m);
             } catch (Exception e) { return null; }
         }
         public Group[]  list() { return list(root, ""); }
@@ -208,18 +208,18 @@ public class NNTP {
                 } else if (command.equals("BODY"))      { article(st.hasMoreTokens() ? st.nextToken() : null, false, true); 
                 } else if (command.equals("STAT"))      { article(st.hasMoreTokens() ? st.nextToken() : null, false, false); 
                 } else if (command.equals("HELP"))      { println("100 you are beyond help."); println(".");
-                } else if (command.equals("SLAVE"))     { println("220 I don't care");
+                } else if (command.equals("SLAVE"))     { println("220 SLAVE was removed in RFC3977, you should not use it");
                 } else if (command.equals("XOVER"))     {
                     println("224 Overview information follows");
                     MailboxWrapper api = (MailboxWrapper)this.api;
                     String range = st.hasMoreTokens() ? st.nextToken() : (api.ptr+"-"+api.ptr);
                     int start = Integer.parseInt(range.substring(0, range.indexOf('-')));
                     int end   = Integer.parseInt(range.substring(range.indexOf('-') + 1));
-                    Mailbox.Iterator it = api.current.iterator(Query.messagenum(start, end));
+                    Mailbox.Iterator it = api.current.iterator(Query.nntpNumber(start, end));
                     while(it.next()) {
                         try {
                             Message m = it.cur();
-                            println(it.num()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+
+                            println(it.nntpNumber()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+
                                     m.headers.get("references") + "\t" + m.getLength() + "\t" + m.getNumLines());
                         } catch (Exception e) { Log.error(this, e); }
                     }
index 37686c6..af62e21 100644 (file)
@@ -74,7 +74,7 @@ public class SMTP {
     // Server //////////////////////////////////////////////////////////////////////////////
 
     public static class Server {
-        public void handleRequest(Connection conn) {
+        public void handleRequest(Connection conn) throws IOException {
             conn.setTimeout(5 * 60 * 1000);
             conn.setNewline("\r\n");
             conn.println("220 " + conn.vhost + " SMTP " + this.getClass().getName());
@@ -84,7 +84,7 @@ public class SMTP {
             String remotehost = null;
             for(String command = conn.readln(); ; command = conn.readln()) try {
                 if (command == null) return;
-                Log.warn("**"+conn.getRemoteAddress()+"**", command);
+                //Log.warn("**"+conn.getRemoteAddress()+"**", command);
                 String c = command.toUpperCase();
                 if (c.startsWith("HELO"))        {
                     remotehost = c.substring(5).trim();
@@ -191,11 +191,6 @@ public class SMTP {
                     } catch (MailException.Malformed mfe) {   conn.println("501 " + mfe.toString());
                     } catch (MailException.MailboxFull mbf) { conn.println("452 " + mbf);
                     } catch (Later.LaterException le) {       conn.println("453 try again later");
-                    } catch (IOException ioe) {               
-                        //conn.println("554 " + ioe.toString());
-                        Log.error(this, ioe);
-                        conn.close();
-                        return;
                     }
                 } else                    { conn.println("500 unrecognized command"); }                    
             } catch (Message.Malformed e) { conn.println("501 " + e.toString()); }
index 4fc5176..0a8c013 100644 (file)
@@ -152,7 +152,8 @@ public class FileBasedMailbox extends Mailbox.Default {
         public boolean next() { cur++; return !done(); }
         public boolean seen() { return false; }
         public boolean recent() { return false; }
-        public int num() { return cur+1; }  // EUDORA insists that message numbers start at 1, not 0
+        public int nntpNumber() { return cur+1; }  // FIXME: lame
+        public int imapNumber() { return cur+1; }  // EUDORA insists that message numbers start at 1, not 0
         public int uid() { return done() ? -1 : Integer.parseInt(files[cur].substring(0, files[cur].length()-1)); }
         public void delete() { File f = file(); if (f != null && f.exists()) f.delete(); }
         public void seen(boolean seen) { }
@@ -218,7 +219,7 @@ public class FileBasedMailbox extends Mailbox.Default {
                 s[0] = (m.from==null?"":m.from.toString(true));
                 s[1] = m.subject;
                 s[2] = (m.date + "").trim().replaceAll(" ","&nbsp;");
-                s[3] = it.num() + "";
+                s[3] = it.imapNumber() + "";
                 msgs.addElement(s);
             }
             String[][] messages;
@@ -253,7 +254,7 @@ public class FileBasedMailbox extends Mailbox.Default {
                 int target = Integer.parseInt(request.getParameter("msgnum"));
                 Mailbox.Iterator it = mbox.iterator();
                 while(it.next())
-                    if (it.num() == target)
+                    if (it.imapNumber() == target)
                         break;
                 if (it.cur() != null) {
                     pw.println("    <table width=100% border=0 cellspacing=0 style='border: 1px black solid; background-color:#F0F0E0;'>");
index 13b376f..dbc91bf 100644 (file)
@@ -79,6 +79,7 @@ public abstract class Mailbox extends JS.Obj implements Target {
             public void    recent(boolean on) { }
             public void    set(String key, String val) { throw new MailException("not supported"); }
             public String  get(String key) { throw new MailException("not supported"); }
+            public int     nntpNumber() { throw new MailException("not supported"); }
             public int flags() {
                 return 
                     (deleted() ? Flag.DELETED : 0) |
@@ -118,10 +119,28 @@ public abstract class Mailbox extends JS.Obj implements Target {
         public abstract Message cur();
         public abstract Headers head();
         public abstract boolean next();
-        public abstract int     uid();
-        public abstract int     num();
         public abstract void    delete();
 
+        /** a unique identifier for this message */
+        public abstract int     uid();
+
+        /**
+         *  Message number according to IMAP semantics.
+         *    - no two messages in the same mailbox may have the same imapNumber
+         *    - sorting by uid must yield the same order as sorting them by imapNumber
+         *    - imapNumber may only change if uidValidity changes
+         *    - if uidValidity changes, imapNumbers may change or be reused
+         */
+        public abstract int     imapNumber();
+
+        /**
+         *  Message number according to NNTP semantics.
+         *    - no two messages in the same mailbox may have the same nntpNumber
+         *    - article number may NEVER change or EVER be reused
+         *    - uidValidity is irrelevant
+         */
+        public abstract int     nntpNumber();
+
         public abstract void    set(String key, String val);
         public abstract String  get(String key);
 
@@ -152,7 +171,8 @@ public abstract class Mailbox extends JS.Obj implements Target {
             public boolean next() { return it.next(); }
             public int     uid() { return it.uid(); }
             public int     flags() { return it.flags(); }
-            public int     num() { return it.num(); }
+            public int     nntpNumber() { return it.nntpNumber(); }
+            public int     imapNumber() { return it.imapNumber(); }
             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(); }
@@ -188,7 +208,8 @@ public abstract class Mailbox extends JS.Obj implements Target {
             public boolean next() { return false; }
             public int     uid() { return 0; }
             public int     flags() { return 0; }
-            public int     num() { return 0; }
+            public int     imapNumber() { return 0; }
+            public int     nntpNumber() { throw new RuntimeException("this mailbox does not keep article numbers"); }
             public void    set(String key, String val) { }
             public String  get(String key) { return null; }
             public void    delete() { }
index 4e043e9..d9c3621 100644 (file)
@@ -57,8 +57,8 @@ public class MailmanArchives extends Mailbox.Default {
         int num = 0;
 
         public int     uid() { return num; }
-        public int     num() { return num; }
-
+        public int     nntpNumber() { return num; }
+        public int     imapNumber() { return num; }
         public Message cur() { return messages[num]; }
         public Headers head() { return messages[num].headers; }
         public boolean next() { return (++num) < messages.length; }
index d2335b8..e733504 100644 (file)
@@ -29,7 +29,7 @@ public class MessageArrayMailbox extends Mailbox.Default {
         public Headers head() { return messages[position].headers; }
         public boolean next() { return ++position < messages.length; }
         public int     uid()  { return position+1; }
-        public int     num()  { return position+1; }
+        public int     imapNumber()  { return position+1; }
         public void    delete() { return; }
 
         public void    set(String key, String val) { return; }
index a4830a1..ccc9ccc 100644 (file)
@@ -68,8 +68,9 @@ public class SqliteJdbcMailbox extends Mailbox.Default {
         public Headers head()   { return cur().headers; }
         public boolean next()   { try { m = null; count++; return rs.next(); } catch (Exception e) { throw new RuntimeException(e); } }
         public int     uid()    { throw new RuntimeException("not supported"); }
-        public int     num()    { return count; } // FIXME FIXME 
-        public void    delete() { throw new RuntimeException("not supported"); }
+        public int     nntpNumber() { throw new RuntimeException("not supported"); }
+        public int     imapNumber() { return count; }
+        public void    delete()     { throw new RuntimeException("not supported"); }
     }
 
     private static String streamToString(Stream stream) throws Exception {