package org.limewire.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.util.SystemUtils.SpecialLocations;
/**
* Provides file manipulation methods; ensures a file exists, makes a file
* writable, renames, saves and deletes a file.
*/
public class FileUtils {
private static final Log LOG = LogFactory.getLog(FileUtils.class);
/**
* A cache of files that may or may not be writable. Required for
* workarounds on Windows.
*/
private static final Map<File, Boolean> CAN_WRITE_CACHE = new ConcurrentHashMap<File, Boolean>();
private static final CopyOnWriteArrayList<FileLocker> fileLockers = new CopyOnWriteArrayList<FileLocker>();
/**
* Writes an object to a backup file and then renames that file to a proper
* file. Returns true if this succeeded, false otherwise.
*/
public static boolean writeWithBackupFile(Object toWrite, File backupFile, File properFile,
org.apache.commons.logging.Log log) {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(backupFile)));
out.writeObject(toWrite);
out.flush();
out.close();
out = null;
// If the files were the same, don't bother renaming.
if(!properFile.equals(backupFile)) {
// Rename backup to save, now that it saved.
properFile.delete();
return backupFile.renameTo(properFile);
} else {
if(log != null) {
log.warn("backup file is same as proper file! -- " + backupFile);
}
}
return true;
} catch(IOException iox) {
if(log != null) {
log.debug("IOX writing file to: " + properFile, iox);
}
} finally {
close(out);
}
return false;
}
/**
* Writes the passed Object to corresponding file.
*
* @param f the file to which to write
* @param obj the Object to be stored
*/
public static void writeObject(File f, Object obj) throws IOException {
ObjectOutputStream out = null;
try {
// open the file
out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
// write to the file
out.writeObject(obj);
out.flush();
} finally {
close(out);
}
}
public static Object readObject(String fileName) throws IOException, ClassNotFoundException {
return readObject(new File(fileName));
}
/**
* @param file the file from where to read the serialized Object
* @return The Object that was read
*/
public static Object readObject(File file) throws IOException, ClassNotFoundException {
ObjectInputStream in = null;
try {
// open the file
in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
// read and return the object
return in.readObject();
} finally {
close(in);
}
}
/**
* Gets the canonical path, catching buggy Windows errors.
*/
public static String getCanonicalPath(File f) throws IOException {
try {
return f.getCanonicalPath();
} catch (IOException ioe) {
String msg = ioe.getMessage();
// windows bugs out :(
if (OSUtils.isWindows() && msg != null && msg.indexOf("There are no more files") != -1)
return f.getAbsolutePath();
else
throw ioe;
}
}
/** Safely canonicalizes a file, with no IOExceptions. */
public static File canonicalize(File f) {
try {
return getCanonicalFile(f);
} catch (IOException iox) {
return f;
}
}
/** Same as f.getCanonicalFile() in JDK1.3. */
public static File getCanonicalFile(File f) throws IOException {
try {
return f.getCanonicalFile();
} catch (IOException ioe) {
String msg = ioe.getMessage();
// windows bugs out :(
if (OSUtils.isWindows() && msg != null && msg.indexOf("There are no more files") != -1)
return f.getAbsoluteFile();
else
throw ioe;
}
}
/**
* Determines if file 'ancestor' is an ancestor of file 'child'.
*/
public static boolean isAncestor(File ancestor, File child) {
while (child != null) {
if (child.equals(ancestor))
return true;
child = child.getParentFile();
}
return false;
}
/**
* Detects attempts at directory traversal by testing if testDirectory
* really is the parent of testPath. This method should be used to make sure
* directory traversal tricks aren't being used to trick LimeWire into
* reading or writing to unexpected places.
* <p>
* Directory traversal security problems occur when software doesn't check
* if input paths contain characters (such as "../") that cause the OS to go
* up a directory. This function will ignore benign cases where the path
* goes up one directory and then back down into the original directory.
*
* @return false if testParent is not the parent of testChild.
* @throws IOException if getCanonicalPath throws IOException for either
* input file
*/
public static boolean isReallyParent(File testParent, File testChild) throws IOException {
// Don't check testDirectory.isDirectory...
// If it's not a directory, it won't be the parent anyway.
// This makes the tests more simple.
String testParentName = getCanonicalPath(testParent);
String testChildParentName = getCanonicalPath(testChild.getAbsoluteFile().getParentFile());
if (!testParentName.equals(testChildParentName))
return false;
return true;
}
/**
* Detects attempts at directory traversal by testing if testDirectory
* really is a parent of testPath.
*
* @see isReallyParent
*/
public static boolean isReallyInParentPath(File testParent, File testChild) throws IOException {
String testParentName = getCanonicalPath(testParent);
File testChildParentFile = testChild.getAbsoluteFile().getParentFile();
if (testChildParentFile == null)
testChildParentFile = testChild.getAbsoluteFile();
String testChildParentName = getCanonicalPath(testChildParentFile);
return testChildParentName.startsWith(testParentName);
}
/**
* Returns the filename without an extension.
*/
public static String getFilenameNoExtension(String fullname) {
int i = fullname.lastIndexOf(".");
if (i < 0) {
return fullname;
} else {
return fullname.substring(0, i);
}
}
/**
* Utility method that returns the file extension of the given file.
*
* @param f the <tt>File</tt> instance from which the extension should be
* extracted
* @return the file extension string, or <tt>empty string</tt> if the
* extension could not be extracted
*/
public static String getFileExtension(File f) {
String name = f.getName();
return getFileExtension(name);
}
/**
* Utility method that returns the file extension of the given file.
*
* @param name the file name <tt>String</tt> from which the extension should
* be extracted
* @return the file extension string, or <tt>empty string</tt> if the
* extension could not be extracted
*/
public static String getFileExtension(String name) {
int index = name.lastIndexOf(".");
if (index == -1)
return "";
// the file must have a name other than the extension
if (index == 0)
return "";
// if the last character of the string is the ".", then there's
// no extension
if (index == (name.length() - 1))
return "";
return name.substring(index + 1).intern();
}
/**
* Utility method to set a file as non read only. If the file is already
* writable, does nothing.
*
* @param f the <tt>File</tt> instance whose read only flag should be unset.
*
* @return whether or not <tt>f</tt> is writable after trying to make it
* writeable -- note that if the file doesn't exist, then this
* returns <tt>true</tt>
*/
public static boolean setWriteable(File f) {
if (!f.exists())
return true;
// non Windows-based systems return the wrong value
// for canWrite when the argument is a directory --
// writing is based on the 'x' attribute, not the 'w'
// attribute for directories.
if (FileUtils.canWrite(f)) {
if (OSUtils.isWindows())
return true;
else if (!f.isDirectory())
return true;
}
String fName;
try {
fName = f.getCanonicalPath();
} catch (IOException ioe) {
fName = f.getPath();
}
String cmds[] = null;
if (OSUtils.isWindows() || OSUtils.isMacOSX()) {
SystemUtils.setWriteable(fName);
CAN_WRITE_CACHE.remove(f);
} else if (OSUtils.isOS2()) {
;// cmds = null; // Find the right command for OS/2 and fill in
} else {
if (f.isDirectory())
cmds = new String[] { "chmod", "u+w+x", fName };
else
cmds = new String[] { "chmod", "u+w", fName };
}
if (cmds != null) {
try {
Process p = Runtime.getRuntime().exec(cmds);
p.waitFor();
} catch (SecurityException ignored) {
} catch (IOException ignored) {
} catch (InterruptedException ignored) {
}
}
return FileUtils.canWrite(f);
}
/**
* Touches a file, to ensure it exists. Note: unlike the unix touch this
* does not change the modification time.
*/
public static void touch(File f) throws IOException {
if (f.exists())
return;
File parent = f.getParentFile();
if (parent != null)
parent.mkdirs();
try {
f.createNewFile();
} catch (IOException failed) {
// Okay, createNewFile failed. Let's try the old way.
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
} catch (IOException ioe) {
ioe.initCause(failed);
throw ioe;
} finally {
close(fos);
}
}
}
/**
* Adds a new FileLocker to the list of FileLockers that are checked when a
* lock needs to be released on a file prior to deletion or renaming.
*/
public static void addFileLocker(FileLocker locker) {
fileLockers.addIfAbsent(locker);
}
/**
* Removes <code>locker</code> from the list of FileLockers.
*
* @see #addFileLocker(FileLocker)
*/
public static void removeFileLocker(FileLocker locker) {
fileLockers.remove(locker);
}
/**
* Forcibly renames a file, removing any locks that may be held from any
* FileLockers that were added.
*
* @return true if the rename succeeded
*/
public static boolean forceRename(File src, File dst) {
// First attempt to rename it.
boolean success = src.renameTo(dst);
// If that fails, try releasing the locks one by one.
if (!success) {
for (FileLocker locker : fileLockers) {
if (locker.releaseLock(src)) {
success = src.renameTo(dst);
if (success)
break;
}
}
}
// If that didn't work, try copying the file.
if (!success) {
success = copy(src, dst);
// if copying succeeded, get rid of the original
// at this point any active uploads will have been killed
if (success)
src.delete();
}
return success;
}
/**
* Forcibly deletes a file, removing any locks that may be held from any
* FileLockers that were added.
*
* @param file the file to delete
* @return true if the deletion succeeded
*/
public static boolean forceDelete(File file) {
// First attempt to rename it.
boolean success = file.delete();
// If that fails, try releasing the locks one by one.
if (!success) {
for (FileLocker locker : fileLockers) {
if (locker.releaseLock(file)) {
success = file.delete();
if (success)
break;
}
}
}
if (LOG.isDebugEnabled()) {
LOG.debugf("success= {0}, file.exists()? {1}", success, file.exists());
}
return !file.exists();
}
/**
* Saves the data iff it was written exactly as we wanted.
*/
public static boolean verySafeSave(File dir, String name, byte[] data) {
File tmp;
try {
tmp = FileUtils.createTempFile(name, "tmp", dir);
} catch (IOException hrorible) {
return false;
}
File out = new File(dir, name);
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(tmp));
os.write(data);
os.flush();
} catch (IOException bad) {
return false;
} finally {
close(os);
}
// verify that we wrote everything correctly
byte[] read = readFileFully(tmp);
if (read == null || !Arrays.equals(read, data))
return false;
return forceRename(tmp, out);
}
/**
* Reads a file, filling a byte array.
*/
public static byte[] readFileFully(File source) {
DataInputStream raf = null;
int length = (int) source.length();
if (length <= 0)
return null;
byte[] data = new byte[length];
try {
raf = new DataInputStream(new BufferedInputStream(new FileInputStream(source)));
raf.readFully(data);
} catch (IOException ioe) {
return null;
} finally {
close(raf);
}
return data;
}
/**
* @param directory gets all files under this directory RECURSIVELY.
* @param filter if null, then returns all files. Else, only returns files
* extensions in the filter array.
* @return an array of Files recursively obtained from the directory,
* according to the filter.
*
*/
public static File[] getFilesRecursive(File directory, String... filter) {
List<File> dirs = new ArrayList<File>();
// the return array of files...
List<File> retFileArray = new ArrayList<File>();
File[] retArray = new File[0];
// bootstrap the process
if (directory.exists() && directory.isDirectory())
dirs.add(directory);
// while i have dirs to process
while (dirs.size() > 0) {
File currDir = dirs.remove(0);
String[] listedFiles = currDir.list();
for (int i = 0; (listedFiles != null) && (i < listedFiles.length); i++) {
File currFile = new File(currDir, listedFiles[i]);
if (currFile.isDirectory()) // to be dealt with later
dirs.add(currFile);
else if (currFile.isFile()) { // we have a 'file'....
boolean shouldAdd = false;
if (filter == null || filter.length == 0)
shouldAdd = true;
else {
String ext = FileUtils.getFileExtension(currFile);
for (int j = 0; (j < filter.length) && (!ext.isEmpty()); j++) {
if (ext.equalsIgnoreCase(filter[j])) {
shouldAdd = true;
// don't keep looping through all filters --
// one match is good enough
break;
}
}
}
if (shouldAdd)
retFileArray.add(currFile);
}
}
}
if (!retFileArray.isEmpty()) {
retArray = new File[retFileArray.size()];
for (int i = 0; i < retArray.length; i++)
retArray[i] = retFileArray.get(i);
}
return retArray;
}
/**
* Deletes the given file or directory, moving it to the trash can or
* recycle bin if the platform has one and <code>moveToTrash</code> is true.
*
* @param file the file or directory to trash or delete
* @param moveToTrash whether the file should be moved to the trash bin or
* permanently deleted
* @return true on success
*
* @throws IllegalArgumentException if the OS does not support moving files
* to a trash bin, check with {@link OSUtils#supportsTrash()}.
*/
public static boolean delete(File file, boolean moveToTrash) {
if (!file.exists()) {
return false;
}
if (moveToTrash) {
if (OSUtils.isMacOSX()) {
return moveToTrashOSX(file);
} else if (OSUtils.isWindows()) {
return SystemUtils.recycle(file);
} else {
throw new IllegalArgumentException("OS does not support trash");
}
} else {
return deleteRecursive(file);
}
}
/**
* Moves the given file or directory to Trash.
*
* @param file the file or directory to move to Trash
* @throws IOException if the canonical path cannot be resolved or if the
* move process is interrupted
* @return true on success
*/
private static boolean moveToTrashOSX(File file) {
try {
String[] command = moveToTrashCommand(file);
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream();
Process process = builder.start();
ProcessUtils.consumeAllInput(process);
process.waitFor();
} catch (InterruptedException err) {
LOG.error("InterruptedException", err);
} catch (IOException err) {
LOG.error("IOException", err);
}
return !file.exists();
}
/**
* Creates and returns the the <code>osascript</code> command to move a file or directory
* to the Trash
*
* @param file the file or directory to move to Trash
* @throws IOException if the canonical path cannot be resolved
* @return OSAScript command
*/
private static String[] moveToTrashCommand(File file) {
String path = null;
try {
path = file.getCanonicalPath();
} catch (IOException err) {
LOG.error("IOException", err);
path = file.getAbsolutePath();
}
String fileOrFolder = (file.isFile() ? "file" : "folder");
String[] command = new String[] { "osascript", "-e", "set unixPath to \"" + path + "\"",
"-e", "set hfsPath to POSIX file unixPath", "-e", "tell application \"Finder\"",
"-e", "if " + fileOrFolder + " hfsPath exists then", "-e",
"move " + fileOrFolder + " hfsPath to trash", "-e", "end if", "-e", "end tell" };
return command;
}
/**
* Deletes all files in 'directory'. Returns true if this successfully
* deleted every file recursively, including itself.
*
* @return
*/
public static boolean deleteRecursive(File directory) {
// make sure we only delete canonical children of the parent file we
// wish to delete. I have a hunch this might be an issue on OSX and
// Linux under certain circumstances.
// If anyone can test whether this really happens (possibly related to
// symlinks), I would much appreciate it.
String canonicalParent;
try {
canonicalParent = getCanonicalPath(directory);
} catch (IOException ioe) {
return false;
}
if (!directory.isDirectory())
return directory.delete();
File[] files = directory.listFiles();
if(files != null) {
for (File file : files) {
try {
if (!getCanonicalPath(file).startsWith(canonicalParent))
continue;
} catch (IOException ioe) {
return false;
}
if (!deleteRecursive(file))
return false;
}
}
return directory.delete();
}
/**
* @return true if the two files are the same. If they are both directories
* returns true if there is at least one file that conflicts.
*/
public static boolean conflictsAny(File a, File b) {
if (a.equals(b))
return true;
Set<File> unique = new HashSet<File>();
unique.add(a);
unique.addAll(Arrays.asList(getFilesRecursive(a)));
if (unique.contains(b))
return true;
for (File recursive : getFilesRecursive(b)) {
if (unique.contains(recursive))
return true;
}
return false;
}
/**
* Returns total length of all files by going through the given directory
* (if it's a directory).
*/
public static long getLengthRecursive(File f) {
if (!f.isDirectory())
return f.length();
long ret = 0;
for (File file : getFilesRecursive(f))
ret += file.length();
return ret;
}
/**
* A utility method to close Closeable objects (Readers, Writers, Input- and
* OutputStreams and RandomAccessFiles).
*/
public static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignored) {
}
}
}
/**
* A utility method to flush Flushable objects (Readers, Writers, Input- and
* OutputStreams and RandomAccessFiles).
*/
public static void flush(Flushable flushable) {
if (flushable != null) {
try {
flushable.flush();
} catch (IOException ignored) {
}
}
}
/**
* Attempts to copy the first 'amount' bytes of file 'src' to 'dst',
* returning the number of bytes actually copied. If 'dst' already exists,
* the copy may or may not succeed.
*
* @param src the source file to copy
* @param amount the amount of src to copy, in bytes
* @param dst the place to copy the file
* @return the number of bytes actually copied. Returns 'amount' if the
* entire requested range was copied.
*/
public static long copy(File src, long amount, File dst) {
final int BUFFER_SIZE = 1024;
long amountToRead = amount;
InputStream in = null;
OutputStream out = null;
try {
// I'm not sure whether buffering is needed here. It can't hurt.
in = new BufferedInputStream(new FileInputStream(src));
out = new BufferedOutputStream(new FileOutputStream(dst));
byte[] buf = new byte[BUFFER_SIZE];
while (amountToRead > 0) {
int read = in.read(buf, 0, (int) Math.min(BUFFER_SIZE, amountToRead));
if (read == -1)
break;
amountToRead -= read;
out.write(buf, 0, read);
}
} catch (IOException ignore) {
LOG.error(ignore.getMessage(), ignore);
} finally {
close(in);
flush(out);
close(out);
}
return amount - amountToRead;
}
/**
* Copies the file 'src' to 'dst', returning true iff the copy succeeded. If
* 'dst' already exists, the copy may or may not succeed. May also fail for
* VERY large source files.
*/
public static boolean copy(File src, File dst) {
// Downcasting length can result in a sign change, causing
// copy(File,int,File) to terminate immediately.
long length = src.length();
return copy(src, (int) length, dst) == length;
}
/**
* Creates a temporary file using
* {@link File#createTempFile(String, String, File)}, trying a few times.
* This is a workaround for Sun Bug: 6325169: createTempFile occasionally
* fails (throwing an IOException).
*/
@SuppressWarnings("null")
public static File createTempFile(String prefix, String suffix, File directory)
throws IOException {
IOException iox = null;
for (int i = 0; i < 10; i++) {
try {
return File.createTempFile(prefix, suffix, directory);
} catch (IOException x) {
iox = x;
}
}
throw iox;
}
/**
* Creates a temporary file using
* {@link File#createTempFile(String, String)}, trying a few times. This is
* a workaround for Sun Bug: 6325169: createTempFile occasionally fails
* (throwing an IOException).
*/
@SuppressWarnings("null")
public static File createTempFile(String prefix, String suffix) throws IOException {
IOException iox = null;
for (int i = 0; i < 10; i++) {
try {
return File.createTempFile(prefix, suffix);
} catch (IOException x) {
iox = x;
}
}
throw iox;
}
public static File getJarFromClasspath(String markerFile) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
if (classLoader == null) {
classLoader = FileUtils.class.getClassLoader();
}
if (classLoader == null) {
return null;
}
return getJarFromClasspath(classLoader, markerFile);
}
public static File getJarFromClasspath(ClassLoader classLoader, String markerFile) {
if (classLoader == null) {
throw new IllegalArgumentException();
}
URL messagesURL = classLoader.getResource(markerFile);
if (messagesURL != null) {
String url = CommonUtils.decode(messagesURL.toExternalForm());
if (url != null && url.startsWith("jar:file:")) {
url = url.substring("jar:file:".length(), url.length());
url = url.substring(0, url.length() - markerFile.length() - "!/".length());
return new File(url);
}
}
return null;
}
/**
* Opens the file and path and parses its contents into a Properties object.
*
* @throws IOException path not found or any error
*/
public static Properties readProperties(File path) throws IOException {
InputStream stream = null;
Properties properties = new Properties();
try {
stream = new BufferedInputStream(new FileInputStream(path));
properties.load(stream);
} finally {
FileUtils.close(stream);
}
return properties;
}
/** Writes properties to a file at path. */
public static void writeProperties(File path, Properties properties) throws IOException {
OutputStream stream = null;
try {
stream = new BufferedOutputStream(new FileOutputStream(path));
properties.store(stream, "");
} finally {
FileUtils.close(stream);
}
}
/**
* Confirms path is to a folder on the disk, making folders as needed.
*
* @throw IOException it's not
*/
public static void makeFolder(File path) throws IOException {
if (path.isDirectory())
return; // It's already a folder
if (!path.mkdirs())
throw new IOException("error from File.mkdirs()"); // Turn returning
// false into an
// exception
}
/**
* Resolves the text of a special path into an absolute path specific to
* where the program is running now. Special paths can be complete or
* relative, step upwards, and start with a special folder tag. Here are
* some examples on Windows XP:
*
* <pre>
* Running at: C:\Program Files\LimeWire\LimeWire.jar
*
* Special Path Return File
* ------------------------- --------------------------
* C:\Folder\Subfolder C:\Folder\Subfolder
* Folder Here C:\Program Files\LimeWire\Folder Here
* ..\One Up C:\Program Files\One Up
* Desktop> C:\Documents and Settings\User Name\Desktop
* Documents>In My Documents C:\Documents and Settings\User Name\My Documents\In My Documents
* </pre>
*
* @throws IOException on error
*/
public static File resolveSpecialPath(String path) throws IOException {
if (path == null)
throw new IOException("no path");
// the given path contains a ">", parse for the special folder tag
// before it
int i = path.indexOf(">");
if (i != -1) {
String tag = path.substring(0, i);
SpecialLocations location = SpecialLocations.parse(tag);
if (location == null)
throw new IOException("unknown tag");
String special = SystemUtils.getSpecialPath(location);
if (special == null)
throw new IOException("unable to get path");
path = path.substring(i + 1);
return (new File(special, path)).getAbsoluteFile();
}
// no special tag, just turn a relative path absolute
return getCanonicalFile(new File(path));
}
/**
* Copies all the files and folders in sourceDirectory to
* destinationDirectory.
*
* @param sourceDirectory the source directory to copy, must exist
* @param destinationDirectory the destination path, must not exist
* @throws IOException an error prevented copying the whole directory
*/
public static void copyDirectory(File sourceDirectory, File destinationDirectory)
throws IOException {
if (!sourceDirectory.isDirectory())
throw new IOException("source directory not found");
if (destinationDirectory.exists())
throw new IOException("destination directory already exists");
makeFolder(destinationDirectory);
// Loop for each name in the source directory, like "file.ext" and
// "subfolder name"
String[] contents = sourceDirectory.list();
File source, destination;
for (String name : contents) {
// Make File objects with complete paths for this file or subfolder
source = new File(sourceDirectory, name);
destination = new File(destinationDirectory, name);
// Copy it across
if (source.isDirectory()) {
// Call this same method to copy the subfolder and its contents
copyDirectory(source, destination);
} else {
if (!copy(source, destination))
throw new IOException("unable to copy file");
}
}
}
/**
* Utility method to copy an input stream into the target output stream.
*/
public static void write(InputStream inputStream, OutputStream outputStream) throws IOException {
int numRead = 0;
byte[] buffer = new byte[1024];
while ((numRead = inputStream.read(buffer, 0, buffer.length)) != -1) {
outputStream.write(buffer, 0, numRead);
}
}
/**
* Utility method to generate an MD5 hash from a target file.
*/
public static String getMD5(File file) throws NoSuchAlgorithmException, IOException {
MessageDigest m = MessageDigest.getInstance("MD5");
ByteBuffer byteBuffer = ByteBuffer.allocate(16 * 1024);
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileChannel fileChannel = fileInputStream.getChannel();
while (fileChannel.read(byteBuffer) != -1) {
byteBuffer.flip();
m.update(byteBuffer);
byteBuffer.clear();
}
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
byte[] digest = m.digest();
String md5 = new BigInteger(1, digest).toString(16);
return md5;
}
/**
* Tries to create a symbolic link from the source to the target
* destinations. Returning the exit code of the command run.
*
* @throws UnsupportedOperationException if this method is run on an os
* other than linux.
*/
public static int createSymbolicLink(File source, File target) throws IOException,
InterruptedException {
if (!OSUtils.isLinux()) {
throw new UnsupportedOperationException(
"Creating Symbolic links is only supported on linux.");
}
String[] command = { "ln", "-s", source.getAbsolutePath(), target.getAbsolutePath() };
Process process = Runtime.getRuntime().exec(command);
process.waitFor();
return process.exitValue();
}
public static void unlockFile(File file) {
for (FileLocker locker : fileLockers) {
locker.releaseLock(file);
}
}
/**
* A replacement for {@link File#canWrite()}. Required because Windows
* returns the wrong permissions for files that have special icons or other
* things set.
*/
public static boolean canWrite(File file) {
if (!OSUtils.isWindows() || !file.isDirectory()) {
return file.canWrite();
} else {
if (file.canWrite()) {
return true;
}
// If the file cannot be written, we're kind of stuck
// (between a rock & a hard place...)
Boolean cached = CAN_WRITE_CACHE.get(file);
if (cached != null) {
return cached;
} else {
try {
File f = createTempFile("lw-", "can-write-test", file);
f.delete();
CAN_WRITE_CACHE.put(file, true);
return true;
} catch (IOException iox) {
CAN_WRITE_CACHE.put(file, false);
return false;
}
}
}
}
/**
* Deletes all files in 'directory'. Returns true if this successfully
* deleted every file recursively, including itself.
*
* This takes deletion 1 step further than the standard deleteRecursive in that it uses
* forceDelete on each file to clean up any locks on files that exist.
*/
public static boolean forceDeleteRecursive(File directory) {
// make sure we only delete canonical children of the parent file we
// wish to delete. I have a hunch this might be an issue on OSX and
// Linux under certain circumstances.
// If anyone can test whether this really happens (possibly related to
// symlinks), I would much appreciate it.
String canonicalParent;
try {
canonicalParent = getCanonicalPath(directory);
} catch (IOException ioe) {
return false;
}
if (!directory.isDirectory()) {
return forceDelete(directory);
}
File[] files = directory.listFiles();
if(files != null) {
for (File file : files) {
try {
if (!getCanonicalPath(file).startsWith(canonicalParent))
continue;
} catch (IOException ioe) {
return false;
}
if (!forceDeleteRecursive(file))
return false;
}
}
return forceDelete(directory);
}
/**
* Recursively deletes any empty directories.
* Returns true if the given directory was empty or
* only had empty subdirectories, all of which were deleted.
*/
public static boolean deleteEmptyDirectories(File directory) {
if(directory.isDirectory()) {
boolean empty = true;
File[] files = directory.listFiles();
if(files != null) {
for(File file : files) {
empty &= file.isDirectory() && deleteEmptyDirectories(file);
}
}
if(empty) {
directory.delete();
}
return empty;
}
return false;
}
}