Clean up the nresource patch & older code cache stuff.

Two important changes:

1. Don't change the name of the code cache dir as that would leave a stale code
cache around on old installations that were using the code cache.

2. Don't create a native libs cache dir if there are no native resources.

Everything else is mostly cosmetic and organizational.
This commit is contained in:
Michael Bayne
2018-12-06 11:44:29 -08:00
parent 20fd43fd98
commit 16b8b836e4
13 changed files with 239 additions and 281 deletions
@@ -1,8 +1,11 @@
package com.threerings.getdown.classpath.cache; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import com.threerings.getdown.util.FileUtil; import com.threerings.getdown.util.FileUtil;
/** /**
@@ -44,38 +47,32 @@ public class GarbageCollector
/** /**
* Collect and delete garbage in the native cache. It tries to find a jar file with a matching * Collect and delete garbage in the native cache. It tries to find a jar file with a matching
* lastmodified file, and deletes the entire directory accordingly. * last modified file, and deletes the entire directory accordingly.
*/ */
public static void collectNative (File cacheDir, final long retentionPeriodMillis) { public static void collectNative (File cacheDir, final long retentionPeriodMillis)
FileUtil.walkDirectChildren(cacheDir, new FileUtil.Visitor() { {
@Override File[] subdirs = cacheDir.listFiles();
public void visit(File file) { if (subdirs != null) {
if (file.isDirectory()) { for (File dir : subdirs) {
// Get all the native jars in the directory (there should only be one though) if (dir.isDirectory()) {
File[] nativejars = file.listFiles(new FileFilter() { // Get all the native jars in the directory (there should only be one)
@Override for (File file : dir.listFiles()) {
public boolean accept(File pathname) { if (!file.getName().endsWith(".jar")) {
return pathname.getAbsolutePath().endsWith(".jar");
}
});
for (File nativejar : nativejars) {
if (nativejar==null || !nativejar.exists()) {
continue; continue;
} }
File cachedFile = getCachedFile(file);
File cachedFile = getCachedFile(nativejar); File lastAccessedFile = getLastAccessedFile(file);
File lastAccessedFile = getLastAccessedFile(nativejar); if (!cachedFile.exists() || !lastAccessedFile.exists() ||
shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
if (!cachedFile.exists() || !lastAccessedFile.exists() || shouldDelete(lastAccessedFile, retentionPeriodMillis)) { FileUtil.deleteDirHarder(dir);
FileUtil.deleteDirHarder(file);
} }
} }
} else { } else {
// @TODO There shouldn't be any loose files in native/ but if there are then what? Delete them? // @TODO There shouldn't be any loose files in native/ but if there are then
// file.delete(); // what? Delete them? file.delete();
} }
} }
}); }
} }
private static boolean shouldDelete (File lastAccessedFile, long retentionMillis) private static boolean shouldDelete (File lastAccessedFile, long retentionMillis)
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -14,7 +19,6 @@ public class ResourceCache
public ResourceCache (File _cacheDir) throws IOException public ResourceCache (File _cacheDir) throws IOException
{ {
this._cacheDir = _cacheDir; this._cacheDir = _cacheDir;
createDirectoryIfNecessary(_cacheDir); createDirectoryIfNecessary(_cacheDir);
} }
@@ -26,34 +30,16 @@ public class ResourceCache
} }
/** /**
* Caches the given file under it's {@code digest}. Same as calling {@link #cacheFile(File, String, boolean)} * Caches the given file under its {@code digest}.
* with {@code false} * @param fileToCache file to cache.
* @return the cached file * @param cacheSubdir the subdirectory of the cache directory in which to store the cached
* file. Usually either {@code digest} or a prefix of {@code digest}.
* @param digest a crypto digest of the cached files contents.
* @return the cached file.
*/ */
public File cacheFile (File fileToCache, String digest) throws IOException public File cacheFile (File fileToCache, String cacheSubdir, String digest) throws IOException
{ {
return cacheFile(fileToCache, digest, false); File cacheLocation = new File(_cacheDir, cacheSubdir);
}
/**
* Caches the given file under it's {@code digest}.
* @param fileToCache file to cache
* @param digest used to determine the name of the directory to store file in
* @param useFullHashName if true, the name of the cache directory will not be truncated.
* This is useful if you need to avoid the possibility of two files
* sharing a directory.
* @return the cached file
*/
public File cacheFile (File fileToCache, String digest, boolean useFullHashName) throws IOException
{
File cacheLocation;
if (useFullHashName) {
cacheLocation = new File(_cacheDir, digest);
} else {
cacheLocation = new File(_cacheDir, digest.substring(0, 2));
}
createDirectoryIfNecessary(cacheLocation); createDirectoryIfNecessary(cacheLocation);
File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache)); File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache));
@@ -1,72 +0,0 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.concurrent.TimeUnit;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
public class ClassPaths
{
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
}
/**
* Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
* the app directory.
*/
public static ClassPath buildDefaultClassPath (Application app)
{
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
classPathEntries.add(resource.getFinalTarget());
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
* a cache directory and then referencing them from there. This avoids problems with
* overwriting in-use classpath elements when the application is later updated. This also
* "garbage collects" expired caches if necessary.
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
File codeCacheDir = new File(app.getAppDir(), Application.CACHE_DIR + "/code");
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
if (app.getCodeCacheRetentionDays() != 0) {
runGarbageCollection(app, codeCacheDir);
}
ResourceCache cache = new ResourceCache(codeCacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
for (Resource resource: app.getActiveCodeResources()) {
File entry = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource));
classPathEntries.add(entry);
}
return new ClassPath(classPathEntries);
}
private static void runGarbageCollection (Application app, File cacheDir)
{
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collect(cacheDir, retainMillis);
}
}
@@ -1,83 +0,0 @@
package com.threerings.getdown.classpath;
import com.threerings.getdown.classpath.cache.GarbageCollector;
import com.threerings.getdown.classpath.cache.ResourceCache;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import static com.threerings.getdown.Log.log;
/**
* Similar to {@link ClassPath}, except used to represent directories containing native libraries instead.
*/
public class NativeLibPath extends ClassPath {
public NativeLibPath(LinkedHashSet<File> entries) {
super(entries);
}
/**
* Builds a {@link NativeLibPath} instance by first caching all native jars (indicated by
* nresource=[native jar]), unpacking them, and referencing the locations of each of the unpacked files.
* Also performs garbage collection similar to {@link ClassPaths#buildCachedClassPath}
*
* @param app used to determine native jars and related information
* @param addCurrentLibraryPath if true, it adds the locations referenced by
* {@code System.getProperty("java.library.path")} as well
*/
public static NativeLibPath buildLibsPath(Application app, boolean addCurrentLibraryPath) throws IOException {
List<Resource> resources = app.getNativeJars();
String[] curPaths = System.getProperty("java.library.path").split(File.pathSeparator);
LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
File nativeCacheDir = new File(app.getAppDir(), Application.CACHE_DIR + "/native");
ResourceCache cache = new ResourceCache(nativeCacheDir);
// negative value forces total garbage collection, 0 avoids garbage collection at all
if (app.getCodeCacheRetentionDays() != 0) {
runGarbageCollection(app, nativeCacheDir);
}
for (Resource resource : resources) {
// Use untruncated directory names because in the off chance that two native jars share a directory AND
// contain files with the same names, we'll get overwriting issues when unpacking
File cachedFile = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource), true);
if (!getUnpackedIndicator(cachedFile).exists()) {
try {
FileUtil.unpackJar(new JarFile(cachedFile), cachedFile.getParentFile(), false);
getUnpackedIndicator(cachedFile).createNewFile();
} catch (IOException ioe) {
log.warning("Failed to unpack native jar", "File", cachedFile.getAbsolutePath(), ioe);
// Keep going and unpack the other jars...
}
}
nativedirs.add(cachedFile.getParentFile());
}
if (addCurrentLibraryPath) {
for (String path : curPaths) {
nativedirs.add(new File(path));
}
}
return new NativeLibPath(nativedirs);
}
private static void runGarbageCollection(Application app, File nativeCacheDir) {
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collectNative(nativeCacheDir, retainMillis);
}
private static File getUnpackedIndicator(File cachedFile) {
return new File(cachedFile.getParent(), cachedFile.getName() + ".unpacked");
}
}
@@ -23,9 +23,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import com.threerings.getdown.classpath.ClassPaths;
import com.threerings.getdown.classpath.ClassPath;
import com.threerings.getdown.classpath.NativeLibPath;
import com.threerings.getdown.util.*; import com.threerings.getdown.util.*;
// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+ // avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
import com.threerings.getdown.util.Base64; import com.threerings.getdown.util.Base64;
@@ -55,9 +52,6 @@ public class Application
/** A special classname that means 'use -jar code.jar' instead of a classname. */ /** A special classname that means 'use -jar code.jar' instead of a classname. */
public static final String MANIFEST_CLASS = "manifest"; public static final String MANIFEST_CLASS = "manifest";
/** Name of directory to store cached files in. */
public static final String CACHE_DIR = ".cache";
/** Used to communicate information about the UI displayed when updating the application. */ /** Used to communicate information about the UI displayed when updating the application. */
public static final class UpdateInterface public static final class UpdateInterface
{ {
@@ -379,19 +373,17 @@ public class Application
} }
/** /**
* Returns all jar resources indicated to contain native library files (.dll, .so etc.) * Returns all resources indicated to contain native library files (.dll, .so, etc.).
*/ */
public List<Resource> getNativeJars() { public List<Resource> getNativeResources ()
{
List<Resource> nativeJars = new ArrayList<>(); List<Resource> natives = new ArrayList<>();
for (Resource resource: _resources) { for (Resource resource: _resources) {
if (resource.isNativeJar()) { if (resource.isNative()) {
nativeJars.add(resource); natives.add(resource);
} }
} }
return natives;
return nativeJars;
} }
/** /**
@@ -702,7 +694,10 @@ public class Application
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes); parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
ArrayList<Resource> rsrcs = new ArrayList<>(); ArrayList<Resource> rsrcs = new ArrayList<>();
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs); parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs); parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
parseResources(config, auxgroup + ".presource", Resource.PRELOAD, rsrcs);
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
} }
@@ -934,12 +929,7 @@ public class Application
boolean dashJarMode = MANIFEST_CLASS.equals(_class); boolean dashJarMode = MANIFEST_CLASS.equals(_class);
// add the -classpath arguments if we're not in -jar mode // add the -classpath arguments if we're not in -jar mode
ClassPath classPath = ClassPaths.buildClassPath(this); ClassPath classPath = PathBuilder.buildClassPath(this);
// get the -Djava.library.path value to pass
// @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false?
NativeLibPath nativeLibPaths = NativeLibPath.buildLibsPath(this, true);
if (!dashJarMode) { if (!dashJarMode) {
args.add("-classpath"); args.add("-classpath");
args.add(classPath.asArgumentString()); args.add(classPath.asArgumentString());
@@ -963,8 +953,12 @@ public class Application
// add the marker indicating the app is running in getdown // add the marker indicating the app is running in getdown
args.add("-D" + Properties.GETDOWN + "=true"); args.add("-D" + Properties.GETDOWN + "=true");
// set the native library path // set the native library path if we have native resources
args.add("-Djava.library.path=" + nativeLibPaths.asArgumentString()); // @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false?
ClassPath javaLibPath = PathBuilder.buildLibsPath(this, true);
if (javaLibPath != null) {
args.add("-Djava.library.path=" + javaLibPath.asArgumentString());
}
// pass along any pass-through arguments // pass along any pass-through arguments
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
@@ -1044,7 +1038,7 @@ public class Application
*/ */
public void invokeDirect () throws IOException public void invokeDirect () throws IOException
{ {
ClassPath classPath = ClassPaths.buildClassPath(this); ClassPath classPath = PathBuilder.buildClassPath(this);
URL[] jarUrls = classPath.asUrls(); URL[] jarUrls = classPath.asUrls();
// create custom class loader // create custom class loader
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File; import java.io.File;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@@ -9,9 +14,9 @@ import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
/** /**
* Represents the class path and it's elements of the application to be launched. The class path can * Represents the class path and it's elements of the application to be launched. The class path
* either be represented as an {@link #asArgumentString() argument string} for the java command line * can either be represented as an {@link #asArgumentString() argument string} for the java command
* or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}. * line or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}.
*/ */
public class ClassPath public class ClassPath
{ {
@@ -0,0 +1,135 @@
//
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import com.threerings.getdown.cache.GarbageCollector;
import com.threerings.getdown.cache.ResourceCache;
import com.threerings.getdown.util.FileUtil;
import static com.threerings.getdown.Log.log;
public class PathBuilder
{
/** Name of directory to store cached code files in. */
public static final String CODE_CACHE_DIR = ".cache";
/** Name of directory to store cached native resources in. */
public static final String NATIVE_CACHE_DIR = ".ncache";
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
}
/**
* Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
* the app directory.
*/
public static ClassPath buildDefaultClassPath (Application app)
{
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
classPathEntries.add(resource.getFinalTarget());
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
* a cache directory and then referencing them from there. This avoids problems with
* overwriting in-use classpath elements when the application is later updated. This also
* "garbage collects" expired caches if necessary.
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR);
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
if (retainMillis != 0L) {
GarbageCollector.collect(codeCacheDir, retainMillis);
}
ResourceCache cache = new ResourceCache(codeCacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
for (Resource resource: app.getActiveCodeResources()) {
String digest = app.getDigest(resource);
File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest);
classPathEntries.add(entry);
}
return new ClassPath(classPathEntries);
}
/**
* Builds a {@link ClassPath} instance by first caching all native jars (indicated by
* nresource=[native jar]), unpacking them, and referencing the locations of each of the
* unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath}
*
* @param app used to determine native jars and related information.
* @param addCurrentLibraryPath if true, it adds the locations referenced by
* {@code System.getProperty("java.library.path")} as well.
* @return a classpath instance if at least one native resource was found and unpacked,
* {@code null} if no native resources were used by the application.
*/
public static ClassPath buildLibsPath (Application app,
boolean addCurrentLibraryPath) throws IOException {
List<Resource> resources = app.getNativeResources();
if (resources.isEmpty()) {
return null;
}
LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR);
ResourceCache cache = new ResourceCache(nativeCacheDir);
// negative value forces total garbage collection, 0 avoids garbage collection at all
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
if (retainMillis != 0L) {
GarbageCollector.collectNative(nativeCacheDir, retainMillis);
}
for (Resource resource : resources) {
// Use untruncated cache subdirectory names to avoid overwriting issues when unpacking,
// in the off chance that two native jars share a directory AND contain files with the
// same names
String digest = app.getDigest(resource);
File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest);
File cachedParent = cachedFile.getParentFile();
File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked");
if (!unpackedIndicator.exists()) {
try {
FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false);
unpackedIndicator.createNewFile();
} catch (IOException ioe) {
log.warning("Failed to unpack native jar",
"file", cachedFile.getAbsolutePath(), ioe);
// Keep going and unpack the other jars...
}
}
nativedirs.add(cachedFile.getParentFile());
}
if (addCurrentLibraryPath) {
for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) {
nativedirs.add(new File(path));
}
}
return new ClassPath(nativedirs);
}
}
@@ -46,7 +46,7 @@ public class Resource implements Comparable<Resource>
public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK); public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
public static final EnumSet<Attr> EXEC = EnumSet.of(Attr.EXEC); public static final EnumSet<Attr> EXEC = EnumSet.of(Attr.EXEC);
public static final EnumSet<Attr> PRELOAD = EnumSet.of(Attr.PRELOAD); public static final EnumSet<Attr> PRELOAD = EnumSet.of(Attr.PRELOAD);
public static final EnumSet<Attr> NATIVE = EnumSet.of(Attr.NATIVE); public static final EnumSet<Attr> NATIVE = EnumSet.of(Attr.NATIVE);
/** /**
* Computes the MD5 hash of the supplied file. * Computes the MD5 hash of the supplied file.
@@ -218,12 +218,11 @@ public class Resource implements Comparable<Resource>
/** /**
* Returns true if this resource is a native lib jar. * Returns true if this resource is a native lib jar.
*/ */
public boolean isNativeJar () public boolean isNative ()
{ {
return _attrs.contains(Attr.NATIVE); return _attrs.contains(Attr.NATIVE);
} }
/** /**
* Computes the MD5 hash of this resource's underlying file. * Computes the MD5 hash of this resource's underlying file.
* <em>Note:</em> This is both CPU and I/O intensive. * <em>Note:</em> This is both CPU and I/O intensive.
@@ -79,20 +79,17 @@ public class FileUtil
/** /**
* Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}. * 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} * Note that some children may still be deleted even if {@code false} is returned. Also, since
* is used, the {@code file} could be deleted once the jvm exits even if {@code false} is returned. * {@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 * @param file file to delete.
* @return true iff {@code file} was successfully deleted. * @return true iff {@code file} was successfully deleted.
*/ */
public static boolean deleteDirHarder(File file) { public static boolean deleteDirHarder (File file) {
if (file.isDirectory()) { if (file.isDirectory()) {
for (File child : file.listFiles()) { for (File child : file.listFiles()) {
if (child.isDirectory()) { deleteDirHarder(child);
deleteDirHarder(child);
} else {
deleteHarder(child);
}
} }
} }
return deleteHarder(file); return deleteHarder(file);
@@ -211,7 +208,7 @@ public class FileUtil
} }
/** /**
* Used by {@link #walkTree} and {@link #walkDirectChildren}. * Used by {@link #walkTree}.
*/ */
public interface Visitor public interface Visitor
{ {
@@ -239,21 +236,4 @@ public class FileUtil
} }
} }
} }
/**
* Walks all direct sub-files of {@code root}, calling {@code visitor} on each file.
*/
public static void walkDirectChildren (final File root, Visitor visitor) {
File[] children = root.listFiles();
if (children == null) return;
Deque<File> stack = new ArrayDeque<>(Arrays.asList(children));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
}
}
}
} }
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath.cache; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.cache;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -26,7 +31,7 @@ public class ResourceCacheTest
private File cacheFile() throws IOException private File cacheFile() throws IOException
{ {
return _cache.cacheFile(_fileToCache, "abc123"); return _cache.cacheFile(_fileToCache, "abc123", "abc123");
} }
@Test public void shouldTrackFileUsage () throws IOException @Test public void shouldTrackFileUsage () throws IOException
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -1,4 +1,9 @@
package com.threerings.getdown.classpath; //
// Getdown - application installer, patcher and launcher
// Copyright (C) 2004-2016 Getdown authors
// https://github.com/threerings/getdown/blob/master/LICENSE
package com.threerings.getdown.data;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -14,11 +19,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ClassPathsTest public class PathBuilderTest
{ {
@Before public void setupFilesAndResources () throws IOException @Before public void setupFilesAndResources () throws IOException
{ {
@@ -33,7 +35,7 @@ public class ClassPathsTest
@Test public void shouldBuildDefaultClassPath () throws IOException @Test public void shouldBuildDefaultClassPath () throws IOException
{ {
ClassPath classPath = ClassPaths.buildDefaultClassPath(_application); ClassPath classPath = PathBuilder.buildDefaultClassPath(_application);
String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator + String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator +
_secondJarFile.getAbsolutePath(); _secondJarFile.getAbsolutePath();
assertEquals(expectedClassPath, classPath.asArgumentString()); assertEquals(expectedClassPath, classPath.asArgumentString());
@@ -46,15 +48,15 @@ public class ClassPathsTest
when(_application.getCodeCacheRetentionDays()).thenReturn(1); when(_application.getCodeCacheRetentionDays()).thenReturn(1);
Path firstCachedJarFile = _appdir.getRoot().toPath(). Path firstCachedJarFile = _appdir.getRoot().toPath().
resolve(Application.CACHE_DIR + "/code").resolve("fi").resolve("first.jar"); resolve(PathBuilder.CODE_CACHE_DIR).resolve("fi").resolve("first.jar");
Path secondCachedJarFile = _appdir.getRoot().toPath(). Path secondCachedJarFile = _appdir.getRoot().toPath().
resolve(Application.CACHE_DIR + "/code").resolve("se").resolve("second.jar"); resolve(PathBuilder.CODE_CACHE_DIR).resolve("se").resolve("second.jar");
String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator + String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator +
secondCachedJarFile.toAbsolutePath(); secondCachedJarFile.toAbsolutePath();
ClassPath classPath = ClassPaths.buildCachedClassPath(_application); ClassPath classPath = PathBuilder.buildCachedClassPath(_application);
assertEquals(expectedClassPath, classPath.asArgumentString()); assertEquals(expectedClassPath, classPath.asArgumentString());
} }