KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > columba > mail > imap > IMAPServer


1 // The contents of this file are subject to the Mozilla Public License Version
2
// 1.1
3
//(the "License"); you may not use this file except in compliance with the
4
//License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
5
//
6
//Software distributed under the License is distributed on an "AS IS" basis,
7
//WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
8
//for the specific language governing rights and
9
//limitations under the License.
10
//
11
//The Original Code is "The Columba Project"
12
//
13
//The Initial Developers of the Original Code are Frederik Dietz and Timo
14
// Stich.
15
//Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
16
//
17
//All Rights Reserved.
18
package org.columba.mail.imap;
19
20 import java.io.IOException JavaDoc;
21 import java.io.InputStream JavaDoc;
22 import java.nio.charset.Charset JavaDoc;
23 import java.text.DateFormat JavaDoc;
24 import java.text.MessageFormat JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Arrays JavaDoc;
27 import java.util.Collections JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.LinkedList JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Observable JavaDoc;
32 import java.util.Observer JavaDoc;
33 import java.util.logging.Logger JavaDoc;
34
35 import javax.net.ssl.SSLException;
36 import javax.swing.JOptionPane JavaDoc;
37
38 import org.columba.api.command.IStatusObservable;
39 import org.columba.core.base.Blowfish;
40 import org.columba.core.base.ListTools;
41 import org.columba.core.command.CommandCancelledException;
42 import org.columba.core.filter.FilterCriteria;
43 import org.columba.core.filter.FilterRule;
44 import org.columba.core.filter.IFilterCriteria;
45 import org.columba.core.filter.IFilterRule;
46 import org.columba.core.gui.base.MultiLineLabel;
47 import org.columba.core.gui.frame.FrameManager;
48 import org.columba.mail.config.AccountItem;
49 import org.columba.mail.config.ImapItem;
50 import org.columba.mail.config.IncomingItem;
51 import org.columba.mail.filter.MailFilterCriteria;
52 import org.columba.mail.folder.IMailbox;
53 import org.columba.mail.folder.command.MarkMessageCommand;
54 import org.columba.mail.folder.headercache.CachedHeaderfields;
55 import org.columba.mail.folder.imap.IMAPFolder;
56 import org.columba.mail.folder.imap.IMAPRootFolder;
57 import org.columba.mail.gui.util.PasswordDialog;
58 import org.columba.mail.message.ColumbaHeader;
59 import org.columba.mail.message.IHeaderList;
60 import org.columba.mail.util.AuthenticationManager;
61 import org.columba.mail.util.AuthenticationSecurityComparator;
62 import org.columba.mail.util.MailResourceLoader;
63 import org.columba.ristretto.auth.AuthenticationException;
64 import org.columba.ristretto.auth.AuthenticationFactory;
65 import org.columba.ristretto.imap.IMAPDate;
66 import org.columba.ristretto.imap.IMAPDisconnectedException;
67 import org.columba.ristretto.imap.IMAPException;
68 import org.columba.ristretto.imap.IMAPFlags;
69 import org.columba.ristretto.imap.IMAPHeader;
70 import org.columba.ristretto.imap.IMAPListener;
71 import org.columba.ristretto.imap.IMAPProtocol;
72 import org.columba.ristretto.imap.IMAPResponse;
73 import org.columba.ristretto.imap.ListInfo;
74 import org.columba.ristretto.imap.MailboxStatus;
75 import org.columba.ristretto.imap.NamespaceCollection;
76 import org.columba.ristretto.imap.SearchKey;
77 import org.columba.ristretto.imap.SequenceSet;
78 import org.columba.ristretto.io.SequenceInputStream;
79 import org.columba.ristretto.message.Header;
80 import org.columba.ristretto.message.MailboxInfo;
81 import org.columba.ristretto.message.MimeTree;
82
83 /**
84  * IMAPStore encapsulates IMAPProtocol and the parsers for IMAPFolder.
85  * <p>
86  * This way {@link IMAPFolder}doesn't need to do any parsing work, etc.
87  * <p>
88  * Every {@link IMAPFolder}of a single account has also an
89  * {@link IMAPRootFolder}, which keeps a reference to {@link IMAPServer}.
90  * Which itself uses {@link IMAPProtocol}.
91  * <p>
92  * IMAPStore handles the current state of connection:
93  * <ul>
94  * <li>STATE_NONAUTHENTICATE - not authenticated</li>
95  * <li>STATE_AUTHENTICATE - authenticated</li>
96  * <li>STATE_SELECTED - mailbox is selected</li>
97  * </ul>
98  * <p>
99  * It keeps a reference to the currently selected mailbox.
100  * <p>
101  * IMAPFolder shouldn't use IMAPProtocol directly, instead it should use
102  * IMAPStore.
103  *
104  * @author fdietz
105  */

