001: // The contents of this file are subject to the Mozilla Public License Version
002: // 1.1
003: //(the "License"); you may not use this file except in compliance with the
004: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
005: //
006: //Software distributed under the License is distributed on an "AS IS" basis,
007: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
008: //for the specific language governing rights and
009: //limitations under the License.
010: //
011: //The Original Code is "The Columba Project"
012: //
013: //The Initial Developers of the Original Code are Frederik Dietz and Timo
014: // Stich.
015: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
016: //
017: //All Rights Reserved.
018: package org.columba.mail.pop3;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.util.Date;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.logging.Logger;
028:
029: import org.columba.api.command.IStatusObservable;
030: import org.columba.api.command.IWorkerStatusController;
031: import org.columba.core.base.ListTools;
032: import org.columba.core.base.Lock;
033: import org.columba.core.command.CommandCancelledException;
034: import org.columba.core.command.ProgressObservedInputStream;
035: import org.columba.mail.config.AccountItem;
036: import org.columba.mail.config.MailConfig;
037: import org.columba.mail.config.PopItem;
038: import org.columba.mail.config.SpecialFoldersItem;
039: import org.columba.mail.folder.IMailbox;
040: import org.columba.mail.folder.headercache.BerkeleyDBHeaderList;
041: import org.columba.mail.gui.tree.FolderTreeModel;
042: import org.columba.mail.message.ColumbaHeader;
043: import org.columba.mail.message.ColumbaMessage;
044: import org.columba.mail.message.ICloseableIterator;
045: import org.columba.mail.message.IColumbaHeader;
046: import org.columba.mail.message.IHeaderList;
047: import org.columba.mail.message.IPersistantHeaderList;
048: import org.columba.ristretto.io.Source;
049: import org.columba.ristretto.io.TempSourceFactory;
050: import org.columba.ristretto.message.Header;
051: import org.columba.ristretto.parser.HeaderParser;
052: import org.columba.ristretto.parser.ParserException;
053: import org.columba.ristretto.pop3.MessageNotOnServerException;
054: import org.columba.ristretto.pop3.POP3Exception;
055:
056: /**
057: * Highest Abstraction Layer of the POP3 Protocol. Its responsabilities are the
058: * synchornization of already downloaded mails, deletion of mails after x days
059: * and parsing of the headers to maintain a headerCache of the downloaded mails.
060: *
061: * @author tstich
062: */
063: public class POP3Server {
064: /** JDK 1.4+ logging framework logger, used for logging. */
065: private static final Logger LOG = Logger
066: .getLogger("org.columba.mail.folder.headercache");
067:
068: private static final long DAY_IN_MS = 24 * 60 * 60 * 1000;
069:
070: private AccountItem accountItem;
071:
072: private File file;
073:
074: private POP3Store store;
075:
076: protected IPersistantHeaderList headerList;
077:
078: private Lock lock;
079:
080: /**
081: * Dirty flag. If set to true, we have to save the headercache changes. If
082: * set to false, nothing changed. Therefore no need to save the headercache.
083: */
084: private boolean cacheChanged;
085:
086: public POP3Server(AccountItem accountItem) {
087: this .accountItem = accountItem;
088:
089: int uid = accountItem.getUid();
090:
091: file = new File(MailConfig.getInstance().getPOP3Directory(),
092: (new Integer(uid)).toString());
093:
094: PopItem item = accountItem.getPopItem();
095:
096: store = new POP3Store(item);
097:
098: if (!this .getConfigFile().isDirectory())
099: this .getConfigFile().delete();
100: File headercacheDirectory = new File(file, "pop3" + uid);
101: headerList = new BerkeleyDBHeaderList(headercacheDirectory,
102: "pop3_" + uid, new POP3HeaderBinding());
103: ((BerkeleyDBHeaderList) headerList).setKeyType(String.class);
104:
105: lock = new Lock();
106:
107: setCacheChanged(false);
108: }
109:
110: public void save() throws Exception {
111: headerList.persist();
112: }
113:
114: public File getConfigFile() {
115: return file;
116: }
117:
118: public AccountItem getAccountItem() {
119: return accountItem;
120: }
121:
122: public IMailbox getFolder() {
123: SpecialFoldersItem foldersItem = accountItem
124: .getSpecialFoldersItem();
125: String inboxStr = foldersItem.get("inbox");
126:
127: IMailbox f = (IMailbox) FolderTreeModel.getInstance()
128: .getFolder(inboxStr);
129:
130: return f;
131: }
132:
133: public void logout() throws Exception {
134: getStore().logout();
135: }
136:
137: public List synchronize() throws Exception {
138: if (getHeaderList().count() == 0) {
139: LOG.severe(accountItem.getName()
140: + " - POP3 Headerlist is empty!");
141: }
142:
143: // Get the uids from the headercache
144:
145: LinkedList headerUids = new LinkedList(getHeaderList().keySet());
146:
147: // Get the list of the uids on the server
148: // Important: Use a clone of the List since
149: // we must not change it!
150: List newUids = store.getUIDList();
151:
152: // substract the uids that we already downloaded ->
153: // newUids contains all uids to fetch from the server
154: ListTools.substract(newUids, headerUids);
155:
156: // substract the uids on the server from the downloaded uids ->
157: // headerUids are the uids that have been removed from the server
158: ListTools.substract(headerUids, store.getUIDList());
159:
160: Iterator it = headerUids.iterator();
161:
162: // update the cache
163: while (it.hasNext()) {
164: getHeaderList().remove(it.next());
165: cacheChanged = true;
166: }
167:
168: // return the uids that are new
169: return newUids;
170: }
171:
172: public void deleteMessage(Object uid) throws IOException,
173: POP3Exception, CommandCancelledException {
174: try {
175: store.deleteMessage(uid);
176:
177: getHeaderList().remove(uid);
178:
179: // set dirty flag
180: setCacheChanged(true);
181: } catch (POP3Exception e) {
182: if ((e instanceof MessageNotOnServerException)
183: || (e.getResponse() != null && e.getResponse()
184: .isERR())) {
185: // Message already deleted from server
186: getHeaderList().remove(uid);
187: setCacheChanged(true);
188: } else
189: throw e;
190: }
191:
192: }
193:
194: public void deleteMessagesOlderThan(Date date) throws IOException,
195: POP3Exception, CommandCancelledException {
196: LOG.info("Removing message older than " + date);
197: IHeaderList headerList = getHeaderList();
198: ICloseableIterator it = headerList.headerIterator();
199: while (it.hasNext()) {
200: IColumbaHeader header = (IColumbaHeader) it.next();
201: if (((Date) header.get("columba.date")).before(date)) {
202: deleteMessage(header.get("columba.uid"));
203: }
204: }
205: it.close();
206: }
207:
208: public void cleanUpServer() throws IOException, POP3Exception,
209: CommandCancelledException {
210: PopItem item = getAccountItem().getPopItem();
211:
212: if (item.getBooleanWithDefault("leave_messages_on_server",
213: false)
214: && item.getBooleanWithDefault("remove_old_from_server",
215: false)) {
216: int days = item.getInteger("older_than");
217: long date = new Date().getTime();
218: date -= days * DAY_IN_MS;
219:
220: deleteMessagesOlderThan(new Date(date));
221: } else if (!item.getBooleanWithDefault(
222: "leave_messages_on_server", false)) {
223: removeAllDownloadedMessages();
224: }
225: }
226:
227: private void removeAllDownloadedMessages() throws IOException,
228: CommandCancelledException, POP3Exception {
229: IHeaderList headerList = getHeaderList();
230: ICloseableIterator it = headerList.keyIterator();
231: while (it.hasNext()) {
232: deleteMessage(it.next());
233: }
234: it.close();
235: }
236:
237: private IHeaderList getHeaderList() {
238: return headerList;
239: }
240:
241: public int getMessageCount() throws Exception {
242: return getStore().getMessageCount();
243: }
244:
245: public ColumbaMessage getMessage(Object uid,
246: IWorkerStatusController worker) throws IOException,
247: POP3Exception, CommandCancelledException, ParserException {
248: InputStream messageStream = new ProgressObservedInputStream(
249: getStore().fetchMessage(store.getIndex(uid)), worker,
250: true);
251:
252: // Store the complete stream in a source so that we can parse it
253: Source source = TempSourceFactory.createTempSource(
254: messageStream, -1);
255:
256: // pipe through preprocessing filter
257: // if (popItem.getBoolean("enable_pop3preprocessingfilter", false))
258: // rawString = modifyMessage(rawString);
259: // UPDATE! @author fdietz
260: // was:
261: // Activate PreProcessor again with Source instead of String
262: // new goal:
263: // completely remove preprocessor -> we never change the message source!
264: ColumbaMessage m;
265: try {
266: Header header = HeaderParser.parse(source);
267:
268: m = new ColumbaMessage(header);
269: ColumbaHeader h = (ColumbaHeader) m.getHeader();
270:
271: m.setSource(source);
272: h.getAttributes().put("columba.pop3uid", uid);
273: // message size should be at least 1 KB
274: int size = Math.max(source.length() / 1024, 1);
275: h.getAttributes().put("columba.size", new Integer(size));
276:
277: // set the attachment flag
278: h.getAttributes().put("columba.attachment",
279: h.hasAttachments());
280: h.getAttributes().put("columba.fetchstate", Boolean.TRUE);
281: h.getAttributes().put("columba.accountuid",
282: new Integer(accountItem.getInteger("uid")));
283:
284: getHeaderList().add(h, uid);
285: } catch (ParserException e) {
286: LOG
287: .severe("Skipped message: Error parsing message. Message source:\n "
288: + source);
289: return null;
290: }
291:
292: // set headercache dirty flag
293: setCacheChanged(true);
294:
295: return m;
296: }
297:
298: public int getMessageSize(Object uid) throws Exception {
299: return store.getSize(store.getIndex(uid));
300: }
301:
302: public String getFolderName() {
303: return accountItem.getName();
304: }
305:
306: /**
307: * Returns the store.
308: *
309: * @return POP3Store
310: */
311: public POP3Store getStore() {
312: return store;
313: }
314:
315: public IStatusObservable getObservable() {
316: return store.getObservable();
317: }
318:
319: public boolean tryToGetLock(Object locker) {
320: return lock.tryToGetLock(locker);
321: }
322:
323: public void releaseLock(Object locker) {
324: lock.release(locker);
325: }
326:
327: /**
328: * @return Returns the hasChanged.
329: */
330: public boolean isCacheChanged() {
331: return cacheChanged;
332: }
333:
334: /**
335: * @param hasChanged
336: * The hasChanged to set.
337: */
338: private void setCacheChanged(boolean hasChanged) {
339: this .cacheChanged = hasChanged;
340: }
341:
342: /**
343: * Call this method if the underlying configuration changed.
344: */
345: public void updateConfig() {
346: PopItem item = accountItem.getPopItem();
347:
348: store = new POP3Store(item);
349: }
350:
351: /**
352: * @throws IOException
353: *
354: */
355: public void dropConnection() throws IOException {
356: store.dropConnection();
357: }
358: }
|