001: /*
002: * LocalMailbox.java
003: *
004: * Copyright (C) 2000-2003 Peter Graves
005: * $Id: LocalMailbox.java,v 1.4 2003/06/29 00:19:34 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.IOException;
027: import java.io.InputStreamReader;
028: import java.io.OutputStreamWriter;
029: import java.io.RandomAccessFile;
030: import java.io.UnsupportedEncodingException;
031: import java.util.ArrayList;
032: import java.util.List;
033: import java.util.Vector;
034: import javax.swing.SwingUtilities;
035: import org.armedbear.j.Buffer;
036: import org.armedbear.j.BufferIterator;
037: import org.armedbear.j.Debug;
038: import org.armedbear.j.Directories;
039: import org.armedbear.j.Editor;
040: import org.armedbear.j.EditorIterator;
041: import org.armedbear.j.File;
042: import org.armedbear.j.FastStringBuffer;
043: import org.armedbear.j.Headers;
044: import org.armedbear.j.Line;
045: import org.armedbear.j.LocalFile;
046: import org.armedbear.j.Log;
047: import org.armedbear.j.ProgressNotifier;
048: import org.armedbear.j.Property;
049: import org.armedbear.j.Sidebar;
050: import org.armedbear.j.Utilities;
051: import org.armedbear.j.View;
052:
053: public class LocalMailbox extends Mailbox {
054: private File mailboxFile;
055:
056: public LocalMailbox(MailboxURL url) {
057: super (url);
058: if (url instanceof LocalMailboxURL)
059: mailboxFile = ((LocalMailboxURL) url).getFile();
060: init();
061: }
062:
063: private void init() {
064: supportsUndo = false;
065: type = TYPE_MAILBOX;
066: mode = MailboxMode.getMode();
067: formatter = mode.getFormatter(this );
068: readOnly = true;
069: if (mailboxFile != null)
070: title = mailboxFile.canonicalPath();
071: setInitialized(true);
072: }
073:
074: public String getName() {
075: Debug.assertTrue(mailboxFile != null);
076: return mailboxFile.canonicalPath();
077: }
078:
079: public final File getMailboxFile() {
080: return mailboxFile;
081: }
082:
083: public final void setMailboxFile(File mailboxFile) {
084: this .mailboxFile = mailboxFile;
085: }
086:
087: public int getMessageCount() {
088: if (entries == null)
089: return 0;
090: return entries.size();
091: }
092:
093: public Message getMessage(MailboxEntry entry,
094: ProgressNotifier progressNotifier) {
095: try {
096: RandomAccessFile raf = mailboxFile.getRandomAccessFile("r");
097: String header = getMessageHeader(entry, raf);
098: Headers headers = Headers.parse(header);
099: String charset = null;
100: String contentType = headers.getValue(Headers.CONTENT_TYPE);
101: if (contentType != null)
102: charset = Utilities
103: .getCharsetFromContentType(contentType);
104: String body = getMessageBody(entry, raf, charset);
105: return new Message(header + "\r\n" + body, headers);
106: } catch (IOException e) {
107: Log.error(e);
108: return null;
109: }
110: }
111:
112: private String getMessageHeader(MailboxEntry entry,
113: RandomAccessFile raf) {
114: FastStringBuffer sb = new FastStringBuffer(8192);
115: try {
116: long offset = ((LocalMailboxEntry) entry).getMessageStart();
117: raf.seek(offset);
118: String text = raf.readLine();
119: if (!text.startsWith("From ")) {
120: Log.debug("LocalMailbox.getMessage expected \"From \"");
121: Log.debug("text = |" + text + "|");
122: Log.debug("offset = " + offset);
123: Debug.assertTrue(false);
124: return ""; // BUG!
125: }
126: while (true) {
127: text = raf.readLine();
128: if (text == null)
129: break; // End of file (shouldn't happen).
130: if (text.length() == 0)
131: break; // Reached end of header.
132: sb.append(text);
133: sb.append("\r\n");
134: }
135: } catch (IOException e) {
136: Log.error(e);
137: }
138: return sb.toString();
139: }
140:
141: // File pointer must be at the start of the message body.
142: private String getMessageBody(MailboxEntry entry,
143: RandomAccessFile raf, String charset) {
144: byte[] bytes = null;
145: try {
146: long bodyStart = raf.getFilePointer();
147: long size = ((LocalMailboxEntry) entry)
148: .getNextMessageStart()
149: - bodyStart;
150: Debug.assertTrue(size >= 0);
151: Debug.assertTrue(size < Integer.MAX_VALUE);
152: bytes = new byte[(int) size];
153: raf.readFully(bytes);
154: } catch (IOException e) {
155: Log.error(e);
156: return "";
157: }
158: try {
159: return new String(bytes, Utilities
160: .getEncodingFromCharset(charset));
161: } catch (UnsupportedEncodingException e) {
162: Log.error(e);
163: }
164: // Use platform's default character encoding.
165: return new String(bytes);
166: }
167:
168: public void getNewMessages() {
169: Log.error("LocalMailbox.getNewMessages is not implemented");
170: }
171:
172: public void createFolder() {
173: Log.error("LocalMailbox.createFolder is not implemented");
174: }
175:
176: public void deleteFolder() {
177: Log.error("LocalMailbox.deleteFolder is not implemented");
178: }
179:
180: public void saveToFolder() {
181: Log.error("LocalMailbox.saveToFolder is not implemented");
182: }
183:
184: public void moveToFolder() {
185: Log.error("LocalMailbox.moveToFolder is not implemented");
186: }
187:
188: public void delete() {
189: Editor editor = Editor.currentEditor();
190: if (lock()) {
191: try {
192: editor.setWaitCursor();
193: boolean advanceDot = false;
194: List toBeDeleted = getTaggedEntries();
195: if (toBeDeleted == null) {
196: Line line = editor.getDotLine();
197: if (!(line instanceof MailboxLine))
198: return;
199: toBeDeleted = new Vector();
200: toBeDeleted.add(((MailboxLine) line)
201: .getMailboxEntry());
202: advanceDot = true;
203: }
204: int size = toBeDeleted.size();
205: for (int i = 0; i < size; i++) {
206: MailboxEntry entry = (MailboxEntry) toBeDeleted
207: .get(i);
208: if (!entry.isDeleted()) {
209: entry.setFlags(entry.getFlags()
210: | MailboxEntry.DELETED);
211: dirty = true;
212: updateEntry(entry);
213: }
214: }
215: countMessages();
216: // Update message count in sidebar buffer list.
217: Sidebar.repaintBufferListInAllFrames();
218: if (advanceDot)
219: advanceDot(editor.getDotLine());
220: } finally {
221: unlock();
222: }
223: } else
224: editor.status("Mailbox is locked");
225: }
226:
227: public void undelete() {
228: Editor editor = Editor.currentEditor();
229: if (lock()) {
230: try {
231: editor.setWaitCursor();
232: boolean advanceDot = false;
233: List toBeUndeleted = getTaggedEntries();
234: if (toBeUndeleted == null) {
235: Line line = editor.getDotLine();
236: if (!(line instanceof MailboxLine))
237: return;
238: toBeUndeleted = new Vector();
239: toBeUndeleted.add(((MailboxLine) line)
240: .getMailboxEntry());
241: if (getBooleanProperty(Property.UNDELETE_ADVANCE_DOT))
242: advanceDot = true;
243: }
244: int size = toBeUndeleted.size();
245: for (int i = 0; i < size; i++) {
246: MailboxEntry entry = (MailboxEntry) toBeUndeleted
247: .get(i);
248: if (entry.isDeleted()) {
249: entry.setFlags(entry.getFlags()
250: & ~MailboxEntry.DELETED);
251: dirty = true;
252: updateEntry(entry);
253: }
254: }
255: countMessages();
256: // Update message count in sidebar buffer list.
257: Sidebar.repaintBufferListInAllFrames();
258: if (advanceDot)
259: advanceDot(editor.getDotLine());
260: } finally {
261: unlock();
262: }
263: } else
264: editor.status("Mailbox is locked");
265: }
266:
267: public void markRead() {
268: Editor editor = Editor.currentEditor();
269: if (lock()) {
270: try {
271: boolean advanceDot = false;
272: List list = getTaggedEntries();
273: if (list == null) {
274: Line line = editor.getDotLine();
275: if (!(line instanceof MailboxLine))
276: return;
277: list = new Vector();
278: list.add(((MailboxLine) line).getMailboxEntry());
279: advanceDot = true;
280: }
281: for (int i = 0; i < list.size(); i++) {
282: MailboxEntry entry = (MailboxEntry) list.get(i);
283: if ((entry.getFlags() & MailboxEntry.SEEN) == 0) {
284: entry.setFlags(entry.getFlags()
285: | MailboxEntry.SEEN);
286: dirty = true;
287: updateEntry(entry);
288: }
289: }
290: countMessages();
291: // Update message count in sidebar buffer list.
292: Sidebar.repaintBufferListInAllFrames();
293: if (advanceDot)
294: advanceDot(editor.getDotLine());
295: } finally {
296: unlock();
297: }
298: } else
299: editor.status("Mailbox is locked");
300: }
301:
302: public void markUnread() {
303: Editor editor = Editor.currentEditor();
304: if (lock()) {
305: try {
306: boolean advanceDot = false;
307: List list = getTaggedEntries();
308: if (list == null) {
309: Line line = editor.getDotLine();
310: if (!(line instanceof MailboxLine))
311: return;
312: list = new Vector();
313: list.add(((MailboxLine) line).getMailboxEntry());
314: advanceDot = true;
315: }
316: for (int i = 0; i < list.size(); i++) {
317: MailboxEntry entry = (MailboxEntry) list.get(i);
318: if ((entry.getFlags() & MailboxEntry.SEEN) == MailboxEntry.SEEN) {
319: entry.setFlags(entry.getFlags()
320: & ~MailboxEntry.SEEN);
321: dirty = true;
322: updateEntry(entry);
323: }
324: }
325: countMessages();
326: // Update message count in sidebar buffer list.
327: Sidebar.repaintBufferListInAllFrames();
328: if (advanceDot)
329: advanceDot(editor.getDotLine());
330: } finally {
331: unlock();
332: }
333: } else
334: editor.status("Mailbox is locked");
335: }
336:
337: public void flag() {
338: final Editor editor = Editor.currentEditor();
339: if (lock()) {
340: try {
341: boolean advanceDot = false;
342: List list = getTaggedEntries();
343: if (list == null) {
344: Line line = editor.getDotLine();
345: if (!(line instanceof MailboxLine))
346: return;
347: list = new ArrayList();
348: list.add(((MailboxLine) line).getMailboxEntry());
349: advanceDot = true;
350: }
351: for (int i = 0; i < list.size(); i++) {
352: MailboxEntry entry = (MailboxEntry) list.get(i);
353: entry.toggleFlag();
354: updateEntry(entry);
355: dirty = true;
356: }
357: if (advanceDot)
358: advanceDot(editor.getDotLine());
359: } finally {
360: unlock();
361: }
362: } else
363: editor.status("Mailbox is locked");
364: }
365:
366: public void setAnsweredFlag(MailboxEntry entry) {
367: if ((entry.getFlags() & MailboxEntry.ANSWERED) == 0) {
368: entry.setFlags(entry.getFlags() | MailboxEntry.ANSWERED);
369: setDirty(true);
370: updateEntry(entry);
371: }
372: }
373:
374: public void expunge() {
375: Log.error("LocalMailbox.expunge is not implemented");
376: }
377:
378: public int load() {
379: if (lock()) {
380: setBusy(true);
381: new Thread(loadRunnable).start();
382: setLoaded(true);
383: return LOAD_PENDING;
384: } else
385: return LOAD_FAILED;
386: }
387:
388: private Runnable loadRunnable = new Runnable() {
389: public void run() {
390: try {
391: readMailboxFile(null);
392: refreshBuffer();
393: } finally {
394: unlock();
395: setBusy(false);
396: Runnable completionRunnable = new Runnable() {
397: public void run() {
398: for (EditorIterator it = new EditorIterator(); it
399: .hasNext();) {
400: Editor ed = it.nextEditor();
401: View view = new View();
402: view.setDotEntry(getInitialEntry());
403: ed.setView(LocalMailbox.this , view);
404: if (ed.getBuffer() == LocalMailbox.this ) {
405: ed.bufferActivated(true);
406: ed.updateDisplay();
407: }
408: }
409: }
410: };
411: SwingUtilities.invokeLater(completionRunnable);
412: }
413: }
414: };
415:
416: protected void readMailboxFile(ProgressNotifier progressNotifier) {
417: Log.debug("LocalMailbox.readMailboxFile");
418: long start = System.currentTimeMillis();
419: Mbox mbox = Mbox.getInstance(mailboxFile);
420: if (mbox == null)
421: return;
422: if (mbox.lock()) {
423: entries = mbox.getEntries(progressNotifier);
424: mbox.unlock();
425: }
426: Log.debug("readMailboxFile "
427: + (System.currentTimeMillis() - start) + " ms");
428: }
429:
430: public void readMessage(Line line) {
431: readMessage(line, false);
432: }
433:
434: public void readMessageOtherWindow(Line line) {
435: readMessage(line, true);
436: }
437:
438: private void readMessage(Line line, boolean useOtherWindow) {
439: Editor editor = Editor.currentEditor();
440: MailboxEntry entry = ((MailboxLine) line).getMailboxEntry();
441: Buffer buf = null;
442: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
443: Buffer b = it.nextBuffer();
444: if (b instanceof MessageBuffer) {
445: if (((MessageBuffer) b).getMailboxEntry() == entry) {
446: buf = b;
447: break;
448: }
449: }
450: }
451: if (buf == null)
452: buf = new LocalMessageBuffer(this , entry);
453: activateMessageBuffer(editor, (MessageBuffer) buf,
454: useOtherWindow);
455: }
456:
457: protected boolean rewriteMailbox(boolean purge) {
458: Log.debug("rewriteMailbox");
459: long start = System.currentTimeMillis();
460: boolean succeeded = false;
461: try {
462: BufferedReader reader = new BufferedReader(
463: new InputStreamReader(mailboxFile.getInputStream(),
464: "ISO8859_1"));
465: File tempFile = Utilities.getTempFile(mailboxFile
466: .getParentFile());
467: BufferedWriter writer = new BufferedWriter(
468: new OutputStreamWriter(tempFile.getOutputStream(),
469: "ISO8859_1"));
470: long newOffsets[] = new long[entries.size()];
471: long offset = 0;
472: boolean skip = false;
473: int i = 0;
474: while (true) {
475: String text = reader.readLine();
476: if (text == null)
477: break;
478: if (text.startsWith("From ")) {
479: if (purge)
480: skip = ((MailboxEntry) entries.get(i))
481: .isDeleted();
482: else
483: skip = false;
484: if (skip)
485: newOffsets[i] = -1;
486: else {
487: newOffsets[i] = offset;
488: writer.write(text);
489: writer.write('\n');
490: offset += text.length() + 1;
491: }
492: // Headers.
493: boolean seenXJStatus = false;
494: while (true) {
495: text = reader.readLine();
496: if (text == null)
497: break;
498: if (text.length() == 0)
499: break; // End of headers.
500: if (!skip) {
501: if (text.startsWith("X-J-Status: ")) {
502: text = "X-J-Status: "
503: + getMessageStatus(i);
504: seenXJStatus = true;
505: }
506: writer.write(text);
507: writer.write('\n');
508: offset += text.length() + 1;
509: }
510: }
511: if (!skip) {
512: if (!seenXJStatus) {
513: writer.write("X-J-Status: "
514: + getMessageStatus(i));
515: writer.write('\n');
516: }
517: writer.write('\n');
518: offset += 1;
519: }
520: ++i;
521: } else {
522: // Body of message.
523: if (!skip) {
524: writer.write(text);
525: writer.write('\n');
526: offset += text.length() + 1;
527: }
528: }
529: }
530: writer.flush();
531: writer.close();
532: reader.close();
533: if (Utilities.deleteRename(tempFile, mailboxFile)) {
534: // Update offsets.
535: for (i = 0; i < entries.size(); i++) {
536: LocalMailboxEntry entry = (LocalMailboxEntry) entries
537: .get(i);
538: long newOffset = newOffsets[i];
539: if (newOffset == -1) {
540: if (!entry.isDeleted())
541: Debug.bug("expected entry " + i
542: + " to be deleted");
543: }
544: entry.setMessageStart(newOffset);
545: }
546: if (purge) {
547: // Copy entries to new list, skipping deleted entries.
548: Vector v = new Vector(entries.size(), 10);
549: for (i = 0; i < entries.size(); i++) {
550: MailboxEntry entry = (MailboxEntry) entries
551: .get(i);
552: if (!entry.isDeleted())
553: v.add(entry);
554: }
555: v.trimToSize();
556: entries = v;
557: }
558: // Update nextMessageStart for every entry.
559: LocalMailboxEntry this Entry = null;
560: LocalMailboxEntry lastEntry = null;
561: for (i = 0; i < entries.size(); i++) {
562: this Entry = (LocalMailboxEntry) entries.get(i);
563: if (lastEntry != null)
564: lastEntry.setNextMessageStart(this Entry
565: .getMessageStart());
566: lastEntry = this Entry;
567: }
568: if (this Entry != null)
569: this Entry.setNextMessageStart(offset);
570: // Success!
571: dirty = false;
572: succeeded = true;
573: }
574: } catch (IOException e) {
575: Log.error(e);
576: } finally {
577: long elapsed = System.currentTimeMillis() - start;
578: Log.debug("rewriteMailbox " + elapsed + " ms");
579: }
580: return succeeded;
581: }
582:
583: private final int getMessageStatus(int i) {
584: return ((MailboxEntry) entries.get(i)).getFlags();
585: }
586:
587: private static String localPrefix;
588:
589: private boolean isOwned() {
590: if (localPrefix == null)
591: localPrefix = Directories.getMailDirectory()
592: .canonicalPath().concat(LocalFile.getSeparator());
593: return mailboxFile.canonicalPath().startsWith(localPrefix);
594: }
595:
596: public void dispose() {
597: Log.debug("LocalMailbox.dispose");
598: Mbox.cleanup();
599: MailboxProperties.saveProperties(this );
600: if (isOwned()) {
601: Log.debug("mailbox is owned");
602: } else {
603: Log.debug("mailbox is foreign");
604: return;
605: }
606: Runnable disposeRunnable = new Runnable() {
607: public void run() {
608: try {
609: Log
610: .debug("disposeRunnable.run() calling acquire()...");
611: acquire(); // Blocks, may throw InterruptedException.
612: Log
613: .debug("disposeRunnable.run() back from acquire()");
614: clearRecent();
615: if (dirty) {
616: final Object pending = new Object();
617: Editor.getPendingOperations().add(pending);
618: Log
619: .debug("disposeRunnable.run() calling rewriteMailbox()...");
620: rewriteMailbox(false);
621: Log
622: .debug("disposeRunnable.run() back from rewriteMailbox()");
623: Editor.getPendingOperations().remove(pending);
624: }
625: release();
626: Log
627: .debug("disposeRunnable.run() back from release()");
628: } catch (InterruptedException e) {
629: Log.error(e);
630: }
631: }
632: };
633: new Thread(disposeRunnable).start();
634: }
635:
636: protected void finalize() throws Throwable {
637: Log.debug("LocalMailbox.finalize");
638: super .finalize();
639: }
640:
641: public String toString() {
642: final String name;
643: if (isOwned())
644: name = mailboxFile.getParentFile().getName();
645: else if (mailboxFile.isLocal())
646: name = mailboxFile.canonicalPath();
647: else
648: name = mailboxFile.netPath();
649: final int newMessageCount = getNewMessageCount();
650: if (newMessageCount > 0) {
651: FastStringBuffer sb = new FastStringBuffer(name);
652: sb.append(" (");
653: sb.append(newMessageCount);
654: sb.append(" new)");
655: return sb.toString();
656: } else
657: return name;
658: }
659: }
|