1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
5 package org.ibex.mail.target;
6 import org.prevayler.*;
7 import org.ibex.mail.*;
8 import org.ibex.util.*;
12 import java.nio.channels.*;
17 /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */
18 public class FileBasedMailbox extends Mailbox.Default {
20 public static final long MAGIC_DATE = 0;
21 private static final char slash = File.separatorChar;
22 private static final WeakHashMap<String,FileBasedMailbox> instances = new WeakHashMap<String,FileBasedMailbox>();
23 public String toString() { return "[FileBasedMailbox " + path.getAbsolutePath() + "]"; }
24 public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); }
26 // FIXME: should be a File()
27 public static synchronized FileBasedMailbox getFileBasedMailbox(String path, boolean create) {
29 FileBasedMailbox ret = instances.get(path);
31 if (!create && !(new File(path).exists())) return null;
32 instances.put(path, ret = new FileBasedMailbox(new File(path)));
35 } catch (Exception e) {
36 Log.error(FileBasedMailbox.class, e);
41 // Instance //////////////////////////////////////////////////////////////////////////////
44 private FileLock lock;
46 private int uidValidity;
48 // Helpers //////////////////////////////////////////////////////////////////////////////
50 private static void rmDashRf(File f) throws IOException {
51 if (!f.isDirectory()) { f.delete(); return; }
52 String[] children = f.list();
53 for(int i=0; i<children.length; i++) rmDashRf(new File(f.getAbsolutePath() + slash + children[i]));
57 private FileBasedMailbox(File path) throws MailException, IOException, ClassNotFoundException {
62 File lockfile = new File(this.path.getAbsolutePath() + slash + ".lock");
63 lock = new RandomAccessFile(lockfile, "rw").getChannel().tryLock();
64 if (lock == null) throw new IOException("unable to lock FileBasedMailbox");
65 uidValidity = (int)(lockfile.lastModified() & 0xffffffff);
67 String[] files = path.list();
68 for(int i=0; i<files.length; i++) {
70 if (files[i].indexOf('.') != -1) files[i] = files[i].substring(0, files[i].indexOf('.'));
71 int n = Integer.parseInt(files[i]);
72 if (n>=uidNext) uidNext = n;
73 } catch(Exception e) { /* DELIBERATE */ }
78 public Mailbox.Iterator iterator() { return new Iterator(); }
79 public synchronized void add(Message message) { add(message, Mailbox.Flag.RECENT); }
80 public String[] children() {
82 String[] list = path.list();
83 for(int i=0; i<list.length; i++) {
84 File f = new File(path.getAbsolutePath() + slash + list[i]);
85 if (f.isDirectory() && f.getName().charAt(0) != '.') vec.addElement(list[i]);
87 return (String[])vec.copyInto(new String[vec.size()]);
90 public int uidValidity() { return uidValidity; }
91 public int uidNext() { return uidNext; }
92 public synchronized void add(Message message, int flags) {
94 String name, fullname; File target, f;
95 for(int i = uidNext; ; i++) {
97 fullname = path.getAbsolutePath() + slash + name;
98 target = new File(fullname);
99 f = new File(target.getCanonicalPath() + "-");
100 if (!f.exists() && !target.exists()) break;
101 Log.error(this, "aieeee!!!! target of add() already exists: " + target.getAbsolutePath());
103 Stream stream = new Stream(new FileOutputStream(f));
104 message.getStream().transcribe(stream);
106 f.renameTo(new File(fullname));
108 f = new File(fullname);
109 if ((flags & Mailbox.Flag.SEEN) == Mailbox.Flag.SEEN) f.setLastModified(MAGIC_DATE);
110 } catch (IOException e) { throw new MailException.IOException(e); }
111 Log.info(this, path + " <= " + message.summary());
114 private class Iterator extends Mailbox.Default.Iterator {
116 String[] files = path.list(new FilenameFilter() { public boolean accept(File dir, String name) {
117 return name.endsWith(".");
119 private File file() { return new File(path.getAbsolutePath() + slash + files[cur]); }
120 public boolean done() { return cur >= files.length; }
121 public boolean next() { cur++; return !done(); }
122 public boolean seen() { return false; }
123 public boolean recent() { return false; }
124 public int num() { return cur+1; } // EUDORA insists that message numbers start at 1, not 0
125 public int uid() { return done() ? -1 : Integer.parseInt(files[cur].substring(0, files[cur].length()-1)); }
126 public void delete() { File f = file(); if (f != null && f.exists()) f.delete(); }
127 public void seen(boolean seen) { }
128 public Headers head() {
129 if (done()) return null;
130 FileInputStream fis = null;
132 return new Headers(new Stream(new FileInputStream(file())));
133 } catch (IOException e) { throw new MailException.IOException(e);
134 } finally { if (fis != null) try { fis.close(); } catch (Exception e) { /* DELIBERATE */ } }
136 public Message cur() {
137 FileInputStream fis = null;
139 return Message.newMessage(new Fountain.File(file()));
140 //} catch (IOException e) { throw new MailException.IOException(e);
141 } catch (Message.Malformed e) { throw new MailException(e.getMessage());
142 } finally { if (fis != null) try { fis.close(); } catch (Exception e) { /* DELIBERATE */ } }