
com.threerings.getdown.util.FileUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of getdown-core Show documentation
Show all versions of getdown-core Show documentation
Core Getdown functionality
The newest version!
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2018 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.util;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.jar.*;
import java.util.zip.*;
import com.threerings.getdown.Log;
import static com.threerings.getdown.Log.log;
/**
* File related utilities.
*/
public final class FileUtil
{
/**
* Gets the specified source file to the specified destination file by hook or crook. Windows
* has all sorts of problems which we work around in this method.
*
* @return true if we managed to get the job done, false otherwise.
*/
public static boolean renameTo (File source, File dest)
{
// if we're on a civilized operating system we may be able to simple rename it
if (source.renameTo(dest)) {
return true;
}
// fall back to trying to rename the old file out of the way, rename the new file into
// place and then delete the old file
if (dest.exists()) {
File temp = new File(dest.getPath() + "_old");
if (temp.exists() && !deleteHarder(temp)) {
log.warning("Failed to delete old intermediate file " + temp + ".");
// the subsequent code will probably fail
}
if (dest.renameTo(temp) && source.renameTo(dest)) {
if (!deleteHarder(temp)) {
log.warning("Failed to delete intermediate file " + temp + ".");
}
return true;
}
}
// as a last resort, try copying the old data over the new
try {
copy(source, dest);
} catch (IOException ioe) {
log.warning("Failed to copy " + source + " to " + dest + ": " + ioe);
return false;
}
if (!deleteHarder(source)) {
log.warning("Failed to delete " + source + " after brute force copy to " + dest + ".");
}
return true;
}
/**
* "Tries harder" to delete {@code file} than just calling {@code delete} on it. Presently this
* just means "try a second time if the first time fails, and if that fails then try to delete
* when the virtual machine terminates." On Windows Vista, sometimes deletes fail but then
* succeed if you just try again. Given that delete failure is a rare occurrence, we can
* implement this hacky workaround without any negative consequences for normal behavior.
*/
public static boolean deleteHarder (File file) {
// if at first you don't succeed... try, try again
boolean deleted = (file.delete() || file.delete());
if (!deleted) {
file.deleteOnExit();
}
return deleted;
}
/**
* Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}.
* Note that some children may still be deleted even if {@code false} is returned. Also, since
* {@link #deleteHarder} is used, the {@code file} could be deleted once the jvm exits even if
* {@code false} is returned.
*
* @param file file to delete.
* @return true iff {@code file} was successfully deleted.
*/
public static boolean deleteDirHarder (File file) {
if (file.isDirectory()) {
for (File child : file.listFiles()) {
deleteDirHarder(child);
}
}
return deleteHarder(file);
}
/**
* Reads the contents of the supplied input stream into a list of lines. Closes the reader on
* successful or failed completion.
*/
public static List readLines (Reader in)
throws IOException
{
List lines = new ArrayList<>();
try (BufferedReader bin = new BufferedReader(in)) {
for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {}
}
return lines;
}
/**
* Unpacks the specified jar file into the specified target directory.
* @param cleanExistingDirs if true, all files and subdirectories in all directories contained in {@code jar} will
* be deleted prior to unpacking the jar.
*/
public static void unpackJar (ZipFile jar, File target, boolean cleanExistingDirs)
throws IOException
{
if (cleanExistingDirs) {
Enumeration extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory()) {
File efile = new File(target, entry.getName());
if (efile.exists()) {
deleteDirHarder(efile);
}
}
}
}
Enumeration extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File efile = new File(target, entry.getName());
// if we're unpacking a normal jar file, it will have special path
// entries that allow us to create our directories first
if (entry.isDirectory()) {
if (!efile.exists() && !efile.mkdir()) {
log.warning("Failed to create jar entry path", "jar", jar, "entry", entry);
}
continue;
}
// but some do not, so we want to ensure that our directories exist
// prior to getting down and funky
File parent = new File(efile.getParent());
if (!parent.exists() && !parent.mkdirs()) {
log.warning("Failed to create jar entry parent", "jar", jar, "parent", parent);
continue;
}
try (BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(efile));
InputStream jin = jar.getInputStream(entry)) {
StreamUtil.copy(jin, fout);
} catch (Exception e) {
throw new IOException(
Log.format("Failure unpacking", "jar", jar, "entry", efile), e);
}
}
}
/**
* Copies the given {@code source} file to the given {@code target}.
*/
public static void copy (File source, File target) throws IOException {
try (FileInputStream in = new FileInputStream(source);
FileOutputStream out = new FileOutputStream(target)) {
StreamUtil.copy(in, out);
}
}
/**
* Marks {@code file} as executable, if it exists. Catches and logs any errors that occur.
*/
public static void makeExecutable (File file) {
try {
if (file.exists()) {
if (!file.setExecutable(true, false)) {
log.warning("Failed to mark as executable", "file", file);
}
}
} catch (Exception e) {
log.warning("Failed to mark as executable", "file", file, "error", e);
}
}
/**
* Used by {@link #walkTree}.
*/
public interface Visitor
{
void visit (File file);
}
/**
* Walks all files in {@code root}, calling {@code visitor} on each file in the tree.
*/
public static void walkTree (File root, Visitor visitor)
{
File[] children = root.listFiles();
if (children == null) return;
Deque stack = new ArrayDeque<>(Arrays.asList(children));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
File[] currentChildren = currentFile.listFiles();
if (currentChildren != null) {
for (File file : currentChildren) {
stack.push(file);
}
}
}
}
}
/**
* Returns files as Paths from path {@code basePath}, filtered Glob pattern {@code globPattern}.
*/
public static Set getFilePathsByGlob(String basePath, String globPattern) {
//massaging glob pattern
final String globPatternUnixSeparator = globPattern.replace("\\", "/");
final String glob = String.format("glob:%s%s%s",
basePath.replace("\\", "/"),
globPatternUnixSeparator.startsWith("/") ? "" : "/",
globPatternUnixSeparator);
final PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);
final Set foundFilePaths = new HashSet<>();
try {
Files.walkFileTree(Paths.get(basePath), new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path path,
BasicFileAttributes attrs) {
if (pathMatcher.matches(path) && !Files.isDirectory(path)) {
foundFilePaths.add(path);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
});
} catch (Exception e) {
log.warning(String.format("Failed to get file list with path %s with glob %s",
basePath, globPattern), "error", e);
return Collections.emptySet();
}
return foundFilePaths;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy