From 6e7e40e7100de89b4ad91d63aafa856f7d5e418d Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 9 Jun 2004 22:32:44 +0000 Subject: [PATCH] compiles darcs-hash:20040609223244-5007d-4f739f013ab870f1499118992023862a05bec260.gz --- src/org/ibex/mail/Flag.java | 10 -- src/org/ibex/mail/MailException.java | 8 +- src/org/ibex/mail/Message.java | 167 ++++++++++---------- src/org/ibex/mail/Query.java | 85 +++++----- src/org/ibex/mail/protocol/IMAP.java | 188 ++++++++++------------ src/org/ibex/mail/protocol/Incoming.java | 2 +- src/org/ibex/mail/protocol/SMTP.java | 9 +- src/org/ibex/mail/target/FileBasedMailbox.java | 201 ++++++++++++++---------- src/org/ibex/mail/target/Mailbox.java | 160 ++++++++++--------- src/org/ibex/mail/target/Transcript.java | 20 +-- 10 files changed, 433 insertions(+), 417 deletions(-) delete mode 100644 src/org/ibex/mail/Flag.java diff --git a/src/org/ibex/mail/Flag.java b/src/org/ibex/mail/Flag.java deleted file mode 100644 index 9707d76..0000000 --- a/src/org/ibex/mail/Flag.java +++ /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; -} diff --git a/src/org/ibex/mail/MailException.java b/src/org/ibex/mail/MailException.java index 91883da..9f57978 100644 --- a/src/org/ibex/mail/MailException.java +++ b/src/org/ibex/mail/MailException.java @@ -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); } } } diff --git a/src/org/ibex/mail/Message.java b/src/org/ibex/mail/Message.java index 6ad493b..f4644f8 100644 --- a/src/org/ibex/mail/Message.java +++ b/src/org/ibex/mail/Message.java @@ -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 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 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= 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= min && it.uid() <= max; + case NUM: if (set != null){for(int i=0; i= 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 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')); 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(); } diff --git a/src/org/ibex/mail/protocol/Incoming.java b/src/org/ibex/mail/protocol/Incoming.java index 0f3adcb..fe37582 100644 --- a/src/org/ibex/mail/protocol/Incoming.java +++ b/src/org/ibex/mail/protocol/Incoming.java @@ -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); } } diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java index 2350dfd..8261247 100644 --- a/src/org/ibex/mail/protocol/SMTP.java +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -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) { diff --git a/src/org/ibex/mail/target/FileBasedMailbox.java b/src/org/ibex/mail/target/FileBasedMailbox.java index 7e3f3a0..fb5e9aa 100644 --- a/src/org/ibex/mail/target/FileBasedMailbox.java +++ b/src/org/ibex/mail/target/FileBasedMailbox.java @@ -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(); } + } } diff --git a/src/org/ibex/mail/target/Mailbox.java b/src/org/ibex/mail/target/Mailbox.java index 86bd83f..5e3e67f 100644 --- a/src/org/ibex/mail/target/Mailbox.java +++ b/src/org/ibex/mail/target/Mailbox.java @@ -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; } + } + } + } diff --git a/src/org/ibex/mail/target/Transcript.java b/src/org/ibex/mail/target/Transcript.java index 5f77980..fbb485c 100644 --- a/src/org/ibex/mail/target/Transcript.java +++ b/src/org/ibex/mail/target/Transcript.java @@ -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); } } } -- 1.7.10.4