KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > mail > ImapMailbox


1 /*
2  * ImapMailbox.java
3  *
4  * Copyright (C) 2000-2003 Peter Graves
5  * $Id: ImapMailbox.java,v 1.5 2003/08/09 17:41:41 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

21
22 package org.armedbear.j.mail;
23
24 import java.io.UnsupportedEncodingException JavaDoc;
25 import java.net.MalformedURLException JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.Collections JavaDoc;
28 import java.util.HashMap JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import javax.swing.SwingUtilities JavaDoc;
32 import org.armedbear.j.BackgroundProcess;
33 import org.armedbear.j.Buffer;
34 import org.armedbear.j.BufferIterator;
35 import org.armedbear.j.Debug;
36 import org.armedbear.j.Editor;
37 import org.armedbear.j.EditorIterator;
38 import org.armedbear.j.File;
39 import org.armedbear.j.FastStringBuffer;
40 import org.armedbear.j.Frame;
41 import org.armedbear.j.Headers;
42 import org.armedbear.j.InputDialog;
43 import org.armedbear.j.Line;
44 import org.armedbear.j.Log;
45 import org.armedbear.j.MessageDialog;
46 import org.armedbear.j.ProgressNotifier;
47 import org.armedbear.j.Property;
48 import org.armedbear.j.Sidebar;
49 import org.armedbear.j.StatusBar;
50 import org.armedbear.j.StatusBarProgressNotifier;
51 import org.armedbear.j.Utilities;
52 import org.armedbear.j.View;
53
54 public final class ImapMailbox extends Mailbox
55 {
56     private static final int DEFAULT_PORT = 143;
57
58     private final ImapSession session;
59     private final String JavaDoc folderName;
60
61     private int messageCount = -1;
62     private int uidValidity;
63     private int uidLast;
64     private ImapMailboxCache mailboxCache;
65     private boolean cancelled;
66     private Thread JavaDoc backgroundThread;
67
68     public ImapMailbox(ImapURL url, ImapSession session)
69     {
70         super(url);
71         this.session = session;
72         session.setMailbox(this);
73         String JavaDoc tunnel = getStringProperty(Property.TUNNEL);
74         if (tunnel != null) {
75             Log.debug("tunnel = |" + tunnel + "|");
76             session.setTunnel(tunnel);
77         }
78         folderName = session.getFolderName();
79         Debug.assertTrue(folderName.equals(url.getFolderName()));
80         supportsUndo = false;
81         type = TYPE_MAILBOX;
82         mode = MailboxMode.getMode();
83         formatter = mode.getFormatter(this);
84         readOnly = true;
85         setInitialized(true);
86         Log.debug("ImapMailbox constructor ".concat(url.getCanonicalName()));
87     }
88
89     public String JavaDoc getFileNameForDisplay()
90     {
91         FastStringBuffer sb = new FastStringBuffer(64);
92         sb.append(url.toString());
93         String JavaDoc limitPattern = getLimitPattern();
94         if (limitPattern != null) {
95             sb.append(' ');
96             sb.append(limitPattern);
97         }
98         return sb.toString();
99     }
100
101     public String JavaDoc getName()
102     {
103         FastStringBuffer sb = new FastStringBuffer();
104         sb.append('{');
105         sb.append(session.getUser());
106         sb.append('@');
107         sb.append(session.getHost());
108         sb.append(':');
109         sb.append(session.getPort());
110         sb.append('}');
111         sb.append(folderName);
112         return sb.toString();
113     }
114
115     public final ImapSession getSession()
116     {
117         return session;
118     }
119
120     public final String JavaDoc getFolderName()
121     {
122         return folderName;
123     }
124
125     public final int getUidValidity()
126     {
127         return uidValidity;
128     }
129
130     public final int getMessageCount()
131     {
132         return messageCount;
133     }
134
135     public synchronized long getLastErrorMillis()
136     {
137         return session.getLastErrorMillis();
138     }
139
140     public void setAlertText(final String JavaDoc s)
141     {
142         Log.debug("alert = " + s);
143         Runnable JavaDoc r = new Runnable JavaDoc() {
144             public void run()
145             {
146                 Editor.currentEditor().status(s);
147             }
148         };
149         SwingUtilities.invokeLater(r);
150     }
151
152     public void setStatusText(final String JavaDoc s)
153     {
154         Log.debug("status = " + s);
155         Runnable JavaDoc r = new Runnable JavaDoc() {
156             public void run()
157             {
158                 Editor.currentEditor().status(s);
159             }
160         };
161         SwingUtilities.invokeLater(r);
162     }
163
164     public void messageExpunged(int messageNumber)
165     {
166         // Invalidate message count.
167
messageCount = -1;
168     }
169
170     public void expunge()
171     {
172         Runnable JavaDoc expungeRunnable = new Runnable JavaDoc() {
173             public void run()
174             {
175                 try {
176                     if (session.verifyConnected() && session.verifySelected(folderName)) {
177                         if (session.isReadOnly()) {
178                             Log.debug("expunge - read-only - reselecting...");
179                             session.reselect(folderName);
180                             if (session.isReadOnly()) {
181                                 Log.error("expunge - mailbox is read-only");
182                                 readOnlyError();
183                                 return;
184                             }
185                         }
186                         if (messageCache != null)
187                             messageCache.removeDeletedEntries(Collections.unmodifiableList(entries));
188                         if (session.close()) {
189                             Log.debug("expunge back from close(), calling reselect()");
190                             if (session.reselect(folderName)) {
191                                 Log.debug("expunge back from reselect()");
192                                 getAllMessageHeaders();
193                                 refreshBuffer();
194                                 setBusy(false);
195                                 for (int i = 0; i < Editor.getFrameCount(); i++) {
196                                     Frame frame = Editor.getFrame(i);
197                                     if (frame != null) {
198                                         StatusBar statusBar = frame.getStatusBar();
199                                         if (statusBar != null)
200                                             statusBar.setText(null);
201                                     }
202                                 }
203                                 updateDisplay();
204                                 return;
205                             }
206                         }
207                         // Error!
208
error("Operation failed", "Expunge");
209                     }
210                 }
211                 finally {
212                     setBusy(false);
213                     unlock();
214                 }
215             }
216         };
217         if (lock()) {
218             setBusy(true);
219             saveDisplayState();
220             new Thread JavaDoc(expungeRunnable).start();
221         }
222     }
223
224     public synchronized int load()
225     {
226         if (isLoaded()) {
227             return LOAD_COMPLETED;
228         } else if (lock()) {
229             Debug.assertTrue(backgroundThread == null);
230             backgroundThread = new Thread JavaDoc(loadProcess);
231             backgroundThread.start();
232             setLoaded(true);
233             return LOAD_PENDING;
234         } else {
235             // Not loaded, lock() failed. Shouldn't happen.
236
Debug.bug("ImapMailbox.load can't lock mailbox");
237             return LOAD_FAILED;
238         }
239     }
240
241     private BackgroundProcess loadProcess = new BackgroundProcess() {
242         public void run()
243         {
244             Runnable JavaDoc completionRunnable = null;
245             try {
246                 cancelled = false;
247                 setBusy(true);
248                 setBackgroundProcess(this);
249                 if (getAllMessageHeaders()) {
250                     refreshBuffer();
251                     completionRunnable = new Runnable JavaDoc() {
252                         public void run()
253                         {
254                             setBusy(false);
255                             for (EditorIterator it = new EditorIterator(); it.hasNext();) {
256                                 Editor ed = it.nextEditor();
257                                 View view = new View();
258                                 view.setDotEntry(getInitialEntry());
259                                 ed.setView(ImapMailbox.this, view);
260                                 if (ed.getBuffer() == ImapMailbox.this) {
261                                     ed.bufferActivated(true);
262                                     ed.updateDisplay();
263                                 }
264                             }
265                         }
266                     };
267                 } else {
268                     // Error or user cancelled.
269
completionRunnable = new Runnable JavaDoc() {
270                         public void run()
271                         {
272                             if (Editor.getBufferList().contains(ImapMailbox.this))
273                                 kill();
274                             for (EditorIterator it = new EditorIterator(); it.hasNext();)
275                                 it.nextEditor().updateDisplay();
276                         }
277                     };
278                 }
279             }
280             finally {
281                 setBackgroundProcess(null);
282                 backgroundThread = null;
283                 unlock();
284                 if (completionRunnable != null)
285                     SwingUtilities.invokeLater(completionRunnable);
286             }
287         }
288
289         public void cancel()
290         {
291             abort();
292         }
293     };
294
295     private void abort()
296     {
297         Log.debug("ImapMailbox.abort");
298         cancelled = true;
299         if (backgroundThread != null && backgroundThread.isAlive())
300             backgroundThread.interrupt();
301         session.disconnect();
302     }
303
304     public final void getNewMessages()
305     {
306         if (lock())
307             getNewMessages(true);
308         else
309             Editor.currentEditor().status("Mailbox is locked");
310     }
311
312     public void getNewMessages(boolean interactive)
313     {
314         Debug.assertTrue(isLocked());
315         setBusy(true);
316         if (interactive)
317             saveDisplayState();
318         Debug.assertTrue(backgroundThread == null);
319         backgroundThread = new Thread JavaDoc(new GetNewMessagesProcess(interactive));
320         backgroundThread.start();
321     }
322
323     private class GetNewMessagesProcess implements BackgroundProcess
324     {
325         private final boolean interactive;
326
327         public GetNewMessagesProcess(boolean interactive)
328         {
329             this.interactive = interactive;
330         }
331
332         public void run()
333         {
334             cancelled = false;
335             setBackgroundProcess(this);
336             boolean changed = false;
337             if (interactive)
338                 changed = clearRecent();
339             try {
340                 // verifyConnected() sends a NOOP command to the server, which
341
// gives the server a chance to report changes to the mailbox.
342
if (!session.verifyConnected()) {
343                     Log.error("GetNewMessagesProcess.run can't connect");
344                     return;
345                 }
346                 if (cancelled) {
347                     Log.debug("cancelled, disconnecting...");
348                     session.disconnect();
349                     return;
350                 }
351                 if (!session.verifySelected(folderName)) {
352                     Log.error("GetNewMessagesProcess.run can't select " +
353                         folderName);
354                     return;
355                 }
356                 if (cancelled) {
357                     Log.debug("cancelled, disconnecting...");
358                     session.disconnect();
359                     return;
360                 }
361                 if (session.isReadOnly()) {
362                     Log.warn("GetNewMessagesProcess.run " + folderName +
363                         " is read-only, returning...");
364                     return;
365                 }
366                 if (uidValidity != session.getUidValidity()) {
367                     getAllMessageHeaders();
368                     changed = true;
369                 } else if (interactive) {
370                     changed = getNewMessageHeaders() || changed;
371                 } else {
372                     // Not interactive.
373
if (session.getMessageCount() != messageCount) {
374                         Log.debug("session.getMessageCount() = " +
375                             session.getMessageCount() +
376                             " mailbox message count = " +
377                             messageCount);
378                         changed = getNewMessageHeaders();
379                     }
380                 }
381                 if (cancelled) {
382                     Log.debug("cancelled, disconnecting...");
383                     session.disconnect();
384                 }
385             }
386             finally {
387                 setBackgroundProcess(null);
388                 backgroundThread = null;
389                 if (changed) {
390                     refreshBuffer();
391                     setBusy(false);
392                     updateDisplay();
393                     newMessagesStatus();
394                 } else {
395                     setBusy(false);
396                     for (EditorIterator it = new EditorIterator(); it.hasNext();) {
397                         Editor ed = it.nextEditor();
398                         if (ed != null && ed.getBuffer() == ImapMailbox.this)
399                             ed.setDefaultCursor();
400                     }
401                 }
402                 setLastCheckMillis(System.currentTimeMillis());
403                 unlock();
404             }
405         }
406
407         public void cancel()
408         {
409             abort();
410         }
411     }
412
413     public void createFolder()
414     {
415         final Editor editor = Editor.currentEditor();
416         final String JavaDoc input =
417             InputDialog.showInputDialog(editor, "Folder:", "Create Folder");
418         if (input == null || input.length() == 0)
419             return;
420         final String JavaDoc name = extractFolderName(input);
421         if (name == null)
422             return;
423         Runnable JavaDoc createRunnable = new Runnable JavaDoc() {
424             public void run()
425             {
426                 boolean succeeded = false;
427                 try {
428                     if (session.verifyConnected() && session.verifySelected(folderName)) {
429                         session.setEcho(true);
430                         session.writeTagged("create " + name);
431                         succeeded = session.getResponse() == ImapSession.OK;
432                         session.setEcho(false);
433                     }
434                 }
435                 finally {
436                     unlock();
437                     setBusy(false);
438                     Editor.updateDisplayLater(ImapMailbox.this);
439                     if (succeeded)
440                         success("Folder created");
441                     else
442                         error("Unable to create folder", "Error");
443                 }
444             }
445         };
446         // Even though we're not changing this mailbox per se, we need to lock
447
// it here so we have exclusive use of the session.
448
if (lock()) {
449             setBusy(true);
450             new Thread JavaDoc(createRunnable).start();
451         }
452     }
453
454     public void deleteFolder()
455     {
456         final Editor editor = Editor.currentEditor();
457         final String JavaDoc input =
458             InputDialog.showInputDialog(editor, "Folder:", "Delete Folder");
459         if (input == null || input.length() == 0)
460             return;
461         final String JavaDoc name = extractFolderName(input);
462         if (name == null)
463             return;
464         String JavaDoc message = "Delete folder \"" + name + "\" on " + session.getHost() + "?";
465         if (!editor.confirm("Delete Folder", message))
466             return;
467         Runnable JavaDoc deleteFolderRunnable = new Runnable JavaDoc() {
468             public void run()
469             {
470                 boolean succeeded = false;
471                 try {
472                     if (session.verifyConnected() && session.verifySelected(folderName)) {
473                         session.setEcho(true);
474                         session.writeTagged("delete " + name);
475                         succeeded = session.getResponse() == ImapSession.OK;
476                         session.setEcho(false);
477                     }
478                 }
479                 finally {
480                     unlock();
481                     setBusy(false);
482                     Editor.updateDisplayLater(ImapMailbox.this);
483                     if (succeeded)
484                         success("Folder deleted");
485                     else
486                         error("Unable to delete folder", "Error");
487                 }
488             }
489         };
490         // Even though we're not changing this mailbox per se, we need to lock
491
// it here so we have exclusive use of the session.
492
if (lock()) {
493             setBusy(true);
494             new Thread JavaDoc(deleteFolderRunnable).start();
495         }
496     }
497
498     public void saveToFolder()
499     {
500         final Editor editor = Editor.currentEditor();
501         boolean advanceDot = false;
502         List JavaDoc list = getTaggedEntries();
503         if (list == null) {
504             Line line = editor.getDotLine();
505             if (!(line instanceof MailboxLine))
506                 return;
507             advanceDot = true;
508             list = new ArrayList JavaDoc();
509             list.add(((MailboxLine)line).getMailboxEntry());
510         }
511         final List JavaDoc toBeCopied = list;
512         final int count = toBeCopied.size();
513         String JavaDoc title;
514         if (count > 1)
515             title = "Save Tagged Messages to Folder";
516         else
517             title = "Save Message to Folder";
518         FastStringBuffer sb = new FastStringBuffer("Save ");
519         sb.append(count);
520         sb.append(" message");
521         if (count > 1)
522             sb.append('s');
523         sb.append(" to:");
524         final String JavaDoc destination = ChooseFolderDialog.chooseFolder(editor,
525             sb.toString(), title);
526         if (destination == null)
527             return;
528         final Line dotLine = advanceDot ? editor.getDotLine() : null;
529         Runnable JavaDoc saveRunnable = new Runnable JavaDoc() {
530             public void run()
531             {
532                 boolean succeeded = false;
533                 try {
534                     if (session.verifyConnected() && session.verifySelected(folderName)) {
535                         if (destination.startsWith("mailbox:")) {
536                             // Destination is local.
537
succeeded = saveLocal(toBeCopied, destination, false);
538                         } else {
539                             session.setEcho(true);
540                             final String JavaDoc messageSet = getMessageSet(toBeCopied);
541                             FastStringBuffer sbuf = new FastStringBuffer("uid copy ");
542                             sbuf.append(messageSet);
543                             sbuf.append(' ');
544                             sbuf.append(destination);
545                             if (session.writeTagged(sbuf.toString()))
546                                 if (session.getResponse() == ImapSession.OK)
547                                     succeeded = true;
548                             session.setEcho(false);
549                         }
550                     }
551                 }
552                 finally {
553                     if (succeeded && dotLine != null)
554                         advanceDot(dotLine);
555                     setBusy(false);
556                     unlock();
557                     editor.updateDisplayLater();
558                     if (succeeded) {
559                         FastStringBuffer sbuf = new FastStringBuffer("Saved ");
560                         sbuf.append(toBeCopied.size());
561                         sbuf.append(" message");
562                         if (toBeCopied.size() != 1)
563                             sbuf.append('s');
564                         success(sbuf.toString());
565                     } else
566                         error("Save failed", "Error");
567                 }
568             }
569         };
570         if (lock()) {
571             setBusy(true);
572             new Thread JavaDoc(saveRunnable).start();
573         }
574     }
575
576     public void moveToFolder()
577     {
578         final Editor editor = Editor.currentEditor();
579         boolean advanceDot = false;
580         List JavaDoc list = getTaggedEntries();
581         if (list == null) {
582             Line line = editor.getDotLine();
583             if (!(line instanceof MailboxLine))
584                 return;
585             advanceDot = true;
586             list = new ArrayList JavaDoc();
587             list.add(((MailboxLine)line).getMailboxEntry());
588         }
589         final List JavaDoc toBeMoved = list;
590         final int count = toBeMoved.size();
591         String JavaDoc title;
592         if (count > 1)
593             title = "Move Tagged Messages to Folder";
594         else
595             title = "Move Message to Folder";
596         FastStringBuffer sb = new FastStringBuffer("Move ");
597         sb.append(count);
598         sb.append(" message");
599         if (count > 1)
600             sb.append('s');
601         sb.append(" to:");
602         final String JavaDoc destination = ChooseFolderDialog.chooseFolder(editor,
603             sb.toString(), title);
604         if (destination == null)
605             return;
606         final Line dotLine = advanceDot ? editor.getDotLine() : null;
607         Runnable JavaDoc moveRunnable = new Runnable JavaDoc() {
608             public void run()
609             {
610                 boolean succeeded = false;
611                 String JavaDoc errorText = "Move failed";
612                 try {
613                     succeeded = moveToFolder(toBeMoved, destination);
614                 }
615                 catch (MailException e) {
616                     errorText = e.getMessage();
617                 }
618                 finally {
619                     // Update message count in sidebar buffer list. We should
620
// do this even if there was an error, since some messages
621
// may have been moved successfully.
622
countMessages();
623                     Sidebar.repaintBufferListInAllFrames();
624                     if (succeeded && dotLine != null)
625                         advanceDot(dotLine);
626                     setBusy(false);
627                     unlock();
628                     editor.updateDisplayLater();
629                     if (succeeded) {
630                         FastStringBuffer sbuf = new FastStringBuffer("Moved ");
631                         sbuf.append(toBeMoved.size());
632                         sbuf.append(" message");
633                         if (toBeMoved.size() != 1)
634                             sbuf.append('s');
635                         success(sbuf.toString());
636                     } else
637                         error(errorText, "Error");
638                 }
639             }
640         };
641         if (lock()) {
642             setBusy(true);
643             new Thread JavaDoc(moveRunnable).start();
644         }
645     }
646
647     private boolean moveToFolder(List JavaDoc toBeMoved, String JavaDoc destination) throws MailException
648     {
649         if (destination == null)
650             return false;
651         if (!destination.startsWith("mailbox:")) {
652             // Not local. Extract folder name from URL.
653
destination = extractFolderName(destination);
654             if (destination == null)
655                 return false;
656         }
657         boolean succeeded = false;
658         if (session.verifyConnected() && session.verifySelected(folderName)) {
659             if (session.isReadOnly()) {
660                 Log.debug("moveToFolder " + folderName + " is read-only - reselecting...");
661                 session.reselect(folderName);
662                 if (session.isReadOnly())
663                     throw new MailException("Mailbox " + folderName + " is read-only");
664             }
665             if (destination.startsWith("mailbox:")) {
666                 // Destination is local.
667
succeeded = saveLocal(toBeMoved, destination, true);
668             } else {
669                 // Destination is an IMAP folder.
670
session.setEcho(true);
671                 final String JavaDoc messageSet = getMessageSet(toBeMoved);
672                 FastStringBuffer sb = new FastStringBuffer("uid copy ");
673                 sb.append(messageSet);
674                 sb.append(' ');
675                 sb.append(destination);
676                 session.writeTagged(sb.toString());
677                 if (session.getResponse() == ImapSession.OK) {
678                     sb.setLength(0);
679                     sb.append("uid store ");
680                     sb.append(messageSet);
681                     sb.append(" +flags.silent (\\deleted)");
682                     session.writeTagged(sb.toString());
683                     if (session.getResponse() == ImapSession.OK) {
684                         succeeded = true;
685                         for (int i = 0; i < toBeMoved.size(); i++) {
686                             ImapMailboxEntry entry = (ImapMailboxEntry) toBeMoved.get(i);
687                             entry.setFlags(entry.getFlags() | MailboxEntry.DELETED);
688                             updateEntry(entry);
689                         }
690                     }
691                 }
692                 session.setEcho(false);
693             }
694         }
695         return succeeded;
696     }
697
698     private boolean saveLocal(List JavaDoc toBeSaved, String JavaDoc destination, boolean delete)
699     {
700         File file = File.getInstance(destination.substring(8));
701         if (file == null)
702             return false;
703         if (!file.isLocal())
704             return false;
705         Mbox mbox = Mbox.getInstance(file);
706         if (!mbox.lock())
707             return false;
708         boolean error = false;
709         for (int i = 0; i < toBeSaved.size(); i++) {
710             ImapMailboxEntry entry = (ImapMailboxEntry) toBeSaved.get(i);
711             Message message = getMessage(entry, null);
712             if (message != null &&
713                 mbox.appendMessage(message, entry.getFlags() & ~MailboxEntry.TAGGED)) {
714                 if (delete) {
715                     session.writeTagged("uid store " + entry.getUid() + " +flags.silent (\\deleted)");
716                     if (session.getResponse() == ImapSession.OK) {
717                         entry.setFlags(entry.getFlags() | MailboxEntry.DELETED);
718                         updateEntry(entry);
719                     } else {
720                         error = true;
721                         break;
722                     }
723                 }
724             } else {
725                 error = true;
726                 break;
727             }
728         }
729         mbox.unlock();
730         mbox.updateViews();
731         return !error;
732     }
733
734     public String JavaDoc extractFolderName(String JavaDoc input)
735     {
736         if (input.startsWith("{")) {
737             try {
738                 ImapURL targetUrl = new ImapURL(input);
739                 if (!url.getHost().equals(targetUrl.getHost())) {
740                     // We don't support operations involving folders on other
741
// servers.
742
return null;
743                 }
744                 String JavaDoc name = targetUrl.getFolderName();
745                 Log.debug("folder name = |" + name + "|");
746                 return name;
747             }
748             catch (MalformedURLException JavaDoc e) {
749                 Log.error(e);
750                 return null;
751             }
752         } else {
753             // Assume input is already a relative path.
754
return input;
755         }
756     }
757
758     public void delete()
759     {
760         final Editor editor = Editor.currentEditor();
761         boolean advanceDot = false;
762         List JavaDoc list = getTaggedEntries();
763         if (list == null) {
764             Line line = editor.getDotLine();
765             if (!(line instanceof MailboxLine))
766                 return;
767             advanceDot = true;
768             list = new ArrayList JavaDoc();
769             list.add(((MailboxLine)line).getMailboxEntry());
770         }
771         final List JavaDoc toBeDeleted = list;
772         final Line dotLine = advanceDot ? editor.getDotLine() : null;
773         Runnable JavaDoc deleteRunnable = new Runnable JavaDoc() {
774             public void run()
775             {
776                 boolean succeeded = false;
777                 String JavaDoc errorText = "Delete failed";
778                 try {
779                     succeeded = delete(toBeDeleted);
780                 }
781                 catch (MailException e) {
782                     errorText = e.getMessage();
783                 }
784                 finally {
785                     if (succeeded) {
786                         // Update message count in sidebar buffer list.
787
countMessages();
788                         Sidebar.repaintBufferListInAllFrames();
789                         if (dotLine != null)
790                             advanceDot(dotLine);
791                     }
792                     setBusy(false);
793                     unlock();
794                     editor.updateDisplayLater();
795                     if (!succeeded)
796                         error(errorText, "Error");
797                 }
798             }
799         };
800         if (lock()) {
801             setBusy(true);
802             new Thread JavaDoc(deleteRunnable).start();
803         }
804     }
805
806     private boolean delete(List JavaDoc toBeDeleted) throws MailException
807     {
808         boolean succeeded = false;
809         if (session.verifyConnected() && session.verifySelected(folderName)) {
810             if (session.isReadOnly()) {
811                 Log.debug("delete " + folderName + " is read-only - reselecting...");
812                 session.reselect(folderName);
813                 if (session.isReadOnly())
814                     throw new MailException("Mailbox " + folderName + " is read-only");
815             }
816             session.setEcho(true);
817             session.uidStore(getMessageSet(toBeDeleted), "+flags.silent (\\deleted)");
818             if (session.getResponse() == ImapSession.OK) {
819                 succeeded = true;
820                 for (int i = 0; i < toBeDeleted.size(); i++) {
821                     ImapMailboxEntry entry = (ImapMailboxEntry) toBeDeleted.get(i);
822                     entry.setFlags(entry.getFlags() | MailboxEntry.DELETED);
823                     updateEntry(entry);
824                 }
825             }
826             session.setEcho(false);
827         }
828         return succeeded;
829     }
830
831     public void undelete()
832     {
833         storeFlagsInternal(ACTION_UNDELETE);
834     }
835
836     public void markRead()
837     {
838         storeFlagsInternal(ACTION_MARK_READ);
839     }
840
841     public void markUnread()
842     {
843         storeFlagsInternal(ACTION_MARK_UNREAD);
844     }
845
846     private static final int ACTION_UNDELETE = 0;
847     private static final int ACTION_MARK_UNREAD = 1;
848     private static final int ACTION_MARK_READ = 2;
849
850     private void storeFlagsInternal(final int action)
851     {
852         final Editor editor = Editor.currentEditor();
853         boolean advanceDot = false;
854         List JavaDoc list = getTaggedEntries();
855         if (list == null) {
856             Line line = editor.getDotLine();
857             if (!(line instanceof MailboxLine))
858                 return;
859             if (action == ACTION_UNDELETE)
860                 advanceDot = getBooleanProperty(Property.UNDELETE_ADVANCE_DOT);
861             else
862                 advanceDot = true;
863             list = new ArrayList JavaDoc();
864             list.add(((MailboxLine)line).getMailboxEntry());
865         }
866         final List JavaDoc entriesToBeProcessed = list;
867         final Line dotLine = advanceDot ? editor.getDotLine() : null;
868         Runnable JavaDoc storeFlagsRunnable = new Runnable JavaDoc() {
869             public void run()
870             {
871                 try {
872                     if (session.verifyConnected() && session.verifySelected(folderName)) {
873                         if (session.isReadOnly()) {
874                             Log.debug("storeFlagsInternal - read-only - reselecting...");
875                             session.reselect(folderName);
876                             if (session.isReadOnly()) {
877                                 readOnlyError();
878                                 return;
879                             }
880                         }
881                         session.setEcho(true);
882                         final String JavaDoc messageSet = getMessageSet(entriesToBeProcessed);
883                         switch (action) {
884                             case ACTION_UNDELETE:
885                                 session.uidStore(messageSet, "-flags.silent (\\deleted)");
886                                 break;
887                             case ACTION_MARK_READ:
888                                 session.uidStore(messageSet, "+flags.silent (\\seen)");
889                                 break;
890                             case ACTION_MARK_UNREAD:
891                                 session.uidStore(messageSet, "-flags.silent (\\seen)");
892                                 break;
893                             default:
894                                 Debug.assertTrue(false);
895                                 break;
896                         }
897                         if (session.getResponse() == ImapSession.OK) {
898                             for (int i = 0; i < entriesToBeProcessed.size(); i++) {
899                                 ImapMailboxEntry entry = (ImapMailboxEntry) entriesToBeProcessed.get(i);
900                                 switch (action) {
901                                     case ACTION_UNDELETE:
902                                         entry.setFlags(entry.getFlags() & ~MailboxEntry.DELETED);
903                                         break;
904                                     case ACTION_MARK_READ:
905                                         entry.setFlags(entry.getFlags() | MailboxEntry.SEEN);
906                                         break;
907                                     case ACTION_MARK_UNREAD:
908                                         entry.setFlags(entry.getFlags() & ~MailboxEntry.SEEN);
909                                         break;
910                                     default:
911                                         Debug.assertTrue(false);
912                                         break;
913                                 }
914                                 updateEntry(entry);
915                             }
916                             if (dotLine != null)
917                                 advanceDot(dotLine);
918                         }
919                         session.setEcho(false);
920                     }
921                     countMessages();
922                 }
923                 finally {
924                     setBusy(false);
925                     unlock();
926                     editor.updateDisplayLater();
927                     // Update message count in sidebar buffer list.
928
Sidebar.repaintBufferListInAllFrames();
929                 }
930             }
931         };
932         if (lock()) {
933             setBusy(true);
934             new Thread JavaDoc(storeFlagsRunnable).start();
935         }
936     }
937
938     public void flag()
939     {
940         final Editor editor = Editor.currentEditor();
941         boolean advanceDot = false;
942         List JavaDoc list = getTaggedEntries();
943         if (list == null) {
944             Line line = editor.getDotLine();
945             if (!(line instanceof MailboxLine))
946                 return;
947             advanceDot = true;
948             list = new ArrayList JavaDoc();
949             list.add(((MailboxLine)line).getMailboxEntry());
950         }
951         final List JavaDoc entriesToBeSet = new ArrayList JavaDoc();
952         final List JavaDoc entriesToBeCleared = new ArrayList JavaDoc();
953         for (int i = 0; i < list.size(); i++) {
954             MailboxEntry entry = (MailboxEntry) list.get(i);
955             if (entry.isFlagged())
956                 entriesToBeCleared.add(entry);
957             else
958                 entriesToBeSet.add(entry);
959         }
960         final Line dotLine = advanceDot ? editor.getDotLine() : null;
961         Runnable JavaDoc flagRunnable = new Runnable JavaDoc() {
962             public void run()
963             {
964                 try {
965                     if (session.verifyConnected() && session.verifySelected(folderName)) {
966                         if (session.isReadOnly()) {
967                             Log.debug("storeFlagsInternal - read-only - reselecting...");
968                             session.reselect(folderName);
969                             if (session.isReadOnly()) {
970                                 readOnlyError();
971                                 return;
972                             }
973                         }
974                         boolean error = false;
975                         session.setEcho(true);
976                         if (entriesToBeSet.size() > 0) {
977                             session.uidStore(getMessageSet(entriesToBeSet), "+flags.silent (\\flagged)");
978                             if (session.getResponse() == ImapSession.OK) {
979                                 for (int i = 0; i < entriesToBeSet.size(); i++) {
980                                     MailboxEntry entry = (MailboxEntry) entriesToBeSet.get(i);
981                                     entry.flag();
982                                     updateEntry(entry);
983                                 }
984                             } else
985                                 error = true;
986                         }
987                         if (!error && entriesToBeCleared.size() > 0) {
988                             session.uidStore(getMessageSet(entriesToBeCleared), "-flags.silent (\\flagged)");
989                             if (session.getResponse() == ImapSession.OK) {
990                                 for (int i = 0; i < entriesToBeCleared.size(); i++) {
991                                     MailboxEntry entry = (MailboxEntry) entriesToBeCleared.get(i);
992                                     entry.unflag();
993                                     updateEntry(entry);
994                                 }
995                             } else
996                                 error = true;
997                         }
998                         session.setEcho(false);
999                         if (!error && dotLine != null)
1000                            advanceDot(dotLine);
1001                    }
1002                }
1003                finally {
1004                    setBusy(false);
1005                    unlock();
1006                    editor.updateDisplayLater();
1007                    // Update message count in sidebar buffer list.
1008
Sidebar.repaintBufferListInAllFrames();
1009                }
1010            }
1011        };
1012        if (lock()) {
1013            setBusy(true);
1014            new Thread JavaDoc(flagRunnable).start();
1015        }
1016    }
1017
1018    public void setAnsweredFlag(final MailboxEntry entry)
1019    {
1020        Runnable JavaDoc setAnsweredFlagRunnable = new Runnable JavaDoc() {
1021            public void run()
1022            {
1023                try {
1024                    if (session.verifyConnected() && session.verifySelected(folderName)) {
1025                        session.setEcho(true);
1026                        session.uidStore(((ImapMailboxEntry)entry).getUid(), "+flags (\\answered)");
1027                        if (session.getResponse() == ImapSession.OK) {
1028                            entry.setFlags(entry.getFlags() | MailboxEntry.ANSWERED);
1029                            updateEntry(entry);
1030                        }
1031                        session.setEcho(false);
1032                    }
1033                }
1034                finally {
1035                    setBusy(false);
1036                    unlock();
1037                    Editor.updateDisplayLater(ImapMailbox.this);
1038                }
1039            }
1040        };
1041        if (lock()) {
1042            setBusy(true);
1043            new Thread JavaDoc(setAnsweredFlagRunnable).start();
1044        }
1045    }
1046
1047    private List JavaDoc retrieveMessageHeaders(int uidBegin, int uidEnd,
1048        boolean recent)
1049    {
1050        Log.debug("retrieveMessageHeaders " + folderName + " " + uidBegin +
1051            " " + uidEnd);
1052        long start = System.currentTimeMillis();
1053        uidValidity = session.getUidValidity();
1054        messageCount = session.getMessageCount();
1055        ArrayList JavaDoc list = new ArrayList JavaDoc();
1056        FastStringBuffer sbCommand = new FastStringBuffer("uid fetch ");
1057        sbCommand.append(uidBegin);
1058        sbCommand.append(':');
1059        if (uidEnd < 0)
1060            sbCommand.append('*');
1061        else
1062            sbCommand.append(uidEnd);
1063        sbCommand.append(" (uid flags internaldate rfc822.size envelope");
1064        sbCommand.append(" body.peek[header.fields (references)]");
1065        sbCommand.append(')');
1066        Log.debug("command = |" + sbCommand.toString() + "|");
1067        session.writeTagged(sbCommand.toString());
1068        StatusBarProgressNotifier progressNotifier =
1069            new StatusBarProgressNotifier(this);
1070        progressNotifier.progressStart();
1071        try {
1072            FastStringBuffer sb = new FastStringBuffer();
1073            final String JavaDoc endPrefix = session.lastTag() + " ";
1074            while (true) {
1075                String JavaDoc s = session.readLine();
1076                if (s == null) {
1077                    Log.debug("retrieveMessageHeaders s == null");
1078                    break;
1079                }
1080                if (s.startsWith(endPrefix))
1081                    break;
1082                if (s.indexOf("ENVELOPE (") >= 0) {
1083                    // New entry starting.
1084
if (sb.length() > 0) {
1085                        // Add previous entry to list.
1086
addEntry(list, sb.toString(), uidBegin, recent);
1087                        progressNotifier.progress(getProgressText(list.size()));
1088                        if (cancelled) {
1089                            Log.debug("retrieveMessageHeaders cancelled, disconnecting...");
1090                            session.disconnect();
1091                            return list;
1092                        }
1093                        sb.setLength(0);
1094                    }
1095                    sb.append(s);
1096                } else {
1097                    // Continuation of previous entry.
1098
sb.append("\r\n");
1099                    sb.append(s);
1100                }
1101            }
1102            if (sb.length() > 0) {
1103                // Add final entry to list.
1104
addEntry(list, sb.toString(), uidBegin, recent);
1105                progressNotifier.progress(getProgressText(list.size()));
1106            }
1107        }
1108        catch (Exception JavaDoc e) {
1109            Log.error(e);
1110        }
1111        finally {
1112            progressNotifier.progressStop();
1113            long elapsed = System.currentTimeMillis() - start;
1114            Log.debug("retrieveMessageHeaders " + list.size() + " messages in " + elapsed + " ms");
1115        }
1116        return list;
1117    }
1118
1119    private void addEntry(List JavaDoc list, String JavaDoc s, int uidBegin, boolean recent)
1120    {
1121        ImapMailboxEntry entry = ImapMailboxEntry.parseEntry(this, s);
1122        if (entry != null) {
1123            if (entry.getUid() >= uidBegin) {
1124                if (recent)
1125                    entry.setFlags(entry.getFlags() | MailboxEntry.RECENT);
1126                list.add(entry);
1127            } else {
1128                Log.debug("not adding message " + entry.getMessageNumber() +
1129                    " uid " + entry.getUid() + " uidBegin = " + uidBegin);
1130                if (messageCount < 0) {
1131                    // Mailbox message count is invalid.
1132
messageCount = entry.getMessageNumber();
1133                } else if (entry.getMessageNumber() != messageCount)
1134                    Debug.bug();
1135            }
1136        } else
1137            Log.error("can't parse envelope |" + s + "|");
1138    }
1139
1140    // This method does not make any assumptions about the order of the entries.
1141
private void updateLastUid()
1142    {
1143        uidLast = 0;
1144        if (entries != null) {
1145            for (int i = entries.size()-1; i >= 0; i--) {
1146                ImapMailboxEntry entry = (ImapMailboxEntry) entries.get(i);
1147                if (entry.getUid() > uidLast)
1148                    uidLast = entry.getUid();
1149            }
1150        }
1151    }
1152
1153    public void readOnlyError()
1154    {
1155        Runnable JavaDoc r = new Runnable JavaDoc() {
1156            public void run()
1157            {
1158                MessageDialog.showMessageDialog("Mailbox is read-only",
1159                    "Error");
1160            }
1161        };
1162        SwingUtilities.invokeLater(r);
1163    }
1164
1165    private void fatal(final String JavaDoc text, final String JavaDoc title)
1166    {
1167        Runnable JavaDoc r = new Runnable JavaDoc() {
1168            public void run()
1169            {
1170                MessageDialog.showMessageDialog(Editor.currentEditor(),
1171                    text, title);
1172                if (Editor.getBufferList().contains(ImapMailbox.this))
1173                    kill();
1174            }
1175        };
1176        SwingUtilities.invokeLater(r);
1177    }
1178
1179    private boolean getAllMessageHeaders()
1180    {
1181        Thread JavaDoc t = new Thread JavaDoc() {
1182            public void run()
1183            {
1184                mailboxCache = ImapMailboxCache.readCache(ImapMailbox.this);
1185            }
1186        };
1187        t.start();
1188        if (!session.verifyConnected()){
1189            Log.error("getAllMessageHeaders can't connect to " +
1190                session.getHost() + " on port " + session.getPort());
1191            setBusy(false);
1192            if (!cancelled)
1193                fatal(session.getErrorText(), "Error");
1194            return false;
1195        }
1196        if (!session.verifySelected(folderName)) {
1197            Log.error("getAllMessageHeaders can't SELECT " + folderName +
1198                " on " + session.getHost());
1199            setBusy(false);
1200            if (!cancelled)
1201                fatal(session.getErrorText(), "Error");
1202            return false;
1203        }
1204        if (t != null) {
1205            try {
1206                t.join();
1207            }
1208            catch (InterruptedException JavaDoc e) {
1209                Log.error(e);
1210            }
1211        }
1212        entries = null;
1213        uidLast = 0;
1214        if (mailboxCache != null && mailboxCache.isValid()) {
1215            Log.debug("mailboxCache is valid");
1216            List JavaDoc cachedEntries = mailboxCache.getEntries();
1217            Log.debug("cachedEntries.size() = " + cachedEntries.size());
1218            updateCachedEntries(cachedEntries);
1219            int size = cachedEntries.size();
1220            entries = new ArrayList JavaDoc(size);
1221            // Add entries from cache, skipping any that have been nulled out.
1222
for (int i = 0; i < size; i++) {
1223                Object JavaDoc o = cachedEntries.get(i);
1224                if (o != null)
1225                    entries.add(o);
1226            }
1227            Log.debug("entries.size() = " + entries.size());
1228            // We don't need the cache any more.
1229
mailboxCache = null;
1230            updateLastUid();
1231        }
1232        if (cancelled) {
1233            session.disconnect();
1234            return false;
1235        }
1236        // Get any new entries from the server. Set the recent flag on these
1237
// entries unless we're retrieving the whole mailbox (uidLast == 0).
1238
boolean recent = uidLast > 0;
1239        final List JavaDoc newEntries = retrieveMessageHeaders(uidLast+1, -1, recent);
1240        if (newEntries != null && newEntries.size() > 0) {
1241            addEntriesToAddressBook(newEntries);
1242            processIncomingFilters(newEntries);
1243            if (entries != null)
1244                entries.addAll(newEntries);
1245            else
1246                entries = new ArrayList JavaDoc(newEntries);
1247        }
1248        uidValidity = session.getUidValidity();
1249        if (entries == null)
1250            entries = new ArrayList JavaDoc();
1251        else if (entries instanceof ArrayList JavaDoc)
1252            ((ArrayList JavaDoc)entries).trimToSize();
1253        new ImapMailboxCache(this).writeCache();
1254        updateLastUid();
1255        return true;
1256    }
1257
1258    private boolean getNewMessageHeaders()
1259    {
1260        List JavaDoc newEntries = retrieveMessageHeaders(uidLast+1, -1, true);
1261        if (newEntries == null || newEntries.size() == 0)
1262            return false;
1263        addEntriesToAddressBook(newEntries);
1264        processIncomingFilters(newEntries);
1265        entries.addAll(newEntries);
1266        if (entries instanceof ArrayList JavaDoc)
1267            ((ArrayList JavaDoc)entries).trimToSize();
1268        new ImapMailboxCache(this).writeCache();
1269        updateLastUid();
1270        return true;
1271    }
1272
1273    private void processIncomingFilters(List JavaDoc entryList)
1274    {
1275        Log.debug("processIncomingFilters");
1276        final List JavaDoc filterList = IncomingFilter.getFilterList();
1277        if (filterList == null || filterList.size() == 0)
1278            return;
1279        // For now, we just process incoming filters for the user's inbox.
1280
String JavaDoc inbox = Editor.preferences().getStringProperty(Property.INBOX);
1281        if (inbox == null)
1282            return;
1283        MailboxURL inboxUrl = MailboxURL.parse(inbox);
1284        if (!(inboxUrl instanceof ImapURL)) {
1285            Log.debug("processIncomingFilters not inbox " + url.toString());
1286            return;
1287        }
1288        if (!url.getHost().equals(inboxUrl.getHost())) {
1289            Log.debug("processIncomingFilters not inbox " + url.toString());
1290            return;
1291        }
1292        if (!folderName.equals(((ImapURL)inboxUrl).getFolderName())) {
1293            Log.debug("processIncomingFilters not inbox " + url.toString());
1294            return;
1295        }
1296        SmtpSession smtp = null;
1297        for (int i = 0; i < entryList.size(); i++) {
1298            ImapMailboxEntry entry = (ImapMailboxEntry) entryList.get(i);
1299            // Don't process deleted entries.
1300
if (entry.isDeleted())
1301                continue;
1302            for (int j = 0; j < filterList.size(); j++) {
1303                IncomingFilter incomingFilter = (IncomingFilter) filterList.get(j);
1304                if (incomingFilter != null) {
1305                    MailboxFilter mf = incomingFilter.getFilter();
1306                    if (mf != null && mf.accept(entry)) {
1307                        switch (incomingFilter.getAction()) {
1308                            case IncomingFilter.MOVE: {
1309                                processMove(entry, incomingFilter.getParameter());
1310                                break;
1311                            }
1312                            case IncomingFilter.BOUNCE: {
1313                                boolean succeeded = false;
1314                                if (smtp == null)
1315                                    smtp = SmtpSession.getDefaultSession();
1316                                if (smtp != null)
1317                                    succeeded = processBounce(entry, incomingFilter.getParameter(), smtp);
1318                                if (!succeeded)
1319                                    Log.error("processBounce failed");
1320                                break;
1321                            }
1322                            case IncomingFilter.BOUNCE_AND_DELETE: {
1323                                boolean succeeded = false;
1324                                if (smtp == null)
1325                                    smtp = SmtpSession.getDefaultSession();
1326                                if (smtp != null)
1327                                    succeeded = processBounce(entry, incomingFilter.getParameter(), smtp);
1328                                if (succeeded)
1329                                    processDelete(entry);
1330                                else
1331                                    Log.error("processBounce failed");
1332                                break;
1333                            }
1334                            default:
1335                                break;
1336                        }
1337                        break;
1338                    }
1339                }
1340            }
1341        }
1342        // Processing completed.
1343
if (smtp != null)
1344            smtp.quit();
1345    }
1346
1347    private void processMove(ImapMailboxEntry entry, String JavaDoc destination)
1348    {
1349        if (destination != null) {
1350            Log.debug("destination = |" + destination + "|");
1351            ArrayList JavaDoc list = new ArrayList JavaDoc(1);
1352            list.add(entry);
1353            try {
1354                Log.debug("processMove calling moveToFolder");
1355                moveToFolder(list, destination);
1356                Log.debug("processMove back from moveToFolder");
1357            }
1358            catch (Exception JavaDoc e) {
1359                Log.error(e);
1360            }
1361        }
1362    }
1363
1364    private boolean processBounce(ImapMailboxEntry entry, String JavaDoc bounceTo, SmtpSession smtp)
1365    {
1366        if (bounceTo == null)
1367            return false;
1368        Log.debug("bounceTo = |" + bounceTo + "|");
1369        MailAddress[] to = MailAddress.parseAddresses(bounceTo);
1370        if (to == null)
1371            return false;
1372        if (to.length == 0)
1373            return false;
1374        Message message = getMessage(entry, null);
1375        if (message == null) {
1376            Log.error("processBounce getMessage() failed");
1377            return false;
1378        }
1379        return Mail.bounceMessage(message, to, smtp);
1380    }
1381
1382    private void processDelete(ImapMailboxEntry entry)
1383    {
1384        ArrayList JavaDoc list = new ArrayList JavaDoc(1);
1385        list.add(entry);
1386        try {
1387            delete(list);
1388        }
1389        catch (Exception JavaDoc e) {
1390            Log.error(e);
1391        }
1392    }
1393
1394    private void updateCachedEntries(List JavaDoc cachedEntries)
1395    {
1396        if (cachedEntries == null)
1397            return;
1398        final int size = cachedEntries.size();
1399        if (size == 0)
1400            return;
1401        long start = System.currentTimeMillis();
1402        session.writeTagged("uid fetch 1:* (uid flags)");
1403        HashMap JavaDoc map = new HashMap JavaDoc(size);
1404        Iterator JavaDoc iter = cachedEntries.iterator();
1405        while (iter.hasNext()) {
1406            ImapMailboxEntry entry = (ImapMailboxEntry) iter.next();
1407            map.put(new Integer JavaDoc(entry.getUid()), entry);
1408        }
1409        Log.debug("built map " + (System.currentTimeMillis() - start) + " ms");
1410        iter = null;
1411        final String JavaDoc endPrefix = session.lastTag() + " ";
1412        // Set mailbox field and update flags for all messages on server.
1413
while (true) {
1414            final String JavaDoc s = session.readLine();
1415            if (s == null) {
1416                Log.debug("updateCachedEntries s is null");
1417                break;
1418            }
1419            if (s.startsWith(endPrefix))
1420                break;
1421            int uid = ImapMailboxEntry.parseUid(s);
1422            if (uid == 0) {
1423                Log.debug("uid = 0 s = |" + s + "|");
1424                continue;
1425            }
1426            ImapMailboxEntry entry =
1427                (ImapMailboxEntry) map.get(new Integer JavaDoc(uid));
1428            if (entry != null) {
1429                Debug.assertTrue(entry.getMailbox() == null);
1430                entry.setMailbox(this);
1431                entry.setFlags(ImapMailboxEntry.parseFlags(s));
1432            }
1433        }
1434        map.clear();
1435        // Null out entries whose mailbox field is not set (i.e. entries
1436
// deleted from server).
1437
for (int i = 0; i < size; i++) {
1438            ImapMailboxEntry entry = (ImapMailboxEntry) cachedEntries.get(i);
1439            if (entry.getMailbox() == null)
1440                cachedEntries.set(i, null);
1441        }
1442        long elapsed = System.currentTimeMillis() - start;
1443        Log.debug("updateCachedEntries " + elapsed + " ms " +
1444            (float)elapsed/cachedEntries.size() + " ms per entry");
1445    }
1446
1447    public void readMessage(Line line)
1448    {
1449        readMessage(line, false);
1450    }
1451
1452    public void readMessageOtherWindow(Line line)
1453    {
1454        readMessage(line, true);
1455    }
1456
1457    private void readMessage(Line line, boolean useOtherWindow)
1458    {
1459        Editor editor = Editor.currentEditor();
1460        ImapMailboxEntry entry =
1461            (ImapMailboxEntry) ((MailboxLine)line).getMailboxEntry();
1462        Buffer buf = null;
1463        for (BufferIterator it = new BufferIterator(); it.hasNext();) {
1464            Buffer b = it.nextBuffer();
1465            if (b instanceof ImapMessageBuffer) {
1466                ImapMessageBuffer mb = (ImapMessageBuffer) b;
1467                if (mb.getMailboxEntry() == entry) {
1468                    buf = b;
1469                    break;
1470                }
1471            }
1472        }
1473        if (buf != null) {
1474            // Found existing buffer.
1475
activateMessageBuffer(editor, (ImapMessageBuffer) buf,
1476                useOtherWindow);
1477            return;
1478        }
1479        if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE)) {
1480            String JavaDoc rawText = getMessageTextFromCache(entry.getUid());
1481            if (rawText != null) {
1482                ImapMessageBuffer mb =
1483                    new ImapMessageBuffer(this, entry, rawText);
1484                activateMessageBuffer(editor, mb, useOtherWindow);
1485                if ((entry.getFlags() & MailboxEntry.SEEN) == 0) {
1486                    if (session.isReadOnly())
1487                        markReadLocal(entry);
1488                    else
1489                        markRead(entry);
1490                }
1491                return;
1492            }
1493        }
1494        // Lock the mailbox before creating the message buffer. It will be
1495
// unlocked at the end of the message buffer's loadProcess.run().
1496
if (lock()) {
1497            setBusy(true);
1498            ImapMessageBuffer mb = new ImapMessageBuffer(this, entry);
1499            activateMessageBuffer(editor, mb, useOtherWindow);
1500        } else
1501            editor.status("Mailbox is locked");
1502    }
1503
1504    private void markRead(final ImapMailboxEntry entry)
1505    {
1506        Runnable JavaDoc markReadRunnable = new Runnable JavaDoc() {
1507            public void run()
1508            {
1509                try {
1510                    if (session.verifyConnected() && session.verifySelected(folderName)) {
1511                        session.uidStore(entry.getUid(), "+flags.silent (\\seen)");
1512                        if (session.getResponse() == ImapSession.OK)
1513                            markReadLocal(entry);
1514                    }
1515                }
1516                finally {
1517                    setBusy(false);
1518                    unlock();
1519                    Editor.updateDisplayLater(ImapMailbox.this);
1520                }
1521            }
1522        };
1523        if (lock()) {
1524            setBusy(true);
1525            new Thread JavaDoc(markReadRunnable).start();
1526        }
1527    }
1528
1529    private void markReadLocal(ImapMailboxEntry entry)
1530    {
1531        entry.setFlags(entry.getFlags() | MailboxEntry.SEEN);
1532        updateEntry(entry);
1533        countMessages();
1534    }
1535
1536    public ImapMailboxEntry getMailboxEntry(String JavaDoc messageId)
1537    {
1538        for (int i = entries.size()-1; i >= 0; i--) {
1539            ImapMailboxEntry entry = (ImapMailboxEntry) entries.get(i);
1540            if (messageId.equals(entry.getMessageId()))
1541                return entry;
1542        }
1543        return null;
1544    }
1545
1546    public Message getMessage(MailboxEntry entry,
1547        ProgressNotifier progressNotifier)
1548    {
1549        if (!(entry instanceof ImapMailboxEntry))
1550            return null;
1551        final int uid = ((ImapMailboxEntry)entry).getUid();
1552        if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE)) {
1553            String JavaDoc rawText = getMessageTextFromCache(uid);
1554            if (rawText != null)
1555                return new Message(rawText);
1556        }
1557        if (!isLocked())
1558            Debug.bug("ImapMailbox.getMessage mailbox is not locked!");
1559        if (!session.verifyConnected())
1560            return null;
1561        if (progressNotifier != null && progressNotifier.cancelled())
1562            return null;
1563        if (!session.verifySelected(folderName))
1564            return null;
1565        if (progressNotifier != null && progressNotifier.cancelled())
1566            return null;
1567        String JavaDoc header = fetchPart(uid, "header", null, progressNotifier);
1568        if (header == null)
1569            return null;
1570        Headers headers = Headers.parse(header);
1571        String JavaDoc charset = null;
1572        String JavaDoc contentType = headers.getValue(Headers.CONTENT_TYPE);
1573        if (contentType != null)
1574            charset = Utilities.getCharsetFromContentType(contentType);
1575        Log.debug("charset = " + charset);
1576        String JavaDoc encoding = Utilities.getEncodingFromCharset(charset);
1577        if (encoding.equalsIgnoreCase("us-ascii"))
1578            encoding = null;
1579        else if (encoding.equalsIgnoreCase("iso-8859-1"))
1580            encoding = null;
1581        String JavaDoc body = fetchPart(uid, "text", encoding, progressNotifier);
1582        if (body == null)
1583            return null;
1584        if (getBooleanProperty(Property.IMAP_USE_LOCAL_CACHE))
1585            cacheMessage(uid, header + body, encoding);
1586        return new Message(header.concat(body), headers);
1587    }
1588
1589    private String JavaDoc fetchPart(int uid, String JavaDoc part, String JavaDoc encoding,
1590        ProgressNotifier progressNotifier)
1591    {
1592        FastStringBuffer sb = new FastStringBuffer("uid fetch ");
1593        sb.append(uid);
1594        sb.append(" body[");
1595        sb.append(part);
1596        sb.append(']');
1597        session.writeTagged(sb.toString());
1598        sb = null;
1599        int length = -1;
1600        try {
1601            if (progressNotifier != null && progressNotifier.cancelled()) {
1602                session.disconnect();
1603                return null;
1604            }
1605            String JavaDoc s = session.readLine();
1606            if (s == null)
1607                return null;
1608            if (s.startsWith("* ")) {
1609                int index = s.indexOf('(');
1610                if (index < 0) {
1611                    Log.error("can't parse response s = |" + s + "|");
1612                    return null;
1613                }
1614                String JavaDoc before = s.substring(0, index).trim();
1615                if (!before.endsWith(" FETCH")) {
1616                    Log.error("no FETCH");
1617                    return null;
1618                }
1619                String JavaDoc after = s.substring(index);
1620                int begin = after.indexOf('{');
1621                if (begin < 0) {
1622                    Log.error("no '{'");
1623                    Log.error("s = |" + s + "|");
1624                    begin = after.indexOf('"');
1625                    if (begin < 0)
1626                        return null;
1627                    else
1628                        return parseQuotedString(after.substring(begin));
1629                }
1630                int end = after.indexOf('}', begin + 1);
1631                if (end < 0) {
1632                    Log.error("no '}'");
1633                    return null;
1634                }
1635                try {
1636                    length = Integer.parseInt(after.substring(begin + 1, end));
1637                }
1638                catch (NumberFormatException JavaDoc e) {
1639                    Log.error(e);
1640                }
1641            }
1642            if (length < 0) {
1643                Log.error("can't determine length");
1644                Log.error("s = |" + s + "|");
1645                return null;
1646            }
1647            sb = new FastStringBuffer(length + 64);
1648            while (true) {
1649                if (progressNotifier != null && progressNotifier.cancelled()) {
1650                    session.disconnect();
1651                    return null;
1652                }
1653                s = session.readLine();
1654                if (s == null)
1655                    break;
1656                if (s.startsWith(session.lastTag() + " OK"))
1657                    break;
1658                // Otherwise we need to append the string...
1659
if (encoding != null) {
1660                    // Must do conversion.
1661
int len = s.length();
1662                    byte[] bytes = new byte[len];
1663                    for (int i = 0; i < len; i++)
1664                        bytes[i] = (byte) s.charAt(i);
1665                    try {
1666                        s = new String JavaDoc(bytes, encoding);
1667                    }
1668                    catch (UnsupportedEncodingException JavaDoc e) {
1669                        Log.debug(e);
1670                        // Conversion isn't going to work, so give up on it.
1671
encoding = null;
1672                    }
1673                }
1674                sb.append(s);
1675                sb.append("\r\n");
1676                if (progressNotifier != null)
1677                    progressNotifier.progress("Received ", sb.length(), length);
1678            }
1679        }
1680        catch (Exception JavaDoc e) {
1681            Log.error(e);
1682        }
1683        if (sb != null) {
1684            Log.debug("advertised length = " + length);
1685            Log.debug("actual length = " + sb.length());
1686            sb.setLength(length);
1687            return sb.toString();
1688        } else
1689            return null;
1690    }
1691
1692    private String JavaDoc parseQuotedString(final String JavaDoc s)
1693    {
1694        Debug.assertTrue(s.length() > 0);
1695        Debug.assertTrue(s.charAt(0) == '"');
1696        FastStringBuffer sb = new FastStringBuffer();
1697        final int length = s.length();
1698        for (int i = 1; i < length; i++) {
1699            char c = s.charAt(i);
1700            if (c == '\\' && i+1 < length)
1701                sb.append(s.charAt(++i));
1702            else if (c == '"')
1703                break;
1704            else
1705                sb.append(c);
1706        }
1707        return sb.toString();
1708    }
1709
1710    public void dispose()
1711    {
1712        Log.debug("ImapMailbox.dispose " + folderName + " on " +
1713            session.getHost());
1714        Runnable JavaDoc r = new Runnable JavaDoc() {
1715            public void run()
1716            {
1717                session.logout();
1718            }
1719        };
1720        new Thread JavaDoc(r).start();
1721        MailboxProperties.saveProperties(this);
1722    }
1723
1724    protected void finalize() throws Throwable JavaDoc
1725    {
1726        Log.debug("ImapMailbox.finalize " + folderName + " on " +
1727            session.getHost());
1728        super.finalize();
1729    }
1730
1731    private String JavaDoc getProgressText(int n)
1732    {
1733        FastStringBuffer sb = new FastStringBuffer(32);
1734        sb.append("Retrieved ");
1735        sb.append(n);
1736        sb.append(" message header");
1737        if (n > 1)
1738            sb.append('s');
1739        return sb.toString();
1740    }
1741
1742    // Package scope for testing.
1743
/*package*/ static String JavaDoc getMessageSet(List JavaDoc list)
1744    {
1745        FastStringBuffer sb = new FastStringBuffer();
1746        int limit = list.size();
1747        int begin = -1;
1748        int end = -1;
1749        for (int i = 0; i < limit; i++) {
1750            ImapMailboxEntry entry = (ImapMailboxEntry) list.get(i);
1751            if (begin < 0) {
1752                begin = entry.getUid();
1753                end = entry.getUid();
1754            } else if (entry.getUid() == end + 1) {
1755                end = entry.getUid();
1756            } else {
1757                if (sb.length() > 0)
1758                    sb.append(',');
1759                if (begin != end) {
1760                    Debug.assertTrue(end > begin);
1761                    sb.append(begin);
1762                    sb.append(':');
1763                    sb.append(end);
1764                    begin = end = entry.getUid();
1765                } else {
1766                    sb.append(begin);
1767                    begin = end = entry.getUid();
1768                }
1769            }
1770        }
1771        if (sb.length() > 0)
1772            sb.append(',');
1773        if (begin != end) {
1774            Debug.assertTrue(end > begin);
1775            sb.append(begin);
1776            sb.append(':');
1777            sb.append(end);
1778        } else
1779            sb.append(begin);
1780        return sb.toString();
1781    }
1782
1783    public String JavaDoc toString()
1784    {
1785        int newMessageCount = getNewMessageCount();
1786        if (newMessageCount > 0) {
1787            FastStringBuffer sb = new FastStringBuffer(url.toString());
1788            sb.append(" (");
1789            sb.append(newMessageCount);
1790            sb.append(" new)");
1791            return sb.toString();
1792        } else
1793            return url.toString();
1794    }
1795
1796    public String JavaDoc getTitle()
1797    {
1798        return toString();
1799    }
1800
1801    private ImapMessageCache messageCache;
1802
1803    private void cacheMessage(int uid, String JavaDoc message, String JavaDoc encoding)
1804    {
1805        if (messageCache != null) {
1806            if (messageCache.getUidValidity() != session.getUidValidity())
1807                messageCache = null;
1808        }
1809        if (messageCache == null) {
1810            messageCache = ImapMessageCache.getMessageCache(this);
1811            if (messageCache == null)
1812                return;
1813        }
1814        messageCache.store(uid, message, encoding);
1815    }
1816
1817    private String JavaDoc getMessageTextFromCache(int uid)
1818    {
1819        if (messageCache != null) {
1820            if (messageCache.getUidValidity() != session.getUidValidity())
1821                messageCache = null;
1822        }
1823        if (messageCache == null) {
1824            messageCache = ImapMessageCache.getMessageCache(this);
1825            if (messageCache == null)
1826                return null;
1827        }
1828        return messageCache.getMessageText(uid);
1829    }
1830}
1831
Popular Tags