106 public class IMAPServer implements IMAPListener, Observer JavaDoc, IImapServer {
107
108     private static final int STEP_SIZE = 50;
109
110     private static final int UID_FETCH_STEPS = 500;
111
112     private static final Logger JavaDoc LOG = Logger.getLogger("org.columba.mail.imap");
113
114     private static final Charset JavaDoc UTF8 = Charset.forName("UTF-8");
115
116     private static final Charset JavaDoc DEFAULT = Charset.forName(System
117             .getProperty("file.encoding"));
118
119     /**
120      * currently selected mailbox
121      */

122     private IMailbox selectedFolder;
123
124     /**
125      * Holds the actual MailboxStatus. Updated by the IMAPListener.
126      */

127     private MailboxStatus selectedStatus;
128
129     /**
130      * mailbox name delimiter
131      * <p>
132      * example: "/" (uw-imap), or "." (cyrus)
133      */

134     private String JavaDoc delimiter;
135
136     /**
137      * reference to IMAP protocol
138      */

139     private IMAPProtocol protocol;
140
141     /**
142      * configuration options of this IMAP account
143      */

144     private ImapItem item;
145
146     private MimeTree aktMimeTree;
147
148     private Object JavaDoc aktMessageUid;
149
150     private MailboxInfo messageFolderInfo;
151
152     private boolean firstLogin;
153
154     boolean usingSSL;
155
156     String JavaDoc[] capabilities;
157
158     private long lastCommunication;
159
160     private IStatusObservable observable;
161
162     // minimal unchecked time is 30 Seconds
163
private int MIN_IDLE = 30 * 1000; // in ms
164

165     // Used to control the state in which
166
// the automatic updated mechanism is
167
private boolean updatesEnabled = true;
168
169     private IFirstLoginAction firstLoginAction;
170
171     private IUpdateFlagAction updateFlagAction;
172
173     private IExistsChangedAction existsChangedAction;
174
175     private boolean statusDirty;
176
177     public IMAPServer(ImapItem item) {
178         this.item = item;
179
180         item.getRoot().addObserver(this);
181
182         // create IMAP protocol
183
protocol = new IMAPProtocol(item.get("host"), item.getInteger("port"));
184         // register interest on status updates
185
protocol.addIMAPListener(this);
186
187         firstLogin = true;
188         usingSSL = false;
189
190         lastCommunication = System.currentTimeMillis();
191     }
192
193     /**
194      * @return
195      */

196     protected IStatusObservable getObservable() {
197         return observable;
198     }
199
200     /**
201      * @param message
202      */

203     protected void printStatusMessage(String JavaDoc message) {
204         if (getObservable() != null) {
205             getObservable().setMessage(item.get("host") + ": " + message);
206         }
207     }
208
209     /**
210      * Returns mailbox name delimiter
211      * <p/>
212      * example: "/" (uw-imap), or "." (cyrus)
213      *
214      * @return mailbox name delimiter
215      */
/* (non-Javadoc)
216      * @see org.columba.mail.imap.IImapServer#getDelimiter()
217      */

218     public String JavaDoc getDelimiter() throws IOException JavaDoc, IMAPException,
219             CommandCancelledException {
220         if (delimiter == null) {
221             // try to determine delimiter
222
delimiter = fetchDelimiter();
223         }
224
225         return delimiter;
226     }
227
228     /* (non-Javadoc)
229      * @see org.columba.mail.imap.IImapServer#logout()
230      */

231     public void logout() throws Exception JavaDoc {
232         if (protocol.getState() != IMAPProtocol.NOT_CONNECTED) {
233             try {
234                 protocol.logout();
235             } catch (Exception JavaDoc e) {
236                 // don't care
237
}
238         }
239     }
240
241     private void openConnection() throws IOException JavaDoc, IMAPException,
242             CommandCancelledException {
243         printStatusMessage(MailResourceLoader.getString("statusbar", "message",
244                 "connecting"));
245
246         int sslType = item.getIntegerWithDefault("ssl_type", IncomingItem.TLS);
247         boolean sslEnabled = item.getBoolean("enable_ssl");
248
249         // open a port to the server
250
if (sslEnabled && sslType == IncomingItem.IMAPS_POP3S) {
251             try {
252                 protocol.openSSLPort();
253                 usingSSL = true;
254             } catch (SSLException e) {
255                 int result = showErrorDialog(MailResourceLoader.getString(
256                         "dialog", "error", "ssl_handshake_error")
257                         + ": "
258                         + e.getLocalizedMessage()
259                         + "\n"
260                         + MailResourceLoader.getString("dialog", "error",
261                                 "ssl_turn_off"));
262
263                 if (result == 1) {
264                     throw new CommandCancelledException();
265                 }
266
267                 // turn off SSL for the future
268
item.setBoolean("enable_ssl", false);
269                 item.setInteger("port", IMAPProtocol.DEFAULT_PORT);
270
271                 // reopen the port
272
protocol.openPort();
273             }
274         } else {
275             protocol.openPort();
276         }
277
278         // shall we switch to SSL?
279
if (!usingSSL && sslEnabled && sslType == IncomingItem.TLS) {
280             // if CAPA was not support just give it a try...
281
if (isSupported("STLS") || isSupported("STARTTLS") || (capabilities.length == 0)) {
282                 try {
283                     protocol.startTLS();
284
285                     usingSSL = true;
286                     LOG.info("Switched to SSL");
287                 } catch (IOException JavaDoc e) {
288                     int result = showErrorDialog(MailResourceLoader.getString(
289                             "dialog", "error", "ssl_handshake_error")
290                             + ": "
291                             + e.getLocalizedMessage()
292                             + "\n"
293                             + MailResourceLoader.getString("dialog", "error",
294                                     "ssl_turn_off"));
295
296                     if (result == 1) {
297                         throw new CommandCancelledException();
298                     }
299
300                     // turn off SSL for the future
301
item.setBoolean("enable_ssl", false);
302
303                     // reopen the port
304
protocol.openPort();
305                 } catch (IMAPException e) {
306                     int result = showErrorDialog(MailResourceLoader.getString(
307                             "dialog", "error", "ssl_not_supported")
308                             + "\n"
309                             + MailResourceLoader.getString("dialog", "error",
310                                     "ssl_turn_off"));
311
312                     if (result == 1) {
313                         throw new CommandCancelledException();
314                     }
315
316                     // turn off SSL for the future
317
item.setBoolean("enable_ssl", false);
318                 }
319             } else {
320                 // CAPAs say that SSL is not supported
321
int result = showErrorDialog(MailResourceLoader.getString(
322                         "dialog", "error", "ssl_not_supported")
323                         + "\n"
324                         + MailResourceLoader.getString("dialog", "error",
325                                 "ssl_turn_off"));
326
327                 if (result == 1) {
328                     throw new CommandCancelledException();
329                 }
330
331                 // turn off SSL for the future
332
item.setBoolean("enable_ssl", false);
333             }
334         }
335
336     }
337
338     /* (non-Javadoc)
339      * @see org.columba.mail.imap.IImapServer#checkSupportedAuthenticationMethods()
340      */

341     public List JavaDoc checkSupportedAuthenticationMethods() throws IOException JavaDoc {
342
343         ArrayList JavaDoc supportedMechanisms = new ArrayList JavaDoc();
344         // LOGIN is always supported
345
supportedMechanisms.add(new Integer JavaDoc(AuthenticationManager.LOGIN));
346
347         try {
348             String JavaDoc serverSaslMechansims[] = getCapas("AUTH");
349             // combine them to one string
350
StringBuffer JavaDoc oneLine = new StringBuffer JavaDoc("AUTH");
351             for (int i = 0; i < serverSaslMechansims.length; i++) {
352                 oneLine.append(' ');
353                 oneLine.append(serverSaslMechansims[i].substring(5)); // remove
354
// the
355
// 'AUTH='
356
}
357
358             // AUTH?
359
if (serverSaslMechansims != null) {
360                 List JavaDoc authMechanisms = AuthenticationFactory.getInstance()
361                         .getSupportedMechanisms(oneLine.toString());
362                 Iterator JavaDoc it = authMechanisms.iterator();
363                 while (it.hasNext()) {
364                     supportedMechanisms.add(new Integer JavaDoc(AuthenticationManager
365                             .getSaslCode((String JavaDoc) it.next())));
366                 }
367             }
368         } catch (IOException JavaDoc e) {
369         }
370
371         return supportedMechanisms;
372     }
373
374     /**
375      * @param command
376      * @return
377      */

378     private String JavaDoc[] getCapas(String JavaDoc command) throws IOException JavaDoc {
379         fetchCapas();
380         ArrayList JavaDoc list = new ArrayList JavaDoc();
381
382         for (int i = 0; i < capabilities.length; i++) {
383             if (capabilities[i].startsWith(command)) {
384                 list.add(capabilities[i]);
385             }
386         }
387
388         return (String JavaDoc[]) list.toArray(new String JavaDoc[0]);
389     }
390
391     /* (non-Javadoc)
392      * @see org.columba.mail.imap.IImapServer#isSupported(java.lang.String)
393      */

394     public boolean isSupported(String JavaDoc command) throws IOException JavaDoc {
395         fetchCapas();
396
397         for (int i = 0; i < capabilities.length; i++) {
398             if (capabilities[i].startsWith(command)) {
399                 return true;
400             }
401         }
402
403         return false;
404     }
405
406     /**
407      * @throws IOException
408      */

409     private void fetchCapas() throws IOException JavaDoc {
410         if (capabilities == null) {
411             try {
412                 ensureConnectedState();
413
414                 capabilities = protocol.capability();
415             } catch (IMAPException e) {
416                 // CAPA not supported
417
capabilities = new String JavaDoc[0];
418             } catch (CommandCancelledException e) {
419
420             }
421         }
422     }
423
424     /**
425      * Gets the selected Authentication method or else the most secure.
426      *
427      * @return the authentication method
428      */

429     private int getLoginMethod() throws CommandCancelledException, IOException JavaDoc {
430         String JavaDoc loginMethod = item.get("login_method");
431         int result = 0;
432
433         try {
434             result = Integer.parseInt(loginMethod);
435         } catch (NumberFormatException JavaDoc e) {
436             // Just use the default as fallback
437
}
438
439         if (result == 0) {
440             List JavaDoc supported = checkSupportedAuthenticationMethods();
441
442             if (usingSSL) {
443                 // NOTE if SSL is possible we just need the plain login
444
// since SSL does the encryption for us.
445
result = ((Integer JavaDoc) supported.get(0)).intValue();
446             } else {
447                 Collections.sort(supported,
448                         new AuthenticationSecurityComparator());
449                 result = ((Integer JavaDoc) supported.get(supported.size() - 1))
450                         .intValue();
451             }
452
453         }
454
455         return result;
456     }
457
458     /**
459      * Login to IMAP server.
460      * <p>
461      * Ask user for password.
462      *
463      * TODO (@author tstich): cleanup if all these ugly if, else cases
464      *
465      * @throws Exception
466      */

467     private void login() throws IOException JavaDoc, IMAPException,
468             CommandCancelledException {
469         PasswordDialog dialog = new PasswordDialog();
470         ensureConnectedState();
471
472         boolean authenticated = false;
473         boolean first = true;
474
475         char[] password = new char[0];
476
477         printStatusMessage(MailResourceLoader.getString("statusbar", "message",
478                 "authenticating"));
479
480         int loginMethod = getLoginMethod();
481
482         // Try to get Password from Configuration
483
if (item.get("password").length() != 0) {
484             password = Blowfish.decrypt(item.get("password"));
485         }
486         // Login loop until authenticated
487
while (!authenticated) {
488             // On the first try check if we need to show the password dialog
489
// -> not necessary when password was stored
490
if (!first || password.length == 0) {
491                 // Show the password dialog
492
dialog.showDialog(MessageFormat.format(MailResourceLoader
493                         .getString("dialog", "password", "enter_password"),
494                         new Object JavaDoc[] { item.get("user"), item.get("host") }),
495                         new String JavaDoc(password), item.getBoolean("save_password"));
496                 if (dialog.success()) {
497                     // User pressed OK
498
password = dialog.getPassword();
499
500                     // Save or Clear the password in the configuration
501
item.setBoolean("save_password", dialog.getSave());
502                     if (dialog.getSave()) {
503                         item.setString("password", Blowfish.encrypt(password));
504                     } else {
505                         item.setString("password", "");
506                     }
507                 } else {
508                     // User cancelled authentication
509

510                     throw new CommandCancelledException();
511                 }
512             }
513
514             // From this point we have a username and password
515
// from configuration of from the dialog
516

517             try {
518                 if (loginMethod == AuthenticationManager.LOGIN) {
519                     protocol.login(item.get("user"), password);
520
521                     // If no exception happened we have successfully logged
522
// in
523
authenticated = true;
524                 } else {
525                     try {
526                         // AUTH
527
protocol.authenticate(AuthenticationManager
528                                 .getSaslName(loginMethod), item.get("user"),
529                                 password);
530
531                         // If no exception happened we have successfully logged
532
// in
533
authenticated = true;
534                     } catch (AuthenticationException e) {
535                         // If the cause is a IMAPExcpetion then only password
536
// wrong
537
// else bogus authentication mechanism
538
if (e.getCause() instanceof IMAPException)
539                             throw (IMAPException) e.getCause();
540
541                         // Some error in the client/server communication
542
// --> fall back to default login process
543
int result = JOptionPane
544                                 .showConfirmDialog(
545                                         FrameManager.getInstance()
546                                                 .getActiveFrame(),
547                                         new MultiLineLabel(
548                                                 e.getMessage()
549                                                         + "\n"
550                                                         + MailResourceLoader
551                                                                 .getString(
552                                                                         "dialog",
553                                                                         "error",
554                                                                         "authentication_fallback_to_default")),
555                                         MailResourceLoader.getString("dialog",
556                                                 "error",
557                                                 "authentication_process_error"),
558                                         JOptionPane.OK_CANCEL_OPTION);
559
560                         if (result == JOptionPane.OK_OPTION) {
561                             loginMethod = AuthenticationManager.LOGIN;
562                             item.setString("login_method", Integer
563                                     .toString(loginMethod));
564                         } else {
565                             throw new CommandCancelledException();
566                         }
567                     }
568                 }
569
570             } catch (IMAPException ex) {
571                 // login failed?
572
IMAPResponse response = ex.getResponse();
573                 if (response == null || !response.isNO()) {
574                     // This exception is not because wrong username or
575
// password
576
throw ex;
577                 }
578             }
579             first = false;
580         }
581
582         // Sync subscribed folders if this is the first login
583
// in this session
584
if (firstLogin) {
585             if( firstLoginAction != null) {
586                 firstLoginAction.actionPerformed();
587             }
588         }
589
590         firstLogin = false;
591     }
592
593     /* (non-Javadoc)
594      * @see org.columba.mail.imap.IImapServer#setFirstLoginAction(org.columba.mail.imap.IFirstLoginAction)
595      */

596     public void setFirstLoginAction( IFirstLoginAction action) {
597         this.firstLoginAction = action;
598     }
599
600     /* (non-Javadoc)
601      * @see org.columba.mail.imap.IImapServer#ensureSelectedState(org.columba.mail.folder.imap.IMAPFolder)
602      */

603     public void ensureSelectedState(IMAPFolder folder) throws IOException JavaDoc,
604             IMAPException, CommandCancelledException {
605         // ensure that we are logged in already
606
ensureLoginState();
607         String JavaDoc path = folder.getImapPath();
608
609         // if mailbox is not already selected select it
610
if (protocol.getState() != IMAPProtocol.SELECTED
611                 || !protocol.getSelectedMailbox().equals(path)) {
612
613             printStatusMessage(MessageFormat.format(MailResourceLoader
614                     .getString("statusbar", "message", "select"),
615                     new Object JavaDoc[] { folder.getName() }));
616
617             // Here we get the new mailboxinfo for the folder
618
messageFolderInfo = protocol.select(path);
619
620             // Set the readOnly flag
621
folder.setReadOnly(!messageFolderInfo.isWriteAccess());
622
623             // Convert to a MailboxStatus
624
selectedStatus = new MailboxStatus(messageFolderInfo);
625             statusDirty = false;
626
627             selectedFolder = folder;
628
629             // delete any cached information
630
aktMimeTree = null;
631             aktMessageUid = null;
632         }
633     }
634
635     public int getLargestRemoteUid(IMAPFolder folder) throws IOException JavaDoc, IMAPException, CommandCancelledException {
636         MailboxStatus status = getStatus(folder);
637         if(status.getUidNext() < 0 && status.getMessages() > 0 ) {
638             return fetchUid(new SequenceSet(status.getMessages()), folder);
639         } else {
640             return (int)(status.getUidNext() -1);
641         }
642
643     }
644
645     /* (non-Javadoc)
646      * @see org.columba.mail.imap.IImapServer#getStatus(org.columba.mail.folder.imap.IMAPFolder)
647      */

648     public MailboxStatus getStatus(IMAPFolder folder) throws IOException JavaDoc,
649             IMAPException, CommandCancelledException {
650         ensureLoginState();
651
652         if (selectedFolder != null && selectedFolder.equals(folder) && !statusDirty) {
653             // We don't need to issue a additional NOOP
654
// here since the ensureLogin() call above
655
// ensures also the correct Status in a
656
// MIN_IDLE interval timeframe.
657

658             return selectedStatus;
659         }
660
661         if( selectedFolder == null || protocol.getState() < IMAPProtocol.SELECTED) {
662             // if none selected select this folder instead of getting the status
663
ensureSelectedState(folder);
664             return selectedStatus;
665         }
666
667         printStatusMessage(MessageFormat.format(MailResourceLoader.getString(
668                 "statusbar", "message", "status"), new Object JavaDoc[] { folder
669                 .getName() }));
670
671         MailboxStatus result = protocol.status(folder.getImapPath(), new String JavaDoc[] { "MESSAGES",
672                 "UIDNEXT", "RECENT", "UNSEEN", "UIDVALIDITY" });
673
674         // No response means zero!
675
if( result.getUnseen() == -1) result.setUnseen(0);
676         if( result.getRecent() == -1) result.setRecent(0);
677         statusDirty = false;
678
679         return result;
680     }
681
682     /**
683      * Fetch delimiter.
684      *
685      */

686     protected String JavaDoc fetchDelimiter() throws IOException JavaDoc, IMAPException,
687             CommandCancelledException {
688         // make sure we are already logged in
689
ensureLoginState();
690
691         try {
692             ListInfo[] listInfo = protocol.list("", "");
693             return listInfo[0].getDelimiter();
694         } catch (IMAPDisconnectedException e1) {
695             ListInfo[] listInfo = protocol.list("", "");
696             return listInfo[0].getDelimiter();
697         }
698     }
699
700     /* (non-Javadoc)
701      * @see org.columba.mail.imap.IImapServer#list(java.lang.String, java.lang.String)
702      */

703     public ListInfo[] list(String JavaDoc reference, String JavaDoc pattern) throws Exception JavaDoc {
704         ensureLoginState();
705
706         try {
707             return protocol.list(reference, pattern);
708         } catch (IMAPDisconnectedException e) {
709             return protocol.list(reference, pattern);
710         }
711     }
712
713     /* (non-Javadoc)
714      * @see org.columba.mail.imap.IImapServer#append(java.io.InputStream, org.columba.ristretto.imap.IMAPFlags, org.columba.mail.folder.imap.IMAPFolder)
715      */

716     public Integer JavaDoc append(InputStream JavaDoc messageSource, IMAPFlags flags,
717             IMAPFolder folder) throws Exception JavaDoc {
718         // make sure we are already logged in
719
ensureLoginState();
720
721         // close the mailbox if it is selected
722
if (protocol.getState() == IMAPProtocol.SELECTED
723                 && protocol.getSelectedMailbox().equals(folder)) {
724             protocol.close();
725         }
726
727         MailboxStatus status = protocol.status(folder.getImapPath(),
728                 new String JavaDoc[] { "UIDNEXT" });
729
730         if (flags != null) {
731             protocol.append(folder.getImapPath(), messageSource,
732                     new Object JavaDoc[] { flags });
733         } else {
734             protocol.append(folder.getImapPath(), messageSource);
735
736         }
737
738         return new Integer JavaDoc((int) status.getUidNext());
739     }
740
741     /* (non-Javadoc)
742      * @see org.columba.mail.imap.IImapServer#append(java.io.InputStream, org.columba.mail.folder.imap.IMAPFolder)
743      */

744     public Integer JavaDoc append(InputStream JavaDoc messageSource, IMAPFolder folder)
745             throws Exception JavaDoc {
746         return append(messageSource, null, folder);
747     }
748
749     /* (non-Javadoc)
750      * @see org.columba.mail.imap.IImapServer#createMailbox(java.lang.String, org.columba.mail.folder.imap.IMAPFolder)
751      */

752     public void createMailbox(String JavaDoc mailboxName, IMAPFolder folder)
753             throws IOException JavaDoc, IMAPException, CommandCancelledException {
754         // make sure we are logged in
755
ensureLoginState();
756
757         // concate the full name of the new mailbox
758
String JavaDoc fullName;
759         String JavaDoc path = (folder == null ? "" : folder.getImapPath());
760
761         if (path.length() > 0)
762             fullName = path + getDelimiter() + mailboxName;
763         else
764             fullName = mailboxName;
765
766         // check if the mailbox already exists -> subscribe only
767
if (protocol.list("", fullName).length == 0) {
768             // create the mailbox on the server
769
protocol.create(fullName);
770         }
771
772         // subscribe to the new mailbox
773
protocol.subscribe(fullName);
774     }
775
776     /* (non-Javadoc)
777      * @see org.columba.mail.imap.IImapServer#deleteFolder(java.lang.String)
778      */

779     public void deleteFolder(String JavaDoc path) throws Exception JavaDoc {
780         // make sure we are already logged in
781
ensureLoginState();
782
783         if (protocol.getState() == IMAPProtocol.SELECTED
784                 && protocol.getSelectedMailbox().equals(path)) {
785             protocol.close();
786         }
787
788         protocol.unsubscribe(path);
789
790         protocol.delete(path);
791     }
792
793     /* (non-Javadoc)
794      * @see org.columba.mail.imap.IImapServer#renameFolder(java.lang.String, java.lang.String)
795      */

796     public void renameFolder(String JavaDoc oldMailboxName, String JavaDoc newMailboxName)
797             throws IOException JavaDoc, IMAPException, CommandCancelledException {
798         // make sure we are already logged in
799
ensureLoginState();
800         protocol.rename(oldMailboxName, newMailboxName);
801         protocol.unsubscribe(oldMailboxName);
802         protocol.subscribe(newMailboxName);
803     }
804
805     /* (non-Javadoc)
806      * @see org.columba.mail.imap.IImapServer#subscribeFolder(java.lang.String)
807      */

808     public void subscribeFolder(String JavaDoc mailboxName) throws IOException JavaDoc,
809             IMAPException, CommandCancelledException {
810         // make sure we are already logged in
811
ensureLoginState();
812
813         protocol.subscribe(mailboxName);
814     }
815
816     /* (non-Javadoc)
817      * @see org.columba.mail.imap.IImapServer#unsubscribeFolder(java.lang.String)
818      */

819     public void unsubscribeFolder(String JavaDoc mailboxName) throws IOException JavaDoc,
820             IMAPException, CommandCancelledException {
821         // make sure we are already logged in
822
ensureLoginState();
823
824         protocol.unsubscribe(mailboxName);
825     }
826
827     /* (non-Javadoc)
828      * @see org.columba.mail.imap.IImapServer#expunge(org.columba.mail.folder.imap.IMAPFolder)
829      */

830     public void expunge(IMAPFolder folder) throws IOException JavaDoc, IMAPException,
831             CommandCancelledException {
832         ensureSelectedState(folder);
833
834         updatesEnabled = false;
835         protocol.expunge();
836         updatesEnabled = true;
837         statusDirty = true;
838     }
839
840     /* (non-Javadoc)
841      * @see org.columba.mail.imap.IImapServer#copy(org.columba.mail.folder.imap.IMAPFolder, java.lang.Object[], org.columba.mail.folder.imap.IMAPFolder)
842      */

843     public Integer JavaDoc[] copy(IMAPFolder destFolder, Object JavaDoc[] uids,
844             IMAPFolder folder) throws Exception JavaDoc {
845
846         ensureSelectedState(folder);
847
848         // We need to sort the uids in order
849
// to have the correct association
850
// between the new and old uid
851
List JavaDoc sortedUids = Arrays.asList(uids);
852         Collections.sort(sortedUids);
853
854         MailboxStatus statusBefore = protocol.status(destFolder.getImapPath(),
855                 new String JavaDoc[] { "UIDNEXT" });
856
857         protocol.uidCopy(new SequenceSet(Arrays.asList(uids)), destFolder
858                 .getImapPath());
859
860         MailboxStatus statusAfter = protocol.status(destFolder.getImapPath(),
861                 new String JavaDoc[] { "UIDNEXT" });
862
863         // the UIDS start UIDNext till UIDNext + uids.length
864
int copied = (int) (statusAfter.getUidNext() - statusBefore
865                 .getUidNext());
866         Integer JavaDoc[] destUids = new Integer JavaDoc[copied];
867         for (int i = 0; i < copied; i++) {
868             destUids[i] = new Integer JavaDoc((int) (statusBefore.getUidNext() + i));
869         }
870
871         return destUids;
872     }
873
874     /* (non-Javadoc)
875      * @see org.columba.mail.imap.IImapServer#fetchUid(org.columba.ristretto.imap.SequenceSet, org.columba.mail.folder.imap.IMAPFolder)
876      */

877     public int fetchUid( SequenceSet set, IMAPFolder folder ) throws IOException JavaDoc, IMAPException, CommandCancelledException {
878         ensureSelectedState(folder);
879         Integer JavaDoc[] result = protocol.fetchUid(set);
880         if( result.length == 1)
881             return result[0].intValue();
882         else
883             return -1;
884
885     }
886
887     /* (non-Javadoc)
888      * @see org.columba.mail.imap.IImapServer#fetchUids(org.columba.ristretto.imap.SequenceSet, org.columba.mail.folder.imap.IMAPFolder)
889      */

890     public Integer JavaDoc[] fetchUids(SequenceSet set, IMAPFolder folder)
891             throws IOException JavaDoc, IMAPException, CommandCancelledException {
892         IStatusObservable observable = getObservable();
893         printStatusMessage(MailResourceLoader.getString("statusbar", "message",
894                 "fetch_uid_list"));
895
896         ensureSelectedState(folder);
897         if (messageFolderInfo.getExists() > 0) {
898             SequenceSet[] packs = divide(set);
899             Integer JavaDoc[] result = new Integer JavaDoc[set.getLength(messageFolderInfo
900                     .getExists())];
901
902             // update the progress
903
if (observable != null) {
904                 observable.setCurrent(0);
905                 observable.setMax(result.length);
906             }
907
908             int pos = 0;
909
910             for (int i = 0; i < packs.length; i++) {
911                 int packLength = packs[i].getLength(messageFolderInfo
912                         .getExists());
913                 System.arraycopy(protocol.fetchUid(packs[i]), 0, result, pos,
914                         packLength);
915                 pos += packLength;
916
917                 // update the progress
918
if (observable != null) {
919                     observable.setCurrent(pos);
920                 }
921             }
922
923             return result;
924         } else {
925             return new Integer JavaDoc[0];
926         }
927     }
928
929     private SequenceSet[] divide(SequenceSet in) {
930         int length = in.getLength(messageFolderInfo.getExists());
931
932         if (length > UID_FETCH_STEPS) {
933             int[] decomposed = in.toArray(messageFolderInfo.getExists());
934
935             List JavaDoc result = new ArrayList JavaDoc();
936             int pos = 0;
937             // divide in packs
938
while (decomposed.length - pos > UID_FETCH_STEPS) {
939                 result.add(new SequenceSet(decomposed, pos, UID_FETCH_STEPS));
940                 pos += UID_FETCH_STEPS;
941             }
942             // dont forget the rest
943
if (decomposed.length - pos > 0) {
944                 result.add(new SequenceSet(decomposed, pos, decomposed.length
945                         - pos));
946             }
947
948             return (SequenceSet[]) result.toArray(new SequenceSet[0]);
949         } else {
950             return new SequenceSet[] { in };
951         }
952
953     }
954
955     /* (non-Javadoc)
956      * @see org.columba.mail.imap.IImapServer#fetchFlagsListStartFrom(int, org.columba.mail.folder.imap.IMAPFolder)
957      */

958     public IMAPFlags[] fetchFlagsListStartFrom(int startIdx, IMAPFolder folder)
959             throws IOException JavaDoc, IMAPException, CommandCancelledException {
960         IStatusObservable observable = getObservable();
961
962         ensureSelectedState(folder);
963         if (selectedStatus.getMessages() - startIdx >= 0) {
964             SequenceSet set = new SequenceSet();
965             set.addOpenRange(startIdx);
966
967             SequenceSet[] packs = divide(set);
968
969             // update the progress
970
if (observable != null) {
971                 observable.setCurrent(0);
972                 observable.setMax(set.getLength(selectedStatus.getMessages()));
973             }
974
975             List JavaDoc allResults = new ArrayList JavaDoc(packs.length);
976
977             int pos = 0;
978
979             // store the intermediate results in a list
980
for (int i = 0; i < packs.length; i++) {
981                 try {
982                     IMAPFlags[] r = protocol.fetchFlags(packs[i]);
983                     pos += r.length;
984
985                     allResults.add(r);
986                 } catch (IMAPException e) {
987                     // Entry does not exist on server
988
// -> add nothing
989
}
990
991                 // update the progress
992
if (observable != null) {
993                     observable.setCurrent(pos);
994                 }
995             }
996
997             // Combine the results in one array
998
IMAPFlags[] result = new IMAPFlags[pos];
999             Iterator JavaDoc it = allResults.iterator();
1000
1001            pos = 0;
1002            while (it.hasNext()) {
1003                IMAPFlags[] r = (IMAPFlags[]) it.next();
1004                System.arraycopy(r, 0, result, pos, r.length);
1005
1006                pos += r.length;
1007            }
1008
1009            return result;
1010        } else {
1011            return new IMAPFlags[0];
1012        }
1013    }
1014
1015
1016    /* (non-Javadoc)
1017     * @see org.columba.mail.imap.IImapServer#fetchFlagsListStartFrom2(int, org.columba.mail.folder.imap.IMAPFolder)
1018     */

1019    public IMAPFlags[] fetchFlagsListStartFrom2(int startIdx, IMAPFolder folder)
1020            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1021        ensureSelectedState(folder);
1022        if (selectedStatus.getMessages() - startIdx >= 0) {
1023            SequenceSet set = new SequenceSet();
1024            set.add(startIdx, Math.min(startIdx + 9, selectedStatus.getMessages()));
1025
1026            IMAPFlags[] result = protocol.fetchFlags(set);
1027
1028            return result;
1029        } else {
1030            return new IMAPFlags[0];
1031        }
1032    }
1033
1034    /* (non-Javadoc)
1035     * @see org.columba.mail.imap.IImapServer#fetchNamespaces()
1036     */

