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 {
*/
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) {
}
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)
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);
} 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);
}
}
package org.ibex.mail;
import java.util.*;
+import org.ibex.mail.target.*;
/**
* [immutable] This class encapsulates a query against a mailbox.
*/
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;
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");
}
}
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");
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");
}
}
}
- 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;
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); }
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");
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 (?)
}
}
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 + "\"");
}
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
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());
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;
}
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++) {
--- /dev/null
+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); }
+ }
+
+}
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); }
--- /dev/null
+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); }
+ }
+}
+