0001: /*
0002: * ImapMailbox.java
0003: *
0004: * Copyright (C) 2000-2003 Peter Graves
0005: * $Id: ImapMailbox.java,v 1.5 2003/08/09 17:41:41 piso Exp $
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License
0009: * as published by the Free Software Foundation; either version 2
0010: * of the License, or (at your option) any later version.
0011: *
0012: * This program is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0015: * GNU General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * along with this program; if not, write to the Free Software
0019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0020: */
0021:
0022: package org.armedbear.j.mail;
0023:
0024: import java.io.UnsupportedEncodingException;
0025: import java.net.MalformedURLException;
0026: import java.util.ArrayList;
0027: import java.util.Collections;
0028: import java.util.HashMap;
0029: import java.util.Iterator;
0030: import java.util.List;
0031: import javax.swing.SwingUtilities;
0032: import org.armedbear.j.BackgroundProcess;
0033: import org.armedbear.j.Buffer;
0034: import org.armedbear.j.BufferIterator;
0035: import org.armedbear.j.Debug;
0036: import org.armedbear.j.Editor;
0037: import org.armedbear.j.EditorIterator;
0038: import org.armedbear.j.File;
0039: import org.armedbear.j.FastStringBuffer;
0040: import org.armedbear.j.Frame;
0041: import org.armedbear.j.Headers;
0042: import org.armedbear.j.InputDialog;
0043: import org.armedbear.j.Line;
0044: import org.armedbear.j.Log;
0045: import org.armedbear.j.MessageDialog;
0046: import org.armedbear.j.ProgressNotifier;
0047: import org.armedbear.j.Property;
0048: import org.armedbear.j.Sidebar;
0049: import org.armedbear.j.StatusBar;
0050: import org.armedbear.j.StatusBarProgressNotifier;
0051: import org.armedbear.j.Utilities;
0052: import org.armedbear.j.View;
0053:
0054: public final class ImapMailbox extends Mailbox {
0055: private static final int DEFAULT_PORT = 143;
0056:
0057: private final ImapSession session;
0058: private final String folderName;
0059:
0060: private int messageCount = -1;
0061: private int uidValidity;
0062: private int uidLast;
0063: private ImapMailboxCache mailboxCache;
0064: private boolean cancelled;
0065: private Thread backgroundThread;
0066:
0067: public ImapMailbox(ImapURL url, ImapSession session) {
0068: super (url);
0069: this .session = session;
0070: session.setMailbox(this );
0071: String tunnel = getStringProperty(Property.TUNNEL);
0072: if (tunnel != null) {
0073: Log.debug("tunnel = |" + tunnel + "|");
0074: session.setTunnel(tunnel);
0075: }
0076: folderName = session.getFolderName();
0077: Debug.assertTrue(folderName.equals(url.getFolderName()));
0078: supportsUndo = false;
0079: type = TYPE_MAILBOX;
0080: mode = MailboxMode.getMode();
0081: formatter = mode.getFormatter(this );
0082: readOnly = true;
0083: setInitialized(true);
0084: Log.debug("ImapMailbox constructor ".concat(url
0085: .getCanonicalName()));
0086: }
0087:
0088: public String getFileNameForDisplay() {
0089: FastStringBuffer sb = new FastStringBuffer(64);
0090: sb.append(url.toString());
0091: String limitPattern = getLimitPattern();
0092: if (limitPattern != null) {
0093: sb.append(' ');
0094: sb.append(limitPattern);
0095: }
0096: return sb.toString();
0097: }
0098:
0099: public String getName() {
0100: FastStringBuffer sb = new FastStringBuffer();
0101: sb.append('{');
0102: sb.append(session.getUser());
0103: sb.append('@');
0104: sb.append(session.getHost());
0105: sb.append(':');
0106: sb.append(session.getPort());
0107: sb.append('}');
0108: sb.append(folderName);
0109: return sb.toString();
0110: }
0111:
0112: public final ImapSession getSession() {
0113: return session;
0114: }
0115:
0116: public final String getFolderName() {
0117: return folderName;
0118: }
0119:
0120: public final int getUidValidity() {
0121: return uidValidity;
0122: }
0123:
0124: public final int getMessageCount() {
0125: return messageCount;
0126: }
0127:
0128: public synchronized long getLastErrorMillis() {
0129: return session.getLastErrorMillis();
0130: }
0131:
0132: public void setAlertText(final String s) {
0133: Log.debug("alert = " + s);
0134: Runnable r = new Runnable() {
0135: public void run() {
0136: Editor.currentEditor().status(s);
0137: }
0138: };
0139: SwingUtilities.invokeLater(r);
0140: }
0141:
0142: public void setStatusText(final String s) {
0143: Log.debug("status = " + s);
0144: Runnable r = new Runnable() {
0145: public void run() {
0146: Editor.currentEditor().status(s);
0147: }
0148: };
0149: SwingUtilities.invokeLater(r);
0150: }
0151:
0152: public void messageExpunged(int messageNumber) {
0153: // Invalidate message count.
0154: messageCount = -1;
0155: }
0156:
0157: public void expunge() {
0158: Runnable expungeRunnable = new Runnable() {
0159: public void run() {
0160: try {
0161: if (session.verifyConnected()
0162: && session.verifySelected(folderName)) {
0163: if (session.isReadOnly()) {
0164: Log
0165: .debug("expunge - read-only - reselecting...");
0166: session.reselect(folderName);
0167: if (session.isReadOnly()) {
0168: Log
0169: .error("expunge - mailbox is read-only");
0170: readOnlyError();
0171: return;
0172: }
0173: }
0174: if (messageCache != null)
0175: messageCache
0176: .removeDeletedEntries(Collections
0177: .unmodifiableList(entries));
0178: if (session.close()) {
0179: Log
0180: .debug("expunge back from close(), calling reselect()");
0181: if (session.reselect(folderName)) {
0182: Log
0183: .debug("expunge back from reselect()");
0184: getAllMessageHeaders();
0185: refreshBuffer();
0186: setBusy(false);
0187: for (int i = 0; i < Editor
0188: .getFrameCount(); i++) {
0189: Frame frame = Editor.getFrame(i);
0190: if (frame != null) {
0191: StatusBar statusBar = frame
0192: .getStatusBar();
0193: if (statusBar != null)
0194: statusBar.setText(null);
0195: }
0196: }
0197: updateDisplay();
0198: return;
0199: }
0200: }
0201: // Error!
0202: error("Operation failed", "Expunge");
0203: }
0204: } finally {
0205: setBusy(false);
0206: unlock();
0207: }
0208: }
0209: };
0210: if (lock()) {
0211: setBusy(true);
0212: saveDisplayState();
0213: new Thread(expungeRunnable).start();
0214: }
0215: }
0216:
0217: public synchronized int load() {
0218: if (isLoaded()) {
0219: return LOAD_COMPLETED;
0220: } else if (lock()) {
0221: Debug.assertTrue(backgroundThread == null);
0222: backgroundThread = new Thread(loadProcess);
0223: backgroundThread.start();
0224: setLoaded(true);
0225: return LOAD_PENDING;
0226: } else {
0227: // Not loaded, lock() failed. Shouldn't happen.
0228: Debug.bug("ImapMailbox.load can't lock mailbox");
0229: return LOAD_FAILED;
0230: }
0231: }
0232:
0233: private BackgroundProcess loadProcess = new BackgroundProcess() {
0234: public void run() {
0235: Runnable completionRunnable = null;
0236: try {
0237: cancelled = false;
0238: setBusy(true);
0239: setBackgroundProcess(this );
0240: if (getAllMessageHeaders()) {
0241: refreshBuffer();
0242: completionRunnable = new Runnable() {
0243: public void run() {
0244: setBusy(false);
0245: for (EditorIterator it = new EditorIterator(); it
0246: .hasNext();) {
0247: Editor ed = it.nextEditor();
0248: View view = new View();
0249: view.setDotEntry(getInitialEntry());
0250: ed.setView(ImapMailbox.this , view);
0251: if (ed.getBuffer() == ImapMailbox.this ) {
0252: ed.bufferActivated(true);
0253: ed.updateDisplay();
0254: }
0255: }
0256: }
0257: };
0258: } else {
0259: // Error or user cancelled.
0260: completionRunnable = new Runnable() {
0261: public void run() {
0262: if (Editor.getBufferList().contains(
0263: ImapMailbox.this ))
0264: kill();
0265: for (EditorIterator it = new EditorIterator(); it
0266: .hasNext();)
0267: it.nextEditor().updateDisplay();
0268: }
0269: };
0270: }
0271: } finally {
0272: setBackgroundProcess(null);
0273: backgroundThread = null;
0274: unlock();
0275: if (completionRunnable != null)
0276: SwingUtilities.invokeLater(completionRunnable);
0277: }
0278: }
0279:
0280: public void cancel() {
0281: abort();
0282: }
0283: };
0284:
0285: private void abort() {
0286: Log.debug("ImapMailbox.abort");
0287: cancelled = true;
0288: if (backgroundThread != null && backgroundThread.isAlive())
0289: backgroundThread.interrupt();
0290: session.disconnect();
0291: }
0292:
0293: public final void getNewMessages() {
0294: if (lock())
0295: getNewMessages(true);
0296: else
0297: Editor.currentEditor().status("Mailbox is locked");
0298: }
0299:
0300: public void getNewMessages(boolean interactive) {
0301: Debug.assertTrue(isLocked());
0302: setBusy(true);
0303: if (interactive)
0304: saveDisplayState();
0305: Debug.assertTrue(backgroundThread == null);
0306: backgroundThread = new Thread(new GetNewMessagesProcess(
0307: interactive));
0308: backgroundThread.start();
0309: }
0310:
0311: private class GetNewMessagesProcess implements BackgroundProcess {
0312: private final boolean interactive;
0313:
0314: public GetNewMessagesProcess(boolean interactive) {
0315: this .interactive = interactive;
0316: }
0317:
0318: public void run() {
0319: cancelled = false;
0320: setBackgroundProcess(this );
0321: boolean changed = false;
0322: if (interactive)
0323: changed = clearRecent();
0324: try {
0325: // verifyConnected() sends a NOOP command to the server, which
0326: // gives the server a chance to report changes to the mailbox.
0327: if (!session.verifyConnected()) {
0328: Log
0329: .error("GetNewMessagesProcess.run can't connect");
0330: return;
0331: }
0332: if (cancelled) {
0333: Log.debug("cancelled, disconnecting...");
0334: session.disconnect();
0335: return;
0336: }
0337: if (!session.verifySelected(folderName)) {
0338: Log.error("GetNewMessagesProcess.run can't select "
0339: + folderName);
0340: return;
0341: }
0342: if (cancelled) {
0343: Log.debug("cancelled, disconnecting...");
0344: session.disconnect();
0345: return;
0346: }
0347: if (session.isReadOnly()) {
0348: Log.warn("GetNewMessagesProcess.run " + folderName
0349: + " is read-only, returning...");
0350: return;
0351: }
0352: if (uidValidity != session.getUidValidity()) {
0353: getAllMessageHeaders();
0354: changed = true;
0355: } else if (interactive) {
0356: changed = getNewMessageHeaders() || changed;
0357: } else {
0358: // Not interactive.
0359: if (session.getMessageCount() != messageCount) {
0360: Log.debug("session.getMessageCount() = "
0361: + session.getMessageCount()
0362: + " mailbox message count = "
0363: + messageCount);
0364: changed = getNewMessageHeaders();
0365: }
0366: }
0367: if (cancelled) {
0368: Log.debug("cancelled, disconnecting...");
0369: session.disconnect();
0370: }
0371: } finally {
0372: setBackgroundProcess(null);
0373: backgroundThread = null;
0374: if (changed) {
0375: refreshBuffer();
0376: setBusy(false);
0377: updateDisplay();
0378: newMessagesStatus();
0379: } else {
0380: setBusy(false);
0381: for (EditorIterator it = new EditorIterator(); it
0382: .hasNext();) {
0383: Editor ed = it.nextEditor();
0384: if (ed != null
0385: && ed.getBuffer() == ImapMailbox.this )
0386: ed.setDefaultCursor();
0387: }
0388: }
0389: setLastCheckMillis(System.currentTimeMillis());
0390: unlock();
0391: }
0392: }
0393:
0394: public void cancel() {
0395: abort();
0396: }
0397: }
0398:
0399: public void createFolder() {
0400: final Editor editor = Editor.currentEditor();
0401: final String input = InputDialog.showInputDialog(editor,
0402: "Folder:", "Create Folder");
0403: if (input == null || input.length() == 0)
0404: return;
0405: final String name = extractFolderName(input);
0406: if (name == null)
0407: return;
0408: Runnable createRunnable = new Runnable() {
0409: public void run() {
0410: boolean succeeded = false;
0411: try {
0412: if (session.verifyConnected()
0413: && session.verifySelected(folderName)) {
0414: session.setEcho(true);
0415: session.writeTagged("create " + name);
0416: succeeded = session.getResponse() == ImapSession.OK;
0417: session.setEcho(false);
0418: }
0419: } finally {
0420: unlock();
0421: setBusy(false);
0422: Editor.updateDisplayLater(ImapMailbox.this );
0423: if (succeeded)
0424: success("Folder created");
0425: else
0426: error("Unable to create folder", "Error");
0427: }
0428: }
0429: };
0430: // Even though we're not changing this mailbox per se, we need to lock
0431: // it here so we have exclusive use of the session.
0432: if (lock()) {
0433: setBusy(true);
0434: new Thread(createRunnable).start();
0435: }
0436: }
0437:
0438: public void deleteFolder() {
0439: final Editor editor = Editor.currentEditor();
0440: final String input = InputDialog.showInputDialog(editor,
0441: "Folder:", "Delete Folder");
0442: if (input == null || input.length() == 0)
0443: return;
0444: final String name = extractFolderName(input);
0445: if (name == null)
0446: return;
0447: String message = "Delete folder \"" + name + "\" on "
0448: + session.getHost() + "?";
0449: if (!editor.confirm("Delete Folder", message))
0450: return;
0451: Runnable deleteFolderRunnable = new Runnable() {
0452: public void run() {
0453: boolean succeeded = false;
0454: try {
0455: if (session.verifyConnected()
0456: && session.verifySelected(folderName)) {
0457: session.setEcho(true);
0458: session.writeTagged("delete " + name);
0459: succeeded = session.getResponse() == ImapSession.OK;
0460: session.setEcho(false);
0461: }
0462: } finally {
0463: unlock();
0464: setBusy(false);
0465: Editor.updateDisplayLater(ImapMailbox.this );
0466: if (succeeded)
0467: success("Folder deleted");
0468: else
0469: error("Unable to delete folder", "Error");
0470: }
0471: }
0472: };
0473: // Even though we're not changing this mailbox per se, we need to lock
0474: // it here so we have exclusive use of the session.
0475: if (lock()) {
0476: setBusy(true);
0477: new Thread(deleteFolderRunnable).start();
0478: }
0479: }
0480:
0481: public void saveToFolder() {
0482: final Editor editor = Editor.currentEditor();
0483: boolean advanceDot = false;
0484: List list = getTaggedEntries();
0485: if (list == null) {
0486: Line line = editor.getDotLine();
0487: if (!(line instanceof MailboxLine))
0488: return;
0489: advanceDot = true;
0490: list = new ArrayList();
0491: list.add(((MailboxLine) line).getMailboxEntry());
0492: }
0493: final List toBeCopied = list;
0494: final int count = toBeCopied.size();
0495: String title;
0496: if (count > 1)
0497: title = "Save Tagged Messages to Folder";
0498: else
0499: title = "Save Message to Folder";
0500: FastStringBuffer sb = new FastStringBuffer("Save ");
0501: sb.append(count);
0502: sb.append(" message");
0503: if (count > 1)
0504: sb.append('s');
0505: sb.append(" to:");
0506: final String destination = ChooseFolderDialog.chooseFolder(
0507: editor, sb.toString(), title);
0508: if (destination == null)
0509: return;
0510: final Line dotLine = advanceDot ? editor.getDotLine() : null;
0511: Runnable saveRunnable = new Runnable() {
0512: public void run() {
0513: boolean succeeded = false;
0514: try {
0515: if (session.verifyConnected()
0516: && session.verifySelected(folderName)) {
0517: if (destination.startsWith("mailbox:")) {
0518: // Destination is local.
0519: succeeded = saveLocal(toBeCopied,
0520: destination, false);
0521: } else {
0522: session.setEcho(true);
0523: final String messageSet = getMessageSet(toBeCopied);
0524: FastStringBuffer sbuf = new FastStringBuffer(
0525: "uid copy ");
0526: sbuf.append(messageSet);
0527: sbuf.append(' ');
0528: sbuf.append(destination);
0529: if (session.writeTagged(sbuf.toString()))
0530: if (session.getResponse() == ImapSession.OK)
0531: succeeded = true;
0532: session.setEcho(false);
0533: }
0534: }
0535: } finally {
0536: if (succeeded && dotLine != null)
0537: advanceDot(dotLine);
0538: setBusy(false);
0539: unlock();
0540: editor.updateDisplayLater();
0541: if (succeeded) {
0542: FastStringBuffer sbuf = new FastStringBuffer(
0543: "Saved ");
0544: sbuf.append(toBeCopied.size());
0545: sbuf.append(" message");
0546: if (toBeCopied.size() != 1)
0547: sbuf.append('s');
0548: success(sbuf.toString());
0549: } else
0550: error("Save failed", "Error");
0551: }
0552: }
0553: };
0554: if (lock()) {
0555: setBusy(true);
0556: new Thread(saveRunnable).start();
0557: }
0558: }
0559:
0560: public void moveToFolder() {
0561: final Editor editor = Editor.currentEditor();
0562: boolean advanceDot = false;
0563: List list = getTaggedEntries();
0564: if (list == null) {
0565: Line line = editor.getDotLine();
0566: if (!(line instanceof MailboxLine))
0567: return;
0568: advanceDot = true;
0569: list = new ArrayList();
0570: list.add(((MailboxLine) line).getMailboxEntry());
0571: }
0572: final List toBeMoved = list;
0573: final int count = toBeMoved.size();
0574: String title;
0575: if (count > 1)
0576: title = "Move Tagged Messages to Folder";
0577: else
0578: title = "Move Message to Folder";
0579: FastStringBuffer sb = new FastStringBuffer("Move ");
0580: sb.append(count);
0581: sb.append(" message");
0582: if (count > 1)
0583: sb.append('s');
0584: sb.append(" to:");
0585: final String destination = ChooseFolderDialog.chooseFolder(
0586: editor, sb.toString(), title);
0587: if (destination == null)
0588: return;
0589: final Line dotLine = advanceDot ? editor.getDotLine() : null;
0590: Runnable moveRunnable = new Runnable() {
0591: public void run() {
0592: boolean succeeded = false;
0593: String errorText = "Move failed";
0594: try {
0595: succeeded = moveToFolder(toBeMoved, destination);
0596: } catch (MailException e) {
0597: errorText = e.getMessage();
0598: } finally {
0599: // Update message count in sidebar buffer list. We should
0600: // do this even if there was an error, since some messages
0601: // may have been moved successfully.
0602: countMessages();
0603: Sidebar.repaintBufferListInAllFrames();
0604: if (succeeded && dotLine != null)
0605: advanceDot(dotLine);
0606: setBusy(false);
0607: unlock();
0608: editor.updateDisplayLater();
0609: if (succeeded) {
0610: FastStringBuffer sbuf = new FastStringBuffer(
0611: "Moved ");
0612: sbuf.append(toBeMoved.size());
0613: sbuf.append(" message");
0614: if (toBeMoved.size() != 1)
0615: sbuf.append('s');
0616: success(sbuf.toString());
0617: } else
0618: error(errorText, "Error");
0619: }
0620: }
0621: };
0622: if (lock()) {
0623: setBusy(true);
0624: new Thread(moveRunnable).start();
0625: }
0626: }
0627:
0628: private boolean moveToFolder(List toBeMoved, String destination)
0629: throws MailException {
0630: if (destination == null)
0631: return false;
0632: if (!destination.startsWith("mailbox:")) {
0633: // Not local. Extract folder name from URL.
0634: destination = extractFolderName(destination);
0635: if (destination == null)
0636: return false;
0637: }
0638: boolean succeeded = false;
0639: if (session.verifyConnected()
0640: && session.verifySelected(folderName)) {
0641: if (session.isReadOnly()) {
0642: Log.debug("moveToFolder " + folderName
0643: + " is read-only - reselecting...");
0644: session.reselect(folderName);
0645: if (session.isReadOnly())
0646: throw new MailException("Mailbox " + folderName
0647: + " is read-only");
0648: }
0649: if (destination.startsWith("mailbox:")) {
0650: // Destination is local.
0651: succeeded = saveLocal(toBeMoved, destination, true);
0652: } else {
0653: // Destination is an IMAP folder.
0654: session.setEcho(true);
0655: final String messageSet = getMessageSet(toBeMoved);
0656: FastStringBuffer sb = new FastStringBuffer("uid copy ");
0657: sb.append(messageSet);
0658: sb.append(' ');
0659: sb.append(destination);
0660: session.writeTagged(sb.toString());
0661: if (session.getResponse() == ImapSession.OK) {
0662: sb.setLength(0);
0663: sb.append("uid store ");
0664: sb.append(messageSet);
0665: sb.append(" +flags.silent (\\deleted)");
0666: session.writeTagged(sb.toString());
0667: if (session.getResponse() == ImapSession.OK) {
0668: succeeded = true;
0669: for (int i = 0; i < toBeMoved.size(); i++) {
0670: ImapMailboxEntry entry = (ImapMailboxEntry) toBeMoved
0671: .get(i);
0672: entry.setFlags(entry.getFlags()
0673: | MailboxEntry.DELETED);
0674: updateEntry(entry);
0675: }
0676: }
0677: }
0678: session.setEcho(false);
0679: }
0680: }
0681: return succeeded;
0682: }
0683:
0684: private boolean saveLocal(List toBeSaved, String destination,
0685: boolean delete) {
0686: File file = File.getInstance(destination.substring(8));
0687: if (file == null)
0688: return false;
0689: if (!file.isLocal())
0690: return false;
0691: Mbox mbox = Mbox.getInstance(file);
0692: if (!mbox.lock())
0693: return false;
0694: boolean error = false;
0695: for (int i = 0; i < toBeSaved.size(); i++) {
0696: ImapMailboxEntry entry = (ImapMailboxEntry) toBeSaved
0697: .get(i);
0698: Message message = getMessage(entry, null);
0699: if (message != null
0700: && mbox.appendMessage(message, entry.getFlags()
0701: & ~MailboxEntry.TAGGED)) {
0702: if (delete) {
0703: session.writeTagged("uid store " + entry.getUid()
0704: + " +flags.silent (\\deleted)");
0705: if (session.getResponse() == ImapSession.OK) {
0706: entry.setFlags(entry.getFlags()
0707: | MailboxEntry.DELETED);
0708: updateEntry(entry);
0709: } else {
0710: error = true;
0711: break;
0712: }
0713: }
0714: } else {
0715: error = true;
0716: break;
0717: }
0718: }
0719: mbox.unlock();
0720: mbox.updateViews();
0721: return !error;
0722: }
0723:
0724: public String extractFolderName(String input) {
0725: if (input.startsWith("{")) {
0726: try {
0727: ImapURL targetUrl = new ImapURL(input);
0728: if (!url.getHost().equals(targetUrl.getHost())) {
0729: // We don't support operations involving folders on other
0730: // servers.
0731: return null;
0732: }
0733: String name = targetUrl.getFolderName();
0734: Log.debug("folder name = |" + name + "|");
0735: return name;
0736: } catch (MalformedURLException e) {
0737: Log.error(e);
0738: return null;
0739: }
0740: } else {
0741: // Assume input is already a relative path.
0742: return input;
0743: }
0744: }
0745:
0746: public void delete() {
0747: final Editor editor = Editor.currentEditor();
0748: boolean advanceDot = false;
0749: List list = getTaggedEntries();
0750: if (list == null) {
0751: Line line = editor.getDotLine();
0752: if (!(line instanceof MailboxLine))
0753: return;
0754: advanceDot = true;
0755: list = new ArrayList();
0756: list.add(((MailboxLine) line).getMailboxEntry());
0757: }
0758: final List toBeDeleted = list;
0759: final Line dotLine = advanceDot ? editor.getDotLine() : null;
0760: Runnable deleteRunnable = new Runnable() {
0761: public void run() {
0762: boolean succeeded = false;
0763: String errorText = "Delete failed";
0764: try {
0765: succeeded = delete(toBeDeleted);
0766: } catch (MailException e) {
0767: errorText = e.getMessage();
0768: } finally {
0769: if (succeeded) {
0770: // Update message count in sidebar buffer list.
0771: countMessages();
0772: Sidebar.repaintBufferListInAllFrames();
0773: if (dotLine != null)
0774: advanceDot(dotLine);
0775: }
0776: setBusy(false);
0777: unlock();
0778: editor.updateDisplayLater();
0779: if (!succeeded)
0780: error(errorText, "Error");
0781: }
0782: }
0783: };
0784: if (lock()) {
0785: setBusy(true);
0786: new Thread(deleteRunnable).start();
0787: }
0788: }
0789:
0790: private boolean delete(List toBeDeleted) throws MailException {
0791: boolean succeeded = false;
0792: if (session.verifyConnected()
0793: && session.verifySelected(folderName)) {
0794: if (session.isReadOnly()) {
0795: Log.debug("delete " + folderName
0796: + " is read-only - reselecting...");
0797: session.reselect(folderName);
0798: if (session.isReadOnly())
0799: throw new MailException("Mailbox " + folderName
0800: + " is read-only");
0801: }
0802: session.setEcho(true);
0803: session.uidStore(getMessageSet(toBeDeleted),
0804: "+flags.silent (\\deleted)");
0805: if (session.getResponse() == ImapSession.OK) {
0806: succeeded = true;
0807: for (int i = 0; i < toBeDeleted.size(); i++) {
0808: ImapMailboxEntry entry = (ImapMailboxEntry) toBeDeleted
0809: .get(i);
0810: entry.setFlags(entry.getFlags()
0811: | MailboxEntry.DELETED);
0812: updateEntry(entry);
0813: }
0814: }
0815: session.setEcho(false);
0816: }
0817: return succeeded;
0818: }
0819:
0820: public void undelete() {
0821: storeFlagsInternal(ACTION_UNDELETE);
0822: }
0823:
0824: public void markRead() {
0825: storeFlagsInternal(ACTION_MARK_READ);
0826: }
0827:
0828: public void markUnread() {
0829: storeFlagsInternal(ACTION_MARK_UNREAD);
0830: }
0831:
0832: private static final int ACTION_UNDELETE = 0;
0833: private static final int ACTION_MARK_UNREAD = 1;
0834: private static final int ACTION_MARK_READ = 2;
0835:
0836: private void storeFlagsInternal(final int action) {
0837: final Editor editor = Editor.currentEditor();
0838: boolean advanceDot = false;
0839: List list = getTaggedEntries();
0840: if (list == null) {
0841: Line line = editor.getDotLine();
0842: if (!(line instanceof MailboxLine))
0843: return;
0844: if (action == ACTION_UNDELETE)
0845: advanceDot = getBooleanProperty(Property.UNDELETE_ADVANCE_DOT);
0846: else
0847: advanceDot = true;
0848: list = new ArrayList();
0849: list.add(((MailboxLine) line).getMailboxEntry());
0850: }
0851: final List entriesToBeProcessed = list;
0852: final Line dotLine = advanceDot ? editor.getDotLine() : null;
0853: Runnable storeFlagsRunnable = new Runnable() {
0854: public void run() {
0855: try {
0856: if (session.verifyConnected()
0857: && session.verifySelected(folderName)) {
0858: if (session.isReadOnly()) {
0859: Log
0860: .debug("storeFlagsInternal - read-only - reselecting...");
0861: session.reselect(folderName);
0862: if (session.isReadOnly()) {
0863: readOnlyError();
0864: return;
0865: }
0866: }
0867: session.setEcho(true);
0868: final String messageSet = getMessageSet(entriesToBeProcessed);
0869: switch (action) {
0870: case ACTION_UNDELETE:
0871: session.uidStore(messageSet,
0872: "-flags.silent (\\deleted)");
0873: break;
0874: case ACTION_MARK_READ:
0875: session.uidStore(messageSet,
0876: "+flags.silent (\\seen)");
0877: break;
0878: case ACTION_MARK_UNREAD:
0879: session.uidStore(messageSet,
0880: "-flags.silent (\\seen)");
0881: break;
0882: default:
0883: Debug.assertTrue(false);
0884: break;
0885: }
0886: if (session.getResponse() == ImapSession.OK) {
0887: for (int i = 0; i < entriesToBeProcessed
0888: .size(); i++) {
0889: ImapMailboxEntry entry = (ImapMailboxEntry) entriesToBeProcessed
0890: .get(i);
0891: switch (action) {
0892: case ACTION_UNDELETE:
0893: entry.setFlags(entry.getFlags()
0894: & ~MailboxEntry.DELETED);
0895: break;
0896: case ACTION_MARK_READ:
0897: entry.setFlags(entry.getFlags()
0898: | MailboxEntry.SEEN);
0899: break;
0900: case ACTION_MARK_UNREAD:
0901: entry.setFlags(entry.getFlags()
0902: & ~MailboxEntry.SEEN);
0903: break;
0904: default:
0905: Debug.assertTrue(false);
0906: break;
0907: }
0908: updateEntry(entry);
0909: }
0910: if (dotLine != null)
0911: advanceDot(dotLine);
0912: }
0913: session.setEcho(false);
0914: }
0915: countMessages();
0916: } finally {
0917: setBusy(false);
0918: unlock();
0919: editor.updateDisplayLater();
0920: // Update message count in sidebar buffer list.
0921: Sidebar.repaintBufferListInAllFrames();
0922: }
0923: }
0924: };
0925: if (lock()) {
0926: setBusy(true);
0927: new Thread(storeFlagsRunnable).start();
0928: }
0929: }
0930:
0931: public void flag() {
0932: final Editor editor = Editor.currentEditor();
0933: boolean advanceDot = false;
0934: List list = getTaggedEntries();
0935: if (list == null) {
0936: Line line = editor.getDotLine();
0937: if (!(line instanceof MailboxLine))
0938: return;
0939: advanceDot = true;
0940: list = new ArrayList();
0941: list.add(((MailboxLine) line).getMailboxEntry());
0942: }
0943: final List entriesToBeSet = new ArrayList();
0944: final List entriesToBeCleared = new ArrayList();
0945: for (int i = 0; i < list.size(); i++) {
0946: MailboxEntry entry = (MailboxEntry) list.get(i);
0947: if (entry.isFlagged())
0948: entriesToBeCleared.add(entry);
0949: else
0950: entriesToBeSet.add(entry);
0951: }
0952: final Line dotLine = advanceDot ? editor.getDotLine() : null;
0953: Runnable flagRunnable = new Runnable() {
0954: public void run() {
0955: try {
0956: if (session.verifyConnected()
0957: && session.verifySelected(folderName)) {
0958: if (session.isReadOnly()) {
0959: Log
0960: .debug("storeFlagsInternal - read-only - reselecting...");
0961: session.reselect(folderName);
0962: if (session.isReadOnly()) {
0963: readOnlyError();
0964: return;
0965: }
0966: }
0967: boolean error = false;
0968: session.setEcho(true);
0969: if (entriesToBeSet.size() > 0) {
0970: session.uidStore(
0971: getMessageSet(entriesToBeSet),
0972: "+flags.silent (\\flagged)");
0973: if (session.getResponse() == ImapSession.OK) {
0974: for (int i = 0; i < entriesToBeSet
0975: .size(); i++) {
0976: MailboxEntry entry = (MailboxEntry) entriesToBeSet
0977: .get(i);
0978: entry.flag();
0979: updateEntry(entry);
0980: }
0981: } else
0982: error = true;
0983: }
0984: if (!error && entriesToBeCleared.size() > 0) {
0985: session.uidStore(
0986: getMessageSet(entriesToBeCleared),
0987: "-flags.silent (\\flagged)");
0988: if (session.getResponse() == ImapSession.OK) {
0989: for (int i = 0; i < entriesToBeCleared
0990: .size(); i++) {
0991: MailboxEntry entry = (MailboxEntry) entriesToBeCleared
0992: .get(i);
0993: entry.unflag();
0994: updateEntry(entry);
0995: }
0996: } else
0997: error = true;
0998: }
0999: session.setEcho(false);
1000: if (!error && dotLine != null)
1001: advanceDot(dotLine);
1002: }
1003: } finally {
1004: setBusy(false);
1005: unlock();
1006: editor.updateDisplayLater();
1007: // Update message count in sidebar buffer list.
1008: Sidebar.repaintBufferListInAllFrames();
1009: }
1010: }
1011: };
1012: if (lock()) {
1013: setBusy(true);
1014: new Thread(flagRunnable).start();
1015: }
1016: }
1017:
1018: public void setAnsweredFlag(final MailboxEntry entry) {
1019: Runnable setAnsweredFlagRunnable = new Runnable() {
1020: public void run() {
1021: try {
1022: if (session.verifyConnected()
1023: && session.verifySelected(folderName)) {
1024: session.setEcho(true);
1025: session.uidStore(((ImapMailboxEntry) entry)
1026: .getUid(), "+flags (\\answered)");
1027: if (session.getResponse() == ImapSession.OK) {
1028: entry.setFlags(entry.getFlags()
1029: | MailboxEntry.ANSWERED);
1030: updateEntry(entry);
1031: }
1032: session.setEcho(false);
1033: }
1034: } finally {
1035: setBusy(false);
1036: unlock();
1037: Editor.updateDisplayLater(ImapMailbox.this );
1038: }
1039: }
1040: };
1041: if (lock()) {
1042: setBusy(true);
1043: new Thread(setAnsweredFlagRunnable).start();
1044: }
1045: }
1046:
1047: private List retrieveMessageHeaders(int uidBegin, int uidEnd,
1048: boolean recent) {
1049: Log.debug("retrieveMessageHeaders " + folderName + " "
1050: + uidBegin + " " + uidEnd);
1051: long start = System.currentTimeMillis();
1052: uidValidity = session.getUidValidity();
1053: messageCount = session.getMessageCount();
1054: ArrayList list = new ArrayList();
1055: FastStringBuffer sbCommand = new FastStringBuffer("uid fetch ");
1056: sbCommand.append(uidBegin);
1057: sbCommand.append(':');
1058: if (uidEnd < 0)
1059: sbCommand.append('*');
1060: else
1061: sbCommand.append(uidEnd);
1062: sbCommand
1063: .append(" (uid flags internaldate rfc822.size envelope");
1064: sbCommand.append(" body.peek[header.fields (references)]");
1065: sbCommand.append(')');
1066: Log.debug("command = |" + sbCommand.toString() + "|");
1067: session.writeTagged(sbCommand.toString());
1068: StatusBarProgressNotifier progressNotifier = new StatusBarProgressNotifier(
1069: this );
1070: progressNotifier.progressStart();
1071: try {
1072: FastStringBuffer sb = new FastStringBuffer();
1073: final String endPrefix = session.lastTag() + " ";
1074: while (true) {
1075: String s = session.readLine();
1076: if (s == null) {
1077: Log.debug("retrieveMessageHeaders s == null");
1078: break;
1079: }
1080: if (s.startsWith(endPrefix))
1081: break;
1082: if (s.indexOf("ENVELOPE (") >= 0) {
1083: // New entry starting.
1084: if (sb.length() > 0) {
1085: // Add previous entry to list.
1086: addEntry(list, sb.toString(), uidBegin, recent);
1087: progressNotifier.progress(getProgressText(list
1088: .size()));
1089: if (cancelled) {
1090: Log
1091: .debug("retrieveMessageHeaders cancelled, disconnecting...");
1092: session.disconnect();
1093: return list;
1094: }
1095: sb.setLength(0);
1096: }
1097: sb.append(s);
1098: } else {
1099: // Continuation of previous entry.
1100: sb.append("\r\n");
1101: sb.append(s);
1102: }
1103: }
1104: if (sb.length() > 0) {
1105: // Add final entry to list.
1106: addEntry(list, sb.toString(), uidBegin, recent);
1107: progressNotifier.progress(getProgressText(list.size()));
1108: }
1109: } catch (Exception e) {
1110: Log.error(e);
1111: } finally {
1112: progressNotifier.progressStop();
1113: long elapsed = System.currentTimeMillis() - start;
1114: Log.debug("retrieveMessageHeaders " + list.size()
1115: + " messages in " + elapsed + " ms");
1116: }
1117: return list;
1118: }
1119:
1120: private void addEntry(List list, String s, int uidBegin,
1121: boolean recent) {
1122: ImapMailboxEntry entry = ImapMailboxEntry.parseEntry(this , s);
1123: if (entry != null) {
1124: if (entry.getUid() >= uidBegin) {
1125: if (recent)
1126: entry.setFlags(entry.getFlags()
1127: | MailboxEntry.RECENT);
1128: list.add(entry);
1129: } else {
1130: Log.debug("not adding message "
1131: + entry.getMessageNumber() + " uid "
1132: + entry.getUid() + " uidBegin = " + uidBegin);
1133: if (messageCount < 0) {
1134: // Mailbox message count is invalid.
1135: messageCount = entry.getMessageNumber();
1136: } else if (entry.getMessageNumber() != messageCount)
1137: Debug.bug();
1138: }
1139: } else
1140: Log.error("can't parse envelope |" + s + "|");
1141: }
1142:
1143: // This method does not make any assumptions about the order of the entries.
1144: private void updateLastUid() {
1145: uidLast = 0;
1146: if (entries != null) {
1147: for (int i = entries.size() - 1; i >= 0; i--) {
1148: ImapMailboxEntry entry = (ImapMailboxEntry) entries
1149: .get(i);
1150: if (entry.getUid() > uidLast)
1151: uidLast = entry.getUid();
1152: }
1153: }
1154: }
1155:
1156: public void readOnlyError() {
1157: Runnable r = new Runnable() {
1158: public void run() {
1159: MessageDialog.showMessageDialog("Mailbox is read-only",
1160: "Error");
1161: }
1162: };
1163: SwingUtilities.invokeLater(r);
1164: }
1165:
1166: private void fatal(final String text, final String title) {
1167: Runnable r = new Runnable() {
1168: public void run() {
1169: MessageDialog.showMessageDialog(Editor.currentEditor(),
1170: text, title);
1171: if (Editor.getBufferList().contains(ImapMailbox.this ))
1172: kill();
1173: }
1174: };
1175: SwingUtilities.invokeLater(r);
1176: }
1177:
1178: private boolean getAllMessageHeaders() {
1179: Thread t = new Thread() {
1180: public void run() {
1181: mailboxCache = ImapMailboxCache
1182: .readCache(ImapMailbox.this );
1183: }
1184: };
1185: t.start();
1186: if (!session.verifyConnected()) {
1187: Log.error("getAllMessageHeaders can't connect to "
1188: + session.getHost() + " on port "
1189: + session.getPort());
1190: setBusy(false);
1191: if (!cancelled)
1192: fatal(session.getErrorText(), "Error");
1193: return false;
1194: }
1195: if (!session.verifySelected(folderName)) {
1196: Log.error("getAllMessageHeaders can't SELECT " + folderName
1197: + " on " + session.getHost());
1198: setBusy(false);
1199: if (!cancelled)
1200: fatal(session.getErrorText(), "Error");
1201: return false;
1202: }
1203: if (t != null) {
1204: try {
1205: t.join();
1206: } catch (InterruptedException e) {
1207: Log.error(e);
1208: }
1209: }
1210: entries = null;
1211: uidLast = 0;
1212: if (mailboxCache != null && mailboxCache.isValid()) {
1213: Log.debug("mailboxCache is valid");
1214: List cachedEntries = mailboxCache.getEntries();
1215: Log.debug("cachedEntries.size() = " + cachedEntries.size());
1216: updateCachedEntries(cachedEntries);
1217: int size = cachedEntries.size();
1218: entries = new ArrayList(size);
1219: // Add entries from cache, skipping any that have been nulled out.
1220: for (int i = 0; i < size; i++) {
1221: Object o = cachedEntries.get(i);
1222: if (o != null)
1223: entries.add(o);
1224: }
1225: Log.debug("entries.size() = " + entries.size());
1226: // We don't need the cache any more.
1227: mailboxCache = null;
1228: updateLastUid();
1229: }
1230: if (cancelled) {
1231: session.disconnect();
1232: return false;
1233: }
1234: // Get any new entries from the server. Set the recent flag on these
1235: // entries unless we're retrieving the whole mailbox (uidLast == 0).
1236: boolean recent = uidLast > 0;
1237: final List newEntries = retrieveMessageHeaders(uidLast + 1, -1,
1238: recent);
1239: if (newEntries != null && newEntries.size() > 0) {
1240: addEntriesToAddressBook(newEntries);
1241: processIncomingFilters(newEntries);
1242: if (entries != null)
1243: entries.addAll(newEntries);
1244: else
1245: entries = new ArrayList(newEntries);
1246: }
1247: uidValidity = session.getUidValidity();
1248: if (entries == null)
1249: entries = new ArrayList();
1250: else if (entries instanceof ArrayList)
1251: ((ArrayList) entries).trimToSize();
1252: new ImapMailboxCache(this ).writeCache();
1253: updateLastUid();
1254: return true;
1255: }
1256:
1257: private boolean getNewMessageHeaders() {
1258: List newEntries = retrieveMessageHeaders(uidLast + 1, -1, true);
1259: if (newEntries == null || newEntries.size() == 0)
1260: return false;
1261: addEntriesToAddressBook(newEntries);
1262: processIncomingFilters(newEntries);
1263: entries.addAll(newEntries);
1264: if (entries instanceof ArrayList)
1265: ((ArrayList) entries).trimToSize();
1266: new ImapMailboxCache(this ).writeCache();
1267: updateLastUid();
1268: return true;
1269: }
1270:
1271: private void processIncomingFilters(List entryList) {
1272: Log.debug("processIncomingFilters");
1273: final List filterList = IncomingFilter.getFilterList();
1274: if (filterList == null || filterList.size() == 0)
1275: return;
1276: // For now, we just process incoming filters for the user's inbox.
1277: String inbox = Editor.preferences().getStringProperty(
1278: Property.INBOX);
1279: if (inbox == null)
1280: return;
1281: MailboxURL inboxUrl = MailboxURL.parse(inbox);
1282: if (!(inboxUrl instanceof ImapURL)) {
1283: Log.debug("processIncomingFilters not inbox "
1284: + url.toString());
1285: return;
1286: }
1287: if (!url.getHost().equals(inboxUrl.getHost())) {
1288: Log.debug("processIncomingFilters not inbox "
1289: + url.toString());
1290: return;
1291: }
1292: if (!folderName.equals(((ImapURL) inboxUrl).getFolderName())) {
1293: Log.debug("processIncomingFilters not inbox "
1294: + url.toString());
1295: return;
1296: }
1297: SmtpSession smtp = null;
1298: for (int i = 0; i < entryList.size(); i++) {
1299: ImapMailboxEntry entry = (ImapMailboxEntry) entryList
1300: .get(i);
1301: // Don't process deleted entries.
1302: if (entry.isDeleted())
1303: continue;
1304: for (int j = 0; j < filterList.size(); j++) {
1305: IncomingFilter incomingFilter = (IncomingFilter) filterList
1306: .get(j);
1307: if (incomingFilter != null) {
1308: MailboxFilter mf = incomingFilter.getFilter();
1309: if (mf != null && mf.accept(entry)) {
1310: switch (incomingFilter.getAction()) {
1311: case IncomingFilter.MOVE: {
1312: processMove(entry, incomingFilter
1313: .getParameter());
1314: break;
1315: }
1316: case IncomingFilter.BOUNCE: {
1317: boolean succeeded = false;
1318: if (smtp == null)
1319: smtp = SmtpSession.getDefaultSession();
1320: if (smtp != null)
1321: succeeded = processBounce(entry,
1322: incomingFilter.getParameter(),
1323: smtp);
1324: if (!succeeded)
1325: Log.error("processBounce failed");
1326: break;
1327: }
1328: case IncomingFilter.BOUNCE_AND_DELETE: {
1329: boolean succeeded = false;
1330: if (smtp == null)
1331: smtp = SmtpSession.getDefaultSession();
1332: if (smtp != null)
1333: succeeded = processBounce(entry,
1334: incomingFilter.getParameter(),
1335: smtp);
1336: if (succeeded)
1337: processDelete(entry);
1338: else
1339: Log.error("processBounce failed");
1340: break;
1341: }
1342: default:
1343: break;
1344: }
1345: break;
1346: }
1347: }
1348: }
1349: }
1350: // Processing completed.
1351: if (smtp != null)
1352: smtp.quit();
1353: }
1354:
1355: private void processMove(ImapMailboxEntry entry, String destination) {
1356: if (destination != null) {
1357: Log.debug("destination = |" + destination + "|");
1358: ArrayList list = new ArrayList(1);
1359: list.add(entry);
1360: try {
1361: Log.debug("processMove calling moveToFolder");
1362: moveToFolder(list, destination);
1363: Log.debug("processMove back from moveToFolder");
1364: } catch (Exception e) {
1365: Log.error(e);
1366: }
1367: }
1368: }
1369:
1370: private boolean processBounce(ImapMailboxEntry entry,
1371: String bounceTo, SmtpSession smtp) {
1372: if (bounceTo == null)
1373: return false;
1374: Log.debug("bounceTo = |" + bounceTo + "|");
1375: MailAddress[] to = MailAddress.parseAddresses(bounceTo);
1376: if (to == null)
1377: return false;
1378: if (to.length == 0)
1379: return false;
1380: Message message = getMessage(entry, null);
1381: if (message == null) {
1382: Log.error("processBounce getMessage() failed");
1383: return false;
1384: }
1385: return Mail.bounceMessage(message, to, smtp);
1386: }
1387:
1388: private void processDelete(ImapMailboxEntry entry) {
1389: ArrayList list = new ArrayList(1);
1390: list.add(entry);
1391: try {
1392: delete(list);
1393: } catch (Exception e) {
1394: Log.error(e);
1395: }
1396: }
1397:
1398: private void updateCachedEntries(List cachedEntries) {
1399: if (cachedEntries == null)
1400: return;
1401: final int size = cachedEntries.size();
1402: if (size == 0)
1403: return;
1404: long start = System.currentTimeMillis();
1405: session.writeTagged("uid fetch 1:* (uid flags)");
1406: HashMap map = new HashMap(size);
1407: Iterator iter = cachedEntries.iterator();
1408: while (iter.hasNext()) {
1409: ImapMailboxEntry entry = (ImapMailboxEntry) iter.next();
1410: map.put(new Integer(entry.getUid()), entry);
1411: }
1412: Log.debug("built map " + (System.currentTimeMillis() - start)
1413: + " ms");
1414: iter = null;
1415: final String endPrefix = session.lastTag() + " ";
1416: // Set mailbox field and update flags for all messages on server.
1417: while (true) {
1418: final String s = session.readLine();
1419: if (s == null) {
1420: Log.debug("updateCachedEntries s is null");
1421: break;
1422: }
1423: if (s.startsWith(endPrefix))
1424: break;
1425: int uid = ImapMailboxEntry.parseUid(s);
1426: if (uid == 0) {
1427: Log.debug("uid = 0 s = |" + s + "|");
1428: continue;
1429: }
1430: ImapMailboxEntry entry = (ImapMailboxEntry) map
1431: .get(new Integer(uid));
1432: if (entry != null) {
1433: Debug.assertTrue(entry.getMailbox() == null);
1434: entry.setMailbox(this );
1435: entry.setFlags(ImapMailboxEntry.parseFlags(s));
1436: }
1437: }
1438: map.clear();
1439: // Null out entries whose mailbox field is not set (i.e. entries
1440: // deleted from server).
1441: for (int i = 0; i < size; i++) {
1442: ImapMailboxEntry entry = (ImapMailboxEntry) cachedEntries
1443: .get(i);
1444: if (entry.getMailbox() == null)
1445: cachedEntries.set(i, null);
1446: }
1447: long elapsed = System.currentTimeMillis() - start;
1448: Log.debug("updateCachedEntries " + elapsed + " ms "
1449: + (float) elapsed / cachedEntries.size()
1450: + " ms per entry");
1451: }
1452:
1453: public void readMessage(Line line) {
1454: readMessage(line, false);
1455: }
1456:
1457: public void readMessageOtherWindow(Line line) {
1458: readMessage(line, true);
1459: }
1460:
1461: private void readMessage(Line line, boolean useOtherWindow) {
1462: Editor editor = Editor.currentEditor();
1463: ImapMailboxEntry entry = (ImapMailboxEntry) ((MailboxLine) line)
1464: .getMailboxEntry();
1465: Buffer buf = null;
1466: for (BufferIterator it = new BufferIterator(); it.hasNext();) {
1467: Buffer b = it.nextBuffer();
1468: if (b instanceof ImapMessageBuffer) {
1469: ImapMessageBuffer mb = (ImapMessageBuffer) b;
1470: if (mb.getMailboxEntry() == entry) {
1471: buf = b;
1472: break;
1473: }
1474: }
1475: }
1476: if (buf != null) {
1477: // Found existing buffer.
1478: activateMessageBuffer(editor, (ImapMessageBuffer) buf,
1479: useOtherWindow);
1480: return;
1481: }
1482: if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE)) {
1483: String rawText = getMessageTextFromCache(entry.getUid());
1484: if (rawText != null) {
1485: ImapMessageBuffer mb = new ImapMessageBuffer(this ,
1486: entry, rawText);
1487: activateMessageBuffer(editor, mb, useOtherWindow);
1488: if ((entry.getFlags() & MailboxEntry.SEEN) == 0) {
1489: if (session.isReadOnly())
1490: markReadLocal(entry);
1491: else
1492: markRead(entry);
1493: }
1494: return;
1495: }
1496: }
1497: // Lock the mailbox before creating the message buffer. It will be
1498: // unlocked at the end of the message buffer's loadProcess.run().
1499: if (lock()) {
1500: setBusy(true);
1501: ImapMessageBuffer mb = new ImapMessageBuffer(this , entry);
1502: activateMessageBuffer(editor, mb, useOtherWindow);
1503: } else
1504: editor.status("Mailbox is locked");
1505: }
1506:
1507: private void markRead(final ImapMailboxEntry entry) {
1508: Runnable markReadRunnable = new Runnable() {
1509: public void run() {
1510: try {
1511: if (session.verifyConnected()
1512: && session.verifySelected(folderName)) {
1513: session.uidStore(entry.getUid(),
1514: "+flags.silent (\\seen)");
1515: if (session.getResponse() == ImapSession.OK)
1516: markReadLocal(entry);
1517: }
1518: } finally {
1519: setBusy(false);
1520: unlock();
1521: Editor.updateDisplayLater(ImapMailbox.this );
1522: }
1523: }
1524: };
1525: if (lock()) {
1526: setBusy(true);
1527: new Thread(markReadRunnable).start();
1528: }
1529: }
1530:
1531: private void markReadLocal(ImapMailboxEntry entry) {
1532: entry.setFlags(entry.getFlags() | MailboxEntry.SEEN);
1533: updateEntry(entry);
1534: countMessages();
1535: }
1536:
1537: public ImapMailboxEntry getMailboxEntry(String messageId) {
1538: for (int i = entries.size() - 1; i >= 0; i--) {
1539: ImapMailboxEntry entry = (ImapMailboxEntry) entries.get(i);
1540: if (messageId.equals(entry.getMessageId()))
1541: return entry;
1542: }
1543: return null;
1544: }
1545:
1546: public Message getMessage(MailboxEntry entry,
1547: ProgressNotifier progressNotifier) {
1548: if (!(entry instanceof ImapMailboxEntry))
1549: return null;
1550: final int uid = ((ImapMailboxEntry) entry).getUid();
1551: if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE)) {
1552: String rawText = getMessageTextFromCache(uid);
1553: if (rawText != null)
1554: return new Message(rawText);
1555: }
1556: if (!isLocked())
1557: Debug.bug("ImapMailbox.getMessage mailbox is not locked!");
1558: if (!session.verifyConnected())
1559: return null;
1560: if (progressNotifier != null && progressNotifier.cancelled())
1561: return null;
1562: if (!session.verifySelected(folderName))
1563: return null;
1564: if (progressNotifier != null && progressNotifier.cancelled())
1565: return null;
1566: String header = fetchPart(uid, "header", null, progressNotifier);
1567: if (header == null)
1568: return null;
1569: Headers headers = Headers.parse(header);
1570: String charset = null;
1571: String contentType = headers.getValue(Headers.CONTENT_TYPE);
1572: if (contentType != null)
1573: charset = Utilities.getCharsetFromContentType(contentType);
1574: Log.debug("charset = " + charset);
1575: String encoding = Utilities.getEncodingFromCharset(charset);
1576: if (encoding.equalsIgnoreCase("us-ascii"))
1577: encoding = null;
1578: else if (encoding.equalsIgnoreCase("iso-8859-1"))
1579: encoding = null;
1580: String body = fetchPart(uid, "text", encoding, progressNotifier);
1581: if (body == null)
1582: return null;
1583: if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE))
1584: cacheMessage(uid, header + body, encoding);
1585: return new Message(header.concat(body), headers);
1586: }
1587:
1588: private String fetchPart(int uid, String part, String encoding,
1589: ProgressNotifier progressNotifier) {
1590: FastStringBuffer sb = new FastStringBuffer("uid fetch ");
1591: sb.append(uid);
1592: sb.append(" body[");
1593: sb.append(part);
1594: sb.append(']');
1595: session.writeTagged(sb.toString());
1596: sb = null;
1597: int length = -1;
1598: try {
1599: if (progressNotifier != null
1600: && progressNotifier.cancelled()) {
1601: session.disconnect();
1602: return null;
1603: }
1604: String s = session.readLine();
1605: if (s == null)
1606: return null;
1607: if (s.startsWith("* ")) {
1608: int index = s.indexOf('(');
1609: if (index < 0) {
1610: Log.error("can't parse response s = |" + s + "|");
1611: return null;
1612: }
1613: String before = s.substring(0, index).trim();
1614: if (!before.endsWith(" FETCH")) {
1615: Log.error("no FETCH");
1616: return null;
1617: }
1618: String after = s.substring(index);
1619: int begin = after.indexOf('{');
1620: if (begin < 0) {
1621: Log.error("no '{'");
1622: Log.error("s = |" + s + "|");
1623: begin = after.indexOf('"');
1624: if (begin < 0)
1625: return null;
1626: else
1627: return parseQuotedString(after.substring(begin));
1628: }
1629: int end = after.indexOf('}', begin + 1);
1630: if (end < 0) {
1631: Log.error("no '}'");
1632: return null;
1633: }
1634: try {
1635: length = Integer.parseInt(after.substring(
1636: begin + 1, end));
1637: } catch (NumberFormatException e) {
1638: Log.error(e);
1639: }
1640: }
1641: if (length < 0) {
1642: Log.error("can't determine length");
1643: Log.error("s = |" + s + "|");
1644: return null;
1645: }
1646: sb = new FastStringBuffer(length + 64);
1647: while (true) {
1648: if (progressNotifier != null
1649: && progressNotifier.cancelled()) {
1650: session.disconnect();
1651: return null;
1652: }
1653: s = session.readLine();
1654: if (s == null)
1655: break;
1656: if (s.startsWith(session.lastTag() + " OK"))
1657: break;
1658: // Otherwise we need to append the string...
1659: if (encoding != null) {
1660: // Must do conversion.
1661: int len = s.length();
1662: byte[] bytes = new byte[len];
1663: for (int i = 0; i < len; i++)
1664: bytes[i] = (byte) s.charAt(i);
1665: try {
1666: s = new String(bytes, encoding);
1667: } catch (UnsupportedEncodingException e) {
1668: Log.debug(e);
1669: // Conversion isn't going to work, so give up on it.
1670: encoding = null;
1671: }
1672: }
1673: sb.append(s);
1674: sb.append("\r\n");
1675: if (progressNotifier != null)
1676: progressNotifier.progress("Received ", sb.length(),
1677: length);
1678: }
1679: } catch (Exception e) {
1680: Log.error(e);
1681: }
1682: if (sb != null) {
1683: Log.debug("advertised length = " + length);
1684: Log.debug("actual length = " + sb.length());
1685: sb.setLength(length);
1686: return sb.toString();
1687: } else
1688: return null;
1689: }
1690:
1691: private String parseQuotedString(final String s) {
1692: Debug.assertTrue(s.length() > 0);
1693: Debug.assertTrue(s.charAt(0) == '"');
1694: FastStringBuffer sb = new FastStringBuffer();
1695: final int length = s.length();
1696: for (int i = 1; i < length; i++) {
1697: char c = s.charAt(i);
1698: if (c == '\\' && i + 1 < length)
1699: sb.append(s.charAt(++i));
1700: else if (c == '"')
1701: break;
1702: else
1703: sb.append(c);
1704: }
1705: return sb.toString();
1706: }
1707:
1708: public void dispose() {
1709: Log.debug("ImapMailbox.dispose " + folderName + " on "
1710: + session.getHost());
1711: Runnable r = new Runnable() {
1712: public void run() {
1713: session.logout();
1714: }
1715: };
1716: new Thread(r).start();
1717: MailboxProperties.saveProperties(this );
1718: }
1719:
1720: protected void finalize() throws Throwable {
1721: Log.debug("ImapMailbox.finalize " + folderName + " on "
1722: + session.getHost());
1723: super .finalize();
1724: }
1725:
1726: private String getProgressText(int n) {
1727: FastStringBuffer sb = new FastStringBuffer(32);
1728: sb.append("Retrieved ");
1729: sb.append(n);
1730: sb.append(" message header");
1731: if (n > 1)
1732: sb.append('s');
1733: return sb.toString();
1734: }
1735:
1736: // Package scope for testing.
1737: /*package*/static String getMessageSet(List list) {
1738: FastStringBuffer sb = new FastStringBuffer();
1739: int limit = list.size();
1740: int begin = -1;
1741: int end = -1;
1742: for (int i = 0; i < limit; i++) {
1743: ImapMailboxEntry entry = (ImapMailboxEntry) list.get(i);
1744: if (begin < 0) {
1745: begin = entry.getUid();
1746: end = entry.getUid();
1747: } else if (entry.getUid() == end + 1) {
1748: end = entry.getUid();
1749: } else {
1750: if (sb.length() > 0)
1751: sb.append(',');
1752: if (begin != end) {
1753: Debug.assertTrue(end > begin);
1754: sb.append(begin);
1755: sb.append(':');
1756: sb.append(end);
1757: begin = end = entry.getUid();
1758: } else {
1759: sb.append(begin);
1760: begin = end = entry.getUid();
1761: }
1762: }
1763: }
1764: if (sb.length() > 0)
1765: sb.append(',');
1766: if (begin != end) {
1767: Debug.assertTrue(end > begin);
1768: sb.append(begin);
1769: sb.append(':');
1770: sb.append(end);
1771: } else
1772: sb.append(begin);
1773: return sb.toString();
1774: }
1775:
1776: public String toString() {
1777: int newMessageCount = getNewMessageCount();
1778: if (newMessageCount > 0) {
1779: FastStringBuffer sb = new FastStringBuffer(url.toString());
1780: sb.append(" (");
1781: sb.append(newMessageCount);
1782: sb.append(" new)");
1783: return sb.toString();
1784: } else
1785: return url.toString();
1786: }
1787:
1788: public String getTitle() {
1789: return toString();
1790: }
1791:
1792: private ImapMessageCache messageCache;
1793:
1794: private void cacheMessage(int uid, String message, String encoding) {
1795: if (messageCache != null) {
1796: if (messageCache.getUidValidity() != session
1797: .getUidValidity())
1798: messageCache = null;
1799: }
1800: if (messageCache == null) {
1801: messageCache = ImapMessageCache.getMessageCache(this );
1802: if (messageCache == null)
1803: return;
1804: }
1805: messageCache.store(uid, message, encoding);
1806: }
1807:
1808: private String getMessageTextFromCache(int uid) {
1809: if (messageCache != null) {
1810: if (messageCache.getUidValidity() != session
1811: .getUidValidity())
1812: messageCache = null;
1813: }
1814: if (messageCache == null) {
1815: messageCache = ImapMessageCache.getMessageCache(this);
1816: if (messageCache == null)
1817: return null;
1818: }
1819: return messageCache.getMessageText(uid);
1820: }
1821: }
|