001: /*
002: * Mbox.java
003: *
004: * Copyright (C) 2000-2002 Peter Graves
005: * $Id: Mbox.java,v 1.1.1.1 2002/09/24 16:09:45 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.io.BufferedReader;
025: import java.io.BufferedWriter;
026: import java.io.FileWriter;
027: import java.io.IOException;
028: import java.io.StringReader;
029: import java.text.SimpleDateFormat;
030: import java.util.ArrayList;
031: import java.util.Calendar;
032: import java.util.Iterator;
033: import java.util.List;
034: import org.armedbear.j.Buffer;
035: import org.armedbear.j.BufferIterator;
036: import org.armedbear.j.Debug;
037: import org.armedbear.j.Editor;
038: import org.armedbear.j.File;
039: import org.armedbear.j.FastStringBuffer;
040: import org.armedbear.j.Log;
041: import org.armedbear.j.Mutex;
042: import org.armedbear.j.ProgressNotifier;
043:
044: public final class Mbox {
045: private static ArrayList mboxList;
046:
047: private final Mutex mutex = new Mutex();
048: private final File file;
049:
050: private long lastModified;
051: private ArrayList entries;
052:
053: private Mbox(File file) {
054: this .file = file;
055: Debug.assertTrue(file != null);
056: }
057:
058: public static synchronized Mbox getInstance(File file) {
059: if (mboxList == null)
060: mboxList = new ArrayList();
061: else {
062: for (int i = mboxList.size() - 1; i >= 0; i--) {
063: Mbox mbox = (Mbox) mboxList.get(i);
064: if (mbox.getFile().equals(file))
065: return mbox;
066: }
067: }
068: // Not found.
069: Mbox mbox = new Mbox(file);
070: mboxList.add(mbox);
071: return mbox;
072: }
073:
074: public static synchronized void cleanup() {
075: Log.debug("Mbox.cleanup");
076: if (mboxList == null || mboxList.size() == 0)
077: return;
078: Iterator iter = mboxList.iterator();
079: while (iter.hasNext()) {
080: Mbox mbox = (Mbox) iter.next();
081: if (findMailbox(mbox) == null) {
082: Log.debug("removing Mbox for " + mbox.getFile());
083: iter.remove();
084: }
085: }
086: }
087:
088: private static Mailbox findMailbox(Mbox mbox) {
089: File file = mbox.getFile();
090: BufferIterator iter = new BufferIterator();
091: while (iter.hasNext()) {
092: Buffer buf = iter.nextBuffer();
093: if (buf instanceof LocalMailbox) {
094: LocalMailbox mb = (LocalMailbox) buf;
095: if (mb.getMailboxFile().equals(file))
096: return mb;
097: }
098: }
099: return null;
100: }
101:
102: public final File getFile() {
103: return file;
104: }
105:
106: // Return a copy.
107: public synchronized final List getEntries(
108: ProgressNotifier progressNotifier) {
109: Log.debug("Mbox.getEntries");
110: Debug.assertTrue(isLocked());
111: if (entries != null) {
112: if (file.lastModified() > lastModified) {
113: Log
114: .debug("mbox last modified later than entries last modified");
115: entries = null;
116: }
117: }
118: if (entries == null) {
119: File summaryFile = getSummaryFile();
120: if (summaryFile.isFile()
121: && summaryFile.lastModified() > file.lastModified()) {
122: Log.debug("using summary");
123: MboxSummary summary = MboxSummary.read(summaryFile);
124: if (summary != null) {
125: Log.debug("summary is valid");
126: if (summary.lastModified() == file.lastModified()) {
127: if (summary.length() == file.length()) {
128: entries = summary.getEntries();
129: lastModified = file.lastModified();
130: }
131: }
132: }
133: }
134: if (entries == null) {
135: Log.debug("entries == null, calling read...");
136: read(progressNotifier);
137: }
138: }
139: return new ArrayList(entries);
140: }
141:
142: public synchronized boolean lock() {
143: Log.debug("Mbox.lock " + file.canonicalPath());
144: try {
145: return mutex.attempt();
146: } catch (InterruptedException e) {
147: return false;
148: }
149: }
150:
151: public synchronized void unlock() {
152: Log.debug("Mbox.unlock " + file.canonicalPath());
153: mutex.release();
154: }
155:
156: public synchronized boolean isLocked() {
157: return mutex.isInUse();
158: }
159:
160: private synchronized void read(ProgressNotifier progressNotifier) {
161: Log.debug("entering Mbox.read");
162: long start = System.currentTimeMillis();
163: Debug.assertTrue(isLocked());
164: entries = new ArrayList(1000);
165: long messageStart = 0;
166: MailReader reader = null;
167: try {
168: if (!file.isFile())
169: return;
170: reader = new MailReader(file.getInputStream());
171: FastStringBuffer sb = new FastStringBuffer(1024);
172: boolean complete = false;
173: while (true) {
174: long here = reader.getOffset();
175: String text = reader.readLine();
176: if (text == null) {
177: // End of file.
178: Log.debug("read - end of file");
179: if (entries.size() > 0) {
180: LocalMailboxEntry entry = (LocalMailboxEntry) entries
181: .get(entries.size() - 1);
182: entry.setSize((int) (here - messageStart));
183: entry.setNextMessageStart(here);
184: }
185: complete = true;
186: break;
187: }
188: if (text.startsWith("From ")) {
189: if (entries.size() > 0) {
190: LocalMailboxEntry entry = (LocalMailboxEntry) entries
191: .get(entries.size() - 1);
192: entry.setSize((int) (here - messageStart));
193: entry.setNextMessageStart(here);
194: messageStart = here;
195: }
196: if (progressNotifier != null
197: && progressNotifier.cancelled()) {
198: Log.debug("Mbox.read cancelled!");
199: break;
200: }
201: sb.setLength(0);
202: while (true) {
203: text = reader.readLine();
204: if (text == null)
205: return; // Shouldn't happen.
206: if (text.length() == 0)
207: break; // End of header.
208: sb.append(text);
209: sb.append('\n');
210: }
211: LocalMailboxEntry entry = new LocalMailboxEntry(
212: entries.size() + 1, here, sb.toString());
213: entries.add(entry);
214: if (progressNotifier != null) {
215: sb.setLength(0);
216: sb.append("Read ");
217: sb.append(entries.size());
218: sb.append(" message");
219: if (entries.size() > 1)
220: sb.append('s');
221: progressNotifier.progress(sb.toString());
222: }
223: }
224: }
225: if (complete) {
226: long elapsed = System.currentTimeMillis() - start;
227: Log.debug("Mbox.read " + elapsed + " ms");
228: // User did not cancel.
229: writeSummary();
230: Log.debug("Mbox.read - after writeSummary");
231: lastModified = file.lastModified();
232: }
233: } catch (IOException e) {
234: Log.error(e);
235: } finally {
236: if (reader != null) {
237: try {
238: reader.close();
239: } catch (IOException e) {
240: Log.error(e);
241: }
242: }
243: Log.debug("leaving Mbox.read");
244: }
245: }
246:
247: public synchronized boolean appendMessage(Message message,
248: final int flags) {
249: Log.debug("Mbox.appendMessage flags = " + flags);
250: Debug.assertTrue(isLocked());
251: try {
252: BufferedReader reader = new BufferedReader(
253: new StringReader(message.getRawText()));
254: BufferedWriter writer = new BufferedWriter(new FileWriter(
255: file.canonicalPath(), true));
256: final long messageStart = file.length();
257: writer.write("From - ");
258: SimpleDateFormat dateFormatter = new SimpleDateFormat(
259: "EEE MMM d HH:mm:ss yyyy");
260: Calendar cal = Calendar.getInstance();
261: String dateString = dateFormatter.format(cal.getTime());
262: writer.write(dateString);
263: writer.write('\n');
264: // Headers.
265: FastStringBuffer sb = new FastStringBuffer(2048);
266: while (true) {
267: String s = reader.readLine();
268: if (s == null)
269: return false; // Error! (Reached end of stream before reaching end of headers.)
270: if (s.length() == 0) {
271: // Reached end of headers.
272: // Add X-J-Status.
273: String status = "X-J-Status: " + flags + "\n";
274: writer.write(status);
275: sb.append(status);
276: writer.write('\n');
277: break;
278: }
279: // Skip X-UIDL.
280: if (s.toUpperCase().startsWith("X-UIDL"))
281: continue;
282: // Skip X-J-Status.
283: if (s.startsWith("X-J-Status:"))
284: continue;
285: writer.write(s);
286: writer.write('\n');
287: sb.append(s);
288: sb.append('\n');
289: }
290: // Body.
291: while (true) {
292: String s = reader.readLine();
293: if (s == null)
294: break;
295: if (s.startsWith("From ")) {
296: // Mangle lines starting with "From " in body of message.
297: writer.write('>');
298: }
299: writer.write(s);
300: writer.write('\n');
301: }
302: // Add a newline after the end of the message.
303: writer.write('\n');
304: writer.flush();
305: writer.close();
306: reader.close();
307: if (entries != null) {
308: final long nextMessageStart = file.length();
309: LocalMailboxEntry entry = new LocalMailboxEntry(entries
310: .size() + 1, messageStart, sb.toString());
311: entry.setNextMessageStart(nextMessageStart);
312: entry.setSize((int) (nextMessageStart - messageStart));
313: entries.add(entry);
314: } else
315: Log.debug("appendMessage entries == null");
316: return true;
317: } catch (IOException e) {
318: Log.error(e);
319: return false;
320: }
321: }
322:
323: public synchronized void updateViews() {
324: if (entries == null)
325: return;
326: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
327: Buffer buf = it.nextBuffer();
328: if (buf instanceof LocalMailbox) {
329: LocalMailbox mb = (LocalMailbox) buf;
330: if (mb.getMailboxFile().equals(file)) {
331: if (mb.lock()) {
332: try {
333: mb.saveDisplayState();
334: mb.setEntries(new ArrayList(entries));
335: mb.refreshBuffer();
336: mb.updateDisplay();
337: } finally {
338: mb.unlock();
339: }
340: }
341: }
342: }
343: }
344: }
345:
346: private final File getSummaryFile() {
347: return File.getInstance(file.canonicalPath() + ".summary");
348: }
349:
350: // Called only from read().
351: private void writeSummary() {
352: Debug.assertTrue(isLocked());
353: File summaryFile = getSummaryFile();
354: MboxSummary summary = new MboxSummary(file, entries);
355: summary.write(summaryFile);
356: }
357:
358: protected void finalize() throws Throwable {
359: Log.debug("Mbox.finalize " + file);
360: super.finalize();
361: }
362: }
|