001: /*
002: * PopMailbox.java
003: *
004: * Copyright (C) 2000-2003 Peter Graves
005: * $Id: PopMailbox.java,v 1.3 2003/06/29 00:19:34 piso Exp $
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.armedbear.j.mail;
023:
024: import java.io.BufferedReader;
025: import java.io.BufferedWriter;
026: import java.io.FileNotFoundException;
027: import java.io.FileReader;
028: import java.io.FileWriter;
029: import java.io.IOException;
030: import java.io.InputStream;
031: import java.io.OutputStream;
032: import java.io.RandomAccessFile;
033: import java.text.SimpleDateFormat;
034: import java.util.ArrayList;
035: import java.util.Calendar;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Locale;
040: import java.util.Properties;
041: import javax.swing.SwingUtilities;
042: import org.armedbear.j.BackgroundProcess;
043: import org.armedbear.j.Debug;
044: import org.armedbear.j.Editor;
045: import org.armedbear.j.Directories;
046: import org.armedbear.j.EditorIterator;
047: import org.armedbear.j.File;
048: import org.armedbear.j.FastStringBuffer;
049: import org.armedbear.j.Log;
050: import org.armedbear.j.PasswordDialog;
051: import org.armedbear.j.Property;
052: import org.armedbear.j.StatusBarProgressNotifier;
053: import org.armedbear.j.Utilities;
054: import org.armedbear.j.View;
055:
056: public final class PopMailbox extends LocalMailbox {
057: private final PopSession session;
058:
059: private File localStore;
060: private StatusBarProgressNotifier progressNotifier;
061: private boolean cancelled;
062: private Thread backgroundThread;
063:
064: public PopMailbox(PopURL url, PopSession session) {
065: super (url);
066: this .session = session;
067: if (url.getUser() == null)
068: url.setUser(session.getUser());
069: setMailboxFile(getLocalStore());
070: setInitialized(true);
071: }
072:
073: public String getFileNameForDisplay() {
074: FastStringBuffer sb = new FastStringBuffer(64);
075: sb.append(url.toString());
076: String limitPattern = getLimitPattern();
077: if (limitPattern != null) {
078: sb.append(' ');
079: sb.append(limitPattern);
080: }
081: return sb.toString();
082: }
083:
084: public final String getName() {
085: return url.toString();
086: }
087:
088: public synchronized int load() {
089: if (isLoaded())
090: return LOAD_COMPLETED;
091: if (lock()) {
092: Debug.assertTrue(backgroundThread == null);
093: backgroundThread = new Thread(loadProcess);
094: backgroundThread.start();
095: setLoaded(true);
096: return LOAD_PENDING;
097: }
098: // Not loaded, lock() failed. Shouldn't happen.
099: Debug.bug("PopMailbox.load can't lock mailbox");
100: return LOAD_FAILED;
101: }
102:
103: private BackgroundProcess loadProcess = new BackgroundProcess() {
104: public void run() {
105: // Mailbox is already locked at this point.
106: boolean abort = false;
107: try {
108: setBusy(true);
109: cancelled = false;
110: progressNotifier = new StatusBarProgressNotifier(
111: PopMailbox.this );
112: progressNotifier.progressStart();
113: setBackgroundProcess(this );
114: readMailboxFile(progressNotifier); // Error handling?
115: if (cancelled) {
116: abort = true;
117: return;
118: }
119: readExpungedUidlsList();
120: clearRecent();
121: if (cancelled || getBooleanProperty(Property.OFFLINE))
122: return;
123: if (retrieveNewMessages()) {
124: addEntriesToAddressBook(entries);
125: setLastCheckMillis(System.currentTimeMillis());
126: } else if (!cancelled) {
127: // Error!
128: error(session.getErrorText(), url.toString());
129: }
130: } finally {
131: if (abort) {
132: Runnable r = new Runnable() {
133: public void run() {
134: kill();
135: for (EditorIterator it = new EditorIterator(); it
136: .hasNext();)
137: it.nextEditor().updateDisplay();
138: }
139: };
140: SwingUtilities.invokeLater(r);
141: } else {
142: refreshBuffer();
143: Runnable r = new Runnable() {
144: public void run() {
145: setBusy(false);
146: for (EditorIterator it = new EditorIterator(); it
147: .hasNext();) {
148: Editor ed = it.nextEditor();
149: View view = new View();
150: view.setDotEntry(getInitialEntry());
151: ed.setView(PopMailbox.this , view);
152: if (ed.getBuffer() == PopMailbox.this ) {
153: ed.bufferActivated(true);
154: ed.updateDisplay();
155: }
156: }
157: }
158: };
159: SwingUtilities.invokeLater(r);
160: }
161: setBackgroundProcess(null);
162: backgroundThread = null;
163: if (progressNotifier != null) {
164: progressNotifier.setText(cancelled ? "Cancelled"
165: : "");
166: progressNotifier.progressStop();
167: progressNotifier = null;
168: }
169: unlock();
170: }
171: }
172:
173: public void cancel() {
174: Log.debug("loadProcess.cancel");
175: cancelled = true;
176: progressNotifier.cancel();
177: progressNotifier.setText("Cancelled, cleaning up...");
178: if (backgroundThread != null && backgroundThread.isAlive())
179: backgroundThread.interrupt();
180: session.disconnect();
181: }
182: };
183:
184: public void getNewMessages() {
185: if (session.getPassword() == null) {
186: String password = PasswordDialog.showPasswordDialog(Editor
187: .currentEditor(), "Password:", "Password");
188: if (password == null || password.length() == 0)
189: return;
190: session.setPassword(password);
191: }
192: if (lock())
193: getNewMessages(true);
194: else
195: Editor.currentEditor().status("Mailbox is locked");
196: }
197:
198: public void getNewMessages(boolean userInitiated) {
199: Debug.assertTrue(isLocked());
200: // This method can get called in the background so we can't put up a
201: // dialog.
202: if (session.getPassword() == null)
203: return;
204: Log.debug("PopMailbox.getNewMessages " + userInitiated);
205: setBusy(true);
206: Log.debug("PopMailbox.getNewMessages back from setBusy(true)");
207: if (userInitiated)
208: saveDisplayState();
209: Debug.assertTrue(backgroundThread == null);
210: backgroundThread = new Thread(new GetNewMessagesProcess(
211: userInitiated));
212: backgroundThread.start();
213: }
214:
215: private class GetNewMessagesProcess implements BackgroundProcess {
216: private boolean userInitiated;
217:
218: // If this constructor is private, we run into jikes 1.15 bug #2256.
219: /*private*/GetNewMessagesProcess(boolean userInitiated) {
220: this .userInitiated = userInitiated;
221: }
222:
223: public void run() {
224: try {
225: boolean changed = false;
226: if (userInitiated)
227: changed = clearRecent();
228: cancelled = false;
229: progressNotifier = new StatusBarProgressNotifier(
230: PopMailbox.this );
231: progressNotifier.progressStart();
232: setBackgroundProcess(this );
233: int oldSize = entries.size();
234: boolean ok = retrieveNewMessages();
235: if (changed || entries.size() != oldSize) {
236: refreshBuffer();
237: addEntriesToAddressBook(entries);
238: updateDisplay();
239: }
240: newMessagesStatus();
241: if (!ok) {
242: if (userInitiated && !cancelled)
243: error(session.getErrorText(), url.toString());
244: }
245: } finally {
246: setBusy(false);
247: if (progressNotifier != null) {
248: progressNotifier.setText(cancelled ? "Cancelled"
249: : "");
250: progressNotifier.progressStop();
251: progressNotifier = null;
252: }
253: setBackgroundProcess(null);
254: backgroundThread = null;
255: setLastCheckMillis(System.currentTimeMillis());
256: unlock();
257: Editor.updateDisplayLater(PopMailbox.this );
258: }
259: }
260:
261: public void cancel() {
262: Log.debug("GetNewMessagesProcess.cancel");
263: cancelled = true;
264: progressNotifier.cancel();
265: progressNotifier.setText("Cancelled, cleaning up...");
266: if (backgroundThread != null && backgroundThread.isAlive()) {
267: Log.debug("interrupting background thread...");
268: backgroundThread.interrupt();
269: }
270: session.disconnect();
271: }
272: }
273:
274: private boolean retrieveNewMessages() {
275: Log.debug("PopMailbox.retrieveNewMessages");
276: if (!connect())
277: return false;
278: File outputFile = null;
279: long start = System.currentTimeMillis();
280: try {
281: if (Thread.currentThread().isInterrupted() || cancelled)
282: return true;
283: int count = stat();
284: if (count < 0)
285: return false; // Error.
286: if (count == 0)
287: return true; // No messages on the server.
288: if (Thread.currentThread().isInterrupted() || cancelled)
289: return true;
290: List serverMessageList = getServerMessageList(count);
291: if (serverMessageList == null)
292: return false; // Error.
293: if (Thread.currentThread().isInterrupted() || cancelled)
294: return true;
295: // Removed uidls from the expunged uidls list if the corresponding
296: // message no longer exists on the server.
297: pruneExpungedUidlsList(serverMessageList);
298: if (Thread.currentThread().isInterrupted() || cancelled)
299: return true;
300: // Remove messages from the server message list if we already have
301: // them or if they've been expunged locally.
302: List messagesToBeRetrieved = getMessagesToBeRetrieved(serverMessageList);
303: if (Thread.currentThread().isInterrupted() || cancelled)
304: return true;
305: // Is there anything left?
306: if (messagesToBeRetrieved.size() == 0) {
307: // If we're not keeping messages on the server, delete the old
308: // messages.
309: if (!getBooleanProperty(Property.POP_KEEP_MESSAGES_ON_SERVER))
310: deleteMessagesOnServer(serverMessageList);
311: Log.debug("no new messages");
312: return true;
313: }
314: if (getLocalStore() == null)
315: return false; // Error.
316: if (localStore.isFile() && localStore.length() > 0) {
317: outputFile = Utilities.getTempFile(localStore
318: .getParentFile());
319: Log.debug("calling copyFile");
320: long copyStart = System.currentTimeMillis();
321: if (!Utilities.copyFile(localStore, outputFile))
322: return false;
323: Log.debug("back from copyFile "
324: + (System.currentTimeMillis() - copyStart)
325: + " ms");
326: } else
327: outputFile = localStore;
328: MailboxFileWriter writer = MailboxFileWriter.getInstance(
329: outputFile, true); // Append.
330: if (writer == null)
331: return false; // Error.
332: boolean ok = retrieveMessages(messagesToBeRetrieved, writer);
333: try {
334: writer.flush();
335: writer.close();
336: } catch (IOException e) {
337: Log.error(e);
338: return false;
339: }
340: if (!ok) {
341: Log.debug("not ok...");
342: // retrieveMessages() was interrupted. Truncate output file to
343: // proper length.
344: if (entries.size() == 0)
345: return false;
346: LocalMailboxEntry entry = (LocalMailboxEntry) entries
347: .get(entries.size() - 1);
348: long offset = entry.getNextMessageStart(); // Truncate to here.
349: try {
350: RandomAccessFile raf = outputFile
351: .getRandomAccessFile("rw");
352: Log.debug("before raf.length() = " + raf.length());
353: Log.debug("truncating to " + offset);
354: raf.setLength(offset);
355: Log.debug("after raf.length() = " + raf.length());
356: raf.close();
357: } catch (IOException e) {
358: Log.error(e);
359: return false;
360: }
361: }
362: if (outputFile != localStore)
363: if (!Utilities.deleteRename(outputFile, localStore))
364: return false;
365: if (getBooleanProperty(Property.POP_KEEP_MESSAGES_ON_SERVER) == false)
366: deleteMessagesOnServer(serverMessageList);
367: outputFile = null;
368: } finally {
369: Log.debug("retrieveNewMessages calling logout() ...");
370: session.logout();
371: if (outputFile != null && outputFile.isFile())
372: outputFile.delete();
373: }
374: Log.debug("retrieveNewMessages "
375: + (System.currentTimeMillis() - start) + " ms");
376: return true; // Success!
377: }
378:
379: private boolean connect() {
380: if (progressNotifier != null)
381: progressNotifier.setText("Connecting to "
382: + session.getHost());
383: if (session.connect()) {
384: if (progressNotifier != null)
385: progressNotifier.setText("Connected to "
386: + session.getHost());
387: return true;
388: }
389: return false;
390: }
391:
392: // Returns number of messages on server or -1 if there is an error.
393: private int stat() {
394: int count = -1;
395: session.write("stat");
396: String response = session.readLine();
397: if (response.startsWith("+OK")) {
398: try {
399: count = Utilities
400: .parseInt(response.substring(3).trim());
401: } catch (NumberFormatException e) {
402: Log.error(e);
403: }
404: } else {
405: Log.error("stat failed");
406: Log.error(response);
407: }
408: return count;
409: }
410:
411: private List getServerMessageList(int count) {
412: long start = System.currentTimeMillis();
413: session.write("uidl");
414: String response = session.readLine();
415: if (!response.startsWith("+OK")) {
416: Log.error("getServerMessageList uidl failed");
417: Log.error(response);
418: return null;
419: }
420: List list = new ArrayList(count);
421: while (true) {
422: String s = session.readLine();
423: if (s == null)
424: return null; // Shouldn't happen.
425: if (s.equals("."))
426: break;
427: int index = s.indexOf(' ');
428: if (index >= 0) {
429: int messageNumber = Integer.parseInt(s.substring(0,
430: index)); // BUG! Error handling!
431: String uidl = s.substring(index + 1);
432: if (messageNumber >= 1)
433: list.add(new MessageListEntry(messageNumber, uidl));
434: }
435: }
436: Log.debug("getServerMessageList "
437: + (System.currentTimeMillis() - start) + " ms");
438: Log.debug("getServerMessageList count = " + count
439: + " list size = " + list.size());
440: return list;
441: }
442:
443: private List getMessagesToBeRetrieved(List serverMessageList) {
444: long start = System.currentTimeMillis();
445: HashSet hashSet = null;
446: if (entries != null) {
447: int size = entries.size();
448: hashSet = new HashSet(size);
449: for (int i = 0; i < size; i++) {
450: LocalMailboxEntry mailboxEntry = (LocalMailboxEntry) entries
451: .get(i);
452: hashSet.add(mailboxEntry.getUidl());
453: }
454: }
455: List toBeReturned = new ArrayList();
456: int size = serverMessageList.size();
457: for (int i = 0; i < size; i++) {
458: MessageListEntry messageListEntry = (MessageListEntry) serverMessageList
459: .get(i);
460: String uidl = messageListEntry.uidl;
461: if (isExpunged(uidl))
462: continue;
463: if (hashSet != null && hashSet.contains(uidl))
464: continue;
465: toBeReturned.add(messageListEntry);
466: }
467: Log.debug("getMessagesToBeRetrieved "
468: + (System.currentTimeMillis() - start) + " ms");
469: return toBeReturned;
470: }
471:
472: private boolean retrieveMessages(List messageList,
473: MailboxFileWriter writer) {
474: Log.debug("entering retrieveMessages");
475: long start = System.currentTimeMillis();
476: for (int i = 0; i < messageList.size(); i++) {
477: String text = "Retrieving message " + (i + 1) + " of "
478: + messageList.size();
479: if (i == 0)
480: progressNotifier.setText(text); // Make sure the user sees this.
481: else
482: progressNotifier.progress(text);
483: MessageListEntry entry = (MessageListEntry) messageList
484: .get(i);
485: if (!retrieveMessage(entry.messageNumber, entry.uidl,
486: writer)) {
487: Log.error("retrieveMessages error retrieving message "
488: + entry.messageNumber);
489: return false;
490: }
491: }
492: progressNotifier.setText(cancelled ? "Cancelled, cleaning up"
493: : "");
494: Log.debug("leaving retrieveMessages "
495: + (System.currentTimeMillis() - start) + " ms");
496: return true;
497: }
498:
499: // Returns true if no error.
500: private boolean retrieveMessage(int i, String uidl,
501: MailboxFileWriter writer) {
502: String command = "list " + i;
503: session.write(command);
504: int size = 0;
505: String response = session.readLine();
506: String expected = "+OK " + i + " ";
507: if (response != null && response.startsWith(expected)) {
508: try {
509: size = Integer.parseInt(response.substring(expected
510: .length()));
511: } catch (NumberFormatException e) {
512: Log.error(e);
513: return false; // Error!
514: }
515: } else {
516: Log.error("retrieveMessage command failed: " + command);
517: Log.error("response = " + response);
518: }
519: command = "retr " + i;
520: session.write(command);
521: response = session.readLine();
522: if (response == null || !response.startsWith("+OK")) {
523: Log.error("retrieveMessage command failed: " + command);
524: Log.error(response);
525: return false; // Error!
526: }
527: try {
528: final long messageStart = writer.getOffset();
529: writer.write("From - ");
530: writer.write(getDateTimeStamp());
531: writer.write('\n');
532: // Headers.
533: FastStringBuffer sb = new FastStringBuffer(2048);
534: while (true) {
535: String s = session.readLine();
536: if (s == null)
537: return false; // Error! (Reached end of stream before reaching end of headers.)
538: if (s.length() == 0) {
539: // Reached end of headers.
540: // Add X-J-Status.
541: String status = "X-J-Status: 0\n";
542: writer.write(status);
543: sb.append(status);
544: // Add X-UIDL.
545: if (uidl != null) {
546: writer.write("X-UIDL: ");
547: writer.write(uidl);
548: writer.write('\n');
549: sb.append("X-UIDL: ");
550: sb.append(uidl);
551: sb.append('\n');
552: }
553: writer.write('\n');
554: break;
555: }
556: if (s.toUpperCase().startsWith("X-UIDL"))
557: continue;
558: writer.write(s);
559: writer.write('\n');
560: sb.append(s);
561: sb.append('\n');
562: }
563: // Body.
564: boolean echo = session.getEcho();
565: session.setEcho(false);
566: while (true) {
567: String s = session.readLine();
568: if (s == null)
569: return false; // Error! (Reached end of stream before reaching end of message.)
570: if (s.equals("."))
571: break; // End of message.
572: if (s.length() > 1 && s.charAt(0) == '.'
573: && s.charAt(1) == '.') {
574: // Remove dot-stuffing.
575: s = s.substring(1);
576: } else if (s.startsWith("From ")) {
577: // Mangle lines starting with "From " in body of message.
578: writer.write('>');
579: }
580: writer.write(s);
581: writer.write('\n');
582: }
583: session.setEcho(echo);
584: // Add a newline after the end of the message.
585: writer.write('\n');
586: LocalMailboxEntry entry = new LocalMailboxEntry(entries
587: .size() + 1, messageStart, sb.toString());
588: entry.setNextMessageStart(writer.getOffset());
589: entry.setSize(size);
590: entry.setFlags(MailboxEntry.RECENT);
591: entries.add(entry);
592: setDirty(true);
593: return true; // No error.
594: } catch (IOException e) {
595: Log.error(e);
596: return false; // Error!
597: }
598: }
599:
600: private boolean deleteMessagesOnServer(List serverMessageList) {
601: Log.debug("deleteMessagesOnServer need to delete "
602: + serverMessageList.size() + " messages");
603: for (int i = 0; i < serverMessageList.size(); i++) {
604: MessageListEntry messageListEntry = (MessageListEntry) serverMessageList
605: .get(i);
606: session.write("dele " + messageListEntry.messageNumber);
607: String response = session.readLine();
608: if (response == null || !response.startsWith("+OK")) {
609: Log
610: .error("deleteMessagesOnServer dele failed response = "
611: + response);
612: session.write("rset");
613: session.readLine();
614: return false; // Error!
615: }
616: }
617: Log.debug("deleteMessagesOnServer success!");
618: return true;
619: }
620:
621: public void expunge() {
622: if (lock()) {
623: setBusy(true);
624: saveDisplayState();
625: Debug.assertTrue(backgroundThread == null);
626: backgroundThread = new Thread(expungeProcess);
627: backgroundThread.start();
628: }
629: }
630:
631: private Runnable expungeProcess = new BackgroundProcess() {
632: public void run() {
633: try {
634: setBackgroundProcess(this );
635: progressNotifier = new StatusBarProgressNotifier(
636: PopMailbox.this );
637: progressNotifier.progressStart();
638: cancelled = false;
639: expungeInternal();
640: } finally {
641: setBackgroundProcess(null);
642: backgroundThread = null;
643: setBusy(false);
644: if (progressNotifier != null) {
645: if (cancelled)
646: progressNotifier.setText("Cancelled");
647: progressNotifier.progressStop();
648: progressNotifier = null;
649: }
650: unlock();
651: updateDisplay();
652: }
653: }
654:
655: public void cancel() {
656: Log.debug("expungeProcess.cancel");
657: cancelled = true;
658: if (backgroundThread != null && backgroundThread.isAlive())
659: backgroundThread.interrupt();
660: session.disconnect();
661: }
662: };
663:
664: private void expungeInternal() {
665: if (entries == null)
666: return; // No error.
667: if (getBooleanProperty(Property.POP_EXPUNGE_DELETED_MESSAGES_ON_SERVER) == false
668: || getBooleanProperty(Property.POP_KEEP_MESSAGES_ON_SERVER) == false) {
669: // This is the "local expunge only" case.
670: Log.debug("expungeInternal \"local expunge only\" case");
671: // First add all deleted entries to expunged list.
672: for (int i = entries.size() - 1; i >= 0; i--) {
673: MailboxEntry entry = (MailboxEntry) entries.get(i);
674: if (entry.isDeleted())
675: addToExpungedUidlsList(entry.getUidl());
676: }
677: writeExpungedUidlsList(); // BUG! Error handling?
678: rewriteMailbox(true); // BUG! Error handling!
679: refreshBuffer();
680: return;
681: }
682: // Reaching here, we want to expunge the deleted messages on the server too.
683: Log.debug("expungeInternal \"expunge through\" case");
684: if (!connect()) {
685: Log.error("expungeInternal can't connect");
686: return;
687: }
688: try {
689: int count = stat();
690: if (count < 0)
691: return; // Error.
692: if (count > 0) {
693: List serverMessageList = getServerMessageList(count);
694: for (int i = 0; i < entries.size(); i++) {
695: MailboxEntry entry = (MailboxEntry) entries.get(i);
696: if (entry.isDeleted()) {
697: String uidl = entry.getUidl();
698: if (!expungeUidl(uidl, serverMessageList))
699: return; // Error!
700: if (cancelled) {
701: progressNotifier.setText("Cancelled");
702: return;
703: }
704: }
705: }
706: if (expungedUidlsList != null) {
707: // Expunge on the server messages that were previously expunged locally.
708: Iterator it = expungedUidlsList.iterator();
709: while (it.hasNext()) {
710: String uidl = (String) it.next();
711: if (!expungeUidl(uidl, serverMessageList))
712: return; // Error!
713: if (cancelled) {
714: progressNotifier.setText("Cancelled");
715: return;
716: }
717: }
718: }
719: }
720: progressNotifier.progress("Logging out...");
721: if (!session.logout())
722: return; // Error!
723: // All deletions have been completed on the server.
724: progressNotifier.progress("Saving mailbox...");
725: rewriteMailbox(true); // BUG! Error handling!
726: progressNotifier.setText("Saving mailbox...done");
727: refreshBuffer();
728: } finally {
729: session.disconnect();
730: }
731: }
732:
733: private boolean expungeUidl(String uidl, List serverMessageList) {
734: if (uidl != null) {
735: for (int j = serverMessageList.size() - 1; j >= 0; j--) {
736: MessageListEntry messageListEntry = (MessageListEntry) serverMessageList
737: .get(j);
738: if (uidl.equals(messageListEntry.uidl)) {
739: if (progressNotifier != null) {
740: FastStringBuffer sb = new FastStringBuffer(
741: "Deleting message ");
742: sb.append(messageListEntry.messageNumber);
743: sb.append(" on server");
744: progressNotifier.progress(sb.toString());
745: }
746: session.write("dele "
747: + messageListEntry.messageNumber);
748: String response = session.readLine();
749: if (response != null && response.startsWith("+OK"))
750: return true;
751: // Error!
752: Log.error("expungeUidl dele failed response = "
753: + response);
754: session.write("rset");
755: session.readLine();
756: return false;
757: }
758: }
759: }
760: // Didn't find uidl.
761: return true;
762: }
763:
764: private HashSet expungedUidlsList;
765:
766: private final boolean isExpunged(String uidl) {
767: if (expungedUidlsList == null)
768: return false;
769: return expungedUidlsList.contains(uidl);
770: }
771:
772: private final void addToExpungedUidlsList(String uidl) {
773: if (expungedUidlsList == null)
774: expungedUidlsList = new HashSet();
775: expungedUidlsList.add(uidl);
776: }
777:
778: // Prune our list of expunged uidls, removing entries that no longer exist
779: // on the server.
780: private void pruneExpungedUidlsList(List serverMessageList) {
781: Log.debug("pruneExpungedUidlsList");
782: if (expungedUidlsList == null)
783: return; // Nothing to do.
784: boolean changed = false;
785: long start = System.currentTimeMillis();
786: int size = serverMessageList.size();
787: HashSet serverUidls = new HashSet(size);
788: for (int i = 0; i < size; i++) {
789: MessageListEntry entry = (MessageListEntry) serverMessageList
790: .get(i);
791: serverUidls.add(entry.uidl);
792: }
793: Iterator it = expungedUidlsList.iterator();
794: while (it.hasNext()) {
795: String uidl = (String) it.next();
796: if (!serverUidls.contains(uidl)) {
797: Log.warn("removing uidl " + uidl
798: + " (no longer exists on server)");
799: it.remove();
800: changed = true;
801: }
802: }
803: if (changed)
804: writeExpungedUidlsList();
805: Log.debug("pruneExpungedUidlsList "
806: + (System.currentTimeMillis() - start) + " ms");
807: }
808:
809: private void readExpungedUidlsList() {
810: File mailboxFile = getMailboxFile();
811: if (mailboxFile == null) {
812: Debug.bug("readExpungedUidlsList mailboxFile is null");
813: return;
814: }
815: String filename = mailboxFile.canonicalPath() + ".expunged";
816: try {
817: BufferedReader reader = new BufferedReader(new FileReader(
818: filename));
819: String s;
820: while ((s = reader.readLine()) != null) {
821: if (expungedUidlsList == null)
822: expungedUidlsList = new HashSet();
823: expungedUidlsList.add(s);
824: }
825: reader.close();
826: } catch (FileNotFoundException e) {
827: // Might happen.
828: } catch (IOException e) {
829: Log.error(e);
830: }
831: }
832:
833: private void writeExpungedUidlsList() {
834: File mailboxFile = getMailboxFile();
835: if (mailboxFile == null) {
836: Debug.bug("writeExpungedUidls mailboxFile is null");
837: return;
838: }
839: String filename = mailboxFile.canonicalPath() + ".expunged";
840: if (expungedUidlsList == null || expungedUidlsList.size() == 0) {
841: File file = File.getInstance(filename);
842: if (file.isFile())
843: file.delete();
844: return;
845: }
846: try {
847: BufferedWriter writer = new BufferedWriter(new FileWriter(
848: filename));
849: Iterator it = expungedUidlsList.iterator();
850: while (it.hasNext()) {
851: writer.write((String) it.next());
852: writer.newLine();
853: }
854: writer.flush();
855: writer.close();
856: } catch (IOException e) {
857: Log.error(e);
858: }
859: }
860:
861: private static final SimpleDateFormat df = new SimpleDateFormat(
862: "EEE MMM dd HH:mm:ss yyyy", Locale.US);
863:
864: private final String getDateTimeStamp() {
865: return df.format(Calendar.getInstance().getTime());
866: }
867:
868: private File getLocalStore() {
869: if (localStore != null)
870: return localStore;
871: File popDir = File.getInstance(Directories.getMailDirectory(),
872: "pop");
873: if (!popDir.isDirectory()) {
874: popDir.mkdirs();
875: if (!popDir.isDirectory()) {
876: Log.error("can't make directory "
877: + popDir.canonicalPath());
878: return null;
879: }
880: }
881: File catalogFile = File.getInstance(popDir, "catalog");
882: Properties catalog = new Properties();
883: // Load the catalog.
884: try {
885: if (catalogFile.isFile()) {
886: InputStream in = catalogFile.getInputStream();
887: catalog.load(in);
888: in.close();
889: }
890: } catch (IOException e) {
891: Log.error(e);
892: }
893: String mailboxName = getUrl().toString();
894: if (mailboxName.startsWith("pop://"))
895: mailboxName = mailboxName.substring(6);
896: String filename = catalog.getProperty(mailboxName);
897: if (filename != null) // Found existing store.
898: return localStore = File.getInstance(popDir, filename);
899: File file = Utilities.getTempFile(popDir);
900: catalog.put(mailboxName, file.getName());
901: try {
902: OutputStream out = catalogFile.getOutputStream();
903: catalog.save(out, null);
904: out.flush();
905: out.close();
906: return localStore = file;
907: } catch (IOException e) {
908: Log.error(e);
909: return null;
910: }
911: }
912:
913: public void dispose() {
914: Log.debug("PopMailbox.dispose");
915: Runnable disposeRunnable = new Runnable() {
916: public void run() {
917: try {
918: Log
919: .debug("disposeRunnable.run() calling acquire()...");
920: acquire(); // Blocks, may throw InterruptedException.
921: Log
922: .debug("disposeRunnable.run() back from acquire()");
923: if (dirty) {
924: final Object pending = new Object();
925: Editor.getPendingOperations().add(pending);
926: Log
927: .debug("disposeRunnable.run() calling rewriteMailbox()...");
928: rewriteMailbox(false);
929: Log
930: .debug("disposeRunnable.run() back from rewriteMailbox()");
931: Editor.getPendingOperations().remove(pending);
932: }
933: Debug.assertTrue(session != null);
934: Log
935: .debug("disposeRunnable.run() calling session.logout()...");
936: session.logout();
937: release();
938: Log
939: .debug("disposeRunnable.run() back from release()");
940: } catch (InterruptedException e) {
941: Log.error(e);
942: }
943: }
944: };
945: new Thread(disposeRunnable).start();
946: MailboxProperties.saveProperties(this );
947: }
948:
949: protected void finalize() throws Throwable {
950: Log.debug("PopMailbox.finalize");
951: super .finalize();
952: }
953:
954: public String toString() {
955: int newMessageCount = getNewMessageCount();
956: if (newMessageCount > 0) {
957: FastStringBuffer sb = new FastStringBuffer(url.toString());
958: sb.append(" (");
959: sb.append(newMessageCount);
960: sb.append(" new)");
961: return sb.toString();
962: } else
963: return url.toString();
964: }
965:
966: public String getTitle() {
967: return toString();
968: }
969: }
970:
971: class MessageListEntry {
972: int messageNumber;
973: String uidl;
974:
975: MessageListEntry(int messageNumber, String uidl) {
976: this.messageNumber = messageNumber;
977: this.uidl = uidl;
978: }
979: }
|