1 /* 2 * Created on Oct 10, 2003 3 * Modified Apr 14, 2004 by Alon Rohter 4 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License 8 * as published by the Free Software Foundation; either version 2 9 * of the License, or (at your option) any later version. 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 * 18 */ 19 20 package org.gudy.azureus2.core3.util; 21 22 import java.io.*; 23 import java.lang.reflect.Method; 24 import java.net.SocketTimeoutException; 25 import java.net.URI; 26 import java.net.URL; 27 import java.util.*; 28 import java.util.regex.Matcher; 29 import java.util.regex.Pattern; 30 import java.util.zip.GZIPInputStream; 31 32 import org.gudy.azureus2.core3.config.COConfigurationListener; 33 import org.gudy.azureus2.core3.config.COConfigurationManager; 34 import org.gudy.azureus2.core3.config.ParameterListener; 35 import org.gudy.azureus2.core3.logging.LogEvent; 36 import org.gudy.azureus2.core3.logging.LogIDs; 37 import org.gudy.azureus2.core3.logging.Logger; 38 import org.gudy.azureus2.platform.PlatformManager; 39 import org.gudy.azureus2.platform.PlatformManagerCapabilities; 40 import org.gudy.azureus2.platform.PlatformManagerFactory; 41 import org.gudy.azureus2.plugins.platform.PlatformManagerException; 42 43 import com.aelitis.azureus.core.AzureusCore; 44 import com.aelitis.azureus.core.AzureusCoreFactory; 45 import com.aelitis.azureus.core.AzureusCoreOperation; 46 import com.aelitis.azureus.core.AzureusCoreOperationTask; 47 48 /** 49 * File utility class. 50 */ 51 public class FileUtil { 52 private static final LogIDs LOGID = LogIDs.CORE; 53 public static final String DIR_SEP = System.getProperty("file.separator"); 54 55 56 private static final int RESERVED_FILE_HANDLE_COUNT = 4; 57 private static boolean first_reservation = true; 58 private static boolean is_my_lock_file = false; 59 private static List reserved_file_handles = new ArrayList(); 60 private static AEMonitor class_mon = new AEMonitor( "FileUtil:class" ); 61 62 private static Method reflectOnUsableSpace; 63 64 private static char[] char_conversion_mapping = null; 65 66 67 static { 68 69 try 70 { 71 reflectOnUsableSpace = File.class.getMethod("getUsableSpace", (Class[])null); 72 } catch (Throwable e) 73 { 74 reflectOnUsableSpace = null; 75 } 76 } 77 isAncestorOf(File parent, File child)78 public static boolean isAncestorOf(File parent, File child) { 79 parent = canonise(parent); 80 child = canonise(child); 81 if (parent.equals(child)) {return true;} 82 String parent_s = parent.getPath(); 83 String child_s = child.getPath(); 84 if (parent_s.charAt(parent_s.length()-1) != File.separatorChar) { 85 parent_s += File.separatorChar; 86 } 87 return child_s.startsWith(parent_s); 88 } 89 canonise(File file)90 public static File canonise(File file) { 91 try {return file.getCanonicalFile();} 92 catch (IOException ioe) {return file;} 93 } 94 getCanonicalFileName(String filename)95 public static String getCanonicalFileName(String filename) { 96 // Sometimes Windows use filename in 8.3 form and cannot 97 // match .torrent extension. To solve this, canonical path 98 // is used to get back the long form 99 100 String canonicalFileName = filename; 101 try { 102 canonicalFileName = new File(filename).getCanonicalPath(); 103 } 104 catch (IOException ignore) {} 105 return canonicalFileName; 106 } 107 108 getUserFile(String filename)109 public static File getUserFile(String filename) { 110 return new File(SystemProperties.getUserPath(), filename); 111 } 112 113 /** 114 * Get a file relative to this program's install directory. 115 * <p> 116 * On Windows, this is usually %Program Files%\[AppName]\[filename] 117 * <br> 118 * On *nix, this is usually the [Launch Dir]/[filename] 119 * <br> 120 * On Mac, this is "/Users/Shared/Library/Application Support/[AppName]/[filename]" 121 * On legacy (unsigned) Mac, it's usually "[AppName].app/Contents" 122 */ getApplicationFile(String filename)123 public static File getApplicationFile(String filename) { 124 125 String path = SystemProperties.getApplicationPath(); 126 127 if (Constants.isOSX && !new File(path, "Azureus2.jar").exists()) { 128 // Legacy Mac, we stored things inside the .app, which caused 129 // signature breakage on change. 130 path = path + SystemProperties.getApplicationName() + ".app/Contents/"; 131 } 132 133 return new File(path, filename); 134 } 135 136 137 138 /** 139 * Deletes the given dir and all files/dirs underneath 140 */ recursiveDelete(File f)141 public static boolean recursiveDelete(File f) { 142 String defSaveDir = COConfigurationManager.getStringParameter("Default save path"); 143 String moveToDir = COConfigurationManager.getStringParameter("Completed Files Directory", ""); 144 145 try{ 146 moveToDir = new File(moveToDir).getCanonicalPath(); 147 }catch( Throwable e ){ 148 } 149 try{ 150 defSaveDir = new File(defSaveDir).getCanonicalPath(); 151 }catch( Throwable e ){ 152 } 153 154 try { 155 156 if (f.getCanonicalPath().equals(moveToDir)) { 157 System.out.println("FileUtil::recursiveDelete:: not allowed to delete the MoveTo dir !"); 158 return( false ); 159 } 160 if (f.getCanonicalPath().equals(defSaveDir)) { 161 System.out.println("FileUtil::recursiveDelete:: not allowed to delete the default data dir !"); 162 return( false ); 163 } 164 165 if (f.isDirectory()) { 166 File[] files = f.listFiles(); 167 for (int i = 0; i < files.length; i++) { 168 if ( !recursiveDelete(files[i])){ 169 170 return( false ); 171 } 172 } 173 if ( !f.delete()){ 174 175 return( false ); 176 } 177 } 178 else { 179 if ( !f.delete()){ 180 181 return( false ); 182 } 183 } 184 } catch (Exception ignore) {/*ignore*/} 185 186 return( true ); 187 } 188 recursiveDeleteNoCheck(File f)189 public static boolean recursiveDeleteNoCheck(File f) { 190 try { 191 if (f.isDirectory()) { 192 File[] files = f.listFiles(); 193 for (int i = 0; i < files.length; i++) { 194 if ( !recursiveDeleteNoCheck(files[i])){ 195 196 return( false ); 197 } 198 } 199 if ( !f.delete()){ 200 201 return( false ); 202 } 203 } 204 else { 205 if ( !f.delete()){ 206 207 return( false ); 208 } 209 } 210 } catch (Exception ignore) {/*ignore*/} 211 212 return( true ); 213 } 214 215 public static long getFileOrDirectorySize( File file )216 getFileOrDirectorySize( 217 File file ) 218 { 219 if ( file.isFile()){ 220 221 return( file.length()); 222 223 }else{ 224 225 long res = 0; 226 227 File[] files = file.listFiles(); 228 229 if ( files != null ){ 230 231 for (int i=0;i<files.length;i++){ 232 233 res += getFileOrDirectorySize( files[i] ); 234 } 235 } 236 237 return( res ); 238 } 239 } 240 241 protected static void recursiveEmptyDirDelete( File f, Set ignore_set, boolean log_warnings )242 recursiveEmptyDirDelete( 243 File f, 244 Set ignore_set, 245 boolean log_warnings ) 246 { 247 try { 248 String defSaveDir = COConfigurationManager.getStringParameter("Default save path"); 249 String moveToDir = COConfigurationManager.getStringParameter("Completed Files Directory", ""); 250 251 if ( defSaveDir.trim().length() > 0 ){ 252 253 defSaveDir = new File(defSaveDir).getCanonicalPath(); 254 } 255 256 if ( moveToDir.trim().length() > 0 ){ 257 258 moveToDir = new File(moveToDir).getCanonicalPath(); 259 } 260 261 if ( f.isDirectory()){ 262 263 File[] files = f.listFiles(); 264 265 if ( files == null ){ 266 267 if (log_warnings ){ 268 Debug.out("Empty folder delete: failed to list contents of directory " + f ); 269 } 270 271 return; 272 } 273 274 for (int i = 0; i < files.length; i++) { 275 276 File x = files[i]; 277 278 if ( x.isDirectory()){ 279 280 recursiveEmptyDirDelete(files[i],ignore_set,log_warnings); 281 282 }else{ 283 284 if ( ignore_set.contains( x.getName().toLowerCase())){ 285 286 if ( !x.delete()){ 287 288 if ( log_warnings ){ 289 Debug.out("Empty folder delete: failed to delete file " + x ); 290 } 291 } 292 } 293 } 294 } 295 296 if (f.getCanonicalPath().equals(moveToDir)) { 297 298 if ( log_warnings ){ 299 Debug.out("Empty folder delete: not allowed to delete the MoveTo dir !"); 300 } 301 302 return; 303 } 304 305 if (f.getCanonicalPath().equals(defSaveDir)) { 306 307 if ( log_warnings ){ 308 Debug.out("Empty folder delete: not allowed to delete the default data dir !"); 309 } 310 311 return; 312 } 313 314 File[] files_inside = f.listFiles(); 315 if (files_inside.length == 0) { 316 317 if ( !f.delete()){ 318 319 if ( log_warnings ){ 320 Debug.out("Empty folder delete: failed to delete directory " + f ); 321 } 322 } 323 }else{ 324 if ( log_warnings ){ 325 Debug.out("Empty folder delete: " + files_inside.length + " file(s)/folder(s) still in \"" + f + "\" - first listed item is \"" + files_inside[0].getName() + "\". Not removing."); 326 } 327 } 328 } 329 330 } catch (Exception e) { Debug.out(e.toString()); } 331 } 332 333 public static String convertOSSpecificChars( String file_name_in, boolean is_folder )334 convertOSSpecificChars( 335 String file_name_in, 336 boolean is_folder ) 337 { 338 char[] mapping; 339 340 synchronized( FileUtil.class ){ 341 342 if ( char_conversion_mapping == null ){ 343 344 COConfigurationManager.addAndFireListener( 345 new COConfigurationListener() { 346 347 public void configurationSaved() 348 { 349 synchronized( FileUtil.class ){ 350 351 String map = COConfigurationManager.getStringParameter( "File.Character.Conversions" ); 352 353 String[] bits = map.split( "," ); 354 355 List<Character> chars = new ArrayList<Character>(); 356 357 for ( String bit: bits ){ 358 bit = bit.trim(); 359 if ( bit.length()==3){ 360 char from = bit.charAt(0); 361 char to = bit.charAt(2); 362 363 chars.add( from ); 364 chars.add( to ); 365 } 366 } 367 368 char[] new_map = new char[chars.size()]; 369 370 for ( int i=0;i<new_map.length;i++){ 371 372 new_map[i] = chars.get(i); 373 } 374 375 char_conversion_mapping = new_map; 376 } 377 } 378 }); 379 } 380 381 mapping = char_conversion_mapping; 382 } 383 384 // this rule originally from DiskManager 385 386 char[] chars = file_name_in.toCharArray(); 387 388 if ( mapping.length == 2 ){ 389 390 // default case 391 392 char from = mapping[0]; 393 char to = mapping[1]; 394 395 for (int i=0;i<chars.length;i++){ 396 397 if ( chars[i] == from ){ 398 399 chars[i] = to; 400 } 401 } 402 }else if ( mapping.length > 0 ){ 403 404 for (int i=0;i<chars.length;i++){ 405 406 char c = chars[i]; 407 408 for (int j=0;j<mapping.length;j+=2){ 409 410 if ( c == mapping[j] ){ 411 412 chars[i] = mapping[j+1]; 413 } 414 } 415 } 416 } 417 418 if ( !Constants.isOSX ){ 419 420 if ( Constants.isWindows ){ 421 422 // this rule originally from DiskManager 423 424 // The definitive list of characters permitted for Windows is defined here: 425 // http://support.microsoft.com/kb/q120138/ 426 String not_allowed = "\\/:?*<>|"; 427 for (int i=0;i<chars.length;i++){ 428 if (not_allowed.indexOf(chars[i]) != -1) { 429 chars[i] = '_'; 430 } 431 } 432 433 // windows doesn't like trailing dots and whitespaces in folders, replace them 434 435 if ( is_folder ){ 436 437 for(int i = chars.length-1;i >= 0 && (chars[i] == '.' || chars[i] == ' ');chars[i] = '_',i--); 438 } 439 } 440 441 // '/' is valid in mac file names, replace with space 442 // so it seems are cr/lf 443 444 for (int i=0;i<chars.length;i++){ 445 446 char c = chars[i]; 447 448 if ( c == '/' || c == '\r' || c == '\n' ){ 449 450 chars[i] = ' '; 451 } 452 } 453 } 454 455 String file_name_out = new String(chars); 456 457 try{ 458 459 // mac file names can end in space - fix this up by getting 460 // the canonical form which removes this on Windows 461 462 // however, for soem reason getCanonicalFile can generate high CPU usage on some user's systems 463 // in java.io.Win32FileSystem.canonicalize 464 // so changing this to only be used on non-windows 465 466 if ( Constants.isWindows ){ 467 468 while( file_name_out.endsWith( " " )){ 469 470 file_name_out = file_name_out.substring(0,file_name_out.length()-1); 471 } 472 473 }else{ 474 475 String str = new File(file_name_out).getCanonicalFile().toString(); 476 477 int p = str.lastIndexOf( File.separator ); 478 479 file_name_out = str.substring(p+1); 480 } 481 482 }catch( Throwable e ){ 483 // ho hum, carry on, it'll fail later 484 //e.printStackTrace(); 485 } 486 487 //System.out.println( "convertOSSpecificChars: " + file_name_in + " ->" + file_name_out ); 488 489 return( file_name_out ); 490 } 491 492 public static void writeResilientConfigFile( String file_name, Map data )493 writeResilientConfigFile( 494 String file_name, 495 Map data ) 496 { 497 File parent_dir = new File(SystemProperties.getUserPath()); 498 499 boolean use_backups = COConfigurationManager.getBooleanParameter("Use Config File Backups" ); 500 501 writeResilientFile( parent_dir, file_name, data, use_backups ); 502 } 503 504 public static void writeResilientFile( File file, Map data )505 writeResilientFile( 506 File file, 507 Map data ) 508 { 509 writeResilientFile( file.getParentFile(), file.getName(), data, false ); 510 } 511 512 public static boolean writeResilientFileWithResult( File parent_dir, String file_name, Map data )513 writeResilientFileWithResult( 514 File parent_dir, 515 String file_name, 516 Map data ) 517 { 518 return( writeResilientFile( parent_dir, file_name, data )); 519 } 520 521 public static void writeResilientFile( File parent_dir, String file_name, Map data, boolean use_backup )522 writeResilientFile( 523 File parent_dir, 524 String file_name, 525 Map data, 526 boolean use_backup ) 527 { 528 writeResilientFile( parent_dir, file_name, data, use_backup, true ); 529 } 530 531 public static void writeResilientFile( File parent_dir, String file_name, Map data, boolean use_backup, boolean copy_to_backup )532 writeResilientFile( 533 File parent_dir, 534 String file_name, 535 Map data, 536 boolean use_backup, 537 boolean copy_to_backup ) 538 { 539 if ( use_backup ){ 540 541 File originator = new File( parent_dir, file_name ); 542 543 if ( originator.exists()){ 544 545 backupFile( originator, copy_to_backup ); 546 } 547 } 548 549 writeResilientFile( parent_dir, file_name, data ); 550 } 551 552 // synchronise it to prevent concurrent attempts to write the same file 553 554 private static boolean writeResilientFile( File parent_dir, String file_name, Map data )555 writeResilientFile( 556 File parent_dir, 557 String file_name, 558 Map data ) 559 { 560 try{ 561 class_mon.enter(); 562 563 try{ 564 getReservedFileHandles(); 565 File temp = new File( parent_dir, file_name + ".saving"); 566 BufferedOutputStream baos = null; 567 568 try{ 569 byte[] encoded_data = BEncoder.encode(data); 570 FileOutputStream tempOS = new FileOutputStream( temp, false ); 571 baos = new BufferedOutputStream( tempOS, 8192 ); 572 baos.write( encoded_data ); 573 baos.flush(); 574 575 // thinking about removing this - just do so for CVS for the moment 576 577 if ( !Constants.isCVSVersion()){ 578 579 tempOS.getFD().sync(); 580 } 581 582 baos.close(); 583 baos = null; 584 585 //only use newly saved file if it got this far, i.e. it saved successfully 586 587 if ( temp.length() > 1L ){ 588 589 File file = new File( parent_dir, file_name ); 590 591 if ( file.exists()){ 592 593 if ( !file.delete()){ 594 595 Debug.out( "Save of '" + file_name + "' fails - couldn't delete " + file.getAbsolutePath()); 596 } 597 } 598 599 if (file.exists()) { 600 Debug.out(file + " still exists after delete attempt"); 601 } 602 603 if ( temp.renameTo( file )){ 604 605 return( true ); 606 607 } 608 609 // rename failed, sleep a little and try again 610 Thread.sleep(50); 611 if ( temp.renameTo( file )){ 612 //System.err.println("2nd attempt of rename succeeded for " + temp.getAbsolutePath() + " to " + file.getAbsolutePath()); 613 return true; 614 } 615 616 Debug.out( "Save of '" + file_name + "' fails - couldn't rename " + temp.getAbsolutePath() + " to " + file.getAbsolutePath()); 617 } 618 619 return( false ); 620 621 }catch( Throwable e ){ 622 623 Debug.out( "Save of '" + file_name + "' fails", e ); 624 625 return( false ); 626 627 }finally{ 628 629 try{ 630 if (baos != null){ 631 632 baos.close(); 633 } 634 }catch( Exception e){ 635 636 Debug.out( "Save of '" + file_name + "' fails", e ); 637 638 return( false ); 639 } 640 } 641 }finally{ 642 643 releaseReservedFileHandles(); 644 } 645 }finally{ 646 647 class_mon.exit(); 648 } 649 } 650 651 public static boolean resilientConfigFileExists( String name )652 resilientConfigFileExists( 653 String name ) 654 { 655 File parent_dir = new File(SystemProperties.getUserPath()); 656 657 boolean use_backups = COConfigurationManager.getBooleanParameter("Use Config File Backups" ); 658 659 return( new File( parent_dir, name ).exists() || 660 ( use_backups && new File( parent_dir, name + ".bak" ).exists())); 661 } 662 663 /** 664 * 665 * @return Map read from config file, or empty HashMap if error 666 */ 667 public static Map readResilientConfigFile( String file_name )668 readResilientConfigFile( 669 String file_name ) 670 { 671 File parent_dir = new File(SystemProperties.getUserPath()); 672 673 boolean use_backups = COConfigurationManager.getBooleanParameter("Use Config File Backups" ); 674 675 return( readResilientFile( parent_dir, file_name, use_backups )); 676 } 677 678 /** 679 * 680 * @return Map read from config file, or empty HashMap if error 681 */ 682 public static Map readResilientConfigFile( String file_name, boolean use_backups )683 readResilientConfigFile( 684 String file_name, 685 boolean use_backups ) 686 { 687 File parent_dir = new File(SystemProperties.getUserPath()); 688 689 if ( !use_backups ){ 690 691 // override if a backup file exists. This is needed to cope with backups 692 // of the main config file itself as when bootstrapping we can't get the 693 // "use backups" 694 695 if ( new File( parent_dir, file_name + ".bak").exists()){ 696 697 use_backups = true; 698 } 699 } 700 701 return( readResilientFile( parent_dir, file_name, use_backups )); 702 } 703 704 /** 705 * 706 * @return Map read from config file, or empty HashMap if error 707 */ 708 public static Map readResilientFile( File file )709 readResilientFile( 710 File file ) 711 { 712 return( readResilientFile( file.getParentFile(),file.getName(),false, true)); 713 } 714 715 /** 716 * 717 * @return Map read from config file, or empty HashMap if error 718 */ 719 public static Map readResilientFile( File parent_dir, String file_name, boolean use_backup )720 readResilientFile( 721 File parent_dir, 722 String file_name, 723 boolean use_backup ) 724 { 725 return readResilientFile(parent_dir, file_name, use_backup, true); 726 } 727 728 /** 729 * 730 * @param parent_dir 731 * @param file_name 732 * @param use_backup 733 * @param intern_keys 734 * 735 * @return Map read from config file, or empty HashMap if error 736 */ 737 public static Map readResilientFile( File parent_dir, String file_name, boolean use_backup, boolean intern_keys )738 readResilientFile( 739 File parent_dir, 740 String file_name, 741 boolean use_backup, 742 boolean intern_keys ) 743 { 744 File backup_file = new File( parent_dir, file_name + ".bak" ); 745 746 if ( use_backup ){ 747 748 use_backup = backup_file.exists(); 749 } 750 751 // if we've got a backup, don't attempt recovery here as the .bak file may be 752 // fully OK 753 754 Map res = readResilientFileSupport( parent_dir, file_name, !use_backup, intern_keys ); 755 756 if ( res == null && use_backup ){ 757 758 // try backup without recovery 759 760 res = readResilientFileSupport( parent_dir, file_name + ".bak", false, intern_keys ); 761 762 if ( res != null ){ 763 764 Debug.out( "Backup file '" + backup_file + "' has been used for recovery purposes" ); 765 766 // rewrite the good data, don't use backups here as we want to 767 // leave the original backup in place for the moment 768 769 writeResilientFile( parent_dir, file_name, res, false ); 770 771 }else{ 772 773 // neither main nor backup file ok, retry main file with recovery 774 775 res = readResilientFileSupport( parent_dir, file_name, true, true ); 776 } 777 } 778 779 if ( res == null ){ 780 781 res = new HashMap(); 782 } 783 784 return( res ); 785 } 786 787 // synchronised against writes to make sure we get a consistent view 788 789 private static Map readResilientFileSupport( File parent_dir, String file_name, boolean attempt_recovery, boolean intern_keys )790 readResilientFileSupport( 791 File parent_dir, 792 String file_name, 793 boolean attempt_recovery, 794 boolean intern_keys ) 795 { 796 try{ 797 class_mon.enter(); 798 799 try{ 800 getReservedFileHandles(); 801 802 Map res = null; 803 804 try{ 805 res = readResilientFile( file_name, parent_dir, file_name, 0, false, intern_keys ); 806 807 }catch( Throwable e ){ 808 809 // ignore, it'll be rethrown if we can't recover below 810 } 811 812 if ( res == null && attempt_recovery ){ 813 814 res = readResilientFile( file_name, parent_dir, file_name, 0, true, intern_keys ); 815 816 if ( res != null ){ 817 818 Debug.out( "File '" + file_name + "' has been partially recovered, information may have been lost!" ); 819 } 820 } 821 822 return( res ); 823 824 }catch( Throwable e ){ 825 826 Debug.printStackTrace( e ); 827 828 return( null ); 829 830 }finally{ 831 832 releaseReservedFileHandles(); 833 } 834 }finally{ 835 836 class_mon.exit(); 837 } 838 } 839 840 private static Map readResilientFile( String original_file_name, File parent_dir, String file_name, int fail_count, boolean recovery_mode, boolean skip_key_intern)841 readResilientFile( 842 String original_file_name, 843 File parent_dir, 844 String file_name, 845 int fail_count, 846 boolean recovery_mode, 847 boolean skip_key_intern) 848 { 849 // logging in here is only done during "non-recovery" mode to prevent subsequent recovery 850 // attempts logging everything a second time. 851 // recovery-mode allows the decoding process to "succeed" with a partially recovered file 852 853 boolean using_backup = file_name.endsWith(".saving"); 854 855 File file = new File( parent_dir, file_name ); 856 857 //make sure the file exists and isn't zero-length 858 859 if ( (!file.exists()) || file.length() <= 1L ){ 860 861 if ( using_backup ){ 862 863 if ( !recovery_mode ){ 864 865 if ( fail_count == 1 ){ 866 867 Debug.out( "Load of '" + original_file_name + "' fails, no usable file or backup" ); 868 869 }else{ 870 // drop this log, it doesn't really help to inform about the failure to 871 // find a .saving file 872 873 //if (Logger.isEnabled()) 874 // Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "Load of '" 875 // + file_name + "' fails, file not found")); 876 877 } 878 } 879 880 return( null ); 881 } 882 883 if ( !recovery_mode ){ 884 885 // kinda confusing log this as we get it under "normal" circumstances (loading a config 886 // file that doesn't exist legitimately, e.g. shares or bad-ips 887 // if (Logger.isEnabled()) 888 // Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "Load of '" 889 // + file_name + "' failed, " + "file not found or 0-sized.")); 890 } 891 892 return( readResilientFile( original_file_name, parent_dir, file_name + ".saving", 0, recovery_mode, true )); 893 } 894 895 BufferedInputStream bin = null; 896 897 try{ 898 int retry_limit = 5; 899 900 while(true){ 901 902 try{ 903 bin = new BufferedInputStream( new FileInputStream(file), 16384 ); 904 905 break; 906 907 }catch( IOException e ){ 908 909 if ( --retry_limit == 0 ){ 910 911 throw( e ); 912 } 913 914 if (Logger.isEnabled()) 915 Logger.log(new LogEvent(LOGID, "Failed to open '" + file.toString() + "', retrying", e)); 916 917 Thread.sleep(500); 918 } 919 } 920 921 BDecoder decoder = new BDecoder(); 922 923 if ( recovery_mode ){ 924 925 decoder.setRecoveryMode( true ); 926 } 927 928 Map res = decoder.decodeStream(bin, !skip_key_intern); 929 930 if ( using_backup && !recovery_mode ){ 931 932 Debug.out( "Load of '" + original_file_name + "' had to revert to backup file" ); 933 } 934 935 return( res ); 936 937 }catch( Throwable e ){ 938 939 Debug.printStackTrace( e ); 940 941 try { 942 if (bin != null){ 943 944 bin.close(); 945 946 bin = null; 947 } 948 } catch (Exception x) { 949 950 Debug.printStackTrace( x ); 951 } 952 953 // if we're not recovering then backup the file 954 955 if ( !recovery_mode ){ 956 957 // Occurs when file is there but b0rked 958 // copy it in case it actually contains useful data, so it won't be overwritten next save 959 960 File bad; 961 962 int bad_id = 0; 963 964 while(true){ 965 966 File test = new File( parent_dir, file.getName() + ".bad" + (bad_id==0?"":(""+bad_id))); 967 968 if ( !test.exists()){ 969 970 bad = test; 971 972 break; 973 } 974 975 bad_id++; 976 } 977 978 if (Logger.isEnabled()) 979 Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, "Read of '" 980 + original_file_name + "' failed, decoding error. " + "Renaming to " 981 + bad.getName())); 982 983 // copy it so its left in place for possible recovery 984 985 copyFile( file, bad ); 986 } 987 988 if ( using_backup ){ 989 990 if ( !recovery_mode ){ 991 992 Debug.out( "Load of '" + original_file_name + "' fails, no usable file or backup" ); 993 } 994 995 return( null ); 996 } 997 998 return( readResilientFile( original_file_name, parent_dir, file_name + ".saving", 1, recovery_mode, true )); 999 1000 }finally{ 1001 1002 try { 1003 1004 if (bin != null){ 1005 1006 bin.close(); 1007 } 1008 }catch (Exception e) { 1009 1010 Debug.printStackTrace( e ); 1011 } 1012 } 1013 } 1014 1015 public static void deleteResilientFile( File file )1016 deleteResilientFile( 1017 File file ) 1018 { 1019 file.delete(); 1020 new File( file.getParentFile(), file.getName() + ".bak" ).delete(); 1021 } 1022 1023 public static void deleteResilientConfigFile( String name )1024 deleteResilientConfigFile( 1025 String name ) 1026 { 1027 File parent_dir = new File(SystemProperties.getUserPath()); 1028 1029 new File( parent_dir, name ).delete(); 1030 new File( parent_dir, name + ".bak" ).delete(); 1031 } 1032 1033 private static void getReservedFileHandles()1034 getReservedFileHandles() 1035 { 1036 try{ 1037 class_mon.enter(); 1038 1039 while( reserved_file_handles.size() > 0 ){ 1040 1041 // System.out.println( "releasing reserved file handle"); 1042 1043 InputStream is = (InputStream)reserved_file_handles.remove(0); 1044 1045 try{ 1046 is.close(); 1047 1048 }catch( Throwable e ){ 1049 1050 Debug.printStackTrace( e ); 1051 } 1052 } 1053 }finally{ 1054 1055 class_mon.exit(); 1056 } 1057 } 1058 1059 private static void releaseReservedFileHandles()1060 releaseReservedFileHandles() 1061 { 1062 try{ 1063 1064 class_mon.enter(); 1065 1066 File lock_file = new File(SystemProperties.getUserPath() + ".lock"); 1067 1068 if ( first_reservation ){ 1069 1070 first_reservation = false; 1071 1072 lock_file.delete(); 1073 1074 is_my_lock_file = lock_file.createNewFile(); 1075 1076 }else{ 1077 1078 lock_file.createNewFile(); 1079 } 1080 1081 while( reserved_file_handles.size() < RESERVED_FILE_HANDLE_COUNT ){ 1082 1083 // System.out.println( "getting reserved file handle"); 1084 1085 InputStream is = new FileInputStream( lock_file ); 1086 1087 reserved_file_handles.add(is); 1088 } 1089 }catch( Throwable e ){ 1090 1091 Debug.printStackTrace( e ); 1092 1093 }finally{ 1094 1095 class_mon.exit(); 1096 } 1097 } 1098 1099 public static boolean isMyFileLock()1100 isMyFileLock() 1101 { 1102 return( is_my_lock_file ); 1103 } 1104 1105 /** 1106 * Backup the given file to filename.bak, removing the old .bak file if necessary. 1107 * If _make_copy is true, the original file will copied to backup, rather than moved. 1108 * @param _filename name of file to backup 1109 * @param _make_copy copy instead of move 1110 */ backupFile( final String _filename, final boolean _make_copy )1111 public static void backupFile( final String _filename, final boolean _make_copy ) { 1112 backupFile( new File( _filename ), _make_copy ); 1113 } 1114 1115 /** 1116 * Backup the given file to filename.bak, removing the old .bak file if necessary. 1117 * If _make_copy is true, the original file will copied to backup, rather than moved. 1118 * @param _file file to backup 1119 * @param _make_copy copy instead of move 1120 */ backupFile( final File _file, final boolean _make_copy )1121 public static void backupFile( final File _file, final boolean _make_copy ) { 1122 if ( _file.length() > 0L ) { 1123 File bakfile = new File( _file.getAbsolutePath() + ".bak" ); 1124 if ( bakfile.exists() ) bakfile.delete(); 1125 if ( _make_copy ) { 1126 copyFile( _file, bakfile ); 1127 } 1128 else { 1129 _file.renameTo( bakfile ); 1130 } 1131 } 1132 } 1133 1134 1135 /** 1136 * Copy the given source file to the given destination file. 1137 * Returns file copy success or not. 1138 * @param _source_name source file name 1139 * @param _dest_name destination file name 1140 * @return true if file copy successful, false if copy failed 1141 */ copyFile( final String _source_name, final String _dest_name )1142 public static boolean copyFile( final String _source_name, final String _dest_name ) { 1143 return copyFile( new File(_source_name), new File(_dest_name)); 1144 } 1145 1146 /** 1147 * Copy the given source file to the given destination file. 1148 * Returns file copy success or not. 1149 * @param _source source file 1150 * @param _dest destination file 1151 * @return true if file copy successful, false if copy failed 1152 */ 1153 /* 1154 // FileChannel.transferTo() seems to fail under certain linux configurations. 1155 public static boolean copyFile( final File _source, final File _dest ) { 1156 FileChannel source = null; 1157 FileChannel dest = null; 1158 try { 1159 if( _source.length() < 1L ) { 1160 throw new IOException( _source.getAbsolutePath() + " does not exist or is 0-sized" ); 1161 } 1162 source = new FileInputStream( _source ).getChannel(); 1163 dest = new FileOutputStream( _dest ).getChannel(); 1164 1165 source.transferTo(0, source.size(), dest); 1166 return true; 1167 } 1168 catch (Exception e) { 1169 Debug.out( e ); 1170 return false; 1171 } 1172 finally { 1173 try { 1174 if (source != null) source.close(); 1175 if (dest != null) dest.close(); 1176 } 1177 catch (Exception ignore) {} 1178 } 1179 } 1180 */ 1181 copyFile( final File _source, final File _dest )1182 public static boolean copyFile( final File _source, final File _dest ) { 1183 try { 1184 copyFile( new FileInputStream( _source ), new FileOutputStream( _dest ) ); 1185 return true; 1186 } 1187 catch( Throwable e ) { 1188 Debug.printStackTrace( e ); 1189 return false; 1190 } 1191 } 1192 copyFileWithException( final File _source, final File _dest )1193 public static void copyFileWithException( final File _source, final File _dest ) throws IOException{ 1194 copyFile( new FileInputStream( _source ), new FileOutputStream( _dest ) ); 1195 } 1196 copyFile( final File _source, final OutputStream _dest, boolean closeInputStream )1197 public static boolean copyFile( final File _source, final OutputStream _dest, boolean closeInputStream ) { 1198 try { 1199 copyFile( new FileInputStream( _source ), _dest, closeInputStream ); 1200 return true; 1201 } 1202 catch( Throwable e ) { 1203 Debug.printStackTrace( e ); 1204 return false; 1205 } 1206 } 1207 1208 /** 1209 * copys the input stream to the file. always closes the input stream 1210 * @param _source 1211 * @param _dest 1212 * @throws IOException 1213 */ 1214 1215 public static void copyFile( final InputStream _source, final File _dest )1216 copyFile( 1217 final InputStream _source, 1218 final File _dest ) 1219 1220 throws IOException 1221 { 1222 FileOutputStream dest = null; 1223 1224 boolean close_input = true; 1225 1226 try{ 1227 dest = new FileOutputStream(_dest); 1228 1229 // copyFile will close from now on, we don't need to 1230 1231 close_input = false; 1232 1233 copyFile( _source, dest, true ); 1234 1235 }finally{ 1236 1237 try{ 1238 if(close_input){ 1239 _source.close(); 1240 } 1241 }catch( IOException e ){ 1242 } 1243 1244 if ( dest != null ){ 1245 1246 dest.close(); 1247 } 1248 } 1249 } 1250 1251 public static void copyFile( final InputStream _source, final File _dest, boolean _close_input_stream )1252 copyFile( 1253 final InputStream _source, 1254 final File _dest, 1255 boolean _close_input_stream ) 1256 1257 throws IOException 1258 { 1259 FileOutputStream dest = null; 1260 1261 boolean close_input = _close_input_stream; 1262 1263 try{ 1264 dest = new FileOutputStream(_dest); 1265 1266 close_input = false; 1267 1268 copyFile( _source, dest, close_input ); 1269 1270 }finally{ 1271 1272 try{ 1273 if( close_input ){ 1274 1275 _source.close(); 1276 } 1277 }catch( IOException e ){ 1278 } 1279 1280 if ( dest != null ){ 1281 1282 dest.close(); 1283 } 1284 } 1285 } 1286 1287 public static void copyFile( InputStream is, OutputStream os )1288 copyFile( 1289 InputStream is, 1290 OutputStream os ) 1291 1292 throws IOException 1293 { 1294 copyFile(is,os,true); 1295 } 1296 1297 public static void copyFile( InputStream is, OutputStream os, boolean closeInputStream )1298 copyFile( 1299 InputStream is, 1300 OutputStream os, 1301 boolean closeInputStream ) 1302 1303 throws IOException 1304 { 1305 try{ 1306 1307 if ( !(is instanceof BufferedInputStream )){ 1308 1309 is = new BufferedInputStream(is,128*1024); 1310 } 1311 1312 byte[] buffer = new byte[128*1024]; 1313 1314 while(true){ 1315 1316 int len = is.read(buffer); 1317 1318 if ( len == -1 ){ 1319 1320 break; 1321 } 1322 1323 os.write( buffer, 0, len ); 1324 } 1325 }finally{ 1326 try{ 1327 if(closeInputStream){ 1328 is.close(); 1329 } 1330 }catch( IOException e ){ 1331 1332 } 1333 1334 os.close(); 1335 } 1336 } 1337 1338 public static void copyFileOrDirectory( File from_file_or_dir, File to_parent_dir )1339 copyFileOrDirectory( 1340 File from_file_or_dir, 1341 File to_parent_dir ) 1342 1343 throws IOException 1344 { 1345 if ( !from_file_or_dir.exists()){ 1346 1347 throw( new IOException( "File '" + from_file_or_dir.toString() + "' doesn't exist" )); 1348 } 1349 1350 if ( !to_parent_dir.exists()){ 1351 1352 throw( new IOException( "File '" + to_parent_dir.toString() + "' doesn't exist" )); 1353 } 1354 1355 if ( !to_parent_dir.isDirectory()){ 1356 1357 throw( new IOException( "File '" + to_parent_dir.toString() + "' is not a directory" )); 1358 } 1359 1360 if ( from_file_or_dir.isDirectory()){ 1361 1362 File[] files = from_file_or_dir.listFiles(); 1363 1364 File new_parent = new File( to_parent_dir, from_file_or_dir.getName()); 1365 1366 FileUtil.mkdirs(new_parent); 1367 1368 for (int i=0;i<files.length;i++){ 1369 1370 File from_file = files[i]; 1371 1372 copyFileOrDirectory( from_file, new_parent ); 1373 } 1374 }else{ 1375 1376 File target = new File( to_parent_dir, from_file_or_dir.getName()); 1377 1378 if ( !copyFile( from_file_or_dir, target )){ 1379 1380 throw( new IOException( "File copy from " + from_file_or_dir + " to " + target + " failed" )); 1381 } 1382 } 1383 } 1384 1385 /** 1386 * Returns the file handle for the given filename or it's 1387 * equivalent .bak backup file if the original doesn't exist 1388 * or is 0-sized. If neither the original nor the backup are 1389 * available, a null handle is returned. 1390 * @param _filename root name of file 1391 * @return file if successful, null if failed 1392 */ getFileOrBackup( final String _filename )1393 public static File getFileOrBackup( final String _filename ) { 1394 try { 1395 File file = new File( _filename ); 1396 //make sure the file exists and isn't zero-length 1397 if ( file.length() <= 1L ) { 1398 //if so, try using the backup file 1399 File bakfile = new File( _filename + ".bak" ); 1400 if ( bakfile.length() <= 1L ) { 1401 return null; 1402 } 1403 else return bakfile; 1404 } 1405 else return file; 1406 } 1407 catch (Exception e) { 1408 Debug.out( e ); 1409 return null; 1410 } 1411 } 1412 1413 public static File getJarFileFromClass( Class cla )1414 getJarFileFromClass( 1415 Class cla ) 1416 { 1417 try{ 1418 String str = cla.getName(); 1419 1420 str = str.replace( '.', '/' ) + ".class"; 1421 1422 URL url = cla.getClassLoader().getResource( str ); 1423 1424 if ( url != null ){ 1425 1426 String url_str = url.toExternalForm(); 1427 1428 if ( url_str.startsWith("jar:file:")){ 1429 1430 File jar_file = FileUtil.getJarFileFromURL(url_str); 1431 1432 if ( jar_file != null && jar_file.exists()){ 1433 1434 return( jar_file ); 1435 } 1436 } 1437 } 1438 }catch( Throwable e ){ 1439 1440 Debug.printStackTrace(e); 1441 } 1442 1443 return( null ); 1444 } 1445 1446 public static File getJarFileFromURL( String url_str )1447 getJarFileFromURL( 1448 String url_str ) 1449 { 1450 if (url_str.startsWith("jar:file:")) { 1451 1452 // java web start returns a url like "jar:file:c:/sdsd" which then fails as the file 1453 // part doesn't start with a "/". Add it in! 1454 // here's an example 1455 // jar:file:C:/Documents%20and%20Settings/stuff/.javaws/cache/http/Dparg.homeip.net/P9090/DMazureus-jnlp/DMlib/XMAzureus2.jar1070487037531!/org/gudy/azureus2/internat/MessagesBundle.properties 1456 1457 // also on Mac we don't get the spaces escaped 1458 1459 url_str = url_str.replaceAll(" ", "%20" ); 1460 1461 if ( !url_str.startsWith("jar:file:/")){ 1462 1463 1464 url_str = "jar:file:/".concat(url_str.substring(9)); 1465 } 1466 1467 try{ 1468 // you can see that the '!' must be present and that we can safely use the last occurrence of it 1469 1470 int posPling = url_str.lastIndexOf('!'); 1471 1472 String jarName = url_str.substring(4, posPling); 1473 1474 // System.out.println("jarName: " + jarName); 1475 1476 URI uri; 1477 1478 try{ 1479 uri = URI.create(jarName); 1480 1481 if ( !new File(uri).exists()){ 1482 1483 throw( new FileNotFoundException()); 1484 } 1485 }catch( Throwable e ){ 1486 1487 jarName = "file:/" + UrlUtils.encode( jarName.substring( 6 )); 1488 1489 uri = URI.create(jarName); 1490 } 1491 1492 File jar = new File(uri); 1493 1494 return( jar ); 1495 1496 }catch( Throwable e ){ 1497 1498 Debug.printStackTrace( e ); 1499 } 1500 } 1501 1502 return( null ); 1503 } 1504 1505 public static boolean renameFile( File from_file, File to_file )1506 renameFile( 1507 File from_file, 1508 File to_file ) 1509 { 1510 return renameFile(from_file, to_file, true); 1511 } 1512 1513 1514 public static boolean renameFile( File from_file, File to_file, boolean fail_on_existing_directory)1515 renameFile( 1516 File from_file, 1517 File to_file, 1518 boolean fail_on_existing_directory) 1519 { 1520 return renameFile(from_file, to_file, fail_on_existing_directory, null); 1521 } 1522 renameFile( File from_file, File to_file, boolean fail_on_existing_directory, FileFilter file_filter )1523 public static boolean renameFile( 1524 File from_file, 1525 File to_file, 1526 boolean fail_on_existing_directory, 1527 FileFilter file_filter 1528 ) { 1529 1530 if ( !from_file.exists()){ 1531 1532 Debug.out( "renameFile: source file '" + from_file + "' doesn't exist, failing" ); 1533 1534 return( false ); 1535 } 1536 1537 /** 1538 * If the destination exists, we only fail if requested. 1539 */ 1540 if (to_file.exists() && (fail_on_existing_directory || from_file.isFile() || to_file.isFile())) { 1541 1542 Debug.out( "renameFile: target file '" + to_file + "' already exists, failing" ); 1543 1544 return( false ); 1545 } 1546 File to_file_parent = to_file.getParentFile(); 1547 if (!to_file_parent.exists()) {FileUtil.mkdirs(to_file_parent);} 1548 1549 if ( from_file.isDirectory()){ 1550 1551 File[] files = null; 1552 if (file_filter != null) {files = from_file.listFiles(file_filter);} 1553 else {files = from_file.listFiles();} 1554 1555 if ( files == null ){ 1556 1557 // empty dir 1558 1559 return( true ); 1560 } 1561 1562 int last_ok = 0; 1563 1564 if (!to_file.exists()) {to_file.mkdir();} 1565 1566 for (int i=0;i<files.length;i++){ 1567 1568 File ff = files[i]; 1569 File tf = new File( to_file, ff.getName()); 1570 1571 try{ 1572 if ( renameFile( ff, tf, fail_on_existing_directory, file_filter )){ 1573 1574 last_ok++; 1575 1576 }else{ 1577 1578 break; 1579 } 1580 }catch( Throwable e ){ 1581 1582 Debug.out( "renameFile: failed to rename file '" + ff.toString() + "' to '" 1583 + tf.toString() + "'", e ); 1584 1585 break; 1586 } 1587 } 1588 1589 if ( last_ok == files.length ){ 1590 1591 File[] remaining = from_file.listFiles(); 1592 1593 if ( remaining != null && remaining.length > 0 ){ 1594 // This might be important or not. We'll make it a debug message if we had a filter, 1595 // or log it normally otherwise. 1596 if (file_filter == null) { 1597 Debug.out( "renameFile: files remain in '" + from_file.toString() 1598 + "', not deleting"); 1599 } 1600 else { 1601 /* Should we log this? How should we log this? */ 1602 return true; 1603 } 1604 1605 }else{ 1606 1607 if ( !from_file.delete()){ 1608 Debug.out( "renameFile: failed to delete '" + from_file.toString() + "'" ); 1609 } 1610 } 1611 1612 return( true ); 1613 } 1614 1615 // recover by moving files back 1616 1617 for (int i=0;i<last_ok;i++){ 1618 1619 File ff = files[i]; 1620 File tf = new File( to_file, ff.getName()); 1621 1622 try{ 1623 // null - We don't want to use the file filter, it only refers to source paths. 1624 if ( !renameFile( tf, ff, false, null )){ 1625 Debug.out( "renameFile: recovery - failed to move file '" + tf.toString() 1626 + "' to '" + ff.toString() + "'" ); 1627 } 1628 }catch( Throwable e ){ 1629 Debug.out("renameFile: recovery - failed to move file '" + tf.toString() 1630 + "' to '" + ff.toString() + "'", e); 1631 1632 } 1633 } 1634 1635 return( false ); 1636 1637 }else{ 1638 1639 boolean copy_and_delete = COConfigurationManager.getBooleanParameter("Copy And Delete Data Rather Than Move"); 1640 1641 if ( copy_and_delete ){ 1642 1643 boolean move_if_same_drive = COConfigurationManager.getBooleanParameter("Move If On Same Drive"); 1644 1645 if ( move_if_same_drive ){ 1646 1647 // FileSystem class available from Java 7, boo, just do a hack for windowz 1648 1649 if ( Constants.isWindows ){ 1650 1651 try{ 1652 String str1 = from_file.getCanonicalPath(); 1653 String str2 = to_file.getCanonicalPath(); 1654 1655 char drive1 = ':'; 1656 char drive2 = ' '; 1657 1658 if ( str1.length() > 2 && str1.charAt(1) == ':' ){ 1659 1660 drive1 = Character.toLowerCase( str1.charAt( 0 )); 1661 } 1662 if ( str2.length() > 2 && str2.charAt(1) == ':' ){ 1663 1664 drive2 = Character.toLowerCase( str2.charAt( 0 )); 1665 } 1666 1667 if ( drive1 == drive2 ){ 1668 1669 copy_and_delete = false; 1670 } 1671 }catch( Throwable e ){ 1672 1673 } 1674 } 1675 } 1676 } 1677 1678 if ( (!copy_and_delete) && 1679 from_file.renameTo( to_file )){ 1680 1681 return( true ); 1682 1683 }else{ 1684 boolean success = false; 1685 1686 // can't rename across file systems under Linux - try copy+delete 1687 1688 FileInputStream fis = null; 1689 1690 FileOutputStream fos = null; 1691 1692 try{ 1693 fis = new FileInputStream( from_file ); 1694 1695 fos = new FileOutputStream( to_file ); 1696 1697 byte[] buffer = new byte[65536]; 1698 1699 while( true ){ 1700 1701 int len = fis.read( buffer ); 1702 1703 if ( len <= 0 ){ 1704 1705 break; 1706 } 1707 1708 fos.write( buffer, 0, len ); 1709 } 1710 1711 fos.close(); 1712 1713 fos = null; 1714 1715 fis.close(); 1716 1717 fis = null; 1718 1719 if ( !from_file.delete()){ 1720 Debug.out( "renameFile: failed to delete '" 1721 + from_file.toString() + "'" ); 1722 1723 throw( new Exception( "Failed to delete '" + from_file.toString() + "'")); 1724 } 1725 1726 success = true; 1727 1728 return( true ); 1729 1730 }catch( Throwable e ){ 1731 1732 Debug.out( "renameFile: failed to rename '" + from_file.toString() 1733 + "' to '" + to_file.toString() + "'", e ); 1734 1735 return( false ); 1736 1737 }finally{ 1738 1739 if ( fis != null ){ 1740 1741 try{ 1742 fis.close(); 1743 1744 }catch( Throwable e ){ 1745 } 1746 } 1747 1748 if ( fos != null ){ 1749 1750 try{ 1751 fos.close(); 1752 1753 }catch( Throwable e ){ 1754 } 1755 } 1756 1757 // if we've failed then tidy up any partial copy that has been performed 1758 1759 if ( !success ){ 1760 1761 if ( to_file.exists()){ 1762 1763 to_file.delete(); 1764 } 1765 } 1766 } 1767 } 1768 } 1769 } 1770 1771 public static boolean writeStringAsFile( File file, String text )1772 writeStringAsFile( 1773 File file, 1774 String text ) 1775 { 1776 try{ 1777 return( writeBytesAsFile2( file.getAbsolutePath(), text.getBytes( "UTF-8" ))); 1778 1779 }catch( Throwable e ){ 1780 1781 Debug.out( e ); 1782 1783 return( false ); 1784 } 1785 } 1786 1787 public static void writeBytesAsFile( String filename, byte[] file_data )1788 writeBytesAsFile( 1789 String filename, 1790 byte[] file_data ) 1791 { 1792 // pftt, this is used by emp so can't fix signature to make more useful 1793 1794 writeBytesAsFile2( filename, file_data ); 1795 } 1796 1797 public static boolean writeBytesAsFile2( String filename, byte[] file_data )1798 writeBytesAsFile2( 1799 String filename, 1800 byte[] file_data ) 1801 { 1802 try{ 1803 File file = new File( filename ); 1804 1805 if ( !file.getParentFile().exists()){ 1806 1807 file.getParentFile().mkdirs(); 1808 } 1809 1810 FileOutputStream out = new FileOutputStream( file ); 1811 1812 try{ 1813 out.write( file_data ); 1814 1815 }finally{ 1816 1817 out.close(); 1818 } 1819 1820 return( true ); 1821 1822 }catch( Throwable t ){ 1823 1824 Debug.out( "writeBytesAsFile:: error: ", t ); 1825 1826 return( false ); 1827 } 1828 } 1829 1830 public static boolean deleteWithRecycle( File file, boolean force_no_recycle )1831 deleteWithRecycle( 1832 File file, 1833 boolean force_no_recycle ) 1834 { 1835 if ( COConfigurationManager.getBooleanParameter("Move Deleted Data To Recycle Bin" ) && !force_no_recycle ){ 1836 1837 try{ 1838 final PlatformManager platform = PlatformManagerFactory.getPlatformManager(); 1839 1840 if (platform.hasCapability(PlatformManagerCapabilities.RecoverableFileDelete)){ 1841 1842 platform.performRecoverableFileDelete( file.getAbsolutePath()); 1843 1844 return( true ); 1845 1846 }else{ 1847 1848 return( file.delete()); 1849 } 1850 }catch( PlatformManagerException e ){ 1851 1852 return( file.delete()); 1853 } 1854 }else{ 1855 1856 return( file.delete()); 1857 } 1858 } 1859 1860 public static String translateMoveFilePath( String old_root, String new_root, String file_to_move )1861 translateMoveFilePath( 1862 String old_root, 1863 String new_root, 1864 String file_to_move ) 1865 { 1866 // we're trying to get the bit from the file_to_move beyond the old_root and append it to the new_root 1867 1868 if ( !file_to_move.startsWith(old_root)){ 1869 1870 return null; 1871 } 1872 1873 if ( old_root.equals( new_root )){ 1874 1875 // roots are the same -> nothings gonna change 1876 1877 return( file_to_move ); 1878 } 1879 1880 if ( new_root.equals( file_to_move )){ 1881 1882 // new root already the same as the from file, nothing to change 1883 1884 return( file_to_move ); 1885 } 1886 1887 String file_suffix = file_to_move.substring(old_root.length()); 1888 1889 if ( file_suffix.startsWith(File.separator )){ 1890 1891 file_suffix = file_suffix.substring(1); 1892 1893 }else{ 1894 // hack to deal with special known case of this 1895 // old_root: c:\fred\jim.dat 1896 // new_root: c:\temp\egor\grtaaaa 1897 // old_file: c:\fred\jim.dat.az! 1898 1899 if ( new_root.endsWith( File.separator )){ 1900 1901 Debug.out( "Hmm, this is not going to work out well... " + old_root + ", " + new_root + ", " + file_to_move ); 1902 1903 }else{ 1904 1905 // deal with case where new root already has the right suffix 1906 1907 if ( new_root.endsWith( file_suffix )){ 1908 1909 return( new_root ); 1910 } 1911 1912 return( new_root + file_suffix ); 1913 } 1914 } 1915 1916 if ( new_root.endsWith(File.separator)){ 1917 1918 new_root = new_root.substring( 0, new_root.length()-1 ); 1919 } 1920 1921 return new_root + File.separator + file_suffix; 1922 } 1923 1924 public static void runAsTask( AzureusCoreOperationTask task )1925 runAsTask( 1926 AzureusCoreOperationTask task ) 1927 { 1928 AzureusCore core = AzureusCoreFactory.getSingleton(); 1929 1930 core.createOperation( AzureusCoreOperation.OP_FILE_MOVE, task ); 1931 } 1932 1933 /** 1934 * Makes Directories as long as the directory isn't directly in Volumes (OSX) 1935 * @param f 1936 * @return 1937 */ mkdirs(File f)1938 public static boolean mkdirs(File f) { 1939 if (Constants.isOSX) { 1940 Pattern pat = Pattern.compile("^(/Volumes/[^/]+)"); 1941 Matcher matcher = pat.matcher(f.getParent()); 1942 if (matcher.find()) { 1943 String sVolume = matcher.group(); 1944 File fVolume = new File(sVolume); 1945 if (!fVolume.isDirectory()) { 1946 Logger.log(new LogEvent(LOGID, LogEvent.LT_WARNING, sVolume 1947 + " is not mounted or not available.")); 1948 return false; 1949 } 1950 } 1951 } 1952 return f.mkdirs(); 1953 } 1954 1955 /** 1956 * Gets the extension of a file name, ensuring we don't go into the path 1957 * 1958 * @param fName File name 1959 * @return extension, with the '.' 1960 */ getExtension(String fName)1961 public static String getExtension(String fName) { 1962 final int fileSepIndex = fName.lastIndexOf(File.separator); 1963 final int fileDotIndex = fName.lastIndexOf('.'); 1964 if (fileSepIndex == fName.length() - 1 || fileDotIndex == -1 1965 || fileSepIndex > fileDotIndex) { 1966 return ""; 1967 } 1968 1969 return fName.substring(fileDotIndex); 1970 } 1971 1972 public static String readFileAsString( File file, int size_limit, String charset)1973 readFileAsString( 1974 File file, 1975 int size_limit, 1976 String charset) 1977 1978 throws IOException 1979 { 1980 FileInputStream fis = new FileInputStream(file); 1981 try { 1982 return readInputStreamAsString(fis, size_limit, charset); 1983 } finally { 1984 1985 fis.close(); 1986 } 1987 } 1988 1989 public static String readFileAsString( File file, int size_limit )1990 readFileAsString( 1991 File file, 1992 int size_limit ) 1993 1994 throws IOException 1995 { 1996 FileInputStream fis = new FileInputStream(file); 1997 try { 1998 return readInputStreamAsString(fis, size_limit); 1999 } finally { 2000 2001 fis.close(); 2002 } 2003 } 2004 2005 public static String readGZippedFileAsString( File file, int size_limit )2006 readGZippedFileAsString( 2007 File file, 2008 int size_limit ) 2009 2010 throws IOException 2011 { 2012 FileInputStream fis = new FileInputStream(file); 2013 2014 try { 2015 GZIPInputStream zis = new GZIPInputStream( fis ); 2016 2017 return readInputStreamAsString(zis, size_limit); 2018 } finally { 2019 2020 fis.close(); 2021 } 2022 } 2023 public static String readInputStreamAsString( InputStream is, int size_limit )2024 readInputStreamAsString( 2025 InputStream is, 2026 int size_limit ) 2027 2028 throws IOException 2029 { 2030 return readInputStreamAsString(is, size_limit, "ISO-8859-1"); 2031 } 2032 2033 public static String readInputStreamAsString( InputStream is, int size_limit, String charSet)2034 readInputStreamAsString( 2035 InputStream is, 2036 int size_limit, 2037 String charSet) 2038 2039 throws IOException 2040 { 2041 StringBuffer result = new StringBuffer(1024); 2042 2043 byte[] buffer = new byte[64*1024]; 2044 2045 while (true) { 2046 2047 int len = is.read(buffer); 2048 2049 if (len <= 0) { 2050 2051 break; 2052 } 2053 2054 result.append(new String(buffer, 0, len, charSet)); 2055 2056 if (size_limit >= 0 && result.length() > size_limit) { 2057 2058 result.setLength(size_limit); 2059 2060 break; 2061 } 2062 } 2063 2064 return (result.toString()); 2065 } 2066 2067 public static String readInputStreamAsStringWithTruncation( InputStream is, int size_limit )2068 readInputStreamAsStringWithTruncation( 2069 InputStream is, 2070 int size_limit ) 2071 2072 throws IOException 2073 { 2074 StringBuffer result = new StringBuffer(1024); 2075 2076 byte[] buffer = new byte[64*1024]; 2077 2078 try{ 2079 while (true) { 2080 2081 int len = is.read(buffer); 2082 2083 if (len <= 0) { 2084 2085 break; 2086 } 2087 2088 result.append(new String(buffer, 0, len, "ISO-8859-1")); 2089 2090 if (size_limit >= 0 && result.length() > size_limit) { 2091 2092 result.setLength(size_limit); 2093 2094 break; 2095 } 2096 } 2097 }catch( SocketTimeoutException e ){ 2098 } 2099 2100 return (result.toString()); 2101 } 2102 2103 public static String readFileEndAsString( File file, int size_limit )2104 readFileEndAsString( 2105 File file, 2106 int size_limit ) 2107 2108 throws IOException 2109 { 2110 return( readFileEndAsString( file, size_limit, "ISO-8859-1" )); 2111 } 2112 2113 public static String readFileEndAsString( File file, int size_limit, String charset )2114 readFileEndAsString( 2115 File file, 2116 int size_limit, 2117 String charset ) 2118 2119 throws IOException 2120 { 2121 FileInputStream fis = new FileInputStream( file ); 2122 2123 try{ 2124 if ( file.length() > size_limit){ 2125 2126 // doesn't really work with multi-byte chars but woreva 2127 2128 fis.skip(file.length() - size_limit); 2129 } 2130 2131 StringBuffer result = new StringBuffer(1024); 2132 2133 byte[] buffer = new byte[64*1024]; 2134 2135 while( true ){ 2136 2137 int len = fis.read( buffer ); 2138 2139 if ( len <= 0 ){ 2140 2141 break; 2142 } 2143 2144 // doesn't really work with multi-byte chars but woreva 2145 2146 result.append( new String( buffer, 0, len, charset )); 2147 2148 if ( result.length() > size_limit ){ 2149 2150 result.setLength( size_limit ); 2151 2152 break; 2153 } 2154 } 2155 2156 return( result.toString()); 2157 2158 }finally{ 2159 2160 fis.close(); 2161 } 2162 } 2163 2164 public static byte[] readInputStreamAsByteArray( InputStream is )2165 readInputStreamAsByteArray( 2166 InputStream is ) 2167 2168 throws IOException 2169 { 2170 return( readInputStreamAsByteArray( is, Integer.MAX_VALUE )); 2171 } 2172 2173 public static byte[] readInputStreamAsByteArray( InputStream is, int size_limit )2174 readInputStreamAsByteArray( 2175 InputStream is, 2176 int size_limit ) 2177 2178 throws IOException 2179 { 2180 ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024); 2181 2182 byte[] buffer = new byte[32*1024]; 2183 2184 while( true ){ 2185 2186 int len = is.read( buffer ); 2187 2188 if ( len <= 0 ){ 2189 2190 break; 2191 } 2192 2193 baos.write( buffer, 0, len ); 2194 2195 if ( baos.size() > size_limit ){ 2196 2197 throw( new IOException( "size limit exceeded" )); 2198 } 2199 } 2200 2201 return( baos.toByteArray()); 2202 } 2203 2204 public static byte[] readFileAsByteArray( File file )2205 readFileAsByteArray( 2206 File file ) 2207 2208 throws IOException 2209 { 2210 ByteArrayOutputStream baos = new ByteArrayOutputStream((int)file.length()); 2211 2212 byte[] buffer = new byte[32*1024]; 2213 2214 InputStream is = new FileInputStream( file ); 2215 2216 try{ 2217 while( true ){ 2218 2219 int len = is.read( buffer ); 2220 2221 if ( len <= 0 ){ 2222 2223 break; 2224 } 2225 2226 baos.write( buffer, 0, len ); 2227 } 2228 2229 return( baos.toByteArray()); 2230 2231 }finally{ 2232 2233 is.close(); 2234 } 2235 } 2236 getUsableSpaceSupported()2237 public final static boolean getUsableSpaceSupported() 2238 { 2239 return reflectOnUsableSpace != null; 2240 } 2241 getUsableSpace(File f)2242 public final static long getUsableSpace(File f) 2243 { 2244 try{ 2245 return ((Long)reflectOnUsableSpace.invoke(f)).longValue(); 2246 2247 }catch ( Throwable e){ 2248 2249 return -1; 2250 } 2251 } 2252 2253 public static boolean canReallyWriteToAppDirectory()2254 canReallyWriteToAppDirectory() 2255 { 2256 if ( !FileUtil.getApplicationFile("bogus").getParentFile().canWrite()){ 2257 2258 return( false ); 2259 } 2260 2261 // handle vista+ madness 2262 2263 if ( Constants.isWindowsVistaOrHigher ){ 2264 2265 try{ 2266 File write_test = FileUtil.getApplicationFile( "_az_.dll" ); 2267 2268 // should fail if no perms, but sometimes it's created in 2269 // virtualstore (if ran from java(w).exe for example) 2270 2271 FileOutputStream fos = new FileOutputStream( write_test ); 2272 2273 try{ 2274 fos.write(32); 2275 2276 }finally{ 2277 2278 fos.close(); 2279 } 2280 2281 write_test.delete(); 2282 2283 // look for a file to try and rename. Unfortunately someone renamed License.txt to GPL.txt and screwed this up in 3020... 2284 2285 File rename_test = FileUtil.getApplicationFile( "License.txt" ); 2286 2287 if ( !rename_test.exists()){ 2288 2289 rename_test = FileUtil.getApplicationFile( "GPL.txt" ); 2290 } 2291 2292 if ( !rename_test.exists()){ 2293 2294 File[] files = write_test.getParentFile().listFiles(); 2295 2296 if ( files != null ){ 2297 2298 for ( File f: files ){ 2299 2300 String name = f.getName(); 2301 2302 if ( name.endsWith( ".txt" ) || name.endsWith( ".log" )){ 2303 2304 rename_test = f; 2305 2306 break; 2307 } 2308 } 2309 } 2310 } 2311 2312 if ( rename_test.exists()){ 2313 2314 File target = new File( rename_test.getParentFile(), rename_test.getName() + ".bak" ); 2315 2316 target.delete(); 2317 2318 rename_test.renameTo( target ); 2319 2320 if ( rename_test.exists()){ 2321 2322 return( false ); 2323 } 2324 2325 target.renameTo( rename_test ); 2326 2327 }else{ 2328 2329 Debug.out( "Failed to find a suitable file for the rename test" ); 2330 2331 // let's assume we can't to be on the safe side 2332 2333 return( false ); 2334 } 2335 }catch ( Throwable e ){ 2336 2337 return( false ); 2338 } 2339 } 2340 2341 return( true ); 2342 } 2343 2344 public static boolean canWriteToDirectory( File dir )2345 canWriteToDirectory( 2346 File dir ) 2347 { 2348 // (dir).canWrite() seems to return true for local file systems at least on windows regardless 2349 // of effective permissions :( 2350 2351 if ( !dir.isDirectory()){ 2352 2353 return( false ); 2354 } 2355 2356 try{ 2357 File temp = AETemporaryFileHandler.createTempFileInDir( dir ); 2358 2359 if ( !temp.delete()){ 2360 2361 temp.deleteOnExit(); 2362 } 2363 2364 return( true ); 2365 2366 }catch( Throwable e ){ 2367 2368 return( false ); 2369 } 2370 } 2371 /** 2372 * Gets the encoding that should be used when writing script files (currently only 2373 * tested for windows as this is where an issue can arise...) 2374 * We also only test based on the user-data directory name to see if an explicit 2375 * encoding switch is requried... 2376 * @return null - use default 2377 */ 2378 2379 private static boolean sce_checked; 2380 private static String script_encoding; 2381 2382 public static String getScriptCharsetEncoding()2383 getScriptCharsetEncoding() 2384 { 2385 synchronized( FileUtil.class ){ 2386 2387 if ( sce_checked ){ 2388 2389 return( script_encoding ); 2390 } 2391 2392 sce_checked = true; 2393 2394 String file_encoding = System.getProperty( "file.encoding", null ); 2395 String jvm_encoding = System.getProperty( "sun.jnu.encoding", null ); 2396 2397 if ( file_encoding == null || jvm_encoding == null || file_encoding.equals( jvm_encoding )){ 2398 2399 return( null ); 2400 } 2401 2402 try{ 2403 2404 String test_str = SystemProperties.getUserPath(); 2405 2406 if ( !new String( test_str.getBytes( file_encoding ), file_encoding ).equals( test_str )){ 2407 2408 if ( new String( test_str.getBytes( jvm_encoding ), jvm_encoding ).equals( test_str )){ 2409 2410 Debug.out( "Script encoding determined to be " + jvm_encoding + " instead of " + file_encoding ); 2411 2412 script_encoding = jvm_encoding; 2413 } 2414 } 2415 }catch( Throwable e ){ 2416 } 2417 2418 return( script_encoding ); 2419 } 2420 } 2421 2422 public static InternedFile internFileComponents( File file )2423 internFileComponents( 2424 File file ) 2425 { 2426 if ( file == null ){ 2427 2428 return( null ); 2429 } 2430 2431 List<String> comps = new ArrayList<String>(100); 2432 2433 File comp = file; 2434 2435 while( comp != null ){ 2436 2437 String name = comp.getName(); 2438 2439 if ( name.length() > 0 ){ 2440 2441 comps.add( StringInterner.intern( name )); 2442 2443 }else{ 2444 2445 String path = comp.getPath(); 2446 2447 if ( path.length() > 0 ){ 2448 2449 comps.add( StringInterner.intern( path )); 2450 } 2451 } 2452 2453 comp = comp.getParentFile(); 2454 } 2455 2456 InternedFile res = new InternedFile(comps.toArray( new String[comps.size()])); 2457 2458 if ( !res.getFile().equals( file )){ 2459 2460 Debug.out( "intern failed for " + file + " (" + res.getFile() + ")" ); 2461 } 2462 2463 return( res ); 2464 } 2465 2466 public static class 2467 InternedFile 2468 { 2469 private final String[] comps; 2470 2471 private InternedFile( String[] _comps )2472 InternedFile( 2473 String[] _comps ) 2474 { 2475 comps = _comps; 2476 } 2477 2478 public File getFile()2479 getFile() 2480 { 2481 if ( comps.length == 0 ){ 2482 2483 return( new File( "" )); 2484 2485 }else if ( comps.length == 1 ){ 2486 2487 return( new File( comps[0] )); 2488 2489 }else{ 2490 2491 StringBuffer b = new StringBuffer(256); 2492 2493 for (int i=comps.length-1;i>=0;i--){ 2494 2495 if ( b.length() > 0 ){ 2496 2497 b.append( File.separatorChar ); 2498 } 2499 2500 b.append( comps[i] ); 2501 } 2502 2503 return( new File( b.toString())); 2504 } 2505 } 2506 2507 @Override 2508 public boolean equals( Object other )2509 equals( 2510 Object other ) 2511 { 2512 if ( other instanceof InternedFile ){ 2513 2514 InternedFile o = (InternedFile)other; 2515 2516 if ( comps.length != o.comps.length ){ 2517 2518 return( false ); 2519 } 2520 2521 for ( int i=comps.length-1;i>= 0; i-- ){ 2522 2523 if ( !comps[i].equals( o.comps[i] )){ 2524 2525 return( false ); 2526 } 2527 } 2528 2529 return( true ); 2530 2531 }else if ( other instanceof File ){ 2532 2533 return( getFile().equals( other )); 2534 2535 }else{ 2536 2537 return( false ); 2538 } 2539 } 2540 2541 @Override 2542 public int hashCode()2543 hashCode() 2544 { 2545 int h = 0; 2546 2547 for ( String s: comps ){ 2548 2549 h += s.hashCode(); 2550 } 2551 2552 return( h ); 2553 } 2554 } 2555 } 2556