1037    public NamespaceCollection fetchNamespaces() throws IOException JavaDoc,
1038            IMAPException, CommandCancelledException {
1039        ensureLoginState();
1040        return protocol.namespace();
1041    }
1042
1043    /* (non-Javadoc)
1044     * @see org.columba.mail.imap.IImapServer#fetchHeaderList(org.columba.mail.message.IHeaderList, java.util.List, org.columba.mail.folder.imap.IMAPFolder)
1045     */

1046    public void fetchHeaderList(IHeaderList headerList, List JavaDoc list,
1047            IMAPFolder folder) throws Exception JavaDoc {
1048        // make sure this mailbox is selected
1049
ensureSelectedState(folder);
1050
1051        printStatusMessage(MailResourceLoader.getString("statusbar", "message",
1052                "fetch_header_list"));
1053
1054        int count = list.size() / IMAPServer.STEP_SIZE;
1055        int rest = list.size() % IMAPServer.STEP_SIZE;
1056        getObservable().setCurrent(0);
1057        getObservable().setMax(count + 1);
1058        for (int i = 0; i < count; i++) {
1059            doFetchHeaderList(headerList, list.subList(
1060                    i * IMAPServer.STEP_SIZE, (i + 1) * IMAPServer.STEP_SIZE));
1061            getObservable().setCurrent(i);
1062        }
1063
1064        if (rest > 0) {
1065            doFetchHeaderList(headerList, list
1066                    .subList(count * IMAPServer.STEP_SIZE, count
1067                            * IMAPServer.STEP_SIZE + rest));
1068        }
1069
1070        getObservable().setCurrent(count + 1);
1071    }
1072
1073    /**
1074     * @param headerList
1075     * @param list
1076     * @throws IOException
1077     * @throws IMAPException
1078     */

