001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016:
017: package org.columba.mail.folder;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.Collections;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.logging.Logger;
028:
029: import org.columba.core.base.ListTools;
030: import org.columba.core.filter.FilterList;
031: import org.columba.core.io.DiskIO;
032: import org.columba.core.xml.XmlElement;
033: import org.columba.mail.config.FolderItem;
034: import org.columba.mail.folder.headercache.BerkeleyDBHeaderList;
035: import org.columba.mail.folder.headercache.CachedHeaderfields;
036: import org.columba.mail.folder.headercache.SyncHeaderList;
037: import org.columba.mail.folder.search.DefaultSearchEngine;
038: import org.columba.mail.message.ColumbaHeader;
039: import org.columba.mail.message.ColumbaMessage;
040: import org.columba.mail.message.IColumbaHeader;
041: import org.columba.mail.message.IColumbaMessage;
042: import org.columba.mail.message.IHeaderList;
043: import org.columba.mail.message.IPersistantHeaderList;
044: import org.columba.ristretto.io.Source;
045: import org.columba.ristretto.io.SourceInputStream;
046: import org.columba.ristretto.message.Attributes;
047: import org.columba.ristretto.message.Flags;
048: import org.columba.ristretto.message.Header;
049: import org.columba.ristretto.message.LocalMimePart;
050: import org.columba.ristretto.message.MimeTree;
051: import org.columba.ristretto.parser.HeaderParser;
052: import org.columba.ristretto.parser.MessageParser;
053: import org.columba.ristretto.parser.ParserException;
054:
055: /**
056: * AbstractLocalFolder is a near-to working folder, which only needs a specific
057: * {@link IDataStorage},{@link DefaultSearchEngine}and
058: * {@link IHeaderListStorage}"plugged in" to make it work.
059: * <p>
060: * This class is abstract becaused, instead use {@link MHCachedFolder}a
061: * complete implementation.
062: * <p>
063: * AbstractLocalFolder uses an internal {@link ColumbaMessage}object as cache.
064: * This allows parsing of a message only once, while accessing the data of the
065: * message multiple times.
066: * <p>
067: * Attribute <code>nextMessageUid</code> handles the next unique message ID.
068: * When adding a new message to this folder, it gets this ID assigned for later
069: * reference. Then nextMessageUid is simply increased.
070: * <p>
071: *
072: * @see org.columba.mail.folder.mh.MHCachedFolder
073: *
074: * @author fdietz
075: */
076: public abstract class AbstractLocalFolder extends AbstractMessageFolder {
077:
078: /** JDK 1.4+ logging framework logger, used for logging. */
079: private static final Logger LOG = Logger
080: .getLogger("org.columba.mail.folder");
081:
082: /**
083: * the next messag which gets added to this folder receives this unique ID
084: */
085: protected int nextMessageUid = -1;
086:
087: /**
088: * we keep one message in cache in order to not needing to parse it twice
089: * times
090: */
091: protected ColumbaMessage aktMessage;
092:
093: private boolean firstOpen = true;
094:
095: /**
096: * implement your own mailbox format here
097: */
098:
099: protected IDataStorage dataStorage;
100:
101: protected IPersistantHeaderList headerList;
102:
103: /**
104: * @param item
105: * <class>FolderItem </class> contains xml configuration of this
106: * folder
107: */
108: public AbstractLocalFolder(FolderItem item, String path) {
109: super (item, path);
110:
111: // TODO (@author fdietz): move this to AbstractMessageFolder constructor
112: // create filterlist datastructure
113: XmlElement filterListElement = node
114: .getElement(FilterList.XML_NAME);
115:
116: if (filterListElement == null) {
117: // no filterlist treenode found
118: // -> create a new one
119: filterListElement = new XmlElement(FilterList.XML_NAME);
120: getConfiguration().getRoot().addElement(filterListElement);
121: }
122:
123: filterList = new FilterList(filterListElement);
124:
125: setSearchEngine(new DefaultSearchEngine(this ));
126: }
127:
128: /**
129: * @param name
130: * the name of the folder.
131: * @param type
132: * type of folder.
133: */
134: public AbstractLocalFolder(String name, String type, String path) {
135: super (name, type, path);
136:
137: // create filterlist datastructure
138: XmlElement filterListElement = node
139: .getElement(FilterList.XML_NAME);
140:
141: if (filterListElement == null) {
142: // no filterlist treenode found
143: // -> create a new one
144: filterListElement = new XmlElement(FilterList.XML_NAME);
145: getConfiguration().getRoot().addElement(filterListElement);
146: }
147:
148: filterList = new FilterList(filterListElement);
149:
150: setSearchEngine(new DefaultSearchEngine(this ));
151: }
152:
153: /**
154: * Remove folder from tree
155: *
156: * @see org.columba.mail.folder.FolderTreeNode#removeFolder()
157: */
158: public void removeFolder() throws Exception {
159: // delete folder from your harddrive
160: boolean b = DiskIO.deleteDirectory(directoryFile);
161:
162: // if this worked, remove it from tree.xml configuration, too
163: if (b) {
164: super .removeFolder();
165: }
166: }
167:
168: /**
169: *
170: * Generate new unique message ID
171: *
172: * @return <class>Integer </class> containing UID
173: */
174: protected Object generateNextMessageUid() {
175: if (nextMessageUid == -1) {
176: try {
177: if (getHeaderList().count() > 0) {
178: List _headerList = new ArrayList();
179: Object[] _uidList = headerList.getUids();
180: for (int i = 0; i < _uidList.length; i++) {
181: _headerList.add(_uidList[i]);
182: }
183: Integer maxUid = (Integer) Collections
184: .max(_headerList);
185: nextMessageUid = maxUid.intValue() + 1;
186: } else {
187: nextMessageUid = 0;
188: }
189: } catch (Exception e) {
190: nextMessageUid = 0;
191: }
192: }
193:
194: return new Integer(nextMessageUid++);
195: }
196:
197: /**
198: *
199: * Set next unique message ID
200: *
201: * @param next
202: * number of next message
203: */
204: public void setNextMessageUid(int next) {
205: nextMessageUid = next;
206: }
207:
208: /**
209: *
210: * Implement a <class>IDataStorage </class> for the mailbox format of your
211: * pleasure.
212: *
213: * @return instance of <class>IDataStorage </class>
214: */
215: public abstract IDataStorage getDataStorageInstance();
216:
217: /**
218: * @see org.columba.mail.folder.IMailbox#getMimePartTree(java.lang.Object)
219: */
220: public MimeTree getMimePartTree(Object uid) throws Exception {
221: // get message with UID
222: IColumbaMessage message = getMessage(uid);
223:
224: // get tree-like structure of mimeparts
225: MimeTree mptree = message.getMimePartTree();
226:
227: return mptree;
228: }
229:
230: /** {@inheritDoc} */
231: public InputStream getMessageSourceStream(Object uid)
232: throws Exception {
233: return getDataStorageInstance().getMessageStream(uid);
234: }
235:
236: /** {@inheritDoc} */
237: public InputStream getMimePartBodyStream(Object uid,
238: Integer[] address) throws Exception {
239: // get message with UID
240: IColumbaMessage message = getMessage(uid);
241:
242: // Get the mimepart
243: LocalMimePart mimepart = (LocalMimePart) message
244: .getMimePartTree().getFromAddress(address);
245:
246: return mimepart.getInputStream();
247: }
248:
249: /** {@inheritDoc} */
250: public InputStream getMimePartSourceStream(Object uid,
251: Integer[] address) throws Exception {
252: // get message with UID
253: IColumbaMessage message = getMessage(uid);
254:
255: // Get the mimepart
256: LocalMimePart mimepart = (LocalMimePart) message
257: .getMimePartTree().getFromAddress(address);
258:
259: return new SourceInputStream(mimepart.getSource());
260: }
261:
262: /**
263: * Copies a set of messages from this folder to a destination folder.
264: * <p>
265: * First we copy the message source to the destination folder. Then we also
266: * copy the flags attribute of this message.
267: *
268: * @see org.columba.mail.folder.IMailbox#innerCopy(org.columba.mail.folder.IMailbox,
269: * java.lang.Object[])
270: */
271: public void innerCopy(IMailbox destFolder, Object[] uids)
272: throws Exception {
273: if (getObservable() != null) {
274: getObservable().setMax(uids.length);
275: }
276:
277: for (int i = 0; i < uids.length; i++) {
278: // skip this message, if it doesn't exist in source folder
279: if (!exists(uids[i])) {
280: continue;
281: }
282:
283: InputStream messageSourceStream = getMessageSourceStream(uids[i]);
284: destFolder.addMessage(messageSourceStream,
285: getAttributes(uids[i]), getFlags(uids[i]));
286: messageSourceStream.close();
287:
288: /*
289: * ((AbstractLocalFolder) destFolder).setFlags(destuid, (Flags)
290: * getFlags( uids[i]).clone());
291: */
292: // destFolder.fireMessageAdded(uids[i]);
293: if (getObservable() != null) {
294: getObservable().setCurrent(i);
295: }
296: }
297:
298: // we are done - clear the progress bar
299: if (getObservable() != null) {
300: getObservable().resetCurrent();
301: }
302: }
303:
304: public Object addMessage(InputStream in) throws Exception {
305: return addMessage(in, null, null);
306: }
307:
308: public Object addMessage(InputStream in, Attributes attributes,
309: Flags flags) throws Exception {
310: // generate UID for new message
311: Object newUid = generateNextMessageUid();
312:
313: // save message stream to file
314: getDataStorageInstance().saveMessage(newUid, in);
315:
316: // close stream
317: in.close();
318:
319: // parse header
320: Source source = getDataStorageInstance().getMessageSource(
321: newUid);
322: int messageSize = source.length();
323:
324: Header header = HeaderParser.parse(source);
325: ColumbaHeader h;
326: if ((attributes != null) && (flags != null)) {
327: // save header and attributes. Copy the flags!
328: h = new ColumbaHeader(header, (Attributes) attributes
329: .clone(), new Flags(flags.getFlags()));
330: } else {
331: h = new ColumbaHeader(header);
332: h.set("columba.size", new Integer(messageSize / 1024));
333: }
334: source.close();
335: h.set("columba.uid", newUid);
336: getHeaderList().add(h, newUid);
337:
338: fireMessageAdded(newUid, getFlags(newUid));
339: return newUid;
340: }
341:
342: /** {@inheritDoc} */
343: public boolean isInboxFolder() {
344: return getId().equals("101");
345: }
346:
347: /** {@inheritDoc} */
348: public boolean isTrashFolder() {
349: return getId().equals("105");
350: }
351:
352: /** {@inheritDoc} */
353: public boolean supportsAddFolder(String newFolderType) {
354: return (FolderFactory.getInstance().getGroup(newFolderType)
355: .equals("local") || newFolderType
356: .equals("VirtualFolder"));
357: }
358:
359: /**
360: * Returns true since local folders can be moved.
361: *
362: * @return true.
363: */
364: public boolean supportsMove() {
365: return true;
366: }
367:
368: /**
369: * @param uid
370: * @return
371: * @throws Exception
372: */
373: protected ColumbaMessage getMessage(Object uid) throws Exception {
374: // Check if the message is already cached
375: if (aktMessage != null) {
376: if (aktMessage.getUID().equals(uid)) {
377: // this message is already cached
378: return aktMessage;
379: }
380: }
381:
382: ColumbaMessage message;
383:
384: try {
385:
386: Source source = null;
387:
388: source = getDataStorageInstance().getMessageSource(uid);
389:
390: // Parse Message from DataStorage
391: try {
392: message = new ColumbaMessage(MessageParser
393: .parse(source));
394: } catch (ParserException e1) {
395: LOG.fine(e1.getSource().toString());
396: throw e1;
397: }
398: source.close();
399:
400: message.setUID(uid);
401:
402: aktMessage = message;
403:
404: // TODO: fix parser exception
405: } catch (FolderInconsistentException e) {
406: super .removeMessage(uid);
407:
408: throw e;
409: }
410:
411: // We use the attributes and flags from the cache
412: // but the parsed header from the parsed message
413: IColumbaHeader header = getHeaderList().get(uid);
414: header.setHeader(message.getHeader().getHeader());
415: message.setHeader(header);
416:
417: return message;
418: }
419:
420: /**
421: * @see org.columba.mail.folder.IMailbox#getMessageHeader(java.lang.Object)
422: * @TODO dont use deprecated method
423: */
424: protected IColumbaHeader getMessageHeader(Object uid)
425: throws Exception {
426: if ((aktMessage != null) && (aktMessage.getUID().equals(uid))) {
427: // message is already cached
428: // try to compare the headerfield count of
429: // the actually parsed message with the cached
430: // headerfield count
431: IColumbaMessage message = getMessage(uid);
432:
433: // number of headerfields
434: int size = message.getHeader().count();
435:
436: // get header from cache
437: IColumbaHeader h = getHeaderList().get(uid);
438:
439: // message doesn't exist (this shouldn't happen here)
440: if (h == null) {
441: return null;
442: }
443:
444: // number of headerfields
445: int cachedSize = h.count();
446:
447: // if header contains more fields than the cached header
448: if (size > cachedSize) {
449: return (ColumbaHeader) message.getHeader();
450: }
451:
452: return (ColumbaHeader) h;
453: } else {
454: // message isn't cached
455: // -> just return header from cache
456: return getHeaderList().get(uid);
457: }
458: }
459:
460: /**
461: * @see org.columba.mail.folder.IMailbox#remove(java.lang.Object)
462: */
463: public void removeMessage(Object uid) throws Exception {
464: // remove message from disk
465: getDataStorageInstance().removeMessage(uid);
466:
467: // fireMessageRemoved(uid, getFlags(uid));
468: super .removeMessage(uid);
469:
470: }
471:
472: /**
473: * Changes the selected message flags and updates the {@link MailFolderInfo}
474: * accordingly.
475: * <p>
476: * This method is only used for innerCopy().
477: *
478: * @param uid
479: * selected message UID
480: * @param flags
481: * new flags
482: * @throws Exception
483: */
484: protected void setFlags(Object uid, Flags flags) throws Exception {
485: IColumbaHeader h = getHeaderList().get(uid);
486:
487: Flags oldFlags = h.getFlags();
488: h.setFlags(flags);
489:
490: // update MessageFolderInfo
491: if (oldFlags.get(Flags.RECENT) && !flags.get(Flags.RECENT)) {
492: getMessageFolderInfo().decRecent();
493: }
494:
495: if (!oldFlags.get(Flags.RECENT) && flags.get(Flags.RECENT)) {
496: getMessageFolderInfo().incRecent();
497: }
498:
499: if (oldFlags.get(Flags.SEEN) && !flags.get(Flags.SEEN)) {
500: getMessageFolderInfo().incUnseen();
501: }
502:
503: if (!oldFlags.get(Flags.SEEN) && flags.get(Flags.SEEN)) {
504: getMessageFolderInfo().decUnseen();
505: }
506: }
507:
508: /**
509: * This method first tries to find the requested header in the header cache.
510: * If the headerfield is not cached, the message source is parsed.
511: *
512: * @see org.columba.mail.folder.IMailbox#getHeaderFields(java.lang.Object,
513: * java.lang.String[])
514: *
515: */
516: public Header getHeaderFields(Object uid, String[] keys)
517: throws Exception {
518: // cached headerfield list
519: List cachedList = Arrays.asList(CachedHeaderfields
520: .getDefaultHeaderfields());
521:
522: LinkedList keyList = new LinkedList(Arrays.asList(keys));
523:
524: ListTools.substract(keyList, cachedList);
525:
526: if (keyList.size() == 0) {
527: return getHeaderList().get(uid).getHeader();
528: } else {
529: // We need to parse
530: // get message with UID
531: IColumbaMessage message = getMessage(uid);
532:
533: Header header = message.getHeader().getHeader();
534:
535: Header subHeader = new Header();
536: String value;
537:
538: for (int i = 0; i < keys.length; i++) {
539: value = header.get(keys[i]);
540:
541: if (value != null) {
542: subHeader.set(keys[i], value);
543: }
544: }
545:
546: return subHeader;
547: }
548: }
549:
550: /**
551: * @see org.columba.mail.folder.IMailbox#expungeFolder()
552: */
553: public void expungeFolder() throws Exception {
554: // make sure to close all file handles
555: // to the currently cached message
556: // -> necessary for windows to be able to delete the local file
557: if (aktMessage != null) {
558: aktMessage.close();
559: aktMessage = null;
560: }
561:
562: super .expungeFolder();
563: }
564:
565: /**
566: * @see org.columba.mail.folder.IMailbox#getAllHeaderFields(java.lang.Object)
567: */
568: public Header getAllHeaderFields(Object uid) throws Exception {
569:
570: IColumbaMessage message = getMessage(uid);
571:
572: Header header = message.getHeader().getHeader();
573:
574: return header;
575: }
576:
577: public synchronized IHeaderList getHeaderList() throws Exception {
578: if (headerList == null) {
579: // header cache is stored in "headerlist" subfolder
580: File headercacheDirectory = new File(getDirectoryFile(),
581: "headerlist");
582: headerList = new BerkeleyDBHeaderList(headercacheDirectory,
583: getId());
584: final AbstractMessageFolder folder = this ;
585: headerList
586: .addHeaderListCorruptedListener(new IHeaderListCorruptedListener() {
587:
588: public void headerListCorrupted(
589: IHeaderList headerList) {
590: try {
591: SyncHeaderList.sync(folder, headerList);
592: } catch (IOException e) {
593: LOG.severe(e.getMessage());
594: }
595: }
596: });
597: }
598:
599: if (firstOpen) {
600:
601: if (headerList.count() != getDataStorageInstance()
602: .getMessageCount()) {
603: // Must be out of sync!
604: SyncHeaderList.sync(this , headerList);
605: }
606:
607: firstOpen = false;
608: }
609:
610: return headerList;
611: }
612:
613: /**
614: * @see org.columba.mail.folder.AbstractMessageFolder#save()
615: */
616: public void save() throws Exception {
617: super .save();
618: if (headerList != null)
619: headerList.persist();
620: }
621:
622: /*
623: * (non-Javadoc)
624: *
625: * @see org.columba.mail.folder.AbstractMessageFolder#loadMessageFolderInfo()
626: */
627: protected void loadMessageFolderInfo() {
628: super .loadMessageFolderInfo();
629:
630: int storedCount = getDataStorageInstance().getMessageCount();
631: // Check if still consistent
632: if (messageFolderInfo.getExists() != storedCount) {
633: recreateMessageFolderInfo();
634: }
635:
636: }
637: }
|