1 26 27 package gnu.mail.providers.mbox; 28 29 import java.io.BufferedOutputStream ; 30 import java.io.ByteArrayInputStream ; 31 import java.io.ByteArrayOutputStream ; 32 import java.io.File ; 33 import java.io.FileInputStream ; 34 import java.io.FileOutputStream ; 35 import java.io.FilenameFilter ; 36 import java.io.IOException ; 37 import java.io.InputStream ; 38 import java.io.OutputStream ; 39 import java.text.DateFormat ; 40 import java.text.SimpleDateFormat ; 41 import java.util.Date ; 42 import java.util.Vector ; 43 import java.util.zip.GZIPInputStream ; 44 import java.util.zip.GZIPOutputStream ; 45 import javax.mail.Address ; 46 import javax.mail.FetchProfile ; 47 import javax.mail.Flags ; 48 import javax.mail.Folder ; 49 import javax.mail.Message ; 50 import javax.mail.MessagingException ; 51 import javax.mail.Store ; 52 import javax.mail.URLName ; 53 import javax.mail.event.ConnectionEvent ; 54 import javax.mail.event.FolderEvent ; 55 import javax.mail.internet.AddressException ; 56 import javax.mail.internet.InternetAddress ; 57 import javax.mail.internet.MimeMessage ; 58 import gnu.mail.util.LineInputStream; 59 import gnu.mail.treeutil.StatusEvent; 60 61 67 public class MboxFolder 68 extends Folder 69 { 70 71 static final DateFormat df = new SimpleDateFormat ("EEE MMM d H:m:s yyyy"); 72 static final String GNU_MESSAGE_ID = "X-GNU-Message-Id"; 73 static final String FROM = "From "; 74 75 File file; 76 MboxMessage[] messages; 77 boolean open; 78 boolean readOnly; 79 int type; 80 boolean inbox; 81 82 Flags permanentFlags = null; 83 84 87 protected MboxFolder(Store store, String filename, boolean inbox) 88 { 89 super(store); 90 91 file = new File (filename); 92 if (file.exists() && file.isDirectory()) 93 type = HOLDS_FOLDERS; 94 else 95 type = HOLDS_MESSAGES; 96 this.inbox = inbox; 97 open = false; 98 readOnly = true; 99 messages = new MboxMessage[0]; 100 } 101 102 105 protected MboxFolder(Store store, String filename) 106 { 107 this(store, filename, false); 108 } 109 110 113 public String getName() 114 { 115 if (inbox) 116 return "INBOX"; 117 return file.getName(); 118 } 119 120 123 public String getFullName() 124 { 125 if (inbox) 126 return "INBOX"; 127 return file.getPath(); 128 } 129 130 133 public URLName getURLName() 134 throws MessagingException 135 { 136 URLName url = super.getURLName(); 137 return new URLName (url.getProtocol(), 138 null, -1, url.getFile(), 139 null, null); 140 } 141 142 146 public int getType() 147 throws MessagingException 148 { 149 return type; 150 } 151 152 156 public boolean exists() 157 throws MessagingException 158 { 159 return file.exists(); 160 } 161 162 166 public boolean hasNewMessages() 167 throws MessagingException 168 { 169 return getNewMessageCount()>0; 170 } 171 172 178 public void open(int mode) 179 throws MessagingException 180 { 181 if (mode==READ_WRITE) 182 { 183 if (!file.canWrite()) 184 throw new MessagingException ("Folder is read-only"); 185 if (!acquireLock()) 186 throw new MessagingException ("Unable to acquire lock"); 187 readOnly = false; 188 } 189 190 if (!file.canRead()) 191 throw new MessagingException ("Can't read folder: "+file.getName()); 192 193 LineInputStream in = null; 194 String filename = file.getAbsolutePath(); 195 try 196 { 197 MboxStore store = (MboxStore)this.store; 199 store.log("reading "+filename); 200 201 Vector vm = new Vector (256); 202 in = new LineInputStream(getInputStream()); 203 int count = 1; 204 String line, fromLine = null; 205 ByteArrayOutputStream buf = null; 206 207 store.processStatusEvent(new StatusEvent(store, 209 StatusEvent.OPERATION_START, 210 "open")); 211 212 for (line = in.readLine(); line!=null; line = in.readLine()) 213 { 214 if (line.indexOf(FROM)==0) 215 { 216 if (buf!=null) 217 { 218 byte[] bytes = buf.toByteArray(); 219 ByteArrayInputStream bin = new ByteArrayInputStream (bytes); 220 MboxMessage m = new MboxMessage(this, fromLine, bin, count++); 221 vm.addElement(m); 222 223 store.processStatusEvent(new StatusEvent(store, 224 StatusEvent.OPERATION_UPDATE, 225 "open", 226 1, 227 StatusEvent.UNKNOWN, 228 count-1)); 229 } 230 fromLine = line; 231 buf = new ByteArrayOutputStream (); 232 } 233 else if (buf!=null) 234 { 235 byte[] bytes = decodeFrom(line).getBytes(); 236 buf.write(bytes, 0, bytes.length); 237 buf.write(10); } 239 } 240 if (buf!=null) 241 { 242 byte[] bytes = buf.toByteArray(); 243 ByteArrayInputStream bin = new ByteArrayInputStream (bytes); 244 MboxMessage m = new MboxMessage(this, fromLine, bin, count++); 245 vm.addElement(m); 246 247 store.processStatusEvent(new StatusEvent(store, 248 StatusEvent.OPERATION_UPDATE, 249 "open", 250 1, 251 StatusEvent.UNKNOWN, 252 count-1)); 253 } 254 messages = new MboxMessage[vm.size()]; 255 vm.copyInto(messages); 256 buf = null; 257 vm = null; 258 259 store.processStatusEvent(new StatusEvent(store, 260 StatusEvent.OPERATION_END, 261 "open")); 262 263 open = true; 265 notifyConnectionListeners(ConnectionEvent.OPENED); 266 } 267 catch (IOException e) 268 { 269 throw new MessagingException ("Unable to open folder: "+filename, e); 270 } 271 finally 272 { 273 try 275 { 276 if (in!=null) 277 in.close(); 278 } 279 catch (IOException e) 280 { 281 } 283 } 284 } 285 286 289 public static String decodeFrom(String line) 290 { 291 if (line!=null) 292 { 293 int len = line.length(); 294 for (int i=0; i<(len-5); i++) 295 { 296 char c = line.charAt(i); 297 if (i>0 && 298 (c=='F' && 299 line.charAt(i+1)=='r' && 300 line.charAt(i+2)=='o' && 301 line.charAt(i+3)=='m' && 302 line.charAt(i+4)==' ')) 303 return line.substring(1); 304 if (c!='>') 305 break; 306 } 307 } 308 return line; 309 } 310 311 316 public void close(boolean expunge) 317 throws MessagingException 318 { 319 if (open) 320 { 321 if (expunge) 322 expunge(); 323 324 if (!readOnly) 325 { 326 MboxStore store = (MboxStore)this.store; 328 store.log("saving "+file.getAbsolutePath()); 329 synchronized (this) 330 { 331 OutputStream os = null; 332 try 333 { 334 os = getOutputStream(); 335 BufferedOutputStream bos = new BufferedOutputStream (os); 336 MboxOutputStream mos = new MboxOutputStream(bos); 337 338 store.processStatusEvent(new StatusEvent(store, 339 StatusEvent.OPERATION_START, 340 "close")); 341 for (int i=0; i<messages.length; i++) 342 { 343 String fromLine = fromLine(messages[i]); 344 bos.write(fromLine.getBytes()); 345 bos.write('\n'); 346 bos.flush(); 347 messages[i].writeTo(mos); 348 mos.flush(); 349 350 store.processStatusEvent(new StatusEvent(store, 351 StatusEvent.OPERATION_UPDATE, 352 "close", 353 1, 354 messages.length, 355 i+1)); 356 } 357 358 store.processStatusEvent(new StatusEvent(store, 359 StatusEvent.OPERATION_END, 360 "close")); 361 } 362 catch (IOException e) 363 { 364 throw new MessagingException ("I/O error writing mailbox", e); 365 } 366 finally 367 { 368 try 370 { 371 if (os!=null) 372 os.close(); 373 } 374 catch (IOException e) 375 { 376 } 378 } 379 } 380 if (!releaseLock()) 381 store.log("unable to clear up lock file!"); 382 } 383 384 open = false; 385 messages = new MboxMessage[0]; notifyConnectionListeners(ConnectionEvent.CLOSED); 387 } 388 } 389 390 396 protected String fromLine(MboxMessage message) 397 throws MessagingException 398 { 399 String fromLine = message.fromLine; 400 if (fromLine==null) 401 { 402 StringBuffer buf = new StringBuffer ("From "); 403 404 String from = "-"; 405 try 406 { 407 Address [] f = message.getFrom(); 408 if (f!=null && f.length>0) 409 { 410 if (f[0] instanceof InternetAddress ) 411 from = ((InternetAddress )f[0]).getAddress(); 412 else 413 from = f[0].toString(); 414 } 415 } 416 catch (AddressException e) 417 { 418 } 420 buf.append(from); 421 buf.append(' '); 422 423 Date date = message.getSentDate(); 424 if (date==null) 425 date = message.getReceivedDate(); 426 if (date==null) 427 date = new Date (); 428 buf.append(df.format(date)); 429 430 fromLine = buf.toString(); 431 } 432 return fromLine; 433 } 434 435 440 public synchronized Message [] expunge() 441 throws MessagingException 442 { 443 Vector ve = new Vector (); 444 if (open) 445 { 446 Vector vm = new Vector (); 447 for (int i=0; i<messages.length; i++) 448 { 449 Flags flags = messages[i].getFlags(); 450 if (flags.contains(Flags.Flag.DELETED)) 451 { 452 ve.addElement(messages[i]); 453 if (messages[i] instanceof MboxMessage) 454 ((MboxMessage)messages[i]).setExpunged(true); 455 } 456 else 457 vm.addElement(messages[i]); 458 } 459 messages = new MboxMessage[vm.size()]; 460 vm.copyInto(messages); 461 } 462 Message [] expunged = new Message [ve.size()]; 463 ve.copyInto(expunged); 464 if (expunged.length>0) 465 notifyMessageRemovedListeners(true, expunged); 466 return expunged; 467 } 468 469 472 public boolean isOpen() 473 { 474 return open; 475 } 476 477 480 public Flags getPermanentFlags() 481 { 482 if (permanentFlags == null) 483 { 484 Flags flags = new Flags (); 485 flags.add(Flags.Flag.DELETED); 486 flags.add(Flags.Flag.SEEN); 487 flags.add(Flags.Flag.RECENT); 488 permanentFlags = flags; 489 } 490 return permanentFlags; 491 } 492 493 497 public int getMessageCount() 498 throws MessagingException 499 { 500 return messages.length; 501 } 502 503 507 public Message getMessage(int msgnum) 508 throws MessagingException 509 { 510 int index = msgnum-1; 511 if (index<0 || index>=messages.length) 512 throw new MessagingException ("No such message: "+msgnum); 513 return messages[index]; 514 } 515 516 520 public synchronized Message [] getMessages() 521 throws MessagingException 522 { 523 Message [] m = new Message [messages.length]; 525 System.arraycopy(messages, 0, m, 0, messages.length); 526 return m; 527 } 528 529 535 public synchronized void appendMessages(Message [] m) 536 throws MessagingException 537 { 538 Vector appended = new Vector (m.length); 539 int count = messages.length; 540 for (int i=0; i<m.length; i++) 541 { 542 if (m[i] instanceof MimeMessage ) 543 { 544 MimeMessage mimem = (MimeMessage )m[i]; 545 MboxMessage mboxm = new MboxMessage(this, mimem, count++); 546 if (mimem instanceof MboxMessage) 547 mboxm.fromLine = ((MboxMessage)mimem).fromLine; 548 appended.addElement(mboxm); 549 } 550 } 551 int appendedLength = appended.size(); 552 if (appendedLength>0) 553 { 554 MboxMessage[] n = new MboxMessage[appendedLength]; 555 appended.copyInto(n); 556 557 Vector accumulator = new Vector (messages.length+n.length); 559 for (int i=0; i<messages.length; i++) 560 accumulator.addElement(messages[i]); 561 for (int i=0; i<n.length; i++) 562 accumulator.addElement(n[i]); 563 messages = new MboxMessage[accumulator.size()]; 564 accumulator.copyInto(messages); 565 566 notifyMessageAddedListeners(n); 568 } 569 } 570 571 575 public void fetch(Message [] messages, FetchProfile fetchprofile) 576 throws MessagingException 577 { 578 } 579 580 583 public Folder getParent() 584 throws MessagingException 585 { 586 return store.getFolder(file.getParent()); 587 } 588 589 592 public Folder [] list() 593 throws MessagingException 594 { 595 if (type!=HOLDS_FOLDERS) 596 throw new MessagingException ("This folder can't contain subfolders"); 597 try 598 { 599 String [] files = file.list(); 600 Folder [] folders = new Folder [files.length]; 601 for (int i=0; i<files.length; i++) 602 folders[i] = 603 store.getFolder(file.getAbsolutePath()+File.separator+files[i]); 604 return folders; 605 } 606 catch (SecurityException e) 607 { 608 throw new MessagingException ("Access denied", e); 609 } 610 } 611 612 615 public Folder [] list(String pattern) 616 throws MessagingException 617 { 618 if (type!=HOLDS_FOLDERS) 619 throw new MessagingException ("This folder can't contain subfolders"); 620 try 621 { 622 String [] files = file.list(new MboxFilenameFilter(pattern)); 623 Folder [] folders = new Folder [files.length]; 624 for (int i=0; i<files.length; i++) 625 folders[i] = 626 store.getFolder(file.getAbsolutePath()+File.separator+files[i]); 627 return folders; 628 } 629 catch (SecurityException e) 630 { 631 throw new MessagingException ("Access denied", e); 632 } 633 } 634 635 638 public char getSeparator() 639 throws MessagingException 640 { 641 return File.separatorChar; 642 } 643 644 647 public boolean create(int type) 648 throws MessagingException 649 { 650 if (file.exists()) 651 throw new MessagingException ("Folder already exists"); 652 switch (type) 653 { 654 case HOLDS_FOLDERS: 655 try 656 { 657 file.mkdirs(); 658 this.type = type; 659 notifyFolderListeners(FolderEvent.CREATED); 660 return true; 661 } 662 catch (SecurityException e) 663 { 664 throw new MessagingException ("Access denied", e); 665 } 666 case HOLDS_MESSAGES: 667 try 668 { 669 synchronized (this) 670 { 671 createNewFile(file); 672 } 673 this.type = type; 674 notifyFolderListeners(FolderEvent.CREATED); 675 return true; 676 } 677 catch (IOException e) 678 { 679 throw new MessagingException ("I/O error writing mailbox", e); 680 } 681 catch (SecurityException e) 682 { 683 throw new MessagingException ("Access denied", e); 684 } 685 } 686 return false; 687 } 688 689 692 public boolean delete(boolean recurse) 693 throws MessagingException 694 { 695 if (recurse) 696 { 697 try 698 { 699 if (type==HOLDS_FOLDERS) 700 { 701 Folder [] folders = list(); 702 for (int i=0; i<folders.length; i++) 703 if (!folders[i].delete(recurse)) 704 return false; 705 } 706 if (!readOnly) 707 releaseLock(); 708 file.delete(); 709 notifyFolderListeners(FolderEvent.DELETED); 710 return true; 711 } 712 catch (SecurityException e) 713 { 714 throw new MessagingException ("Access denied", e); 715 } 716 } 717 else 718 { 719 try 720 { 721 if (type==HOLDS_FOLDERS) 722 { 723 Folder [] folders = list(); 724 if (folders.length>0) 725 return false; 726 } 727 if (!readOnly) 728 releaseLock(); 729 file.delete(); 730 notifyFolderListeners(FolderEvent.DELETED); 731 return true; 732 } 733 catch (SecurityException e) 734 { 735 throw new MessagingException ("Access denied", e); 736 } 737 } 738 } 739 740 743 public boolean renameTo(Folder folder) 744 throws MessagingException 745 { 746 try 747 { 748 String filename = folder.getFullName(); 749 if (filename!=null) 750 { 751 file.renameTo(new File (filename)); 752 notifyFolderListeners(FolderEvent.RENAMED); 753 return true; 754 } 755 else 756 throw new MessagingException ("Illegal filename: null"); 757 } 758 catch (SecurityException e) 759 { 760 throw new MessagingException ("Access denied", e); 761 } 762 } 763 764 767 public Folder getFolder(String filename) 768 throws MessagingException 769 { 770 String INBOX = "INBOX"; 771 if (INBOX.equalsIgnoreCase(filename)) 772 { 773 try 774 { 775 return store.getFolder(INBOX); 776 } 777 catch (MessagingException e) 778 { 779 } 781 } 782 return store.getFolder(file.getAbsolutePath()+File.separator+filename); 783 } 784 785 789 private boolean isGzip() 790 { 791 return file.getName().toLowerCase().endsWith(".gz"); 792 } 793 794 798 private InputStream getInputStream() 799 throws IOException 800 { 801 InputStream in; 802 803 in = new FileInputStream (file); 804 if (isGzip()) 805 in = new GZIPInputStream (in); 806 return in; 807 } 808 809 813 private OutputStream getOutputStream() 814 throws IOException 815 { 816 OutputStream out; 817 818 out = new FileOutputStream (file); 819 if (isGzip()) 820 out = new GZIPOutputStream (out); 821 return out; 822 } 823 824 831 public synchronized boolean acquireLock() 832 { 833 try 834 { 835 String filename = file.getPath(); 836 String lockFilename = filename+".lock"; 837 File lock = new File (lockFilename); 838 MboxStore store = (MboxStore)this.store; 839 store.log("creating "+lock.getPath()); 840 if (lock.exists()) 841 return false; 842 createNewFile(lock); 845 return true; 846 } 847 catch (IOException e) 848 { 849 MboxStore store = (MboxStore)this.store; 850 store.log("I/O exception acquiring lock on "+file.getPath()); 851 } 852 catch (SecurityException e) 853 { 854 MboxStore store = (MboxStore)this.store; 855 store.log("Security exception acquiring lock on "+file.getPath()); 856 } 857 return false; 858 } 859 860 869 private void createNewFile(File file) 870 throws IOException 871 { 872 BufferedOutputStream out = new BufferedOutputStream (new 875 FileOutputStream (file)); 876 out.flush(); 877 out.close(); 878 879 } 880 881 886 public synchronized boolean releaseLock() 887 { 888 try 889 { 890 String filename = file.getPath(); 891 String lockFilename = filename+".lock"; 892 File lock = new File (lockFilename); 893 MboxStore store = (MboxStore)this.store; 894 store.log("removing "+lock.getPath()); 895 if (lock.exists()) 896 { 897 if (!lock.delete()) 898 return false; 899 } 900 return true; 901 } 902 catch (SecurityException e) 903 { 904 MboxStore store = (MboxStore)this.store; 905 store.log("Security exception releasing lock on "+file.getPath()); 906 } 907 return false; 908 } 909 910 class MboxFilenameFilter 911 implements FilenameFilter 912 { 913 914 String pattern; 915 int asteriskIndex, percentIndex; 916 917 MboxFilenameFilter(String pattern) 918 { 919 this.pattern = pattern; 920 asteriskIndex = pattern.indexOf('*'); 921 percentIndex = pattern.indexOf('%'); 922 } 923 924 public boolean accept(File directory, String name) 925 { 926 if (asteriskIndex>-1) 927 { 928 String start = pattern.substring(0, asteriskIndex); 929 String end = pattern.substring(asteriskIndex+1, pattern.length()); 930 return (name.startsWith(start) && 931 name.endsWith(end)); 932 } 933 else if (percentIndex>-1) 934 { 935 String start = pattern.substring(0, percentIndex); 936 String end = pattern.substring(percentIndex+1, pattern.length()); 937 return (directory.equals(file) && 938 name.startsWith(start) && 939 name.endsWith(end)); 940 } 941 return name.equals(pattern); 942 } 943 } 944 945 } 946 | Popular Tags |