1079    private void doFetchHeaderList(IHeaderList headerList, List JavaDoc list)
1080            throws IOException JavaDoc, IMAPException {
1081        // get list of user-defined headerfields
1082
String JavaDoc[] headerFields = CachedHeaderfields.getDefaultHeaderfields();
1083
1084        IMAPHeader[] headers = protocol.uidFetchHeaderFields(new SequenceSet(
1085                list), headerFields);
1086
1087        for (int i = 0; i < headers.length; i++) {
1088            // add it to the headerlist
1089
ColumbaHeader header = new ColumbaHeader(headers[i].getHeader());
1090            Object JavaDoc uid = headers[i].getUid();
1091
1092            header.getAttributes().put("columba.uid", uid);
1093            header.getAttributes().put("columba.size", headers[i].getSize());
1094            header.getAttributes().put("columba.accountuid", getAccountUid());
1095
1096            // set the attachment flag
1097
header.getAttributes().put("columba.attachment",
1098                    header.hasAttachments());
1099
1100            // make sure that we have a Message-ID
1101
String JavaDoc messageID = (String JavaDoc) header.get("Message-Id");
1102            if (messageID != null)
1103                header.set("Message-ID", header.get("Message-Id"));
1104
1105            headerList.add(header, uid);
1106        }
1107    }
1108
1109    protected Integer JavaDoc getAccountUid() {
1110        AccountItem accountItem = new AccountItem(item.getRoot().getParent());
1111        return new Integer JavaDoc(accountItem.getInteger("uid"));
1112    }
1113
1114    protected synchronized void ensureConnectedState() throws CommandCancelledException,
1115            IOException JavaDoc, IMAPException {
1116        if (Math.abs(System.currentTimeMillis() - lastCommunication) > MIN_IDLE) {
1117            try {
1118                protocol.noop();
1119            } catch (IOException JavaDoc e) {
1120                // Now the state of the procotol is more certain correct
1121
} catch (IMAPDisconnectedException e) {
1122
1123            }
1124        }
1125
1126        if (protocol.getState() < IMAPProtocol.NON_AUTHENTICATED) {
1127            printStatusMessage(MailResourceLoader
1128                    .getString("statusbar", "message", "connecting"));
1129            openConnection();
1130        }
1131
1132        // update this point of time as last communication
1133
// since every functio calls this before communicating with
1134
// the server
1135
lastCommunication = System.currentTimeMillis();
1136    }
1137
1138    /**
1139     * Ensure that we are in login state.
1140     *
1141     * @throws Exception
1142     */

