0001: // The contents of this file are subject to the Mozilla Public License Version
0002: // 1.1
0003: //(the "License"); you may not use this file except in compliance with the
0004: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
0005: //
0006: //Software distributed under the License is distributed on an "AS IS" basis,
0007: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0008: //for the specific language governing rights and
0009: //limitations under the License.
0010: //
0011: //The Original Code is "The Columba Project"
0012: //
0013: //The Initial Developers of the Original Code are Frederik Dietz and Timo
0014: // Stich.
0015: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
0016: //
0017: //All Rights Reserved.
0018: package org.columba.mail.imap;
0019:
0020: import java.io.IOException;
0021: import java.io.InputStream;
0022: import java.nio.charset.Charset;
0023: import java.text.DateFormat;
0024: import java.text.MessageFormat;
0025: import java.util.ArrayList;
0026: import java.util.Arrays;
0027: import java.util.Collections;
0028: import java.util.Iterator;
0029: import java.util.LinkedList;
0030: import java.util.List;
0031: import java.util.Observable;
0032: import java.util.Observer;
0033: import java.util.logging.Logger;
0034:
0035: import javax.net.ssl.SSLException;
0036: import javax.swing.JOptionPane;
0037:
0038: import org.columba.api.command.IStatusObservable;
0039: import org.columba.core.base.Blowfish;
0040: import org.columba.core.base.ListTools;
0041: import org.columba.core.command.CommandCancelledException;
0042: import org.columba.core.filter.FilterCriteria;
0043: import org.columba.core.filter.FilterRule;
0044: import org.columba.core.filter.IFilterCriteria;
0045: import org.columba.core.filter.IFilterRule;
0046: import org.columba.core.gui.base.MultiLineLabel;
0047: import org.columba.core.gui.frame.FrameManager;
0048: import org.columba.mail.config.AccountItem;
0049: import org.columba.mail.config.ImapItem;
0050: import org.columba.mail.config.IncomingItem;
0051: import org.columba.mail.filter.MailFilterCriteria;
0052: import org.columba.mail.folder.IMailbox;
0053: import org.columba.mail.folder.command.MarkMessageCommand;
0054: import org.columba.mail.folder.headercache.CachedHeaderfields;
0055: import org.columba.mail.folder.imap.IMAPFolder;
0056: import org.columba.mail.folder.imap.IMAPRootFolder;
0057: import org.columba.mail.gui.util.PasswordDialog;
0058: import org.columba.mail.message.ColumbaHeader;
0059: import org.columba.mail.message.IHeaderList;
0060: import org.columba.mail.util.AuthenticationManager;
0061: import org.columba.mail.util.AuthenticationSecurityComparator;
0062: import org.columba.mail.util.MailResourceLoader;
0063: import org.columba.ristretto.auth.AuthenticationException;
0064: import org.columba.ristretto.auth.AuthenticationFactory;
0065: import org.columba.ristretto.imap.IMAPDate;
0066: import org.columba.ristretto.imap.IMAPDisconnectedException;
0067: import org.columba.ristretto.imap.IMAPException;
0068: import org.columba.ristretto.imap.IMAPFlags;
0069: import org.columba.ristretto.imap.IMAPHeader;
0070: import org.columba.ristretto.imap.IMAPListener;
0071: import org.columba.ristretto.imap.IMAPProtocol;
0072: import org.columba.ristretto.imap.IMAPResponse;
0073: import org.columba.ristretto.imap.ListInfo;
0074: import org.columba.ristretto.imap.MailboxStatus;
0075: import org.columba.ristretto.imap.NamespaceCollection;
0076: import org.columba.ristretto.imap.SearchKey;
0077: import org.columba.ristretto.imap.SequenceSet;
0078: import org.columba.ristretto.io.SequenceInputStream;
0079: import org.columba.ristretto.message.Header;
0080: import org.columba.ristretto.message.MailboxInfo;
0081: import org.columba.ristretto.message.MimeTree;
0082:
0083: /**
0084: * IMAPStore encapsulates IMAPProtocol and the parsers for IMAPFolder.
0085: * <p>
0086: * This way {@link IMAPFolder}doesn't need to do any parsing work, etc.
0087: * <p>
0088: * Every {@link IMAPFolder}of a single account has also an
0089: * {@link IMAPRootFolder}, which keeps a reference to {@link IMAPServer}.
0090: * Which itself uses {@link IMAPProtocol}.
0091: * <p>
0092: * IMAPStore handles the current state of connection:
0093: * <ul>
0094: * <li>STATE_NONAUTHENTICATE - not authenticated</li>
0095: * <li>STATE_AUTHENTICATE - authenticated</li>
0096: * <li>STATE_SELECTED - mailbox is selected</li>
0097: * </ul>
0098: * <p>
0099: * It keeps a reference to the currently selected mailbox.
0100: * <p>
0101: * IMAPFolder shouldn't use IMAPProtocol directly, instead it should use
0102: * IMAPStore.
0103: *
0104: * @author fdietz
0105: */
0106: public class IMAPServer implements IMAPListener, Observer, IImapServer {
0107:
0108: private static final int STEP_SIZE = 50;
0109:
0110: private static final int UID_FETCH_STEPS = 500;
0111:
0112: private static final Logger LOG = Logger
0113: .getLogger("org.columba.mail.imap");
0114:
0115: private static final Charset UTF8 = Charset.forName("UTF-8");
0116:
0117: private static final Charset DEFAULT = Charset.forName(System
0118: .getProperty("file.encoding"));
0119:
0120: /**
0121: * currently selected mailbox
0122: */
0123: private IMailbox selectedFolder;
0124:
0125: /**
0126: * Holds the actual MailboxStatus. Updated by the IMAPListener.
0127: */
0128: private MailboxStatus selectedStatus;
0129:
0130: /**
0131: * mailbox name delimiter
0132: * <p>
0133: * example: "/" (uw-imap), or "." (cyrus)
0134: */
0135: private String delimiter;
0136:
0137: /**
0138: * reference to IMAP protocol
0139: */
0140: private IMAPProtocol protocol;
0141:
0142: /**
0143: * configuration options of this IMAP account
0144: */
0145: private ImapItem item;
0146:
0147: private MimeTree aktMimeTree;
0148:
0149: private Object aktMessageUid;
0150:
0151: private MailboxInfo messageFolderInfo;
0152:
0153: private boolean firstLogin;
0154:
0155: boolean usingSSL;
0156:
0157: String[] capabilities;
0158:
0159: private long lastCommunication;
0160:
0161: private IStatusObservable observable;
0162:
0163: // minimal unchecked time is 30 Seconds
0164: private int MIN_IDLE = 30 * 1000; // in ms
0165:
0166: // Used to control the state in which
0167: // the automatic updated mechanism is
0168: private boolean updatesEnabled = true;
0169:
0170: private IFirstLoginAction firstLoginAction;
0171:
0172: private IUpdateFlagAction updateFlagAction;
0173:
0174: private IExistsChangedAction existsChangedAction;
0175:
0176: private boolean statusDirty;
0177:
0178: public IMAPServer(ImapItem item) {
0179: this .item = item;
0180:
0181: item.getRoot().addObserver(this );
0182:
0183: // create IMAP protocol
0184: protocol = new IMAPProtocol(item.get("host"), item
0185: .getInteger("port"));
0186: // register interest on status updates
0187: protocol.addIMAPListener(this );
0188:
0189: firstLogin = true;
0190: usingSSL = false;
0191:
0192: lastCommunication = System.currentTimeMillis();
0193: }
0194:
0195: /**
0196: * @return
0197: */
0198: protected IStatusObservable getObservable() {
0199: return observable;
0200: }
0201:
0202: /**
0203: * @param message
0204: */
0205: protected void printStatusMessage(String message) {
0206: if (getObservable() != null) {
0207: getObservable().setMessage(
0208: item.get("host") + ": " + message);
0209: }
0210: }
0211:
0212: /**
0213: * Returns mailbox name delimiter
0214: * <p/>
0215: * example: "/" (uw-imap), or "." (cyrus)
0216: *
0217: * @return mailbox name delimiter
0218: */
0219: /* (non-Javadoc)
0220: * @see org.columba.mail.imap.IImapServer#getDelimiter()
0221: */
0222: public String getDelimiter() throws IOException, IMAPException,
0223: CommandCancelledException {
0224: if (delimiter == null) {
0225: // try to determine delimiter
0226: delimiter = fetchDelimiter();
0227: }
0228:
0229: return delimiter;
0230: }
0231:
0232: /* (non-Javadoc)
0233: * @see org.columba.mail.imap.IImapServer#logout()
0234: */
0235: public void logout() throws Exception {
0236: if (protocol.getState() != IMAPProtocol.NOT_CONNECTED) {
0237: try {
0238: protocol.logout();
0239: } catch (Exception e) {
0240: // don't care
0241: }
0242: }
0243: }
0244:
0245: private void openConnection() throws IOException, IMAPException,
0246: CommandCancelledException {
0247: printStatusMessage(MailResourceLoader.getString("statusbar",
0248: "message", "connecting"));
0249:
0250: int sslType = item.getIntegerWithDefault("ssl_type",
0251: IncomingItem.TLS);
0252: boolean sslEnabled = item.getBoolean("enable_ssl");
0253:
0254: // open a port to the server
0255: if (sslEnabled && sslType == IncomingItem.IMAPS_POP3S) {
0256: try {
0257: protocol.openSSLPort();
0258: usingSSL = true;
0259: } catch (SSLException e) {
0260: int result = showErrorDialog(MailResourceLoader
0261: .getString("dialog", "error",
0262: "ssl_handshake_error")
0263: + ": "
0264: + e.getLocalizedMessage()
0265: + "\n"
0266: + MailResourceLoader.getString("dialog",
0267: "error", "ssl_turn_off"));
0268:
0269: if (result == 1) {
0270: throw new CommandCancelledException();
0271: }
0272:
0273: // turn off SSL for the future
0274: item.setBoolean("enable_ssl", false);
0275: item.setInteger("port", IMAPProtocol.DEFAULT_PORT);
0276:
0277: // reopen the port
0278: protocol.openPort();
0279: }
0280: } else {
0281: protocol.openPort();
0282: }
0283:
0284: // shall we switch to SSL?
0285: if (!usingSSL && sslEnabled && sslType == IncomingItem.TLS) {
0286: // if CAPA was not support just give it a try...
0287: if (isSupported("STLS") || isSupported("STARTTLS")
0288: || (capabilities.length == 0)) {
0289: try {
0290: protocol.startTLS();
0291:
0292: usingSSL = true;
0293: LOG.info("Switched to SSL");
0294: } catch (IOException e) {
0295: int result = showErrorDialog(MailResourceLoader
0296: .getString("dialog", "error",
0297: "ssl_handshake_error")
0298: + ": "
0299: + e.getLocalizedMessage()
0300: + "\n"
0301: + MailResourceLoader.getString("dialog",
0302: "error", "ssl_turn_off"));
0303:
0304: if (result == 1) {
0305: throw new CommandCancelledException();
0306: }
0307:
0308: // turn off SSL for the future
0309: item.setBoolean("enable_ssl", false);
0310:
0311: // reopen the port
0312: protocol.openPort();
0313: } catch (IMAPException e) {
0314: int result = showErrorDialog(MailResourceLoader
0315: .getString("dialog", "error",
0316: "ssl_not_supported")
0317: + "\n"
0318: + MailResourceLoader.getString("dialog",
0319: "error", "ssl_turn_off"));
0320:
0321: if (result == 1) {
0322: throw new CommandCancelledException();
0323: }
0324:
0325: // turn off SSL for the future
0326: item.setBoolean("enable_ssl", false);
0327: }
0328: } else {
0329: // CAPAs say that SSL is not supported
0330: int result = showErrorDialog(MailResourceLoader
0331: .getString("dialog", "error",
0332: "ssl_not_supported")
0333: + "\n"
0334: + MailResourceLoader.getString("dialog",
0335: "error", "ssl_turn_off"));
0336:
0337: if (result == 1) {
0338: throw new CommandCancelledException();
0339: }
0340:
0341: // turn off SSL for the future
0342: item.setBoolean("enable_ssl", false);
0343: }
0344: }
0345:
0346: }
0347:
0348: /* (non-Javadoc)
0349: * @see org.columba.mail.imap.IImapServer#checkSupportedAuthenticationMethods()
0350: */
0351: public List checkSupportedAuthenticationMethods()
0352: throws IOException {
0353:
0354: ArrayList supportedMechanisms = new ArrayList();
0355: // LOGIN is always supported
0356: supportedMechanisms
0357: .add(new Integer(AuthenticationManager.LOGIN));
0358:
0359: try {
0360: String serverSaslMechansims[] = getCapas("AUTH");
0361: // combine them to one string
0362: StringBuffer oneLine = new StringBuffer("AUTH");
0363: for (int i = 0; i < serverSaslMechansims.length; i++) {
0364: oneLine.append(' ');
0365: oneLine.append(serverSaslMechansims[i].substring(5)); // remove
0366: // the
0367: // 'AUTH='
0368: }
0369:
0370: // AUTH?
0371: if (serverSaslMechansims != null) {
0372: List authMechanisms = AuthenticationFactory
0373: .getInstance().getSupportedMechanisms(
0374: oneLine.toString());
0375: Iterator it = authMechanisms.iterator();
0376: while (it.hasNext()) {
0377: supportedMechanisms.add(new Integer(
0378: AuthenticationManager
0379: .getSaslCode((String) it.next())));
0380: }
0381: }
0382: } catch (IOException e) {
0383: }
0384:
0385: return supportedMechanisms;
0386: }
0387:
0388: /**
0389: * @param command
0390: * @return
0391: */
0392: private String[] getCapas(String command) throws IOException {
0393: fetchCapas();
0394: ArrayList list = new ArrayList();
0395:
0396: for (int i = 0; i < capabilities.length; i++) {
0397: if (capabilities[i].startsWith(command)) {
0398: list.add(capabilities[i]);
0399: }
0400: }
0401:
0402: return (String[]) list.toArray(new String[0]);
0403: }
0404:
0405: /* (non-Javadoc)
0406: * @see org.columba.mail.imap.IImapServer#isSupported(java.lang.String)
0407: */
0408: public boolean isSupported(String command) throws IOException {
0409: fetchCapas();
0410:
0411: for (int i = 0; i < capabilities.length; i++) {
0412: if (capabilities[i].startsWith(command)) {
0413: return true;
0414: }
0415: }
0416:
0417: return false;
0418: }
0419:
0420: /**
0421: * @throws IOException
0422: */
0423: private void fetchCapas() throws IOException {
0424: if (capabilities == null) {
0425: try {
0426: ensureConnectedState();
0427:
0428: capabilities = protocol.capability();
0429: } catch (IMAPException e) {
0430: // CAPA not supported
0431: capabilities = new String[0];
0432: } catch (CommandCancelledException e) {
0433:
0434: }
0435: }
0436: }
0437:
0438: /**
0439: * Gets the selected Authentication method or else the most secure.
0440: *
0441: * @return the authentication method
0442: */
0443: private int getLoginMethod() throws CommandCancelledException,
0444: IOException {
0445: String loginMethod = item.get("login_method");
0446: int result = 0;
0447:
0448: try {
0449: result = Integer.parseInt(loginMethod);
0450: } catch (NumberFormatException e) {
0451: // Just use the default as fallback
0452: }
0453:
0454: if (result == 0) {
0455: List supported = checkSupportedAuthenticationMethods();
0456:
0457: if (usingSSL) {
0458: // NOTE if SSL is possible we just need the plain login
0459: // since SSL does the encryption for us.
0460: result = ((Integer) supported.get(0)).intValue();
0461: } else {
0462: Collections.sort(supported,
0463: new AuthenticationSecurityComparator());
0464: result = ((Integer) supported.get(supported.size() - 1))
0465: .intValue();
0466: }
0467:
0468: }
0469:
0470: return result;
0471: }
0472:
0473: /**
0474: * Login to IMAP server.
0475: * <p>
0476: * Ask user for password.
0477: *
0478: * TODO (@author tstich): cleanup if all these ugly if, else cases
0479: *
0480: * @throws Exception
0481: */
0482: private void login() throws IOException, IMAPException,
0483: CommandCancelledException {
0484: PasswordDialog dialog = new PasswordDialog();
0485: ensureConnectedState();
0486:
0487: boolean authenticated = false;
0488: boolean first = true;
0489:
0490: char[] password = new char[0];
0491:
0492: printStatusMessage(MailResourceLoader.getString("statusbar",
0493: "message", "authenticating"));
0494:
0495: int loginMethod = getLoginMethod();
0496:
0497: // Try to get Password from Configuration
0498: if (item.get("password").length() != 0) {
0499: password = Blowfish.decrypt(item.get("password"));
0500: }
0501: // Login loop until authenticated
0502: while (!authenticated) {
0503: // On the first try check if we need to show the password dialog
0504: // -> not necessary when password was stored
0505: if (!first || password.length == 0) {
0506: // Show the password dialog
0507: dialog.showDialog(MessageFormat.format(
0508: MailResourceLoader.getString("dialog",
0509: "password", "enter_password"),
0510: new Object[] { item.get("user"),
0511: item.get("host") }), new String(
0512: password), item.getBoolean("save_password"));
0513: if (dialog.success()) {
0514: // User pressed OK
0515: password = dialog.getPassword();
0516:
0517: // Save or Clear the password in the configuration
0518: item.setBoolean("save_password", dialog.getSave());
0519: if (dialog.getSave()) {
0520: item.setString("password", Blowfish
0521: .encrypt(password));
0522: } else {
0523: item.setString("password", "");
0524: }
0525: } else {
0526: // User cancelled authentication
0527:
0528: throw new CommandCancelledException();
0529: }
0530: }
0531:
0532: // From this point we have a username and password
0533: // from configuration of from the dialog
0534:
0535: try {
0536: if (loginMethod == AuthenticationManager.LOGIN) {
0537: protocol.login(item.get("user"), password);
0538:
0539: // If no exception happened we have successfully logged
0540: // in
0541: authenticated = true;
0542: } else {
0543: try {
0544: // AUTH
0545: protocol.authenticate(AuthenticationManager
0546: .getSaslName(loginMethod), item
0547: .get("user"), password);
0548:
0549: // If no exception happened we have successfully logged
0550: // in
0551: authenticated = true;
0552: } catch (AuthenticationException e) {
0553: // If the cause is a IMAPExcpetion then only password
0554: // wrong
0555: // else bogus authentication mechanism
0556: if (e.getCause() instanceof IMAPException)
0557: throw (IMAPException) e.getCause();
0558:
0559: // Some error in the client/server communication
0560: // --> fall back to default login process
0561: int result = JOptionPane
0562: .showConfirmDialog(
0563: FrameManager.getInstance()
0564: .getActiveFrame(),
0565: new MultiLineLabel(
0566: e.getMessage()
0567: + "\n"
0568: + MailResourceLoader
0569: .getString(
0570: "dialog",
0571: "error",
0572: "authentication_fallback_to_default")),
0573: MailResourceLoader
0574: .getString("dialog",
0575: "error",
0576: "authentication_process_error"),
0577: JOptionPane.OK_CANCEL_OPTION);
0578:
0579: if (result == JOptionPane.OK_OPTION) {
0580: loginMethod = AuthenticationManager.LOGIN;
0581: item.setString("login_method", Integer
0582: .toString(loginMethod));
0583: } else {
0584: throw new CommandCancelledException();
0585: }
0586: }
0587: }
0588:
0589: } catch (IMAPException ex) {
0590: // login failed?
0591: IMAPResponse response = ex.getResponse();
0592: if (response == null || !response.isNO()) {
0593: // This exception is not because wrong username or
0594: // password
0595: throw ex;
0596: }
0597: }
0598: first = false;
0599: }
0600:
0601: // Sync subscribed folders if this is the first login
0602: // in this session
0603: if (firstLogin) {
0604: if (firstLoginAction != null) {
0605: firstLoginAction.actionPerformed();
0606: }
0607: }
0608:
0609: firstLogin = false;
0610: }
0611:
0612: /* (non-Javadoc)
0613: * @see org.columba.mail.imap.IImapServer#setFirstLoginAction(org.columba.mail.imap.IFirstLoginAction)
0614: */
0615: public void setFirstLoginAction(IFirstLoginAction action) {
0616: this .firstLoginAction = action;
0617: }
0618:
0619: /* (non-Javadoc)
0620: * @see org.columba.mail.imap.IImapServer#ensureSelectedState(org.columba.mail.folder.imap.IMAPFolder)
0621: */
0622: public void ensureSelectedState(IMAPFolder folder)
0623: throws IOException, IMAPException,
0624: CommandCancelledException {
0625: // ensure that we are logged in already
0626: ensureLoginState();
0627: String path = folder.getImapPath();
0628:
0629: // if mailbox is not already selected select it
0630: if (protocol.getState() != IMAPProtocol.SELECTED
0631: || !protocol.getSelectedMailbox().equals(path)) {
0632:
0633: printStatusMessage(MessageFormat.format(MailResourceLoader
0634: .getString("statusbar", "message", "select"),
0635: new Object[] { folder.getName() }));
0636:
0637: // Here we get the new mailboxinfo for the folder
0638: messageFolderInfo = protocol.select(path);
0639:
0640: // Set the readOnly flag
0641: folder.setReadOnly(!messageFolderInfo.isWriteAccess());
0642:
0643: // Convert to a MailboxStatus
0644: selectedStatus = new MailboxStatus(messageFolderInfo);
0645: statusDirty = false;
0646:
0647: selectedFolder = folder;
0648:
0649: // delete any cached information
0650: aktMimeTree = null;
0651: aktMessageUid = null;
0652: }
0653: }
0654:
0655: public int getLargestRemoteUid(IMAPFolder folder)
0656: throws IOException, IMAPException,
0657: CommandCancelledException {
0658: MailboxStatus status = getStatus(folder);
0659: if (status.getUidNext() < 0 && status.getMessages() > 0) {
0660: return fetchUid(new SequenceSet(status.getMessages()),
0661: folder);
0662: } else {
0663: return (int) (status.getUidNext() - 1);
0664: }
0665:
0666: }
0667:
0668: /* (non-Javadoc)
0669: * @see org.columba.mail.imap.IImapServer#getStatus(org.columba.mail.folder.imap.IMAPFolder)
0670: */
0671: public MailboxStatus getStatus(IMAPFolder folder)
0672: throws IOException, IMAPException,
0673: CommandCancelledException {
0674: ensureLoginState();
0675:
0676: if (selectedFolder != null && selectedFolder.equals(folder)
0677: && !statusDirty) {
0678: // We don't need to issue a additional NOOP
0679: // here since the ensureLogin() call above
0680: // ensures also the correct Status in a
0681: // MIN_IDLE interval timeframe.
0682:
0683: return selectedStatus;
0684: }
0685:
0686: if (selectedFolder == null
0687: || protocol.getState() < IMAPProtocol.SELECTED) {
0688: // if none selected select this folder instead of getting the status
0689: ensureSelectedState(folder);
0690: return selectedStatus;
0691: }
0692:
0693: printStatusMessage(MessageFormat.format(MailResourceLoader
0694: .getString("statusbar", "message", "status"),
0695: new Object[] { folder.getName() }));
0696:
0697: MailboxStatus result = protocol.status(folder.getImapPath(),
0698: new String[] { "MESSAGES", "UIDNEXT", "RECENT",
0699: "UNSEEN", "UIDVALIDITY" });
0700:
0701: // No response means zero!
0702: if (result.getUnseen() == -1)
0703: result.setUnseen(0);
0704: if (result.getRecent() == -1)
0705: result.setRecent(0);
0706: statusDirty = false;
0707:
0708: return result;
0709: }
0710:
0711: /**
0712: * Fetch delimiter.
0713: *
0714: */
0715: protected String fetchDelimiter() throws IOException,
0716: IMAPException, CommandCancelledException {
0717: // make sure we are already logged in
0718: ensureLoginState();
0719:
0720: try {
0721: ListInfo[] listInfo = protocol.list("", "");
0722: return listInfo[0].getDelimiter();
0723: } catch (IMAPDisconnectedException e1) {
0724: ListInfo[] listInfo = protocol.list("", "");
0725: return listInfo[0].getDelimiter();
0726: }
0727: }
0728:
0729: /* (non-Javadoc)
0730: * @see org.columba.mail.imap.IImapServer#list(java.lang.String, java.lang.String)
0731: */
0732: public ListInfo[] list(String reference, String pattern)
0733: throws Exception {
0734: ensureLoginState();
0735:
0736: try {
0737: return protocol.list(reference, pattern);
0738: } catch (IMAPDisconnectedException e) {
0739: return protocol.list(reference, pattern);
0740: }
0741: }
0742:
0743: /* (non-Javadoc)
0744: * @see org.columba.mail.imap.IImapServer#append(java.io.InputStream, org.columba.ristretto.imap.IMAPFlags, org.columba.mail.folder.imap.IMAPFolder)
0745: */
0746: public Integer append(InputStream messageSource, IMAPFlags flags,
0747: IMAPFolder folder) throws Exception {
0748: // make sure we are already logged in
0749: ensureLoginState();
0750:
0751: // close the mailbox if it is selected
0752: if (protocol.getState() == IMAPProtocol.SELECTED
0753: && protocol.getSelectedMailbox().equals(folder)) {
0754: protocol.close();
0755: }
0756:
0757: MailboxStatus status = protocol.status(folder.getImapPath(),
0758: new String[] { "UIDNEXT" });
0759:
0760: if (flags != null) {
0761: protocol.append(folder.getImapPath(), messageSource,
0762: new Object[] { flags });
0763: } else {
0764: protocol.append(folder.getImapPath(), messageSource);
0765:
0766: }
0767:
0768: return new Integer((int) status.getUidNext());
0769: }
0770:
0771: /* (non-Javadoc)
0772: * @see org.columba.mail.imap.IImapServer#append(java.io.InputStream, org.columba.mail.folder.imap.IMAPFolder)
0773: */
0774: public Integer append(InputStream messageSource, IMAPFolder folder)
0775: throws Exception {
0776: return append(messageSource, null, folder);
0777: }
0778:
0779: /* (non-Javadoc)
0780: * @see org.columba.mail.imap.IImapServer#createMailbox(java.lang.String, org.columba.mail.folder.imap.IMAPFolder)
0781: */
0782: public void createMailbox(String mailboxName, IMAPFolder folder)
0783: throws IOException, IMAPException,
0784: CommandCancelledException {
0785: // make sure we are logged in
0786: ensureLoginState();
0787:
0788: // concate the full name of the new mailbox
0789: String fullName;
0790: String path = (folder == null ? "" : folder.getImapPath());
0791:
0792: if (path.length() > 0)
0793: fullName = path + getDelimiter() + mailboxName;
0794: else
0795: fullName = mailboxName;
0796:
0797: // check if the mailbox already exists -> subscribe only
0798: if (protocol.list("", fullName).length == 0) {
0799: // create the mailbox on the server
0800: protocol.create(fullName);
0801: }
0802:
0803: // subscribe to the new mailbox
0804: protocol.subscribe(fullName);
0805: }
0806:
0807: /* (non-Javadoc)
0808: * @see org.columba.mail.imap.IImapServer#deleteFolder(java.lang.String)
0809: */
0810: public void deleteFolder(String path) throws Exception {
0811: // make sure we are already logged in
0812: ensureLoginState();
0813:
0814: if (protocol.getState() == IMAPProtocol.SELECTED
0815: && protocol.getSelectedMailbox().equals(path)) {
0816: protocol.close();
0817: }
0818:
0819: protocol.unsubscribe(path);
0820:
0821: protocol.delete(path);
0822: }
0823:
0824: /* (non-Javadoc)
0825: * @see org.columba.mail.imap.IImapServer#renameFolder(java.lang.String, java.lang.String)
0826: */
0827: public void renameFolder(String oldMailboxName,
0828: String newMailboxName) throws IOException, IMAPException,
0829: CommandCancelledException {
0830: // make sure we are already logged in
0831: ensureLoginState();
0832: protocol.rename(oldMailboxName, newMailboxName);
0833: protocol.unsubscribe(oldMailboxName);
0834: protocol.subscribe(newMailboxName);
0835: }
0836:
0837: /* (non-Javadoc)
0838: * @see org.columba.mail.imap.IImapServer#subscribeFolder(java.lang.String)
0839: */
0840: public void subscribeFolder(String mailboxName) throws IOException,
0841: IMAPException, CommandCancelledException {
0842: // make sure we are already logged in
0843: ensureLoginState();
0844:
0845: protocol.subscribe(mailboxName);
0846: }
0847:
0848: /* (non-Javadoc)
0849: * @see org.columba.mail.imap.IImapServer#unsubscribeFolder(java.lang.String)
0850: */
0851: public void unsubscribeFolder(String mailboxName)
0852: throws IOException, IMAPException,
0853: CommandCancelledException {
0854: // make sure we are already logged in
0855: ensureLoginState();
0856:
0857: protocol.unsubscribe(mailboxName);
0858: }
0859:
0860: /* (non-Javadoc)
0861: * @see org.columba.mail.imap.IImapServer#expunge(org.columba.mail.folder.imap.IMAPFolder)
0862: */
0863: public void expunge(IMAPFolder folder) throws IOException,
0864: IMAPException, CommandCancelledException {
0865: ensureSelectedState(folder);
0866:
0867: updatesEnabled = false;
0868: protocol.expunge();
0869: updatesEnabled = true;
0870: statusDirty = true;
0871: }
0872:
0873: /* (non-Javadoc)
0874: * @see org.columba.mail.imap.IImapServer#copy(org.columba.mail.folder.imap.IMAPFolder, java.lang.Object[], org.columba.mail.folder.imap.IMAPFolder)
0875: */
0876: public Integer[] copy(IMAPFolder destFolder, Object[] uids,
0877: IMAPFolder folder) throws Exception {
0878:
0879: ensureSelectedState(folder);
0880:
0881: // We need to sort the uids in order
0882: // to have the correct association
0883: // between the new and old uid
0884: List sortedUids = Arrays.asList(uids);
0885: Collections.sort(sortedUids);
0886:
0887: MailboxStatus statusBefore = protocol.status(destFolder
0888: .getImapPath(), new String[] { "UIDNEXT" });
0889:
0890: protocol.uidCopy(new SequenceSet(Arrays.asList(uids)),
0891: destFolder.getImapPath());
0892:
0893: MailboxStatus statusAfter = protocol.status(destFolder
0894: .getImapPath(), new String[] { "UIDNEXT" });
0895:
0896: // the UIDS start UIDNext till UIDNext + uids.length
0897: int copied = (int) (statusAfter.getUidNext() - statusBefore
0898: .getUidNext());
0899: Integer[] destUids = new Integer[copied];
0900: for (int i = 0; i < copied; i++) {
0901: destUids[i] = new Integer(
0902: (int) (statusBefore.getUidNext() + i));
0903: }
0904:
0905: return destUids;
0906: }
0907:
0908: /* (non-Javadoc)
0909: * @see org.columba.mail.imap.IImapServer#fetchUid(org.columba.ristretto.imap.SequenceSet, org.columba.mail.folder.imap.IMAPFolder)
0910: */
0911: public int fetchUid(SequenceSet set, IMAPFolder folder)
0912: throws IOException, IMAPException,
0913: CommandCancelledException {
0914: ensureSelectedState(folder);
0915: Integer[] result = protocol.fetchUid(set);
0916: if (result.length == 1)
0917: return result[0].intValue();
0918: else
0919: return -1;
0920:
0921: }
0922:
0923: /* (non-Javadoc)
0924: * @see org.columba.mail.imap.IImapServer#fetchUids(org.columba.ristretto.imap.SequenceSet, org.columba.mail.folder.imap.IMAPFolder)
0925: */
0926: public Integer[] fetchUids(SequenceSet set, IMAPFolder folder)
0927: throws IOException, IMAPException,
0928: CommandCancelledException {
0929: IStatusObservable observable = getObservable();
0930: printStatusMessage(MailResourceLoader.getString("statusbar",
0931: "message", "fetch_uid_list"));
0932:
0933: ensureSelectedState(folder);
0934: if (messageFolderInfo.getExists() > 0) {
0935: SequenceSet[] packs = divide(set);
0936: Integer[] result = new Integer[set
0937: .getLength(messageFolderInfo.getExists())];
0938:
0939: // update the progress
0940: if (observable != null) {
0941: observable.setCurrent(0);
0942: observable.setMax(result.length);
0943: }
0944:
0945: int pos = 0;
0946:
0947: for (int i = 0; i < packs.length; i++) {
0948: int packLength = packs[i].getLength(messageFolderInfo
0949: .getExists());
0950: System.arraycopy(protocol.fetchUid(packs[i]), 0,
0951: result, pos, packLength);
0952: pos += packLength;
0953:
0954: // update the progress
0955: if (observable != null) {
0956: observable.setCurrent(pos);
0957: }
0958: }
0959:
0960: return result;
0961: } else {
0962: return new Integer[0];
0963: }
0964: }
0965:
0966: private SequenceSet[] divide(SequenceSet in) {
0967: int length = in.getLength(messageFolderInfo.getExists());
0968:
0969: if (length > UID_FETCH_STEPS) {
0970: int[] decomposed = in
0971: .toArray(messageFolderInfo.getExists());
0972:
0973: List result = new ArrayList();
0974: int pos = 0;
0975: // divide in packs
0976: while (decomposed.length - pos > UID_FETCH_STEPS) {
0977: result.add(new SequenceSet(decomposed, pos,
0978: UID_FETCH_STEPS));
0979: pos += UID_FETCH_STEPS;
0980: }
0981: // dont forget the rest
0982: if (decomposed.length - pos > 0) {
0983: result.add(new SequenceSet(decomposed, pos,
0984: decomposed.length - pos));
0985: }
0986:
0987: return (SequenceSet[]) result.toArray(new SequenceSet[0]);
0988: } else {
0989: return new SequenceSet[] { in };
0990: }
0991:
0992: }
0993:
0994: /* (non-Javadoc)
0995: * @see org.columba.mail.imap.IImapServer#fetchFlagsListStartFrom(int, org.columba.mail.folder.imap.IMAPFolder)
0996: */
0997: public IMAPFlags[] fetchFlagsListStartFrom(int startIdx,
0998: IMAPFolder folder) throws IOException, IMAPException,
0999: CommandCancelledException {
1000: IStatusObservable observable = getObservable();
1001:
1002: ensureSelectedState(folder);
1003: if (selectedStatus.getMessages() - startIdx >= 0) {
1004: SequenceSet set = new SequenceSet();
1005: set.addOpenRange(startIdx);
1006:
1007: SequenceSet[] packs = divide(set);
1008:
1009: // update the progress
1010: if (observable != null) {
1011: observable.setCurrent(0);
1012: observable.setMax(set.getLength(selectedStatus
1013: .getMessages()));
1014: }
1015:
1016: List allResults = new ArrayList(packs.length);
1017:
1018: int pos = 0;
1019:
1020: // store the intermediate results in a list
1021: for (int i = 0; i < packs.length; i++) {
1022: try {
1023: IMAPFlags[] r = protocol.fetchFlags(packs[i]);
1024: pos += r.length;
1025:
1026: allResults.add(r);
1027: } catch (IMAPException e) {
1028: // Entry does not exist on server
1029: // -> add nothing
1030: }
1031:
1032: // update the progress
1033: if (observable != null) {
1034: observable.setCurrent(pos);
1035: }
1036: }
1037:
1038: // Combine the results in one array
1039: IMAPFlags[] result = new IMAPFlags[pos];
1040: Iterator it = allResults.iterator();
1041:
1042: pos = 0;
1043: while (it.hasNext()) {
1044: IMAPFlags[] r = (IMAPFlags[]) it.next();
1045: System.arraycopy(r, 0, result, pos, r.length);
1046:
1047: pos += r.length;
1048: }
1049:
1050: return result;
1051: } else {
1052: return new IMAPFlags[0];
1053: }
1054: }
1055:
1056: /* (non-Javadoc)
1057: * @see org.columba.mail.imap.IImapServer#fetchFlagsListStartFrom2(int, org.columba.mail.folder.imap.IMAPFolder)
1058: */
1059: public IMAPFlags[] fetchFlagsListStartFrom2(int startIdx,
1060: IMAPFolder folder) throws IOException, IMAPException,
1061: CommandCancelledException {
1062: ensureSelectedState(folder);
1063: if (selectedStatus.getMessages() - startIdx >= 0) {
1064: SequenceSet set = new SequenceSet();
1065: set.add(startIdx, Math.min(startIdx + 9, selectedStatus
1066: .getMessages()));
1067:
1068: IMAPFlags[] result = protocol.fetchFlags(set);
1069:
1070: return result;
1071: } else {
1072: return new IMAPFlags[0];
1073: }
1074: }
1075:
1076: /* (non-Javadoc)
1077: * @see org.columba.mail.imap.IImapServer#fetchNamespaces()
1078: */
1079: public NamespaceCollection fetchNamespaces() throws IOException,
1080: IMAPException, CommandCancelledException {
1081: ensureLoginState();
1082: return protocol.namespace();
1083: }
1084:
1085: /* (non-Javadoc)
1086: * @see org.columba.mail.imap.IImapServer#fetchHeaderList(org.columba.mail.message.IHeaderList, java.util.List, org.columba.mail.folder.imap.IMAPFolder)
1087: */
1088: public void fetchHeaderList(IHeaderList headerList, List list,
1089: IMAPFolder folder) throws Exception {
1090: // make sure this mailbox is selected
1091: ensureSelectedState(folder);
1092:
1093: printStatusMessage(MailResourceLoader.getString("statusbar",
1094: "message", "fetch_header_list"));
1095:
1096: int count = list.size() / IMAPServer.STEP_SIZE;
1097: int rest = list.size() % IMAPServer.STEP_SIZE;
1098: getObservable().setCurrent(0);
1099: getObservable().setMax(count + 1);
1100: for (int i = 0; i < count; i++) {
1101: doFetchHeaderList(headerList, list.subList(i
1102: * IMAPServer.STEP_SIZE, (i + 1)
1103: * IMAPServer.STEP_SIZE));
1104: getObservable().setCurrent(i);
1105: }
1106:
1107: if (rest > 0) {
1108: doFetchHeaderList(headerList, list.subList(count
1109: * IMAPServer.STEP_SIZE, count
1110: * IMAPServer.STEP_SIZE + rest));
1111: }
1112:
1113: getObservable().setCurrent(count + 1);
1114: }
1115:
1116: /**
1117: * @param headerList
1118: * @param list
1119: * @throws IOException
1120: * @throws IMAPException
1121: */
1122: private void doFetchHeaderList(IHeaderList headerList, List list)
1123: throws IOException, IMAPException {
1124: // get list of user-defined headerfields
1125: String[] headerFields = CachedHeaderfields
1126: .getDefaultHeaderfields();
1127:
1128: IMAPHeader[] headers = protocol.uidFetchHeaderFields(
1129: new SequenceSet(list), headerFields);
1130:
1131: for (int i = 0; i < headers.length; i++) {
1132: // add it to the headerlist
1133: ColumbaHeader header = new ColumbaHeader(headers[i]
1134: .getHeader());
1135: Object uid = headers[i].getUid();
1136:
1137: header.getAttributes().put("columba.uid", uid);
1138: header.getAttributes().put("columba.size",
1139: headers[i].getSize());
1140: header.getAttributes().put("columba.accountuid",
1141: getAccountUid());
1142:
1143: // set the attachment flag
1144: header.getAttributes().put("columba.attachment",
1145: header.hasAttachments());
1146:
1147: // make sure that we have a Message-ID
1148: String messageID = (String) header.get("Message-Id");
1149: if (messageID != null)
1150: header.set("Message-ID", header.get("Message-Id"));
1151:
1152: headerList.add(header, uid);
1153: }
1154: }
1155:
1156: protected Integer getAccountUid() {
1157: AccountItem accountItem = new AccountItem(item.getRoot()
1158: .getParent());
1159: return new Integer(accountItem.getInteger("uid"));
1160: }
1161:
1162: protected synchronized void ensureConnectedState()
1163: throws CommandCancelledException, IOException,
1164: IMAPException {
1165: if (Math.abs(System.currentTimeMillis() - lastCommunication) > MIN_IDLE) {
1166: try {
1167: protocol.noop();
1168: } catch (IOException e) {
1169: // Now the state of the procotol is more certain correct
1170: } catch (IMAPDisconnectedException e) {
1171:
1172: }
1173: }
1174:
1175: if (protocol.getState() < IMAPProtocol.NON_AUTHENTICATED) {
1176: printStatusMessage(MailResourceLoader.getString(
1177: "statusbar", "message", "connecting"));
1178: openConnection();
1179: }
1180:
1181: // update this point of time as last communication
1182: // since every functio calls this before communicating with
1183: // the server
1184: lastCommunication = System.currentTimeMillis();
1185: }
1186:
1187: /**
1188: * Ensure that we are in login state.
1189: *
1190: * @throws Exception
1191: */
1192: protected void ensureLoginState() throws IOException,
1193: IMAPException, CommandCancelledException {
1194: ensureConnectedState();
1195:
1196: if (protocol.getState() < IMAPProtocol.AUTHENTICATED) {
1197: printStatusMessage(MailResourceLoader.getString(
1198: "statusbar", "message", "authenticating"));
1199: login();
1200: }
1201: }
1202:
1203: /* (non-Javadoc)
1204: * @see org.columba.mail.imap.IImapServer#getMimeTree(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1205: */
1206: public MimeTree getMimeTree(Object uid, IMAPFolder folder)
1207: throws IOException, IMAPException,
1208: CommandCancelledException {
1209: try {
1210: ensureSelectedState(folder);
1211:
1212: // Use a caching mechanism for this
1213: if (aktMimeTree == null || !aktMessageUid.equals(uid)) {
1214: aktMimeTree = protocol
1215: .uidFetchBodystructure(((Integer) uid)
1216: .intValue());
1217: aktMessageUid = uid;
1218: }
1219:
1220: return aktMimeTree;
1221: } catch (IMAPDisconnectedException e) {
1222: return getMimeTree(uid, folder);
1223: }
1224: }
1225:
1226: /* (non-Javadoc)
1227: * @see org.columba.mail.imap.IImapServer#getMimePartBodyStream(java.lang.Object, java.lang.Integer[], org.columba.mail.folder.imap.IMAPFolder)
1228: */
1229: public InputStream getMimePartBodyStream(Object uid,
1230: Integer[] address, IMAPFolder folder) throws IOException,
1231: IMAPException, CommandCancelledException {
1232: try {
1233: ensureSelectedState(folder);
1234:
1235: return protocol.uidFetchBody(((Integer) uid).intValue(),
1236: address);
1237: } catch (IMAPDisconnectedException e) {
1238: return getMimePartBodyStream(uid, address, folder);
1239: }
1240: }
1241:
1242: /* (non-Javadoc)
1243: * @see org.columba.mail.imap.IImapServer#getHeaders(java.lang.Object, java.lang.String[], org.columba.mail.folder.imap.IMAPFolder)
1244: */
1245: public Header getHeaders(Object uid, String[] keys,
1246: IMAPFolder folder) throws IOException, IMAPException,
1247: CommandCancelledException {
1248: try {
1249: ensureSelectedState(folder);
1250:
1251: IMAPHeader[] headers = protocol.uidFetchHeaderFields(
1252: new SequenceSet(((Integer) uid).intValue()), keys);
1253:
1254: return headers[0].getHeader();
1255: } catch (IMAPDisconnectedException e) {
1256: return getHeaders(uid, keys, folder);
1257: }
1258: }
1259:
1260: /* (non-Javadoc)
1261: * @see org.columba.mail.imap.IImapServer#getAllHeaders(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1262: */
1263: public Header getAllHeaders(Object uid, IMAPFolder folder)
1264: throws IOException, IMAPException,
1265: CommandCancelledException {
1266: try {
1267: ensureSelectedState(folder);
1268:
1269: IMAPHeader[] headers = protocol
1270: .uidFetchHeader(new SequenceSet(((Integer) uid)
1271: .intValue()));
1272:
1273: return headers[0].getHeader();
1274: } catch (IMAPDisconnectedException e) {
1275: return getAllHeaders(uid, folder);
1276: }
1277: }
1278:
1279: /* (non-Javadoc)
1280: * @see org.columba.mail.imap.IImapServer#getMimePartSourceStream(java.lang.Object, java.lang.Integer[], org.columba.mail.folder.imap.IMAPFolder)
1281: */
1282: public InputStream getMimePartSourceStream(Object uid,
1283: Integer[] address, IMAPFolder folder) throws IOException,
1284: IMAPException, CommandCancelledException {
1285: try {
1286: ensureSelectedState(folder);
1287:
1288: InputStream headerSource = protocol
1289: .uidFetchMimeHeaderSource(((Integer) uid)
1290: .intValue(), address);
1291: InputStream bodySource = protocol.uidFetchBody(
1292: ((Integer) uid).intValue(), address);
1293:
1294: return new SequenceInputStream(headerSource, bodySource);
1295: } catch (IMAPDisconnectedException e) {
1296: return getMimePartSourceStream(uid, address, folder);
1297: }
1298: }
1299:
1300: /* (non-Javadoc)
1301: * @see org.columba.mail.imap.IImapServer#getMessageSourceStream(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1302: */
1303: public InputStream getMessageSourceStream(Object uid,
1304: IMAPFolder folder) throws IOException, IMAPException,
1305: CommandCancelledException {
1306: try {
1307: ensureSelectedState(folder);
1308:
1309: return protocol.uidFetchMessage(((Integer) uid).intValue());
1310: } catch (IMAPDisconnectedException e) {
1311: return getMessageSourceStream(uid, folder);
1312: }
1313: }
1314:
1315: /* (non-Javadoc)
1316: * @see org.columba.mail.imap.IImapServer#markMessage(java.lang.Object[], int, org.columba.mail.folder.imap.IMAPFolder)
1317: */
1318: public void markMessage(Object[] uids, int variant,
1319: IMAPFolder folder) throws IOException, IMAPException,
1320: CommandCancelledException {
1321: try {
1322: ensureSelectedState(folder);
1323:
1324: SequenceSet uidSet = new SequenceSet(Arrays.asList(uids));
1325:
1326: protocol.uidStore(uidSet, variant > 0,
1327: convertToFlags(variant));
1328:
1329: statusDirty = true;
1330: } catch (IMAPDisconnectedException e) {
1331: markMessage(uids, variant, folder);
1332: }
1333: }
1334:
1335: /* (non-Javadoc)
1336: * @see org.columba.mail.imap.IImapServer#setFlags(java.lang.Object[], org.columba.ristretto.imap.IMAPFlags, org.columba.mail.folder.imap.IMAPFolder)
1337: */
1338: public void setFlags(Object[] uids, IMAPFlags flags,
1339: IMAPFolder folder) throws IOException, IMAPException,
1340: CommandCancelledException {
1341: try {
1342: ensureSelectedState(folder);
1343: SequenceSet uidSet = new SequenceSet(Arrays.asList(uids));
1344:
1345: protocol.uidStore(uidSet, true, flags);
1346: } catch (IMAPDisconnectedException e) {
1347: setFlags(uids, flags, folder);
1348: }
1349: }
1350:
1351: /* (non-Javadoc)
1352: * @see org.columba.mail.imap.IImapServer#search(java.lang.Object[], org.columba.core.filter.FilterRule, org.columba.mail.folder.imap.IMAPFolder)
1353: */
1354: public List search(Object[] uids, IFilterRule filterRule,
1355: IMAPFolder folder) throws Exception {
1356: LinkedList result = new LinkedList(search(filterRule, folder));
1357:
1358: ListTools.intersect(result, Arrays.asList(uids));
1359:
1360: return result;
1361: }
1362:
1363: /* (non-Javadoc)
1364: * @see org.columba.mail.imap.IImapServer#getIndex(java.lang.Integer, org.columba.mail.folder.imap.IMAPFolder)
1365: */
1366: public int getIndex(Integer uid, IMAPFolder folder)
1367: throws IOException, IMAPException,
1368: CommandCancelledException {
1369:
1370: try {
1371: ensureSelectedState(folder);
1372:
1373: SearchKey key = new SearchKey(SearchKey.UID, uid);
1374:
1375: Integer[] index = protocol.search(new SearchKey[] { key });
1376: if (index.length > 0) {
1377: return index[0].intValue();
1378: } else {
1379: return -1;
1380: }
1381: } catch (IMAPDisconnectedException e) {
1382: return getIndex(uid, folder);
1383: }
1384: }
1385:
1386: /* (non-Javadoc)
1387: * @see org.columba.mail.imap.IImapServer#search(org.columba.ristretto.imap.SearchKey, org.columba.mail.folder.imap.IMAPFolder)
1388: */
1389: public Integer[] search(SearchKey key, IMAPFolder folder)
1390: throws IOException, IMAPException,
1391: CommandCancelledException {
1392: try {
1393: ensureSelectedState(folder);
1394:
1395: return protocol.uidSearch(new SearchKey[] { key });
1396: } catch (IMAPDisconnectedException e) {
1397: return search(key, folder);
1398: }
1399: }
1400:
1401: /* (non-Javadoc)
1402: * @see org.columba.mail.imap.IImapServer#search(org.columba.core.filter.FilterRule, org.columba.mail.folder.imap.IMAPFolder)
1403: */
1404: public List search(IFilterRule filterRule, IMAPFolder folder)
1405: throws IOException, IMAPException,
1406: CommandCancelledException {
1407:
1408: try {
1409: ensureSelectedState(folder);
1410:
1411: SearchKey[] searchRequest;
1412:
1413: searchRequest = createSearchKey(filterRule);
1414:
1415: Integer[] result = null;
1416: Charset charset = UTF8;
1417:
1418: while (result == null) {
1419: try {
1420: result = protocol.uidSearch(charset, searchRequest);
1421: } catch (IMAPException e) {
1422: if (e.getResponse().isNO() && charset != null) {
1423: // Server does not support UTF-8
1424: // -> fall back to System default
1425: if (charset.equals(UTF8)) {
1426: charset = DEFAULT;
1427: } else if (charset == DEFAULT) {
1428: // If this also does not work
1429: // -> fall back to no charset specified
1430: charset = null;
1431: } else {
1432: // something else is wrong
1433: throw e;
1434: }
1435: } else
1436: throw e;
1437: }
1438: }
1439:
1440: return Arrays.asList(result);
1441: } catch (IMAPDisconnectedException e) {
1442: return search(filterRule, folder);
1443: }
1444: }
1445:
1446: /**
1447: * @param filterRule
1448: */
1449: private SearchKey[] createSearchKey(IFilterRule filterRule) {
1450: SearchKey[] searchRequest;
1451: int argumentSize = filterRule.getChildCount();
1452: // One or many arguments?
1453: if (argumentSize == 1) {
1454: // One is the easiest case
1455: searchRequest = new SearchKey[] { getSearchKey(filterRule
1456: .get(0)) };
1457: } else {
1458: // AND or OR ? -> AND is implicit, OR must be specified
1459: if (filterRule.getConditionInt() == FilterRule.MATCH_ALL) {
1460: // AND : simply create a list of arguments
1461: searchRequest = new SearchKey[argumentSize];
1462:
1463: for (int i = 0; i < argumentSize; i++) {
1464: searchRequest[i] = getSearchKey(filterRule.get(i));
1465: }
1466:
1467: } else {
1468: // OR : the arguments must be glued by a OR SearchKey
1469: SearchKey orKey;
1470:
1471: orKey = new SearchKey(SearchKey.OR,
1472: getSearchKey(filterRule.get(argumentSize - 1)),
1473: getSearchKey(filterRule.get(argumentSize - 2)));
1474:
1475: for (int i = argumentSize - 3; i >= 0; i--) {
1476: orKey = new SearchKey(SearchKey.OR,
1477: getSearchKey(filterRule.get(i)), orKey);
1478: }
1479:
1480: searchRequest = new SearchKey[] { orKey };
1481: }
1482: }
1483:
1484: return searchRequest;
1485: }
1486:
1487: /**
1488: * @param criteria
1489: * @return
1490: */
1491: private SearchKey getSearchKey(IFilterCriteria criteria) {
1492: int operator = criteria.getCriteria();
1493: int type = new MailFilterCriteria(criteria).getType();
1494:
1495: switch (type) {
1496: case MailFilterCriteria.FROM: {
1497: if (operator == FilterCriteria.CONTAINS) {
1498: return new SearchKey(SearchKey.FROM, criteria
1499: .getPatternString());
1500: } else {
1501: // contains not
1502: return new SearchKey(SearchKey.NOT, new SearchKey(
1503: SearchKey.FROM, criteria.getPatternString()));
1504: }
1505: }
1506:
1507: case MailFilterCriteria.CC: {
1508: if (operator == FilterCriteria.CONTAINS) {
1509: return new SearchKey(SearchKey.CC, criteria
1510: .getPatternString());
1511: } else {
1512: // contains not
1513: return new SearchKey(SearchKey.NOT, new SearchKey(
1514: SearchKey.CC, criteria.getPatternString()));
1515: }
1516: }
1517:
1518: case MailFilterCriteria.BCC: {
1519: if (operator == FilterCriteria.CONTAINS) {
1520: return new SearchKey(SearchKey.BCC, criteria
1521: .getPatternString());
1522: } else {
1523: // contains not
1524: return new SearchKey(SearchKey.NOT, new SearchKey(
1525: SearchKey.BCC, criteria.getPatternString()));
1526: }
1527: }
1528:
1529: case MailFilterCriteria.TO: {
1530: if (operator == FilterCriteria.CONTAINS) {
1531: return new SearchKey(SearchKey.TO, criteria
1532: .getPatternString());
1533: } else {
1534: // contains not
1535: return new SearchKey(SearchKey.NOT, new SearchKey(
1536: SearchKey.TO, criteria.getPatternString()));
1537: }
1538: }
1539:
1540: case MailFilterCriteria.SUBJECT: {
1541: if (operator == FilterCriteria.CONTAINS) {
1542: return new SearchKey(SearchKey.SUBJECT, criteria
1543: .getPatternString());
1544: } else {
1545: // contains not
1546: return new SearchKey(SearchKey.NOT, new SearchKey(
1547: SearchKey.SUBJECT, criteria.getPatternString()));
1548: }
1549: }
1550:
1551: case MailFilterCriteria.BODY: {
1552: if (operator == FilterCriteria.CONTAINS) {
1553: return new SearchKey(SearchKey.BODY, criteria
1554: .getPatternString());
1555: } else {
1556: // contains not
1557: return new SearchKey(SearchKey.NOT, new SearchKey(
1558: SearchKey.BODY, criteria.getPatternString()));
1559: }
1560: }
1561:
1562: case MailFilterCriteria.CUSTOM_HEADERFIELD: {
1563: if (operator == FilterCriteria.CONTAINS) {
1564: return new SearchKey(SearchKey.HEADER,
1565: new MailFilterCriteria(criteria)
1566: .getHeaderfieldString(), criteria
1567: .getPatternString());
1568: } else {
1569: // contains not
1570: return new SearchKey(SearchKey.NOT, new SearchKey(
1571: SearchKey.HEADER, new MailFilterCriteria(
1572: criteria).getHeaderfieldString(),
1573: criteria.getPatternString()));
1574: }
1575: }
1576:
1577: case MailFilterCriteria.DATE: {
1578: DateFormat df = DateFormat.getDateInstance();
1579:
1580: IMAPDate searchPattern = null;
1581:
1582: try {
1583: searchPattern = new IMAPDate(df.parse(criteria
1584: .getPatternString()));
1585: } catch (java.text.ParseException ex) {
1586: // should never happen
1587: ex.printStackTrace();
1588: }
1589:
1590: if (operator == FilterCriteria.DATE_BEFORE) {
1591: return new SearchKey(SearchKey.BEFORE, searchPattern);
1592: } else {
1593: // AFTER
1594: return new SearchKey(SearchKey.NOT, new SearchKey(
1595: SearchKey.BEFORE, searchPattern));
1596: }
1597: }
1598:
1599: case MailFilterCriteria.SIZE: {
1600: if (operator == FilterCriteria.SIZE_SMALLER) {
1601: return new SearchKey(SearchKey.SMALLER, criteria
1602: .getPatternString());
1603: } else {
1604: // contains not
1605: return new SearchKey(SearchKey.NOT, new SearchKey(
1606: SearchKey.SMALLER, criteria.getPatternString()));
1607: }
1608: }
1609: }
1610:
1611: return null;
1612: }
1613:
1614: /**
1615: * Check if string contains US-ASCII characters.
1616: *
1617: * @param s
1618: * @return true, if string contains US-ASCII characters
1619: */
1620: protected static boolean isAscii(String s) {
1621: int l = s.length();
1622:
1623: for (int i = 0; i < l; i++) {
1624: if ((int) s.charAt(i) > 0177) { // non-ascii
1625:
1626: return false;
1627: }
1628: }
1629:
1630: return true;
1631: }
1632:
1633: /**
1634: * Create string representation of {@ link MarkMessageCommand}constants.
1635: *
1636: * @param variant
1637: * @return
1638: */
1639: private IMAPFlags convertToFlags(int variant) {
1640: IMAPFlags result = new IMAPFlags();
1641:
1642: switch (variant) {
1643: case MarkMessageCommand.MARK_AS_READ:
1644: case MarkMessageCommand.MARK_AS_UNREAD: {
1645: result.setSeen(true);
1646:
1647: break;
1648: }
1649:
1650: case MarkMessageCommand.MARK_AS_FLAGGED:
1651: case MarkMessageCommand.MARK_AS_UNFLAGGED: {
1652: result.setFlagged(true);
1653:
1654: break;
1655: }
1656:
1657: case MarkMessageCommand.MARK_AS_EXPUNGED:
1658: case MarkMessageCommand.MARK_AS_UNEXPUNGED: {
1659: result.setDeleted(true);
1660:
1661: break;
1662: }
1663:
1664: case MarkMessageCommand.MARK_AS_ANSWERED: {
1665: result.setAnswered(true);
1666:
1667: break;
1668: }
1669:
1670: case MarkMessageCommand.MARK_AS_SPAM:
1671: case MarkMessageCommand.MARK_AS_NOTSPAM: {
1672: result.setJunk(true);
1673:
1674: break;
1675: }
1676: case MarkMessageCommand.MARK_AS_DRAFT: {
1677: result.setDraft(true);
1678:
1679: break;
1680: }
1681: }
1682:
1683: return result;
1684: }
1685:
1686: /* (non-Javadoc)
1687: * @see org.columba.mail.imap.IImapServer#getMessageFolderInfo(org.columba.mail.folder.imap.IMAPFolder)
1688: */
1689: public MailboxInfo getMessageFolderInfo(IMAPFolder folder)
1690: throws IOException, IMAPException,
1691: CommandCancelledException {
1692: ensureSelectedState(folder);
1693:
1694: return messageFolderInfo;
1695: }
1696:
1697: /* (non-Javadoc)
1698: * @see org.columba.mail.imap.IImapServer#fetchSubscribedFolders()
1699: */
1700: public ListInfo[] fetchSubscribedFolders() throws IOException,
1701: IMAPException, CommandCancelledException {
1702: try {
1703: ensureLoginState();
1704: ListInfo[] lsub = protocol.lsub("", "*");
1705:
1706: // Also set the delimiter
1707: if (lsub.length > 0) {
1708: delimiter = lsub[0].getDelimiter();
1709: }
1710:
1711: return lsub;
1712: } catch (IMAPDisconnectedException e) {
1713: return fetchSubscribedFolders();
1714: }
1715: }
1716:
1717: /* (non-Javadoc)
1718: * @see org.columba.mail.imap.IImapServer#isSelected(org.columba.mail.folder.imap.IMAPFolder)
1719: */
1720: public boolean isSelected(IMAPFolder folder) throws IOException,
1721: IMAPException, CommandCancelledException {
1722: ensureLoginState();
1723:
1724: return (protocol.getState() == IMAPProtocol.SELECTED && protocol
1725: .getSelectedMailbox().equals(folder.getImapPath()));
1726: }
1727:
1728: /**
1729: * @param e
1730: * @return
1731: */
1732: private int showErrorDialog(String message) {
1733: Object[] options = new String[] {
1734: MailResourceLoader.getString("", "global", "ok")
1735: .replaceAll("&", ""),
1736: MailResourceLoader.getString("", "global", "cancel")
1737: .replaceAll("&", "") };
1738:
1739: int result = JOptionPane.showOptionDialog(FrameManager
1740: .getInstance().getActiveFrame(), message, "Warning",
1741: JOptionPane.DEFAULT_OPTION,
1742: JOptionPane.WARNING_MESSAGE, null, options, options[0]);
1743: return result;
1744: }
1745:
1746: /* (non-Javadoc)
1747: * @see org.columba.mail.imap.IImapServer#alertMessage(java.lang.String)
1748: */
1749: public void alertMessage(String arg0) {
1750: // TODO: Show dialog
1751: LOG.warning(arg0);
1752: }
1753:
1754: /* (non-Javadoc)
1755: * @see org.columba.mail.imap.IImapServer#connectionClosed(java.lang.String, java.lang.String)
1756: */
1757: public void connectionClosed(String arg0, String arg1) {
1758: LOG.info(arg0);
1759: selectedFolder = null;
1760: }
1761:
1762: /* (non-Javadoc)
1763: * @see org.columba.mail.imap.IImapServer#existsChanged(java.lang.String, int)
1764: */
1765: public void existsChanged(String arg0, int arg1) {
1766: if (selectedStatus == null)
1767: return;
1768:
1769: selectedStatus.setMessages(arg1);
1770: statusDirty = true;
1771:
1772: if (updatesEnabled) {
1773: if (existsChangedAction != null) {
1774: existsChangedAction.actionPerformed(selectedFolder);
1775: }
1776:
1777: LOG.fine("Exists changed -> triggering update");
1778:
1779: }
1780: }
1781:
1782: /* (non-Javadoc)
1783: * @see org.columba.mail.imap.IImapServer#flagsChanged(java.lang.String, org.columba.ristretto.imap.IMAPFlags)
1784: */
1785: public void flagsChanged(String arg0, IMAPFlags arg1) {
1786: LOG.fine("Flag changed -> triggering update");
1787:
1788: if (updateFlagAction != null) {
1789: updateFlagAction.actionPerformed(selectedFolder, arg1);
1790: }
1791:
1792: }
1793:
1794: /* (non-Javadoc)
1795: * @see org.columba.mail.imap.IImapServer#parseError(java.lang.String)
1796: */
1797: public void parseError(String arg0) {
1798: LOG.warning(arg0);
1799: }
1800:
1801: /* (non-Javadoc)
1802: * @see org.columba.mail.imap.IImapServer#recentChanged(java.lang.String, int)
1803: */
1804: public void recentChanged(String arg0, int arg1) {
1805: if (selectedStatus == null)
1806: return;
1807:
1808: selectedStatus.setRecent(arg1);
1809: statusDirty = true;
1810:
1811: // We trigger an update only when the exists changed
1812: // which should be equal with a Recent change.
1813: }
1814:
1815: /* (non-Javadoc)
1816: * @see org.columba.mail.imap.IImapServer#warningMessage(java.lang.String)
1817: */
1818: public void warningMessage(String arg0) {
1819: LOG.warning(arg0);
1820: }
1821:
1822: /**
1823: * Returns configuration options of this IMAP account
1824: *
1825: * @return configuration options of this IMAP account
1826: */
1827: /* (non-Javadoc)
1828: * @see org.columba.mail.imap.IImapServer#getItem()
1829: */
1830: public ImapItem getItem() {
1831: return item;
1832: }
1833:
1834: /* (non-Javadoc)
1835: * @see org.columba.mail.imap.IImapServer#update(java.util.Observable, java.lang.Object)
1836: */
1837: public void update(Observable o, Object arg) {
1838: protocol = new IMAPProtocol(item.get("host"), item
1839: .getInteger("port"));
1840: }
1841:
1842: /* (non-Javadoc)
1843: * @see org.columba.mail.imap.IImapServer#setExistsChangedAction(org.columba.mail.imap.IExistsChangedAction)
1844: */
1845: public void setExistsChangedAction(
1846: IExistsChangedAction existsChangedAction) {
1847: this .existsChangedAction = existsChangedAction;
1848: }
1849:
1850: /* (non-Javadoc)
1851: * @see org.columba.mail.imap.IImapServer#setUpdateFlagAction(org.columba.mail.imap.IUpdateFlagAction)
1852: */
1853: public void setUpdateFlagAction(IUpdateFlagAction updateFlagAction) {
1854: this .updateFlagAction = updateFlagAction;
1855: }
1856:
1857: /* (non-Javadoc)
1858: * @see org.columba.mail.imap.IImapServer#setObservable(org.columba.api.command.IStatusObservable)
1859: */
1860: public void setObservable(IStatusObservable observable) {
1861: this.observable = observable;
1862: }
1863: }
|