1 11 12 package org.eclipse.osgi.framework.internal.reliablefile; 13 14 import java.io.*; 15 import java.util.*; 16 import java.util.zip.CRC32 ; 17 import java.util.zip.Checksum ; 18 import org.eclipse.osgi.framework.internal.core.FrameworkProperties; 19 20 25 public class ReliableFile { 26 34 public static final int OPEN_BEST_AVAILABLE = 0; 35 42 public static final int OPEN_FAIL_ON_PRIMARY = 1; 43 44 47 public static final int GENERATION_LATEST = 0; 48 51 public static final int GENERATIONS_INFINITE = 0; 52 53 58 public static final String tmpExt = ".tmp"; 60 67 public static final String PROP_MAX_BUFFER = "osgi.reliableFile.maxInputStreamBuffer"; 72 public static final String PROP_MAX_GENERATIONS = "osgi.ReliableFile.maxGenerations"; 76 public static final String PROP_OSGI_LOCKING = "osgi.locking"; 78 protected static final int FILETYPE_UNKNOWN = -1; 79 protected static final int FILETYPE_VALID = 0; 80 protected static final int FILETYPE_CORRUPT = 1; 81 protected static final int FILETYPE_NOSIGNATURE = 2; 82 83 protected static final byte identifier1[] = {'.', 'c', 'r', 'c'}; 84 protected static final byte identifier2[] = {'.', 'v', '1', '\n'}; 85 86 private static final int BUF_SIZE = 4096; 87 private static int maxInputStreamBuffer = 128 * 1024; private static int defaultMaxGenerations = 2; 89 private static boolean fileSharing = true; 90 private static File lastGenerationFile = null; 92 private static int[] lastGenerations = null; 93 94 static { 95 String prop = FrameworkProperties.getProperty(PROP_MAX_BUFFER); 96 if (prop != null) { 97 try { 98 maxInputStreamBuffer = Integer.parseInt(prop); 99 } catch (NumberFormatException e) { 100 } 101 } 102 prop = FrameworkProperties.getProperty(PROP_MAX_GENERATIONS); 103 if (prop != null) { 104 try { 105 defaultMaxGenerations = Integer.parseInt(prop); 106 } catch (NumberFormatException e) { 107 } 108 } 109 prop = FrameworkProperties.getProperty(PROP_OSGI_LOCKING); 110 if (prop != null) { 111 if (prop.equals("none")) { fileSharing = false; 113 } 114 } 115 } 116 117 118 private File referenceFile; 119 120 121 private static Hashtable cacheFiles = new Hashtable(20); 122 123 private File inputFile = null; 124 private File outputFile = null; 125 private Checksum appendChecksum = null; 126 127 138 protected static ReliableFile getReliableFile(String name) throws IOException { 139 return getReliableFile(new File(name)); 140 } 141 142 153 protected static ReliableFile getReliableFile(File file) throws IOException { 154 if (file.isDirectory()) { 155 throw new FileNotFoundException("file is a directory"); } 157 return new ReliableFile(file); 158 } 159 160 165 private ReliableFile(File file) { 166 referenceFile = file; 167 } 168 169 private static int[] getFileGenerations(File file) { 170 if (!fileSharing && lastGenerationFile != null) { 171 if (file.equals(lastGenerationFile)) 173 return lastGenerations; 174 } 175 int[] generations = null; 176 try { 177 String name = file.getName(); 178 String prefix = name + '.'; 179 int prefixLen = prefix.length(); 180 File parent = new File(file.getParent()); 181 String [] files = parent.list(); 182 if (files == null) 183 return null; 184 ArrayList list = new ArrayList(defaultMaxGenerations); 185 if (file.exists()) 186 list.add(new Integer (0)); for (int i = 0; i < files.length; i++) { 188 if (files[i].startsWith(prefix)) { 189 try { 190 int id = Integer.parseInt(files[i].substring(prefixLen)); 191 list.add(new Integer (id)); 192 } catch (NumberFormatException e) { 193 } 194 } 195 } 196 if (list.size() == 0) 197 return null; 198 Object [] array = list.toArray(); 199 Arrays.sort(array); 200 generations = new int[array.length]; 201 for (int i = 0, j = array.length - 1; i < array.length; i++, j--) { 202 generations[i] = ((Integer ) array[j]).intValue(); 203 } 204 return generations; 205 } finally { 206 if (!fileSharing) { 207 lastGenerationFile = file; 208 lastGenerations = generations; 209 } 210 } 211 } 212 213 222 protected InputStream getInputStream(int generation, int openMask) throws IOException { 223 if (inputFile != null) { 224 throw new IOException("Input stream already open"); } 226 int[] generations = getFileGenerations(referenceFile); 227 if (generations == null) { 228 throw new FileNotFoundException("File not found"); } 230 String name = referenceFile.getName(); 231 File parent = new File(referenceFile.getParent()); 232 233 boolean failOnPrimary = (openMask & OPEN_FAIL_ON_PRIMARY) != 0; 234 if (failOnPrimary && generation == GENERATIONS_INFINITE) 235 generation = generations[0]; 236 237 File textFile = null; 238 InputStream textIS = null; 239 for (int idx = 0; idx < generations.length; idx++) { 240 if (generation != 0) { 241 if (generations[idx] > generation || (failOnPrimary && generations[idx] != generation)) 242 continue; 243 } 244 File file; 245 if (generations[idx] != 0) 246 file = new File(parent, name + '.' + generations[idx]); 247 else 248 file = referenceFile; 249 InputStream is = null; 250 CacheInfo info; 251 synchronized (cacheFiles) { 252 info = (CacheInfo) cacheFiles.get(file); 253 } 254 long timeStamp = file.lastModified(); 255 if (info == null || timeStamp != info.timeStamp) { 256 try { 257 is = new FileInputStream(file); 258 if (is.available() < maxInputStreamBuffer) 259 is = new BufferedInputStream(is); 260 Checksum cksum = getChecksumCalculator(); 261 int filetype = getStreamType(is, cksum); 262 info = new CacheInfo(filetype, cksum, timeStamp); 263 synchronized (cacheFiles) { 264 cacheFiles.put(file, info); 265 } 266 } catch (IOException e) { 267 } 268 } 269 270 if (failOnPrimary) { 273 if (info != null && info.filetype == FILETYPE_VALID) { 274 inputFile = file; 275 if (is != null) 276 return is; 277 return new FileInputStream(file); 278 } 279 throw new IOException("ReliableFile is corrupt"); } 281 282 if (info == null) 284 continue; 285 286 switch (info.filetype) { 288 case FILETYPE_VALID : 289 inputFile = file; 290 if (is != null) 291 return is; 292 return new FileInputStream(file); 293 294 case FILETYPE_NOSIGNATURE : 295 if (textFile == null) { 296 textFile = file; 297 textIS = is; 298 } 299 break; 300 } 301 } 302 303 if (textFile != null) { 306 inputFile = textFile; 307 if (textIS != null) 308 return textIS; 309 return new FileInputStream(textFile); 310 } 311 throw new IOException("ReliableFile is corrupt"); } 313 314 322 protected OutputStream getOutputStream(boolean append, int appendGeneration) throws IOException { 323 if (outputFile != null) 324 throw new IOException("Output stream is already open"); String name = referenceFile.getName(); 326 File parent = new File(referenceFile.getParent()); 327 File tmpFile = File.createTempFile(name, tmpExt, parent); 328 329 if (!append) { 330 OutputStream os = new FileOutputStream(tmpFile); 331 outputFile = tmpFile; 332 return os; 333 } 334 335 InputStream is; 336 try { 337 is = getInputStream(appendGeneration, OPEN_BEST_AVAILABLE); 338 } catch (FileNotFoundException e) { 339 OutputStream os = new FileOutputStream(tmpFile); 340 outputFile = tmpFile; 341 return os; 342 } 343 344 try { 345 CacheInfo info = (CacheInfo) cacheFiles.get(inputFile); 346 appendChecksum = info.checksum; 347 OutputStream os = new FileOutputStream(tmpFile); 348 if (info.filetype == FILETYPE_NOSIGNATURE) { 349 cp(is, os, 0); 350 } else { 351 cp(is, os, 16); } 353 outputFile = tmpFile; 354 return os; 355 } finally { 356 closeInputFile(); 357 } 358 } 359 360 366 protected void closeOutputFile(Checksum checksum) throws IOException { 367 if (outputFile == null) 368 throw new IOException("Output stream is not open"); int[] generations = getFileGenerations(referenceFile); 370 String name = referenceFile.getName(); 371 File parent = new File(referenceFile.getParent()); 372 File newFile; 373 if (generations == null) 374 newFile = new File(parent, name + ".1"); else 376 newFile = new File(parent, name + '.' + (generations[0] + 1)); 377 378 mv(outputFile, newFile); outputFile = null; 380 appendChecksum = null; 381 CacheInfo info = new CacheInfo(FILETYPE_VALID, checksum, newFile.lastModified()); 382 cacheFiles.put(newFile, info); 383 cleanup(generations, true); 384 lastGenerationFile = null; 385 lastGenerations = null; 386 } 387 388 392 protected void abortOutputFile() { 393 if (outputFile == null) 394 return; 395 outputFile.delete(); 396 outputFile = null; 397 appendChecksum = null; 398 } 399 400 protected File getOutputFile() { 401 return outputFile; 402 } 403 404 407 void closeInputFile() { 408 inputFile = null; 409 } 410 411 private void cleanup(int[] generations, boolean generationAdded) { 412 if (generations == null) 413 return; 414 String name = referenceFile.getName(); 415 File parent = new File(referenceFile.getParent()); 416 int generationCount = generations.length; 417 if (generations[generationCount - 1] == 0) 421 generationCount--; 422 int rmCount = generationCount - defaultMaxGenerations; 424 if (generationAdded) 425 rmCount++; 426 if (rmCount < 1) 427 return; 428 synchronized (cacheFiles) { 429 for (int idx = 0, count = generationCount - rmCount; idx < count; idx++) { 433 File file = new File(parent, name + '.' + generations[idx]); 434 CacheInfo info = (CacheInfo) cacheFiles.get(file); 435 if (info != null) { 436 if (info.filetype == FILETYPE_CORRUPT) 437 rmCount--; 438 } 439 } 440 for (int idx = generationCount - 1; rmCount > 0; idx--, rmCount--) { 441 File rmFile = new File(parent, name + '.' + generations[idx]); 442 rmFile.delete(); 443 cacheFiles.remove(rmFile); 444 } 445 } 446 } 447 448 455 private static void mv(File from, File to) throws IOException { 456 if (!from.renameTo(to)) { 457 throw new IOException("rename failed"); } 459 } 460 461 466 private static void cp(InputStream in, OutputStream out, int truncateSize) throws IOException { 467 try { 468 int length = in.available(); 469 if (truncateSize > length) 470 length = 0; 471 else 472 length -= truncateSize; 473 if (length > 0) { 474 int bufferSize; 475 if (length > BUF_SIZE) { 476 bufferSize = BUF_SIZE; 477 } else { 478 bufferSize = length; 479 } 480 481 byte buffer[] = new byte[bufferSize]; 482 int size = 0; 483 int count; 484 while ((count = in.read(buffer, 0, length)) > 0) { 485 if ((size + count) >= length) 486 count = length - size; 487 out.write(buffer, 0, count); 488 size += count; 489 } 490 } 491 } finally { 492 try { 493 in.close(); 494 } catch (IOException e) { 495 } 496 out.close(); 497 } 498 } 499 500 509 public static boolean exists(File file) { 510 String prefix = file.getName() + '.'; 511 File parent = new File(file.getParent()); 512 int prefixLen = prefix.length(); 513 String [] files = parent.list(); 514 if (files == null) 515 return false; 516 for (int i = 0; i < files.length; i++) { 517 if (files[i].startsWith(prefix)) { 518 try { 519 Integer.parseInt(files[i].substring(prefixLen)); 520 return true; 521 } catch (NumberFormatException e) { 522 } 523 } 524 } 525 return file.exists(); 526 } 527 528 534 public static long lastModified(File file) { 535 int[] generations = getFileGenerations(file); 536 if (generations == null) 537 return 0L; 538 if (generations[0] == 0) 539 return file.lastModified(); 540 String name = file.getName(); 541 File parent = new File(file.getParent()); 542 File newFile = new File(parent, name + '.' + generations[0]); 543 return newFile.lastModified(); 544 } 545 546 553 public long lastModified() { 554 if (inputFile != null) { 555 return inputFile.lastModified(); 556 } 557 return 0L; 558 } 559 560 568 public static int lastModifiedVersion(File file) { 569 int[] generations = getFileGenerations(file); 570 if (generations == null) 571 return -1; 572 return generations[0]; 573 } 574 575 582 public static boolean delete(File deleteFile) { 583 int[] generations = getFileGenerations(deleteFile); 584 if (generations == null) 585 return false; 586 String name = deleteFile.getName(); 587 File parent = new File(deleteFile.getParent()); 588 synchronized (cacheFiles) { 589 for (int idx = 0; idx < generations.length; idx++) { 590 if (generations[idx] == 0) 592 continue; 593 File file = new File(parent, name + '.' + generations[idx]); 594 if (file.exists()) { 595 file.delete(); 596 } 597 cacheFiles.remove(file); 598 } 599 } 600 return true; 601 } 602 603 610 public static String [] getBaseFiles(File directory) throws IOException { 611 if (!directory.isDirectory()) 612 throw new IOException("Not a valid directory"); String files[] = directory.list(); 614 HashSet list = new HashSet(files.length / 2); 615 for (int idx = 0; idx < files.length; idx++) { 616 String file = files[idx]; 617 int pos = file.lastIndexOf('.'); 618 if (pos == -1) 619 continue; 620 String ext = file.substring(pos + 1); 621 int generation = 0; 622 try { 623 generation = Integer.parseInt(ext); 624 } catch (NumberFormatException e) { 625 } 626 if (generation == 0) 627 continue; 628 String base = file.substring(0, pos); 629 list.add(base); 630 } 631 files = new String [list.size()]; 632 int idx = 0; 633 for (Iterator iter = list.iterator(); iter.hasNext();) { 634 files[idx++] = (String ) iter.next(); 635 } 636 return files; 637 } 638 639 643 public static void cleanupGenerations(File base) { 644 ReliableFile rf = new ReliableFile(base); 645 int[] generations = getFileGenerations(base); 646 rf.cleanup(generations, false); 647 lastGenerationFile = null; 648 lastGenerations = null; 649 } 650 651 656 public static void fileUpdated(File file) { 657 lastGenerationFile = null; 658 lastGenerations = null; 659 } 660 661 667 protected void writeChecksumSignature(OutputStream out, Checksum checksum) throws IOException { 668 out.write(ReliableFile.identifier1); 670 out.write(intToHex((int) checksum.getValue())); 671 out.write(ReliableFile.identifier2); 672 } 673 674 684 protected int getSignatureSize() throws IOException { 685 if (inputFile != null) { 686 CacheInfo info; 687 synchronized (cacheFiles) { 688 info = (CacheInfo) cacheFiles.get(inputFile); 689 } 690 if (info != null) { 691 switch (info.filetype) { 692 case FILETYPE_VALID : 693 case FILETYPE_CORRUPT : 694 return 16; 695 case FILETYPE_NOSIGNATURE : 696 return 0; 697 } 698 } 699 } 700 throw new IOException("ReliableFile signature size is unknown"); } 702 703 712 protected Checksum getFileChecksum() throws IOException { 713 if (appendChecksum == null) 714 throw new IOException("Checksum is invalid!"); return appendChecksum; 716 } 717 718 724 protected Checksum getChecksumCalculator() { 725 return new CRC32 (); 727 } 728 729 735 private int getStreamType(InputStream is, Checksum crc) throws IOException { 736 boolean markSupported = is.markSupported(); 737 if (markSupported) 738 is.mark(is.available()); 739 try { 740 int len = is.available(); 741 if (len < 16) { 742 if (crc != null) { 743 byte data[] = new byte[16]; 744 int num = is.read(data); 745 if (num > 0) 746 crc.update(data, 0, num); 747 } 748 return FILETYPE_NOSIGNATURE; 749 } 750 len -= 16; 751 752 int pos = 0; 753 byte data[] = new byte[BUF_SIZE]; 754 755 while (pos < len) { 756 int read = data.length; 757 if (pos + read > len) 758 read = len - pos; 759 760 int num = is.read(data, 0, read); 761 if (num == -1) { 762 throw new IOException("Unable to read entire file."); } 764 765 crc.update(data, 0, num); 766 pos += num; 767 } 768 769 int num = is.read(data); if (num != 16) { 771 throw new IOException("Unable to read entire file."); } 773 774 int i, j; 775 for (i = 0; i < 4; i++) 776 if (identifier1[i] != data[i]) { 777 crc.update(data, 0, 16); return FILETYPE_NOSIGNATURE; 779 } 780 for (i = 0, j = 12; i < 4; i++, j++) 781 if (identifier2[i] != data[j]) { 782 crc.update(data, 0, 16); return FILETYPE_NOSIGNATURE; 784 } 785 long crccmp; 786 try { 787 crccmp = Long.valueOf(new String (data, 4, 8, "UTF-8"), 16).longValue(); } catch (UnsupportedEncodingException e) { 789 crccmp = Long.valueOf(new String (data, 4, 8), 16).longValue(); 790 } 791 if (crccmp == crc.getValue()) { 792 return FILETYPE_VALID; 793 } 794 return FILETYPE_CORRUPT; 796 } finally { 797 if (markSupported) 798 is.reset(); 799 } 800 } 801 802 private static byte[] intToHex(int l) { 803 byte[] buffer = new byte[8]; 804 int count = 8; 805 806 do { 807 int ch = (l & 0xf); 808 if (ch > 9) 809 ch = ch - 10 + 'a'; 810 else 811 ch += '0'; 812 buffer[--count] = (byte) ch; 813 l >>= 4; 814 } while (count > 0); 815 return buffer; 816 } 817 818 private class CacheInfo { 819 int filetype; 820 Checksum checksum; 821 long timeStamp; 822 823 CacheInfo(int filetype, Checksum checksum, long timeStamp) { 824 this.filetype = filetype; 825 this.checksum = checksum; 826 this.timeStamp = timeStamp; 827 } 828 } 829 } 830 | Popular Tags |