1143    protected void ensureLoginState() throws IOException JavaDoc, IMAPException,
1144            CommandCancelledException {
1145        ensureConnectedState();
1146
1147        if (protocol.getState() < IMAPProtocol.AUTHENTICATED) {
1148            printStatusMessage(MailResourceLoader
1149                    .getString("statusbar", "message", "authenticating"));
1150            login();
1151        }
1152    }
1153
1154    /* (non-Javadoc)
1155     * @see org.columba.mail.imap.IImapServer#getMimeTree(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1156     */

1157    public MimeTree getMimeTree(Object JavaDoc uid, IMAPFolder folder)
1158            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1159        try {
1160            ensureSelectedState(folder);
1161
1162            // Use a caching mechanism for this
1163
if (aktMimeTree == null || !aktMessageUid.equals(uid)) {
1164                aktMimeTree = protocol.uidFetchBodystructure(((Integer JavaDoc) uid)
1165                        .intValue());
1166                aktMessageUid = uid;
1167            }
1168
1169            return aktMimeTree;
1170        } catch (IMAPDisconnectedException e) {
1171            return getMimeTree(uid, folder);
1172        }
1173    }
1174
1175    /* (non-Javadoc)
1176     * @see org.columba.mail.imap.IImapServer#getMimePartBodyStream(java.lang.Object, java.lang.Integer[], org.columba.mail.folder.imap.IMAPFolder)
1177     */

