1 19 package org.netbeans.modules.localhistory.store; 20 21 import java.beans.PropertyChangeEvent ; 22 import java.beans.PropertyChangeListener ; 23 import java.beans.PropertyChangeSupport ; 24 import java.io.BufferedInputStream ; 25 import java.io.BufferedOutputStream ; 26 import java.io.DataInputStream ; 27 import java.io.DataOutputStream ; 28 import java.io.EOFException ; 29 import java.io.File ; 30 import java.io.FileInputStream ; 31 import java.io.FileOutputStream ; 32 import java.io.FilenameFilter ; 33 import java.io.IOException ; 34 import java.io.InputStream ; 35 import java.io.OutputStream ; 36 import java.security.MessageDigest ; 37 import java.security.NoSuchAlgorithmException ; 38 import java.util.ArrayList ; 39 import java.util.Collections ; 40 import java.util.HashMap ; 41 import java.util.Iterator ; 42 import java.util.List ; 43 import java.util.Map ; 44 import java.util.Map.Entry; 45 import java.util.Set ; 46 import org.netbeans.modules.localhistory.Diagnostics; 47 import org.netbeans.modules.localhistory.utils.FileUtils; 48 import org.netbeans.modules.turbo.CustomProviders; 49 import org.netbeans.modules.turbo.Turbo; 50 import org.netbeans.modules.turbo.TurboProvider; 51 import org.netbeans.modules.turbo.TurboProvider; 52 import org.netbeans.modules.turbo.TurboProvider; 53 import org.netbeans.modules.turbo.TurboProvider.MemoryCache; 54 import org.openide.ErrorManager; 55 import org.openide.util.RequestProcessor; 56 57 61 class LocalHistoryStoreImpl implements LocalHistoryStore { 62 63 private static final int DELETED = 0; 64 private static final int TOUCHED = 1; 65 66 private static final String DATA_FILE = "data"; private static final String HISTORY_FILE = "history"; private static final String LABELS_FILE = "labels"; 70 private File storage; 71 private Turbo turbo; 72 private DataFilesTurboProvider cacheProvider; 73 private final PropertyChangeSupport propertyChangeSupport; 74 75 private static List <HistoryEntry> emptyHistory = new ArrayList <HistoryEntry>(0); 76 private static Map <Long , String > emptyLabels = new HashMap <Long , String >(); 77 private static StoreEntry[] emptyStoreEntryArray = new StoreEntry[0]; 78 79 private static FilenameFilter fileEntriesFilter = 80 new FilenameFilter () { 81 public boolean accept(File dir, String fileName) { 82 return !( fileName.endsWith(DATA_FILE) || 83 fileName.endsWith(HISTORY_FILE) || 84 fileName.endsWith(LABELS_FILE)); 85 } 86 }; 87 88 LocalHistoryStoreImpl() { 89 initStorage(); 90 91 propertyChangeSupport = new PropertyChangeSupport (this); 92 93 cacheProvider = new DataFilesTurboProvider(); 94 turbo = Turbo.createCustom( 95 new CustomProviders() { 96 private final Set providers = Collections.singleton(cacheProvider); 97 public Iterator providers() { 98 return providers.iterator(); 99 } 100 }, 101 20, -1); } 103 104 public synchronized void fileCreate(File file, long ts) { 105 try { 106 fileCreateImpl(file, ts, null, file.getAbsolutePath()); 107 } catch (IOException ioe) { 108 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 109 } 110 } 111 112 private void fileCreateImpl(File file, long ts, String from, String to) throws IOException { 113 if(lastModified(file) > 0) { 114 return; 115 } 116 String tsString = Long.toString(ts); 117 File storeFile = null; 118 if(file.isFile()) { 119 storeFile = getStoreFile(file, tsString, true); FileUtils.copy(file, StoreEntry.createStoreFileOutputSteam(storeFile)); 121 122 if(Diagnostics.ON) { 123 Diagnostics.logCreate(file, storeFile, ts, from, to); 124 } 125 126 } 127 touch(file, new StoreDataFile(file.getAbsolutePath(), TOUCHED, ts, file.isFile())); 128 File parent = file.getParentFile(); 129 if(parent != null) { 130 writeHistoryForFile(parent, new HistoryEntry[] {new HistoryEntry(ts, from, to, TOUCHED)}); 132 } 133 fireChanged(null, file); 134 } 135 136 public synchronized void fileChange(File file, long ts) { 137 long lastModified = lastModified(file); 138 if(lastModified == ts) { 139 return; 140 } 141 if(file.isFile()) { 142 try { 143 File storeFile = getStoreFile(file, Long.toString(ts), true); 144 FileUtils.copy(file, StoreEntry.createStoreFileOutputSteam(storeFile)); 145 146 if(Diagnostics.ON) { 147 Diagnostics.logChange(file, storeFile, ts); 148 } 149 150 touch(file, new StoreDataFile(file.getAbsolutePath(), TOUCHED, ts, file.isFile())); 151 } catch (IOException ioe) { 152 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 153 } 154 } else { 155 try { 156 touch(file, new StoreDataFile(file.getAbsolutePath(), TOUCHED, ts, file.isFile())); 157 } catch (IOException ioe) { 158 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 159 } 160 } 161 fireChanged(null, file); 162 } 163 164 public synchronized void fileDelete(File file, long ts) { 165 try { 166 fileDeleteImpl(file, null, file.getAbsolutePath(), ts); 167 } catch (IOException ioe) { 168 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 169 } 170 fireChanged(null, file); 171 } 172 173 private void fileDeleteImpl(File file, String from, String to, long ts) throws IOException { 174 StoreDataFile data = readStoreData(file, true); 175 177 if(data == null) { 178 if(Diagnostics.ON) { 179 Diagnostics.println("deleting without data for file : " + file); 180 } 181 return; 182 } 183 long lastModified = data.getLastModified(); 185 boolean isFile = data.isFile(); 186 187 if(Diagnostics.ON) { 188 File storeFile = getDataFile(file); 189 Diagnostics.logDelete(file, storeFile, ts); 190 } 191 192 touch(file, new StoreDataFile(file.getAbsolutePath(), DELETED, lastModified, isFile)); 193 File parent = file.getParentFile(); 194 if(parent != null) { 195 writeHistoryForFile(parent, new HistoryEntry[] {new HistoryEntry(ts, from, to, DELETED)}); 197 } 198 } 199 200 public synchronized void fileCreateFromMove(File from, File to, long ts) { 201 if(lastModified(to) > 0) { 202 return; 203 } 204 try { 205 fileCreateImpl(to, ts, from.getAbsolutePath(), to.getAbsolutePath()); 206 } catch (IOException ioe) { 207 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 208 } 209 fireChanged(null, to); 210 } 211 212 public synchronized void fileDeleteFromMove(File from, File to, long ts) { 213 try { 214 fileDeleteImpl(from, from.getAbsolutePath(), to.getAbsolutePath(), ts); 215 } catch (IOException ioe) { 216 ErrorManager.getDefault().notify(ErrorManager.WARNING, ioe); 217 } 218 fireChanged(null, from); 219 } 220 221 private long lastModified(File file) { 222 StoreDataFile data = readStoreData(file, true); 223 return data != null && data.getStatus() != DELETED ? data.getLastModified() : -1; 224 } 225 226 public synchronized StoreEntry[] getStoreEntries(File file) { 227 return getStoreEntriesImpl(file); 229 } 230 231 private StoreEntry[] getStoreEntriesImpl(File file) { 232 File storeFolder = getStoreFolder(file); 233 File [] storeFiles = storeFolder.listFiles(fileEntriesFilter); 234 if(storeFiles != null && storeFiles.length > 0) { 235 List <StoreEntry> ret = new ArrayList <StoreEntry>(storeFiles.length); 236 if(storeFiles.length > 0) { 237 Map <Long , String > labels = getLabels(getLabelsFile(file)); 238 for (int i = 0; i < storeFiles.length; i++) { 239 long ts = Long.parseLong(storeFiles[i].getName()); 240 String label = labels.get(ts); 241 ret.add(StoreEntry.createStoreEntry(file, storeFiles[i], ts, label)); 242 } 243 return ret.toArray(new StoreEntry[storeFiles.length]); 244 } 245 return emptyStoreEntryArray; 246 } else { 247 return emptyStoreEntryArray; 248 } 249 } 250 251 public StoreEntry[] getFolderState(File root, File [] files, long ts) { 252 253 File parentFile = root.getParentFile(); 255 if(parentFile != null) { 256 List <HistoryEntry> parentHistory = readHistoryForFile(parentFile); 257 if(wasDeleted(root, parentHistory, ts)) { 258 return emptyStoreEntryArray; 259 } 260 } 261 262 List <HistoryEntry> history = readHistoryForFile(root); 263 264 List <StoreEntry> ret = new ArrayList <StoreEntry>(); 266 267 Map <File , HistoryEntry> beforeRevert = new HashMap <File , HistoryEntry>(); 268 Map <File , HistoryEntry> afterRevert = new HashMap <File , HistoryEntry>(); 269 270 for(HistoryEntry he : history) { 271 File file = new File (he.getTo()); 272 if(he.getTimestamp() < ts) { 273 beforeRevert.put(file, he); 276 } else { 277 if(!afterRevert.containsKey(file)) { 280 afterRevert.put(file, he); 281 } 282 } 283 } 284 285 for(File file : files) { 286 HistoryEntry before = beforeRevert.get(file); 287 HistoryEntry after = afterRevert.get(file); 288 289 beforeRevert.remove(file); 291 afterRevert.remove(file); 292 293 if(before != null && before.getStatus() == DELETED) { 294 ret.add(StoreEntry.createDeletedStoreEntry(file, ts)); 296 continue; 297 } 298 299 StoreDataFile data = readStoreData(file, true); 300 if(data == null) { 301 continue; 303 } 304 if(data.isFile()) { 305 StoreEntry se = getStoreEntry(file, ts); 306 if(se != null) { 307 ret.add(se); 308 } else { 309 if(after != null && after.getStatus() == TOUCHED) { 310 ret.add(StoreEntry.createDeletedStoreEntry(file, ts)); 311 } else { 312 } 314 } 316 } else { 317 if(after != null && after.getStatus() == TOUCHED) { 318 ret.add(StoreEntry.createDeletedStoreEntry(file, ts)); 319 } else { 320 } 322 } 324 } 325 326 327 for(Entry<File , HistoryEntry> entry : beforeRevert.entrySet()) { 328 329 File file = entry.getKey(); 330 331 afterRevert.remove(file); 333 334 if(entry.getValue().getStatus() == DELETED) { 337 continue; 339 } 340 341 StoreDataFile data = readStoreData(file, true); 342 if(data != null) { 343 if(data.isFile()) { 344 StoreEntry se = getStoreEntry(file, ts); 345 if(se != null) { 346 ret.add(se); 347 } else { 348 } 350 } else { 351 File storeFile = getStoreFolder(root); StoreEntry folderEntry = StoreEntry.createStoreEntry(new File (data.getAbsolutePath()), storeFile, data.getLastModified(), ""); 354 ret.add(folderEntry); 355 } 356 } else { 357 } 359 } 360 361 return ret.toArray(new StoreEntry[ret.size()]); 366 367 } 368 369 private boolean wasDeleted(File file, List <HistoryEntry> history , long ts) { 370 String path = file.getAbsolutePath(); 371 boolean deleted = false; 372 373 for(int i = 0; i < history.size(); i++) { 374 HistoryEntry he = history.get(i); 375 if(he.getTo().equals(path)) { 376 if(he.getStatus() == DELETED) { 377 deleted = true; 378 } else { 379 deleted = false; 380 } 381 } 382 if(he.ts >= ts) { 383 break; 384 } 385 } 386 return deleted; 387 } 388 389 public synchronized StoreEntry getStoreEntry(File file, long ts) { 390 return getStoreEntryImpl(file, ts, readStoreData(file, true)); 391 } 392 393 private StoreEntry getStoreEntryImpl(File file, long ts, StoreDataFile data) { 394 StoreEntry entry = null; 396 397 if(data == null) { 398 return null; 400 } 401 if(data.isFile()) { 402 StoreEntry[] entries = getStoreEntriesImpl(file); 403 for(StoreEntry se : entries) { 404 if(se.getTimestamp() <= ts) { 405 if( entry == null || se.getTimestamp() > entry.getTimestamp() ) { 406 entry = se; 407 } 408 } 409 } 410 } else { 411 } 413 414 return entry; 415 } 416 417 public synchronized void deleteEntry(File file, long ts) { 418 File storeFile = getStoreFile(file, Long.toString(ts), false); 419 if(storeFile.exists()) { 420 storeFile.delete(); 421 } 422 fireChanged(file, null); 424 } 425 426 public synchronized StoreEntry[] getDeletedFiles(File root) { 427 if(root.isFile()) { 428 return null; 429 } 430 431 Map <String , StoreEntry> deleted = new HashMap <String , StoreEntry>(); 432 List <HistoryEntry> entries = readHistoryForFile(root); 433 434 for(HistoryEntry he : entries) { 435 if(he.getStatus() == DELETED) { 436 String filePath = he.getTo(); 437 if(!deleted.containsKey(filePath)) { 438 StoreDataFile data = readStoreData(new File (he.getTo()), true); 439 if(data != null && data.getStatus() == DELETED) { 440 File storeFile = data.isFile ? 441 getStoreFile(new File (data.getAbsolutePath()), Long.toString(data.getLastModified()), false) : 442 getStoreFolder(root); deleted.put(filePath, StoreEntry.createStoreEntry(new File (data.getAbsolutePath()), storeFile, data.getLastModified(), "")); 444 } 445 } 446 } 447 } 448 return deleted.values().toArray(new StoreEntry[deleted.size()]); 449 } 450 451 public synchronized void setLabel(File file, long ts, String label) { 452 File labelsFile = getLabelsFile(file); 453 File parent = labelsFile.getParentFile(); 454 if(!parent.exists()) { 455 parent.mkdirs(); 456 } 457 458 File labelsNew = null; 459 DataInputStream dis = null; 460 DataOutputStream oos = null; 461 boolean foundLabel = false; 462 try { 463 if(!labelsFile.exists()) { 464 oos = new DataOutputStream (new BufferedOutputStream (new FileOutputStream (labelsFile))); 465 oos.writeLong(ts); 466 writeString(oos, label); 467 } else { 468 labelsNew = new File (labelsFile.getParentFile(), labelsFile.getName() + ".new"); oos = new DataOutputStream (new BufferedOutputStream (new FileOutputStream (labelsNew))); 470 471 dis = getInputStream(labelsFile); 472 long readTs = -1; 473 try { 474 while(true) { 475 readTs = dis.readLong(); 476 if(readTs == ts) { 477 oos.writeLong(readTs); 478 writeString(oos, label); 479 int len = dis.readInt(); 480 skip(dis, len * 2); 481 copyStreams(oos, dis); 482 break; 483 } else { 484 oos.writeLong(readTs); 485 String l = readString(dis); 486 writeString(oos, l); 487 } 488 } 489 } catch (EOFException e) { 490 if(!foundLabel) { 491 oos.writeLong(ts); 492 writeString(oos, label); 493 } 494 } 495 } 496 oos.flush(); 497 } catch (EOFException e) { 498 } catch (Exception e) { 500 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 501 } finally { 502 if (dis != null) { 503 try { dis.close(); } catch (IOException e) { } 504 } 505 if (oos != null) { 506 try { oos.close(); } catch (IOException e) { } 507 } 508 } 509 510 try { 511 if(labelsNew != null ) { 512 FileUtils.renameFile(labelsNew, labelsFile); 513 } 514 } catch (IOException ex) { 515 ErrorManager.getDefault().notify(ex); 516 } 517 518 return; 519 } 520 521 public synchronized void addPropertyChangeListener(PropertyChangeListener l) { 522 propertyChangeSupport.addPropertyChangeListener(l); 523 } 524 525 public synchronized void removePropertyChangeListener(PropertyChangeListener l) { 526 propertyChangeSupport.removePropertyChangeListener(l); 527 } 528 529 public void cleanUp(final long ttl) { 530 RequestProcessor.getDefault().post(new Runnable () { 532 public void run() { 533 if(Diagnostics.ON) { 534 Diagnostics.println("Cleanup Start"); } 536 537 cleanUpImpl(ttl); 538 539 if(Diagnostics.ON) { 540 Diagnostics.println("Cleanup End"); } 542 } 543 }); 544 } 545 546 private void cleanUpImpl(long ttl) { 547 548 550 long now = System.currentTimeMillis(); 551 552 File [] topLevelFiles = storage.listFiles(); 553 if(topLevelFiles == null || topLevelFiles.length == 0) { 554 return; 555 } 556 557 for(File topLevelFile : topLevelFiles) { 558 File [] secondLevelFiles = topLevelFile.listFiles(); 559 if(secondLevelFiles == null || secondLevelFiles.length == 0) { 560 FileUtils.deleteRecursively(topLevelFile); 561 continue; 562 } 563 564 boolean allEmpty = true; 565 for(File secondLevelFile : secondLevelFiles) { 566 boolean empty = cleanUpFolder(secondLevelFile, ttl, now); 567 if(empty) { 568 if(secondLevelFile.exists()) { 569 FileUtils.deleteRecursively(secondLevelFile); 570 } 571 } else { 572 allEmpty = false; 573 } 574 } 575 if(allEmpty) { 576 FileUtils.deleteRecursively(topLevelFile); 577 } 578 } 579 } 580 581 private synchronized boolean cleanUpFolder(File folder, long ttl, long now) { 582 File dataFile = new File (folder, DATA_FILE); 583 584 if(!dataFile.exists()) { 585 return cleanUpStoredFolder(folder, ttl, now); 587 } 588 589 StoreDataFile data = readStoreData(dataFile, false); 590 if(data.getAbsolutePath() == null) { 591 return true; 593 } 594 if(data.isFile()) { 595 return cleanUpStoredFile(folder, ttl, now); 596 } else { 597 return cleanUpStoredFolder(folder, ttl, now); 598 } 599 } 600 601 private boolean cleanUpStoredFile(File store, long ttl, long now) { 602 File dataFile = new File (store, DATA_FILE); 603 604 if(!dataFile.exists()) { 605 return true; 606 } 607 if(dataFile.lastModified() < now - ttl) { 608 purgeDataFile(dataFile); 609 return true; 610 } 611 612 File [] files = store.listFiles(fileEntriesFilter); 613 boolean skipped = false; 614 615 File labelsFile = new File (store, LABELS_FILE); 616 Map <Long , String > labels = emptyLabels; 617 if(labelsFile.exists()) { 618 labels = getLabels(labelsFile); 619 } 620 for(File f : files) { 621 long ts = Long.parseLong(f.getName()); 623 if(ts < now - ttl) { 624 if(labels.size() > 0) { 625 labels.remove(ts); 626 } 627 f.delete(); 628 } else { 629 skipped = true; 630 } 631 } 632 if(!skipped) { 633 labelsFile.delete(); 635 writeStoreData(dataFile, null, false); 636 } else { 637 if(labels.size() > 0) { 638 writeLabels(labelsFile, labels); 639 } 640 } 641 return !skipped; 642 } 643 644 private void writeLabels(File labelsFile, Map <Long , String > labels) { 645 File parent = labelsFile.getParentFile(); 646 if(!parent.exists()) { 647 parent.mkdirs(); 648 } 649 DataInputStream dis = null; 650 DataOutputStream oos = null; 651 try { 652 for(Entry<Long , String > label : labels.entrySet()) { 653 oos = new DataOutputStream (new BufferedOutputStream (new FileOutputStream (labelsFile))); 654 oos.writeLong(label.getKey()); 655 writeString(oos, label.getValue()); 656 } 657 oos.flush(); 658 } catch (Exception e) { 659 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 660 } finally { 661 if (dis != null) { 662 try { dis.close(); } catch (IOException e) { } 663 } 664 if (oos != null) { 665 try { oos.close(); } catch (IOException e) { } 666 } 667 } 668 } 669 670 private boolean cleanUpStoredFolder(File store, long ttl, long now) { 671 File historyFile = new File (store, HISTORY_FILE); 672 File dataFile = new File (store, DATA_FILE); 673 674 boolean dataObsolete = !dataFile.exists() || dataFile.lastModified() < now - ttl; 675 boolean historyObsolete = !historyFile.exists() || historyFile.lastModified() < now - ttl; 676 677 if(!historyObsolete) { 678 List <HistoryEntry> entries = readHistory(historyFile); 679 historyFile.delete(); 680 List <HistoryEntry> newEntries = new ArrayList <HistoryEntry>(); 681 for(HistoryEntry entry : entries) { 682 if(entry.getTimestamp() > now - ttl) { 684 newEntries.add(entry); 685 } 686 } 687 if(newEntries.size() > 0) { 688 writeHistory(historyFile, newEntries.toArray(new HistoryEntry[newEntries.size()])); 689 } else { 690 historyObsolete = true; 691 } 692 } 693 if(dataObsolete) { 694 purgeDataFile(dataFile); 695 } 696 if(historyObsolete) { 697 historyFile.delete(); 698 } 699 700 return dataObsolete && historyObsolete; 701 } 702 703 private void purgeDataFile(File dataFile) { 704 if(dataFile.exists()) { 705 writeStoreData(dataFile, null, false); 706 } 707 } 708 709 private void fireChanged(File oldValue, File newValue) { 710 propertyChangeSupport.firePropertyChange( 711 new PropertyChangeEvent ( 712 this, 713 LocalHistoryStore.PROPERTY_CHANGED, 714 oldValue, 715 newValue)); 716 } 717 718 private void touch(File file, StoreDataFile data) throws IOException { 719 writeStoreData(file, data, true); 720 } 721 722 private void initStorage() { 723 String userDir = System.getProperty("netbeans.user"); storage = new File (new File (userDir , "var"), "filehistory"); if(!storage.exists()) { 726 storage.mkdirs(); 727 } 728 } 729 730 private File getStoreFolder(File file) { 731 String filePath = file.getAbsolutePath(); 732 File storeFolder = getStoreFolderName(filePath); 733 int i = 0; 734 while(storeFolder.exists()) { 735 StoreDataFile data = readStoreData(new File (storeFolder, DATA_FILE), false); 737 if(data == null || data.getAbsolutePath().equals(filePath)) { 738 break; 739 } 740 storeFolder = getStoreFolderName(filePath + "." + i++); 741 } 742 return storeFolder; 743 } 744 745 private File getStoreFolderName(String filePath) { 746 int fileHash = filePath.hashCode(); 747 String storeFileName = getMD5(filePath); 748 String storeIndex = storage.getAbsolutePath() + "/" + Integer.toString(fileHash % 173 + 172); return new File (storeIndex + "/" + storeFileName); } 751 752 private String getMD5(String name) { 753 MessageDigest digest; 754 try { 755 digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { 757 return null; 759 } 760 digest.update(name.getBytes()); 761 byte[] hash = digest.digest(); 762 StringBuffer ret = new StringBuffer (); 763 for (int i = 0; i < hash.length; i++) { 764 String hex = Integer.toHexString(hash[i] & 0x000000FF); 765 if(hex.length()==1) { 766 hex = "0" + hex; } 768 ret.append(hex); 769 } 770 return ret.toString(); 771 } 772 773 private File getStoreFile(File file, String name, boolean mkdirs) { 774 File storeFolder = getStoreFolder(file); 775 if(mkdirs && !storeFolder.exists()) { 776 storeFolder.mkdirs(); 777 } 778 return new File (storeFolder, name); 779 } 780 781 private File getHistoryFile(File file) { 782 File storeFolder = getStoreFolder(file); 783 if(!storeFolder.exists()) { 784 storeFolder.mkdirs(); 785 } 786 return new File (storeFolder, HISTORY_FILE); 787 } 788 789 private File getDataFile(File file) { 790 File storeFolder = getStoreFolder(file); 791 return new File (storeFolder, DATA_FILE); 792 } 793 794 private File getLabelsFile(File file) { 795 File storeFolder = getStoreFolder(file); 796 return new File (storeFolder, LABELS_FILE); 797 } 798 799 private Map <Long , String > getLabels(File labelsFile) { 800 801 if(!labelsFile.exists()) { 802 return emptyLabels; 803 } 804 DataInputStream dis = null; 805 Map <Long , String > ret = new HashMap <Long , String >(); 806 try { 807 dis = getInputStream(labelsFile); 808 while(true) { 809 long ts = dis.readLong(); 810 String label = readString(dis); 811 ret.put(ts, label); 812 } 813 } catch (EOFException e) { 814 return ret; 815 } catch (Exception e) { 816 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 817 } finally { 818 if (dis != null) { 819 try { dis.close(); } catch (IOException e) { } 820 } 821 } 822 return emptyLabels; 823 } 824 825 private void writeHistoryForFile(File file, HistoryEntry[] entries) { 826 if(Diagnostics.ON) { 827 if(getDataFile(file) == null) { 828 Diagnostics.println("writing history for file without data : " + file); } 830 } 831 File history = getHistoryFile(file); 832 writeHistory(history, entries); 833 } 834 835 private void writeHistory(File history, HistoryEntry[] entries) { 836 DataOutputStream dos = null; 837 try { 838 dos = getOutputStream(history, true); 839 for(HistoryEntry entry : entries) { 840 dos.writeLong(entry.getTimestamp()); 841 writeString(dos, entry.getFrom()); 842 writeString(dos, entry.getTo()); 843 dos.writeInt(entry.getStatus()); 844 } 845 dos.flush(); 846 } catch (Exception e) { 847 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 848 return; 849 } 850 finally { 851 if (dos != null) { 852 try { dos.close(); } catch (IOException e) { } 853 } 854 } 855 } 856 857 private List <HistoryEntry> readHistoryForFile(File file) { 858 return readHistory(getHistoryFile(file)); 859 } 860 861 private List <HistoryEntry> readHistory(File history) { 862 if(!history.exists()) { 863 return emptyHistory; 864 } 865 DataInputStream dis = null; 866 List <HistoryEntry> entries = new ArrayList <HistoryEntry>(); 867 try { 868 dis = getInputStream(history); 869 while(true) { 870 long ts = dis.readLong(); 871 String from = readString(dis); 872 String to = readString(dis); 873 int action = dis.readInt(); 874 entries.add(new HistoryEntry(ts, from, to, action)); 875 } 876 } catch (EOFException e) { 877 return entries; 878 } catch (Exception e) { 879 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 880 } finally { 881 if (dis != null) { 882 try { dis.close(); } catch (IOException e) { } 883 } 884 } 885 return emptyHistory; 886 } 887 888 private StoreDataFile readStoreData(File file, boolean isOriginalFile) { 889 if(isOriginalFile) { 890 file = getDataFile(file); 891 } 892 return (StoreDataFile) turbo.readEntry(file, DataFilesTurboProvider.ATTR_DATA_FILES); 893 } 894 895 private void writeStoreData(File file, StoreDataFile data, boolean isOriginalFile) { 896 if(isOriginalFile) { 897 file = getDataFile(file); 898 } 899 turbo.writeEntry(file, DataFilesTurboProvider.ATTR_DATA_FILES, data); 900 } 901 902 private static void writeString(DataOutputStream dos, String str) throws IOException { 903 if(str != null) { 904 dos.writeInt(str.length()); 905 dos.writeChars(str); 906 } else { 907 dos.writeInt(0); 908 } 909 } 910 911 private static String readString(DataInputStream dis) throws IOException { 912 int len = dis.readInt(); 913 if(len == 0) { 914 return ""; 915 } 916 StringBuffer sb = new StringBuffer (); 917 while(len-- > 0) { 918 char c = dis.readChar(); 919 sb.append(c); 920 } 921 return sb.toString(); 922 } 923 924 private static void skip(InputStream is, long len) throws IOException { 925 while (len > 0) { 926 long n = is.skip(len); 927 if (n < 0) throw new EOFException ("Missing " + len + " bytes."); len -= n; 929 } 930 } 931 932 private static void copyStreams(OutputStream out, InputStream in) throws IOException { 933 byte [] buffer = new byte[4096]; 934 for (;;) { 935 int n = in.read(buffer); 936 if (n < 0) break; 937 out.write(buffer, 0, n); 938 } 939 } 940 941 private static DataOutputStream getOutputStream(File file, boolean append) throws IOException , InterruptedException { 942 int retry = 0; 943 while (true) { 944 try { 945 return new DataOutputStream (new BufferedOutputStream (new FileOutputStream (file, append))); 946 } catch (IOException ioex) { 947 retry++; 948 if (retry > 7) { 949 throw ioex; 950 } 951 Thread.sleep(retry * 30); 952 } 953 } 954 } 955 956 private static DataInputStream getInputStream(File file) throws IOException , InterruptedException { 957 int retry = 0; 958 while (true) { 959 try { 960 return new DataInputStream (new BufferedInputStream (new FileInputStream (file))); 961 } catch (IOException ioex) { 962 retry++; 963 if (retry > 7) { 964 throw ioex; 965 } 966 Thread.sleep(retry * 30); 967 } 968 } 969 } 970 971 private class HistoryEntry { 972 private long ts; 973 private String from; 974 private String to; 975 private int status; 976 HistoryEntry(long ts, String from, String to, int action) { 977 this.ts = ts; 978 this.from = from; 979 this.to = to; 980 this.status = action; 981 } 982 long getTimestamp() { 983 return ts; 984 } 985 String getFrom() { 986 return from; 987 } 988 String getTo() { 989 return to; 990 } 991 int getStatus() { 992 return status; 993 } 994 } 995 996 private static class StoreDataFile { 997 private final int status; 998 private final long lastModified; 999 private final String absolutePath; 1000 private final boolean isFile; 1001 1002 private StoreDataFile(String absolutePath, int action, long lastModified, boolean isFile) { 1003 this.status = action; 1004 this.lastModified = lastModified; 1005 this.absolutePath = absolutePath; 1006 this.isFile = isFile; 1007 } 1008 1009 int getStatus() { 1010 return status; 1011 } 1012 1013 long getLastModified() { 1014 return lastModified; 1015 } 1016 1017 String getAbsolutePath() { 1018 return absolutePath; 1019 } 1020 1021 boolean isFile() { 1022 return isFile; 1023 } 1024 1025 static synchronized StoreDataFile read(File storeFile) { 1026 DataInputStream dis = null; 1027 try { 1028 dis = getInputStream(storeFile); 1029 boolean isFile = dis.readBoolean(); 1030 int action = dis.readInt(); 1031 long modified = dis.readLong(); 1032 String fileName = readString(dis); 1033 return new StoreDataFile(fileName, action, modified, isFile); 1034 } catch (Exception e) { 1035 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 1036 } finally { 1037 if (dis != null) { 1038 try { dis.close(); } catch (IOException e) { } 1039 } 1040 } 1041 return null; 1042 } 1043 1044 static synchronized void write(File storeFile, StoreDataFile value) { 1045 DataOutputStream dos = null; 1046 try { 1047 dos = getOutputStream(storeFile, false); 1048 StoreDataFile data = (StoreDataFile) value; 1049 dos.writeBoolean(data.isFile); 1050 dos.writeInt(data.getStatus()); 1051 dos.writeLong(data.getLastModified()); 1052 dos.writeInt(data.getAbsolutePath().length()); 1053 dos.writeChars(data.getAbsolutePath()); 1054 dos.flush(); 1055 } catch (Exception e) { 1056 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 1057 } finally { 1058 if (dos != null) { 1059 try { dos.close(); } catch (IOException e) { } 1060 } 1061 } 1062 } 1063 } 1064 1065 private class DataFilesTurboProvider implements TurboProvider { 1066 1067 static final String ATTR_DATA_FILES = "localhistory.ATTR_DATA_FILES"; 1069 public boolean recognizesAttribute(String name) { 1070 return ATTR_DATA_FILES.equals(name); 1071 } 1072 1073 public boolean recognizesEntity(Object key) { 1074 return key instanceof File ; 1075 } 1076 1077 public synchronized Object readEntry(Object key, String name, MemoryCache memoryCache) { 1078 assert key instanceof File ; 1079 assert name != null; 1080 1081 File storeFile = (File ) key; 1082 if(!storeFile.exists()) { 1083 return null; 1084 } 1085 return StoreDataFile.read(storeFile); 1086 } 1087 1088 public synchronized boolean writeEntry(Object key, String name, Object value) { 1089 assert key instanceof File ; 1090 assert value == null || value instanceof StoreDataFile; 1091 assert name != null; 1092 1093 File storeFile = (File ) key; 1094 if(value == null) { 1095 if(storeFile.exists()) { 1096 storeFile.delete(); 1097 } 1098 return true; 1099 } 1100 1101 File parent = storeFile.getParentFile(); 1102 if(!parent.exists()) { 1103 parent.mkdirs(); 1104 } 1105 StoreDataFile.write(storeFile, (StoreDataFile) value); 1106 return true; 1107 } 1108 } 1109 1110} | Popular Tags |