1 /*
2 * Copyright (c) 2005-2007 Creative Sphere Limited.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *
10 * Creative Sphere - initial API and implementation
11 *
12 */
13 package org.abstracthorizon.mercury.maildir;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.lang.ref.Reference;
18 import java.lang.ref.WeakReference;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.WeakHashMap;
26 import java.util.regex.Pattern;
27
28 import javax.mail.Flags;
29 import javax.mail.Folder;
30 import javax.mail.Message;
31 import javax.mail.MessagingException;
32 import javax.mail.internet.MimeMessage;
33
34 import org.abstracthorizon.mercury.maildir.util.MessageBase;
35 import org.abstracthorizon.mercury.maildir.util.MessageWrapper;
36
37
38 /**
39 * <p>Folder data actually represents the model of the maildir folder. This class
40 * handles all model operations - all operations that are low level and deal
41 * with files and directories.
42 * </p>
43 *
44 * <p>This folder data serves as central repository for messages over
45 * a directory. Each directory will have only one folder data for store.
46 * That is maintained in store itself</p>
47 *
48 * <p>Messages that are present in folder data are later wrapped inside of
49 * folder. Pair of (message, file) is kept in folder data's cache.
50 * Cache for each directory is maintained as long as there are opened folders
51 * over that directory. After that time it is left to be collected throught
52 * weak reference. If folder over folder data is opened before reference
53 * is garbage collected it is reused.
54 * </p>
55 *
56 * @author Daniel Sendula
57 */
58 public class MaildirFolderData extends Folder {
59
60 /**
61 * This constant defines a filename of a zero length
62 * flag file that denotes no subfolders are suppose
63 * to be created for this folder */
64 public static final String NO_SUBFOLDERS_FILENAME = ".nosubfolders";
65
66 /** Permanent flags for root are user defined "\\Noselect" */
67 public static final Flags rootPermanentFlags = new Flags("\\Noselect");
68
69 /** Permanent flags cache */
70 protected Flags permanentFlags;
71
72 /** Maildir store */
73 protected MaildirStore store;
74
75 /** Directory folder data is for */
76 protected File base;
77
78 /** Flag to denote is this root folder or not */
79 protected boolean rootFolder;
80
81 /** Type of the folder. See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}. */
82 protected int type = -1;
83
84 /** Tmp subdirectory */
85 protected File tmp;
86
87 /** Cur subdirectory */
88 protected File cur;
89
90 /** New subdirectory */
91 protected File nw;
92
93 /** Cached folder's full name */
94 protected String cachedFullName;
95
96 /** Cached folder's name */
97 protected String cachedName;
98
99 /** Last time folder data was accessed or 0 if no open folders */
100 protected long lastAccess;
101
102 /** Amount of time between two accesses. TODO - make this as an attribute */
103 protected int delay = 1000; // 1 sec
104
105 /** Delay factor - amount of time needed for reading directory vs delay. TODO - make this as an attribute */
106 protected int delayFactor = 3;
107
108 /** List of open folders */
109 protected WeakHashMap<Folder, Object> openedFolders = new WeakHashMap<Folder, Object>();
110
111 /** Folder's data */
112 protected Data data;
113
114 /** Weak reference to data when there are no open folders */
115 protected Reference<Data> closedRef;
116
117 /** Count of open folders. When count reaches zero, storage may remove this folder data */
118 protected int openCount = 0;
119
120 /**
121 * Constructor
122 * @param store store
123 * @param file directory
124 */
125 public MaildirFolderData(MaildirStore store, File file) {
126 super(store);
127 this.store = store;
128 setFolderFile(file);
129 rootFolder = base.equals(store.getBaseFile());
130 tmp = new File(base, "tmp");
131 cur = new File(base, "cur");
132 nw = new File(base, "new");
133 }
134
135 /**
136 * Returns maildir store
137 * @return maildir store
138 */
139 public MaildirStore getMaildirStore() {
140 return store;
141 }
142
143 /**
144 * Returns folder's directory
145 * @return folder's directory
146 */
147 public File getFolderFile() {
148 return base;
149 }
150
151 /**
152 * Sets folder's directory
153 * @param file folder's directory
154 */
155 protected void setFolderFile(File file) {
156 this.base = file;
157 }
158
159 /**
160 * Returns when this folder data is last accessd
161 * @return when this folder data is last accessd
162 */
163 public long getLastAccessed() {
164 return lastAccess;
165 }
166
167 /**
168 * Returns <code>true</code> if it is root folder
169 * @return <code>true</code> if it is root folder
170 */
171 protected boolean isRootFolder() {
172 return rootFolder;
173 }
174
175 /**
176 * Returns folder's new directory
177 * @return folder's new directory
178 */
179 protected File getNewDir() {
180 return nw;
181 }
182
183 /**
184 * Returns folder's cur directory
185 * @return folder's cur directory
186 */
187 protected File getCurDir() {
188 return cur;
189 }
190
191 /**
192 * Returns folder's tmp directory
193 * @return folder's tmp directory
194 */
195 protected File getTmpDir() {
196 return tmp;
197 }
198
199 /**
200 * Returns folder's name
201 * @return folder's name
202 */
203 public String getName() {
204 if (cachedName != null) {
205 return cachedName;
206 }
207 if (rootFolder) {
208 cachedName = "";
209 } else {
210 String name = getFolderFile().getName();
211 int i = name.lastIndexOf('.');
212 if (i > 0) {
213 name = name.substring(i+1);
214 }
215 if (store.isLeadingDot() && name.startsWith(".")) {
216 name = name.substring(1);
217 }
218 cachedName = name;
219 }
220 return cachedName;
221 }
222
223 /**
224 * Returns folder's full name (path and name)
225 * @return folder's full name
226 */
227 public String getFullName() {
228 if (cachedFullName != null) {
229 return cachedFullName;
230 }
231 if (rootFolder) {
232 cachedFullName = "";
233 } else {
234 String name = getFolderFile().getName();
235 if (store.isLeadingDot() && name.startsWith(".")) {
236 name = name.substring(1);
237 }
238 cachedFullName = name.replace('.', '/');
239 }
240 return cachedFullName;
241 }
242
243 /**
244 * Returns folder parent's name
245 * @return folder parent's name
246 */
247 public String getParentFolderName() {
248 if (rootFolder) {
249 return null;
250 } else {
251 String name = getFullName();
252
253 int i = name.lastIndexOf('/');
254 if (i > 0) {
255 name = name.substring(0, i);
256 } else {
257 name = "";
258 }
259
260 return name;
261 }
262 }
263
264 /**
265 * Returns <code>true</code> if folder exists. This method checks if folder's directory exist.
266 * @return <code>true</code> if folder exists
267 * @throws MessagingException
268 */
269 public boolean exists() throws MessagingException {
270 return getFolderFile().exists();
271 }
272
273 /**
274 * Lists subfolder names.
275 * @param pattern pattern
276 * @return list of subfolder names
277 * @throws MessagingException
278 */
279 public String[] listNames(String pattern) throws MessagingException {
280 if ((pattern == null) || (pattern.length() == 0)) {
281 pattern = ".*";
282 } else {
283 pattern = pattern.replaceAll("[*]", ".*?");
284 pattern = pattern.replaceAll("[%]", "[^/]*?");
285 }
286
287 String n = getFullName();
288
289 if (n.length() > 0) {
290 pattern = n+'/'+pattern;
291 }
292 Pattern p = Pattern.compile(pattern);
293
294 boolean leadingDot = store.isLeadingDot();
295 ArrayList<String> res = new ArrayList<String>();
296
297 File[] files = store.getBaseFile().listFiles();
298 if (files != null) {
299 for (int i = 0; i < files.length; i++) {
300 if (files[i].isDirectory()) {
301 String name = files[i].getName();
302 if (!"tmp".equals(name) && !"new".equals(name) && !"cur".equals(name) &&
303 ((leadingDot && name.startsWith(".")) || (!leadingDot && !name.startsWith(".")))) {
304
305 if (leadingDot) {
306 name = name.substring(1);
307 }
308 name = name.replace('.', '/');
309 if (p.matcher(name).matches()) {
310 res.add(name);
311 }
312 }
313 }
314 }
315 }
316
317 String[] array = new String[res.size()];
318 return res.toArray(array);
319 }
320
321 /**
322 * Returns "/"
323 * @return "/"
324 */
325 public char getSeparator() {
326 return '/';
327 }
328
329 /**
330 * Returns name for given subfolder.
331 * @param name name of subfolder
332 * @return subfolder's full name
333 */
334 public String getSubFolderName(String name) {
335 if (rootFolder) {
336 return name;
337 } else {
338 return getFullName()+getSeparator()+name;
339 }
340 }
341
342 /**
343 * Creates folder.
344 * @param type See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}.
345 * @return <code>true</code> if subfolder is successfully created.
346 * @throws MessagingException in case of a problem while creating folder.
347 */
348 public boolean create(int type) throws MessagingException {
349 if (rootFolder) {
350 return false;
351 }
352 MaildirFolderData parent = store.getFolderData(getParentFolderName());
353 if (!parent.isRootFolder() && !parent.exists()) {
354 if (!parent.create(Folder.HOLDS_FOLDERS | Folder.HOLDS_MESSAGES) || !parent.create(Folder.HOLDS_FOLDERS)) {
355 return false;
356 }
357 }
358
359 if (!getFolderFile().mkdir()) {
360 return false;
361 }
362 checkDirs();
363 if ((type & Folder.HOLDS_FOLDERS) == 0) {
364 File f = new File(getFolderFile(), NO_SUBFOLDERS_FILENAME);
365 try {
366 f.createNewFile();
367 } catch (IOException e) {
368 throw new MessagingException("Cannot prevent folder of having subfolders; cannot create file=" + f, e);
369 }
370 }
371 this.type = type;
372 return true;
373 }
374
375 /**
376 * Deletes folder.
377 * @param recursive if <code>true</code> deletes all subfolders as well.
378 * @return <code>true</code> if operation was successful.
379 * @throws MessagingException in case of an error while deleting folder
380 */
381 public boolean delete(boolean recursive) throws MessagingException {
382
383 if (rootFolder) {
384 return false;
385 }
386 if (recursive) {
387 String[] subfolders = listNames("%");
388 boolean res = true;
389 for (int i = 0; i < subfolders.length; i++) {
390 MaildirFolderData subFolderData = store.getFolderData(subfolders[i]);
391 res = subFolderData.delete(true) && res;
392 }
393 if (!res) {
394 return false;
395 }
396 }
397
398 return deleteAll(getFolderFile());
399 }
400
401 /**
402 * Utility method that deletes all subdirectories and files from given directory.
403 * @param file directory to be deleted
404 * @return <code>true<code> if operation was successful.
405 */
406 protected boolean deleteAll(File file) {
407 boolean res = true;
408
409 if (file.isDirectory()) {
410 File[] files = file.listFiles();
411 for (int i = 0; i < files.length; i++) {
412 if (files[i].isDirectory()) {
413 res = deleteAll(files[i]) && res;
414 } else {
415 res = files[i].delete() && res;
416 }
417 }
418 }
419 if (res) {
420 res = file.delete() && res;
421 }
422 return res;
423 }
424
425 /**
426 * Returns folder's type. It checks for flag file to determine are subfolders are allowed.
427 * In this implementation messages are always allowed.
428 * @return folder's type. See {@link javax.mail.Folder#HOLDS_FOLDERS} and {@link javax.mail.Folder#HOLDS_MESSAGES}.
429 * @throws MessagingException never.
430 */
431 public int getType() throws MessagingException {
432 if (type == -1) {
433 if (rootFolder) {
434 type = Folder.HOLDS_FOLDERS;
435 } else {
436 File f = new File(getFolderFile(), NO_SUBFOLDERS_FILENAME);
437 if (f.exists()) {
438 type = Folder.HOLDS_MESSAGES;
439 } else {
440 type = Folder.HOLDS_FOLDERS | Folder.HOLDS_MESSAGES;
441 }
442 }
443 }
444 return type;
445 }
446
447 /**
448 * Renames folder to given folder data
449 * @param folderData folder data
450 * @return <code>true</code> if folder was successfully renamed
451 * @throws MessagingException thrown in case when there is a problem renaming folder
452 */
453 public MaildirFolderData renameTo(MaildirFolderData folderData) throws MessagingException {
454 if (folderData.store != store) {
455 throw new IllegalStateException("Folder belongs to wrong store; "+getFullName());
456 }
457 if (folderData.exists()) {
458 return null;
459 }
460 // setFolderFile(folderData.getFolderFile());
461 // rootFolder = folderData.rootFolder;
462
463 return folderData;
464 }
465
466 /**
467 * This method checks if new, cur and tmp directories exist. If not it tries to create them.
468 * @throws MessagingException in case subdirectories cannot be created.
469 */
470 protected void checkDirs() throws MessagingException {
471 if (!getTmpDir().exists()) {
472 if (!getTmpDir().mkdirs()) {
473 throw new MessagingException("Cannot open folder; cannot create tmp directory; "+getFullName());
474 }
475 }
476 if (!getCurDir().exists()) {
477 if (!getCurDir().mkdirs()) {
478 throw new MessagingException("Cannot open folder; cannot create cur directory; "+getFullName());
479 }
480 }
481 if (!getNewDir().exists()) {
482 if (!getNewDir().mkdirs()) {
483 throw new MessagingException("Cannot open folder; cannot create new directory; "+getFullName());
484 }
485 }
486 }
487
488 // ---------------------------------------------------------------------------------------
489
490 /**
491 * Returns message count. This implementation checks files on the disk - new and cur directories.
492 * @return message count
493 * @throws MessagingException never
494 */
495 public int getMessageCount() throws MessagingException {
496 synchronized (this) {
497 if (data != null) {
498 return data.messages.size();
499 }
500 }
501
502 int len = 0;
503 if (getNewDir().exists()) {
504 String[] fs = getNewDir().list();
505 if (fs != null) {
506 len = len + fs.length;
507 }
508 }
509 if (getCurDir().exists()) {
510 String[] fs = getCurDir().list();
511 if (fs != null) {
512 len = len + fs.length;
513 }
514 }
515
516 return len;
517 }
518
519 /**
520 * Returns new message count. This implementation checks files on the disk - only in new directory.
521 * @return new message count
522 * @throws MessagingException never
523 */
524 public int getNewMessageCount() throws MessagingException {
525 synchronized (this) {
526 if (data != null) {
527 int cnt = 0;
528 Iterator<MaildirMessage> it = data.messages.iterator();
529 while (it.hasNext()) {
530 Message msg = it.next();
531 if (msg.isSet(Flags.Flag.RECENT)) {
532 cnt++;
533 }
534 }
535 return cnt;
536 }
537 }
538 if (getNewDir().exists()) {
539 return getNewDir().list().length;
540 }
541 return 0;
542 }
543
544 /**
545 * This method is called by folder that is being opened. This implementation reads all files from the new and cur directories and
546 * wraps them in appropriate message message objects (see {@link #createExistingMaildirMessage(File, int)}).
547 * @param folder folder that asked opening
548 * @throws MessagingException thrown if an error is encountered while creating messages
549 * @throws MessagingException thrown if an error is encountered while creating messages
550 */
551 protected void open(MaildirFolder folder) throws MessagingException {
552 openCount = openCount + 1;
553 if (data == null) { // Must be same as openCount == 1
554 if (closedRef != null) {
555 data = closedRef.get();
556 }
557 if (data == null) {
558 data = new Data();
559 data.messages = new ArrayList<MaildirMessage>();
560 data.files = new HashMap<String, MaildirMessage>();
561 }
562 closedRef = null;
563 }
564
565 folder.setFolderMessages(createFolderMessages());
566 obtainMessages();
567
568 // If folder is pronounced as opened here
569 // it won't be filled with newly discovered messages so...
570 synchronized (openedFolders) {
571 openedFolders.put(folder, null);
572 }
573 // ... this statement here will be only one that will add messages
574 folder.addMessages(data.messages, false);
575 }
576
577 /**
578 * This method is called with folder that is closing.
579 * @param folder folder that is closed
580 */
581 protected void close(MaildirFolder folder) {
582 synchronized (openedFolders) {
583 openedFolders.remove(folder);
584 }
585
586 openCount = openCount - 1;
587 if (openCount == 0) {
588 closedRef = new WeakReference<Data>(data);
589 data = null;
590 }
591 }
592
593 /**
594 * Appends messages to the folder.
595 * @param folder folder that initiated appending messages
596 * @param messages array of messages
597 * @throws MessagingException thrown while creating new message.
598 */
599 protected void appendMessages(MaildirFolder folder, Message[] messages) throws MessagingException {
600 ArrayList<MaildirMessage> addedMessages = new ArrayList<MaildirMessage>();
601 Exception exception = null;
602 int num = 0;
603 if ((folder != null) && folder.isOpen()) {
604 num = folder.getMessageCount();
605 }
606 for (int i = 0; i < messages.length; i++) {
607 try {
608 MaildirMessage message = createNewMaildirMessage((MimeMessage)messages[i], num);
609 if (data != null) {
610 data.files.put(message.getBaseName(), message);
611 }
612 num = num + 1;
613 addedMessages.add(message);
614 } catch (IOException e) {
615 exception = e;
616 }
617 }
618 if (addedMessages.size() > 0) {
619 // folder.addMessages(addedMessages, true);
620 addMessages(folder, addedMessages);
621 }
622 if (exception != null) {
623 throw new MessagingException("Adding message failed", exception);
624 }
625 }
626
627 /**
628 * Appends messages to the maildir data. This method doesn't append messages to any
629 * specific folder - but all opened.
630 * @param messages messages to be appended
631 * @throws MessagingException
632 */
633 public void appendMessages(Message[] messages) throws MessagingException {
634 appendMessages(null, messages);
635 }
636
637 /**
638 * Expunges messages for given folder.
639 * @param folder folder
640 * @param explicit should folder be notified of messages
641 * @return list of messages that were expunged
642 * @throws MessagingException thrown in <code>javax.mail.Message.getFlags()</code> method
643 */
644 protected List<? extends Message> expunge(MaildirFolder folder, boolean explicit) throws MessagingException {
645 ArrayList<MaildirMessage> expunged = new ArrayList<MaildirMessage>();
646 List<MaildirMessage> messages = getFolderMessages(folder);
647
648 Iterator<MaildirMessage> it = messages.iterator();
649 while (it.hasNext()) {
650 MaildirMessage message = it.next();
651 if (!message.isExpunged() && message.getFlags().contains(Flags.Flag.DELETED)) {
652 expunged.add(message);
653 }
654 }
655 if (expunged.size() > 0) {
656 it = expunged.iterator();
657 while (it.hasNext()) {
658 MaildirMessage msg = (MaildirMessage)it.next();
659 if (!expungeMessage(msg)) {
660 it.remove();
661 }
662 data.messages.remove(msg);
663 data.files.remove(msg.getBaseName());
664 }
665
666 removeMessages(folder, expunged);
667 // Remove method removes for all but folder
668 return folder.removeMessages(expunged, explicit);
669 } else {
670 // Zero sized list
671 return expunged;
672 }
673 }
674
675
676 /**
677 * This method is only to satisfy Folder interface. Not to be used.
678 * @return <code>null</code>
679 * @throws MessagingException
680 */
681 public Message[] expunge() throws MessagingException {
682 // TODO Try to give this method some meaning...
683 return null;
684 }
685
686 /**
687 * Returns folder's messages for given folder.
688 * This implementation unwraps messages from the folder.
689 *
690 * @param folder
691 * @return list of messages
692 */
693 protected List<MaildirMessage> getFolderMessages(MaildirFolder folder) {
694 List<? extends MessageBase> messages = folder.getFolderMessages();
695 int size = messages.size();
696 ArrayList<MaildirMessage> unwrapped = new ArrayList<MaildirMessage>(size);
697 for (int i = 0; i < size; i++) {
698 MaildirMessage wrapped = (MaildirMessage)((MessageWrapper)messages.get(i)).getMessage();
699 unwrapped.add(wrapped);
700 }
701 return unwrapped;
702 }
703
704 /**
705 * This method reads directory and creates messages for given folder.
706 * @throws MessagingException
707 */
708 @SuppressWarnings("unchecked")
709 protected void obtainMessages() throws MessagingException {
710 try {
711 // It is called from open so we do not need to sycnhronise - open is synchronised.
712 long time = System.currentTimeMillis();
713 if ((time - lastAccess) > delay) {
714 int msgno = 1;
715 HashMap<String, MaildirMessage> expunged = (HashMap<String, MaildirMessage>)data.files.clone();
716 ArrayList<MaildirMessage> newMessages = new ArrayList<MaildirMessage>();
717 File[] oldFiles = getCurDir().listFiles();
718 File[] newFiles = getNewDir().listFiles();
719 if (oldFiles != null) {
720 for (int i = 0; i < oldFiles.length; i++) {
721 String baseName = MaildirMessage.baseNameFromFile(oldFiles[i]);
722 MaildirMessage message = (MaildirMessage)data.files.get(baseName);
723 if (message == null) {
724 // new file
725 message = createExistingMaildirMessage(oldFiles[i], msgno);
726 msgno++;
727 newMessages.add(message);
728 if (data != null) {
729 data.files.put(message.getBaseName(), message);
730 }
731 } else {
732 // we still have that file
733 expunged.remove(baseName);
734 }
735 }
736 }
737 if (newFiles != null) {
738 for (int i = 0; i < newFiles.length; i++) {
739 String baseName = MaildirMessage.baseNameFromFile(newFiles[i]);
740 MaildirMessage message = (MaildirMessage)data.files.get(baseName);
741 if (message == null) {
742 // new file
743 message = createExistingMaildirMessage(newFiles[i], msgno);
744 msgno++;
745 newMessages.add(message);
746 if (data != null) {
747 data.files.put(message.getBaseName(), message);
748 }
749 } else {
750 // we still have that file
751 expunged.remove(baseName);
752 }
753 }
754 }
755 if (newMessages.size() > 0) {
756 addMessages(null, newMessages);
757 }
758 if (expunged.size() > 0) {
759 Iterator it = expunged.values().iterator();
760 while (it.hasNext()) {
761 MaildirMessage msg = (MaildirMessage)it.next();
762 data.messages.remove(msg);
763 data.files.remove(msg.getBaseName());
764 }
765 removeMessages((MaildirFolder)null, expunged.values());
766 }
767
768 long now = System.currentTimeMillis();
769 if ((now - time) > (delay / delayFactor)) {
770 delay = delay * delayFactor;
771 }
772
773 lastAccess = now;
774 }
775 } catch (IOException e) {
776 throw new MessagingException("Cannot create message file", e);
777 }
778 }
779
780
781
782
783 /**
784 * This method adds messages to the folder.
785 * @param folder folder where messages should be added without being notified. All others will be notified. If null supplied then all will be notified.
786 * @param messages messages to be added
787 * @throws MessagingException
788 */
789 protected void addMessages(MaildirFolder folder, List<MaildirMessage> messages) throws MessagingException {
790 if (data != null) {
791 // There are opened folders
792 Collections.sort(messages);
793 renumerateMessages(data.messages.size()+1, messages);
794 data.messages.addAll(messages);
795
796 synchronized (openedFolders) {
797 Iterator<Folder> it = openedFolders.keySet().iterator();
798 while (it.hasNext()) {
799 MaildirFolder extFolder = (MaildirFolder)it.next();
800 extFolder.addMessages(messages, folder != extFolder);
801 }
802 }
803 }
804 }
805
806 /**
807 * This method removes folder messages. Called only for implicite removal.
808 * @param folder folder that initiated call. That folder will be excluded from removal
809 * and method folder.removeMessages must be called separately
810 * @param messages messages to be removed
811 * @throws MessagingException
812 */
813 protected void removeMessages(MaildirFolder folder, Collection<? extends MimeMessage> messages) throws MessagingException {
814 synchronized (openedFolders) {
815 Iterator<Folder> it = openedFolders.keySet().iterator();
816 while (it.hasNext()) {
817 MaildirFolder extFolder = (MaildirFolder)it.next();
818 // extFolder.removeMessages(messages, (folder == extFolder) && explicit);
819 if (extFolder != folder) {
820 extFolder.removeMessages(messages, false);
821 }
822 }
823 }
824 }
825
826 /**
827 * This method creates collection structure for storing messages in the folder.
828 * This implementation returns <code>ArrayList</code>
829 * @return list for messages to be stored in.
830 */
831 protected List<MessageBase> createFolderMessages() {
832 return new ArrayList<MessageBase>();
833 }
834
835 /**
836 * Expunges one message
837 * @param message message to be expunged
838 * @return <code>true</code> if expunge succed
839 */
840 protected boolean expungeMessage(MaildirMessage message) {
841 return message.expunge();
842 }
843
844 /**
845 * Renumerates given list of maildir message objects
846 * @param from first number for renumeration to start with
847 * @param messages maildir message objects
848 */
849 public static void renumerateMessages(int from, List<? extends MessageBase> messages) {
850 for (int i = 0; i < messages.size(); i++) {
851 MessageBase msg = messages.get(i);
852 msg.setMessageNumber(from + i);
853 }
854 }
855
856
857 /**
858 * This method creates new maildir message for folder data (not folder). It is expected
859 * for this method to create appropriate file in proper directory based on flags in
860 * supplied message (cur or new).
861 * @param message message whose content will be copied to new message
862 * @param num message number
863 * @return new maildir message this folder will keep for its folder data
864 * @throws IOException
865 * @throws MessagingException
866 */
867 protected MaildirMessage createNewMaildirMessage(MimeMessage message, int num) throws IOException, MessagingException {
868 return new MaildirMessage(this, message, num);
869 }
870
871 /**
872 * This method creates new maildir message object for existing file in folder data's directory.
873 * @param file file message object is going to be created
874 * @param num message number
875 * @return new maildir message this folder will keep for its folder data
876 * @throws IOException
877 * @throws MessagingException
878 */
879 protected MaildirMessage createExistingMaildirMessage(File file, int num) throws IOException, MessagingException {
880 return new MaildirMessage(this, file, num);
881 }
882
883
884 /**
885 * Folders data class. This class keeps list of messages and map for files, messages pair.
886 * It is moved to separate class just for easier handling of moving instance of this class
887 * under weak reference regime when there are no open folders. That means garbage collector
888 * can more easily remove not used messages.
889 */
890 protected static class Data {
891 /** Messages in this directory (folder(s)) */
892 protected List<MaildirMessage> messages;
893
894 /** Map from files to message objects */
895 protected HashMap<String, MaildirMessage> files;
896 }
897
898
899
900 /**
901 * This method is only to satisfy Folder interface. Not to be used.
902 * @return <code>null</code>
903 * @throws MessagingException
904 */
905 public Folder getParent() throws MessagingException {
906 return null;
907 }
908
909 /**
910 * This method is only to satisfy Folder interface. Not to be used.
911 * @param arg0
912 * @return <code>null</code>
913 * @throws MessagingException
914 */
915 public Folder[] list(String arg0) throws MessagingException {
916 return null;
917 }
918
919 /**
920 * This method is only to satisfy Folder interface. Not to be used.
921 * @return <code>getMessageCount() > 0</code>
922 * @throws MessagingException
923 */
924 public boolean hasNewMessages() throws MessagingException {
925 return getMessageCount() > 0;
926 }
927
928 /**
929 * This method is only to satisfy Folder interface. Not to be used.
930 * @param arg0
931 * @return <code>null</code>
932 * @throws MessagingException
933 */
934 public Folder getFolder(String arg0) throws MessagingException {
935 return null;
936 }
937
938 /**
939 * This method is only to satisfy Folder interface. Not to be used.
940 * @param folder
941 * @return result of {@link #renameTo(MaildirFolderData)} method.
942 * @throws MessagingException
943 */
944 public boolean renameTo(Folder folder) throws MessagingException {
945 if (folder instanceof MaildirFolderData) {
946 return renameTo((MaildirFolderData)folder) != null;
947 }
948 return false;
949 }
950
951 /**
952 * This method is only to satisfy Folder interface. Not to be used.
953 * @param arg0
954 * @throws MessagingException
955 */
956 public void open(int arg0) throws MessagingException {
957 }
958
959 /**
960 * This method is only to satisfy Folder interface. Not to be used.
961 * @param arg0
962 * @throws MessagingException
963 */
964 public void close(boolean arg0) throws MessagingException {
965 }
966
967 /**
968 * This method is only to satisfy Folder interface. Not to be used.
969 * @return <code> openCount > 0</code>
970 */
971 public boolean isOpen() {
972 return openCount > 0;
973 }
974
975 /**
976 * This method is only to satisfy Folder interface. Not to be used.
977 * @return {@link #rootPermanentFlags} if root folder or {@link #permanentFlags} otherwise
978 */
979 public Flags getPermanentFlags() {
980 // TODO add check for this as parameter in store
981 if (isRootFolder()) {
982 return rootPermanentFlags;
983 } else {
984 if (permanentFlags == null) {
985 permanentFlags = new Flags();
986 permanentFlags.add(javax.mail.Flags.Flag.ANSWERED);
987 permanentFlags.add(javax.mail.Flags.Flag.SEEN);
988 permanentFlags.add(javax.mail.Flags.Flag.DELETED);
989 permanentFlags.add(javax.mail.Flags.Flag.DRAFT);
990 permanentFlags.add(javax.mail.Flags.Flag.FLAGGED);
991 }
992 return permanentFlags;
993 }
994 }
995
996 /**
997 * This method is only to satisfy Folder interface. Not to be used.
998 * @param i index
999 * @return returns message with given index
1000 * @throws MessagingException
1001 */
1002 public Message getMessage(int i) throws MessagingException {
1003 if (data != null) {
1004 return (MaildirMessage)data.messages.get(i);
1005 } else {
1006 return null;
1007 }
1008 }
1009 }