1178    public InputStream JavaDoc getMimePartBodyStream(Object JavaDoc uid, Integer JavaDoc[] address,
1179            IMAPFolder folder) throws IOException JavaDoc, IMAPException,
1180            CommandCancelledException {
1181        try {
1182            ensureSelectedState(folder);
1183
1184            return protocol.uidFetchBody(((Integer JavaDoc) uid).intValue(), address);
1185        } catch (IMAPDisconnectedException e) {
1186            return getMimePartBodyStream(uid, address, folder);
1187        }
1188    }
1189
1190    /* (non-Javadoc)
1191     * @see org.columba.mail.imap.IImapServer#getHeaders(java.lang.Object, java.lang.String[], org.columba.mail.folder.imap.IMAPFolder)
1192     */

1193    public Header getHeaders(Object JavaDoc uid, String JavaDoc[] keys, IMAPFolder folder)
1194            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1195        try {
1196            ensureSelectedState(folder);
1197
1198            IMAPHeader[] headers = protocol.uidFetchHeaderFields(
1199                    new SequenceSet(((Integer JavaDoc) uid).intValue()), keys);
1200
1201            return headers[0].getHeader();
1202        } catch (IMAPDisconnectedException e) {
1203            return getHeaders(uid, keys, folder);
1204        }
1205    }
1206
1207    /* (non-Javadoc)
1208     * @see org.columba.mail.imap.IImapServer#getAllHeaders(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1209     */

1210    public Header getAllHeaders(Object JavaDoc uid, IMAPFolder folder)
1211            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1212        try {
1213            ensureSelectedState(folder);
1214
1215            IMAPHeader[] headers = protocol.uidFetchHeader(new SequenceSet(
1216                    ((Integer JavaDoc) uid).intValue()));
1217
1218            return headers[0].getHeader();
1219        } catch (IMAPDisconnectedException e) {
1220            return getAllHeaders(uid, folder);
1221        }
1222    }
1223
1224    /* (non-Javadoc)
1225     * @see org.columba.mail.imap.IImapServer#getMimePartSourceStream(java.lang.Object, java.lang.Integer[], org.columba.mail.folder.imap.IMAPFolder)
1226     */

1227    public InputStream JavaDoc getMimePartSourceStream(Object JavaDoc uid, Integer JavaDoc[] address,
1228            IMAPFolder folder) throws IOException JavaDoc, IMAPException,
1229            CommandCancelledException {
1230        try {
1231            ensureSelectedState(folder);
1232
1233            InputStream JavaDoc headerSource = protocol.uidFetchMimeHeaderSource(
1234                    ((Integer JavaDoc) uid).intValue(), address);
1235            InputStream JavaDoc bodySource = protocol.uidFetchBody(((Integer JavaDoc) uid)
1236                    .intValue(), address);
1237
1238            return new SequenceInputStream(headerSource, bodySource);
1239        } catch (IMAPDisconnectedException e) {
1240            return getMimePartSourceStream(uid, address, folder);
1241        }
1242    }
1243
1244    /* (non-Javadoc)
1245     * @see org.columba.mail.imap.IImapServer#getMessageSourceStream(java.lang.Object, org.columba.mail.folder.imap.IMAPFolder)
1246     */

1247    public InputStream JavaDoc getMessageSourceStream(Object JavaDoc uid, IMAPFolder folder)
1248            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1249        try {
1250            ensureSelectedState(folder);
1251
1252            return protocol.uidFetchMessage(((Integer JavaDoc) uid).intValue());
1253        } catch (IMAPDisconnectedException e) {
1254            return getMessageSourceStream(uid, folder);
1255        }
1256    }
1257
1258    /* (non-Javadoc)
1259     * @see org.columba.mail.imap.IImapServer#markMessage(java.lang.Object[], int, org.columba.mail.folder.imap.IMAPFolder)
1260     */

1261    public void markMessage(Object JavaDoc[] uids, int variant, IMAPFolder folder)
1262            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1263        try {
1264            ensureSelectedState(folder);
1265
1266            SequenceSet uidSet = new SequenceSet(Arrays.asList(uids));
1267
1268            protocol.uidStore(uidSet, variant > 0, convertToFlags(variant));
1269
1270            statusDirty = true;
1271        } catch (IMAPDisconnectedException e) {
1272            markMessage(uids, variant, folder);
1273        }
1274    }
1275
1276    /* (non-Javadoc)
1277     * @see org.columba.mail.imap.IImapServer#setFlags(java.lang.Object[], org.columba.ristretto.imap.IMAPFlags, org.columba.mail.folder.imap.IMAPFolder)
1278     */

1279    public void setFlags(Object JavaDoc[] uids, IMAPFlags flags, IMAPFolder folder)
1280            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1281        try {
1282            ensureSelectedState(folder);
1283            SequenceSet uidSet = new SequenceSet(Arrays.asList(uids));
1284
1285            protocol.uidStore(uidSet, true, flags);
1286        } catch (IMAPDisconnectedException e) {
1287            setFlags(uids, flags, folder);
1288        }
1289    }
1290
1291    /* (non-Javadoc)
1292     * @see org.columba.mail.imap.IImapServer#search(java.lang.Object[], org.columba.core.filter.FilterRule, org.columba.mail.folder.imap.IMAPFolder)
1293     */

1294    public List JavaDoc search(Object JavaDoc[] uids, IFilterRule filterRule, IMAPFolder folder)
1295            throws Exception JavaDoc {
1296        LinkedList JavaDoc result = new LinkedList JavaDoc(search(filterRule, folder));
1297
1298        ListTools.intersect(result, Arrays.asList(uids));
1299
1300        return result;
1301    }
1302
1303    /* (non-Javadoc)
1304     * @see org.columba.mail.imap.IImapServer#getIndex(java.lang.Integer, org.columba.mail.folder.imap.IMAPFolder)
1305     */

1306    public int getIndex(Integer JavaDoc uid, IMAPFolder folder) throws IOException JavaDoc,
1307            IMAPException, CommandCancelledException {
1308
1309        try {
1310            ensureSelectedState(folder);
1311
1312            SearchKey key = new SearchKey(SearchKey.UID, uid);
1313
1314            Integer JavaDoc[] index = protocol.search(new SearchKey[] { key });
1315            if (index.length > 0) {
1316                return index[0].intValue();
1317            } else {
1318                return -1;
1319            }
1320        } catch (IMAPDisconnectedException e) {
1321            return getIndex(uid, folder);
1322        }
1323    }
1324
1325    /* (non-Javadoc)
1326     * @see org.columba.mail.imap.IImapServer#search(org.columba.ristretto.imap.SearchKey, org.columba.mail.folder.imap.IMAPFolder)
1327     */

