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