1
2
3
4
5
6
7
8
9
10
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class MaildirFolderData extends Folder {
59
60
61
62
63
64 public static final String NO_SUBFOLDERS_FILENAME = ".nosubfolders";
65
66
67 public static final Flags rootPermanentFlags = new Flags("\\Noselect");
68
69
70 protected Flags permanentFlags;
71
72
73 protected MaildirStore store;
74
75
76 protected File base;
77
78
79 protected boolean rootFolder;
80
81
82 protected int type = -1;
83
84
85 protected File tmp;
86
87
88 protected File cur;
89
90
91 protected File nw;
92
93
94 protected String cachedFullName;
95
96
97 protected String cachedName;
98
99
100 protected long lastAccess;
101
102
103 protected int delay = 1000;
104
105
106 protected int delayFactor = 3;
107
108
109 protected WeakHashMap<Folder, Object> openedFolders = new WeakHashMap<Folder, Object>();
110
111
112 protected Data data;
113
114
115 protected Reference<Data> closedRef;
116
117
118 protected int openCount = 0;
119
120
121
122
123
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
137
138
139 public MaildirStore getMaildirStore() {
140 return store;
141 }
142
143
144
145
146
147 public File getFolderFile() {
148 return base;
149 }
150
151
152
153
154
155 protected void setFolderFile(File file) {
156 this.base = file;
157 }
158
159
160
161
162
163 public long getLastAccessed() {
164 return lastAccess;
165 }
166
167
168
169
170
171 protected boolean isRootFolder() {
172 return rootFolder;
173 }
174
175
176
177
178
179 protected File getNewDir() {
180 return nw;
181 }
182
183
184
185
186
187 protected File getCurDir() {
188 return cur;
189 }
190
191
192
193
194
195 protected File getTmpDir() {
196 return tmp;
197 }
198
199
200
201
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
225
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
245
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
266
267
268
269 public boolean exists() throws MessagingException {
270 return getFolderFile().exists();
271 }
272
273
274
275
276
277
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
323
324
325 public char getSeparator() {
326 return '/';
327 }
328
329
330
331
332
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
344
345
346
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
377
378
379
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
403
404
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
427
428
429
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
449
450
451
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
461
462
463 return folderData;
464 }
465
466
467
468
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
492
493
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
521
522
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
546
547
548
549
550
551 protected void open(MaildirFolder folder) throws MessagingException {
552 openCount = openCount + 1;
553 if (data == null) {
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
569
570 synchronized (openedFolders) {
571 openedFolders.put(folder, null);
572 }
573
574 folder.addMessages(data.messages, false);
575 }
576
577
578
579
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
595
596
597
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
620 addMessages(folder, addedMessages);
621 }
622 if (exception != null) {
623 throw new MessagingException("Adding message failed", exception);
624 }
625 }
626
627
628
629
630
631
632
633 public void appendMessages(Message[] messages) throws MessagingException {
634 appendMessages(null, messages);
635 }
636
637
638
639
640
641
642
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
668 return folder.removeMessages(expunged, explicit);
669 } else {
670
671 return expunged;
672 }
673 }
674
675
676
677
678
679
680
681 public Message[] expunge() throws MessagingException {
682
683 return null;
684 }
685
686
687
688
689
690
691
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
706
707
708 @SuppressWarnings("unchecked")
709 protected void obtainMessages() throws MessagingException {
710 try {
711
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
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
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
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
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
785
786
787
788
789 protected void addMessages(MaildirFolder folder, List<MaildirMessage> messages) throws MessagingException {
790 if (data != null) {
791
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
808
809
810
811
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
819 if (extFolder != folder) {
820 extFolder.removeMessages(messages, false);
821 }
822 }
823 }
824 }
825
826
827
828
829
830
831 protected List<MessageBase> createFolderMessages() {
832 return new ArrayList<MessageBase>();
833 }
834
835
836
837
838
839
840 protected boolean expungeMessage(MaildirMessage message) {
841 return message.expunge();
842 }
843
844
845
846
847
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
859
860
861
862
863
864
865
866
867 protected MaildirMessage createNewMaildirMessage(MimeMessage message, int num) throws IOException, MessagingException {
868 return new MaildirMessage(this, message, num);
869 }
870
871
872
873
874
875
876
877
878
879 protected MaildirMessage createExistingMaildirMessage(File file, int num) throws IOException, MessagingException {
880 return new MaildirMessage(this, file, num);
881 }
882
883
884
885
886
887
888
889
890 protected static class Data {
891
892 protected List<MaildirMessage> messages;
893
894
895 protected HashMap<String, MaildirMessage> files;
896 }
897
898
899
900
901
902
903
904
905 public Folder getParent() throws MessagingException {
906 return null;
907 }
908
909
910
911
912
913
914
915 public Folder[] list(String arg0) throws MessagingException {
916 return null;
917 }
918
919
920
921
922
923
924 public boolean hasNewMessages() throws MessagingException {
925 return getMessageCount() > 0;
926 }
927
928
929
930
931
932
933
934 public Folder getFolder(String arg0) throws MessagingException {
935 return null;
936 }
937
938
939
940
941
942
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
953
954
955
956 public void open(int arg0) throws MessagingException {
957 }
958
959
960
961
962
963
964 public void close(boolean arg0) throws MessagingException {
965 }
966
967
968
969
970
971 public boolean isOpen() {
972 return openCount > 0;
973 }
974
975
976
977
978
979 public Flags getPermanentFlags() {
980
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
998
999
1000
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 }