1328    public Integer JavaDoc[] search(SearchKey key, IMAPFolder folder)
1329            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1330        try {
1331            ensureSelectedState(folder);
1332
1333            return protocol.uidSearch(new SearchKey[] { key });
1334        } catch (IMAPDisconnectedException e) {
1335            return search(key, folder);
1336        }
1337    }
1338
1339    /* (non-Javadoc)
1340     * @see org.columba.mail.imap.IImapServer#search(org.columba.core.filter.FilterRule, org.columba.mail.folder.imap.IMAPFolder)
1341     */

1342    public List JavaDoc search(IFilterRule filterRule, IMAPFolder folder)
1343            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1344
1345        try {
1346            ensureSelectedState(folder);
1347
1348            SearchKey[] searchRequest;
1349
1350            searchRequest = createSearchKey(filterRule);
1351
1352            Integer JavaDoc[] result = null;
1353            Charset JavaDoc charset = UTF8;
1354
1355            while (result == null) {
1356                try {
1357                    result = protocol.uidSearch(charset, searchRequest);
1358                } catch (IMAPException e) {
1359                    if (e.getResponse().isNO() && charset != null) {
1360                        // Server does not support UTF-8
1361
// -> fall back to System default
1362
if ( charset.equals(UTF8)) {
1363                            charset = DEFAULT;
1364                        } else if (charset == DEFAULT) {
1365                            // If this also does not work
1366
// -> fall back to no charset specified
1367
charset = null;
1368                        } else {
1369                            // something else is wrong
1370
throw e;
1371                        }
1372                    } else
1373                        throw e;
1374                }
1375            }
1376
1377            return Arrays.asList(result);
1378        } catch (IMAPDisconnectedException e) {
1379            return search(filterRule, folder);
1380        }
1381    }
1382
1383    /**
1384     * @param filterRule
1385     */

1386    private SearchKey[] createSearchKey(IFilterRule filterRule) {
1387        SearchKey[] searchRequest;
1388        int argumentSize = filterRule.getChildCount();
1389        // One or many arguments?
1390
if (argumentSize == 1) {
1391            // One is the easiest case
1392
searchRequest = new SearchKey[] { getSearchKey(filterRule.get(0)) };
1393        } else {
1394            // AND or OR ? -> AND is implicit, OR must be specified
1395
if (filterRule.getConditionInt() == FilterRule.MATCH_ALL) {
1396                // AND : simply create a list of arguments
1397
searchRequest = new SearchKey[argumentSize];
1398
1399                for (int i = 0; i < argumentSize; i++) {
1400                    searchRequest[i] = getSearchKey(filterRule.get(i));
1401                }
1402
1403            } else {
1404                // OR : the arguments must be glued by a OR SearchKey
1405
SearchKey orKey;
1406
1407                orKey = new SearchKey(SearchKey.OR, getSearchKey(filterRule
1408                        .get(argumentSize - 1)), getSearchKey(filterRule
1409                        .get(argumentSize - 2)));
1410
1411                for (int i = argumentSize - 3; i >= 0; i--) {
1412                    orKey = new SearchKey(SearchKey.OR, getSearchKey(filterRule
1413                            .get(i)), orKey);
1414                }
1415
1416                searchRequest = new SearchKey[] { orKey };
1417            }
1418        }
1419
1420        return searchRequest;
1421    }
1422
1423    /**
1424     * @param criteria
1425     * @return
1426     */

1427    private SearchKey getSearchKey(IFilterCriteria criteria) {
1428        int operator = criteria.getCriteria();
1429        int type = new MailFilterCriteria(criteria).getType();
1430
1431        switch (type) {
1432        case MailFilterCriteria.FROM: {
1433            if (operator == FilterCriteria.CONTAINS) {
1434                return new SearchKey(SearchKey.FROM, criteria
1435                        .getPatternString());
1436            } else {
1437                // contains not
1438
return new SearchKey(SearchKey.NOT, new SearchKey(
1439                        SearchKey.FROM, criteria.getPatternString()));
1440            }
1441        }
1442
1443        case MailFilterCriteria.CC: {
1444            if (operator == FilterCriteria.CONTAINS) {
1445                return new SearchKey(SearchKey.CC, criteria.getPatternString());
1446            } else {
1447                // contains not
1448
return new SearchKey(SearchKey.NOT, new SearchKey(SearchKey.CC,
1449                        criteria.getPatternString()));
1450            }
1451        }
1452
1453        case MailFilterCriteria.BCC: {
1454            if (operator == FilterCriteria.CONTAINS) {
1455                return new SearchKey(SearchKey.BCC, criteria.getPatternString());
1456            } else {
1457                // contains not
1458
return new SearchKey(SearchKey.NOT, new SearchKey(
1459                        SearchKey.BCC, criteria.getPatternString()));
1460            }
1461        }
1462
1463        case MailFilterCriteria.TO: {
1464            if (operator == FilterCriteria.CONTAINS) {
1465                return new SearchKey(SearchKey.TO, criteria.getPatternString());
1466            } else {
1467                // contains not
1468
return new SearchKey(SearchKey.NOT, new SearchKey(SearchKey.TO,
1469                        criteria.getPatternString()));
1470            }
1471        }
1472
1473        case MailFilterCriteria.SUBJECT: {
1474            if (operator == FilterCriteria.CONTAINS) {
1475                return new SearchKey(SearchKey.SUBJECT, criteria
1476                        .getPatternString());
1477            } else {
1478                // contains not
1479
return new SearchKey(SearchKey.NOT, new SearchKey(
1480                        SearchKey.SUBJECT, criteria.getPatternString()));
1481            }
1482        }
1483
1484        case MailFilterCriteria.BODY: {
1485            if (operator == FilterCriteria.CONTAINS) {
1486                return new SearchKey(SearchKey.BODY, criteria
1487                        .getPatternString());
1488            } else {
1489                // contains not
1490
return new SearchKey(SearchKey.NOT, new SearchKey(
1491                        SearchKey.BODY, criteria.getPatternString()));
1492            }
1493        }
1494
1495        case MailFilterCriteria.CUSTOM_HEADERFIELD: {
1496            if (operator == FilterCriteria.CONTAINS) {
1497                return new SearchKey(SearchKey.HEADER, new MailFilterCriteria(
1498                        criteria).getHeaderfieldString(), criteria
1499                        .getPatternString());
1500            } else {
1501                // contains not
1502
return new SearchKey(SearchKey.NOT, new SearchKey(
1503                        SearchKey.HEADER, new MailFilterCriteria(criteria)
1504                                .getHeaderfieldString(), criteria
1505                                .getPatternString()));
1506            }
1507        }
1508
1509        case MailFilterCriteria.DATE: {
1510            DateFormat JavaDoc df = DateFormat.getDateInstance();
1511
1512            IMAPDate searchPattern = null;
1513
1514            try {
1515                searchPattern = new IMAPDate(df.parse(criteria
1516                        .getPatternString()));
1517            } catch (java.text.ParseException JavaDoc ex) {
1518                // should never happen
1519
ex.printStackTrace();
1520            }
1521
1522            if (operator == FilterCriteria.DATE_BEFORE) {
1523                return new SearchKey(SearchKey.BEFORE, searchPattern);
1524            } else {
1525                // AFTER
1526
return new SearchKey(SearchKey.NOT, new SearchKey(
1527                        SearchKey.BEFORE, searchPattern));
1528            }
1529        }
1530
1531        case MailFilterCriteria.SIZE: {
1532            if (operator == FilterCriteria.SIZE_SMALLER) {
1533                return new SearchKey(SearchKey.SMALLER, criteria
1534                        .getPatternString());
1535            } else {
1536                // contains not
1537
return new SearchKey(SearchKey.NOT, new SearchKey(
1538                        SearchKey.SMALLER, criteria.getPatternString()));
1539            }
1540        }
1541        }
1542
1543        return null;
1544    }
1545
1546    /**
1547     * Check if string contains US-ASCII characters.
1548     *
1549     * @param s
1550     * @return true, if string contains US-ASCII characters
1551     */

1552    protected static boolean isAscii(String JavaDoc s) {
1553        int l = s.length();
1554
1555        for (int i = 0; i < l; i++) {
1556            if ((int) s.charAt(i) > 0177) { // non-ascii
1557

1558                return false;
1559            }
1560        }
1561
1562        return true;
1563    }
1564
1565    /**
1566     * Create string representation of {@ link MarkMessageCommand}constants.
1567     *
1568     * @param variant
1569     * @return
1570     */

1571    private IMAPFlags convertToFlags(int variant) {
1572        IMAPFlags result = new IMAPFlags();
1573
1574        switch (variant) {
1575        case MarkMessageCommand.MARK_AS_READ:
1576        case MarkMessageCommand.MARK_AS_UNREAD: {
1577            result.setSeen(true);
1578
1579            break;
1580        }
1581
1582        case MarkMessageCommand.MARK_AS_FLAGGED:
1583        case MarkMessageCommand.MARK_AS_UNFLAGGED: {
1584            result.setFlagged(true);
1585
1586            break;
1587        }
1588
1589        case MarkMessageCommand.MARK_AS_EXPUNGED:
1590        case MarkMessageCommand.MARK_AS_UNEXPUNGED: {
1591            result.setDeleted(true);
1592
1593            break;
1594        }
1595
1596        case MarkMessageCommand.MARK_AS_ANSWERED: {
1597            result.setAnswered(true);
1598
1599            break;
1600        }
1601
1602        case MarkMessageCommand.MARK_AS_SPAM:
1603        case MarkMessageCommand.MARK_AS_NOTSPAM: {
1604            result.setJunk(true);
1605
1606            break;
1607        }
1608        case MarkMessageCommand.MARK_AS_DRAFT: {
1609            result.setDraft(true);
1610
1611            break;
1612        }
1613        }
1614
1615        return result;
1616    }
1617
1618    /* (non-Javadoc)
1619     * @see org.columba.mail.imap.IImapServer#getMessageFolderInfo(org.columba.mail.folder.imap.IMAPFolder)
1620     */

1621    public MailboxInfo getMessageFolderInfo(IMAPFolder folder)
1622            throws IOException JavaDoc, IMAPException, CommandCancelledException {
1623        ensureSelectedState(folder);
1624
1625        return messageFolderInfo;
1626    }
1627
1628    /* (non-Javadoc)
1629     * @see org.columba.mail.imap.IImapServer#fetchSubscribedFolders()
1630     */

1631    public ListInfo[] fetchSubscribedFolders() throws IOException JavaDoc,
1632            IMAPException, CommandCancelledException {
1633        try {
1634            ensureLoginState();
1635            ListInfo[] lsub = protocol.lsub("", "*");
1636
1637            // Also set the delimiter
1638
if (lsub.length > 0) {
1639                delimiter = lsub[0].getDelimiter();
1640            }
1641
1642            return lsub;
1643        } catch (IMAPDisconnectedException e) {
1644            return fetchSubscribedFolders();
1645        }
1646    }
1647
1648    /* (non-Javadoc)
1649     * @see org.columba.mail.imap.IImapServer#isSelected(org.columba.mail.folder.imap.IMAPFolder)
1650     */

1651    public boolean isSelected(IMAPFolder folder) throws IOException JavaDoc,
1652            IMAPException, CommandCancelledException {
1653        ensureLoginState();
1654
1655        return (protocol.getState() == IMAPProtocol.SELECTED && protocol
1656                .getSelectedMailbox().equals(folder.getImapPath()));
1657    }
1658
1659    /**
1660     * @param e
1661     * @return
1662     */

1663    private int showErrorDialog(String JavaDoc message) {
1664        Object JavaDoc[] options = new String JavaDoc[] {
1665                MailResourceLoader.getString("", "global", "ok").replaceAll(
1666                        "&", ""),
1667                MailResourceLoader.getString("", "global", "cancel")
1668                        .replaceAll("&", "") };
1669
1670        int result = JOptionPane.showOptionDialog(FrameManager.getInstance()
1671                .getActiveFrame(), message, "Warning",
1672                JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null,
1673                options, options[0]);
1674        return result;
1675    }
1676
1677    /* (non-Javadoc)
1678     * @see org.columba.mail.imap.IImapServer#alertMessage(java.lang.String)
1679     */

1680    public void alertMessage(String JavaDoc arg0) {
1681        // TODO: Show dialog
1682
LOG.warning(arg0);
1683    }
1684
1685    /* (non-Javadoc)
1686     * @see org.columba.mail.imap.IImapServer#connectionClosed(java.lang.String, java.lang.String)
1687     */

1688    public void connectionClosed(String JavaDoc arg0, String JavaDoc arg1) {
1689        LOG.info(arg0);
1690        selectedFolder = null;
1691    }
1692
1693    /* (non-Javadoc)
1694     * @see org.columba.mail.imap.IImapServer#existsChanged(java.lang.String, int)
1695     */

1696    public void existsChanged(String JavaDoc arg0, int arg1) {
1697        if( selectedStatus == null) return;
1698
1699        selectedStatus.setMessages(arg1);
1700        statusDirty = true;
1701
1702        if (updatesEnabled) {
1703            if( existsChangedAction != null) {
1704                existsChangedAction.actionPerformed(selectedFolder);
1705            }
1706
1707            LOG.fine("Exists changed -> triggering update");
1708
1709        }
1710    }
1711
1712    /* (non-Javadoc)
1713     * @see org.columba.mail.imap.IImapServer#flagsChanged(java.lang.String, org.columba.ristretto.imap.IMAPFlags)
1714     */

1715    public void flagsChanged(String JavaDoc arg0, IMAPFlags arg1) {
1716        LOG.fine("Flag changed -> triggering update");
1717
1718        if( updateFlagAction != null) {
1719            updateFlagAction.actionPerformed(selectedFolder, arg1);
1720        }
1721
1722    }
1723
1724    /* (non-Javadoc)
1725     * @see org.columba.mail.imap.IImapServer#parseError(java.lang.String)
1726     */

1727    public void parseError(String JavaDoc arg0) {
1728        LOG.warning(arg0);
1729    }
1730
1731    /* (non-Javadoc)
1732     * @see org.columba.mail.imap.IImapServer#recentChanged(java.lang.String, int)
1733     */

1734    public void recentChanged(String JavaDoc arg0, int arg1) {
1735        if( selectedStatus == null) return;
1736
1737        selectedStatus.setRecent(arg1);
1738        statusDirty = true;
1739
1740        // We trigger an update only when the exists changed
1741
// which should be equal with a Recent change.
1742
}
1743
1744    /* (non-Javadoc)
1745     * @see org.columba.mail.imap.IImapServer#warningMessage(java.lang.String)
1746     */

1747    public void warningMessage(String JavaDoc arg0) {
1748        LOG.warning(arg0);
1749    }
1750
1751    /**
1752     * Returns configuration options of this IMAP account
1753     *
1754     * @return configuration options of this IMAP account
1755     */
/* (non-Javadoc)
1756     * @see org.columba.mail.imap.IImapServer#getItem()
1757     */

1758    public ImapItem getItem() {
1759        return item;
1760    }
1761
1762    /* (non-Javadoc)
1763     * @see org.columba.mail.imap.IImapServer#update(java.util.Observable, java.lang.Object)
1764     */

1765    public void update(Observable JavaDoc o, Object JavaDoc arg) {
1766        protocol = new IMAPProtocol(item.get("host"), item.getInteger("port"));
1767    }
1768
1769    /* (non-Javadoc)
1770     * @see org.columba.mail.imap.IImapServer#setExistsChangedAction(org.columba.mail.imap.IExistsChangedAction)
1771     */

1772    public void setExistsChangedAction(IExistsChangedAction existsChangedAction) {
1773        this.existsChangedAction = existsChangedAction;
1774    }
1775
1776    /* (non-Javadoc)
1777     * @see org.columba.mail.imap.IImapServer#setUpdateFlagAction(org.columba.mail.imap.IUpdateFlagAction)
1778     */

1779    public void setUpdateFlagAction(IUpdateFlagAction updateFlagAction) {
1780        this.updateFlagAction = updateFlagAction;
1781    }
1782
1783    /* (non-Javadoc)
1784     * @see org.columba.mail.imap.IImapServer#setObservable(org.columba.api.command.IStatusObservable)
1785     */

1786    public void setObservable(IStatusObservable observable) {
1787        this.observable = observable;
1788    }
1789}
1790
Popular Tags