2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
6 package com.threerings.getdown.data;
9 import java.io.IOException;
10 import java.util.LinkedHashSet;
11 import java.util.List;
12 import java.util.concurrent.TimeUnit;
13 import java.util.jar.JarFile;
15 import com.threerings.getdown.cache.GarbageCollector;
16 import com.threerings.getdown.cache.ResourceCache;
17 import com.threerings.getdown.util.FileUtil;
18 import static com.threerings.getdown.Log.log;
20 public class PathBuilder
22 /** Name of directory to store cached code files in. */
23 public static final String CODE_CACHE_DIR = ".cache";
25 /** Name of directory to store cached native resources in. */
26 public static final String NATIVE_CACHE_DIR = ".ncache";
29 * Builds either a default or cached classpath based on {@code app}'s configuration.
31 public static ClassPath buildClassPath (Application app) throws IOException
33 return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
37 * Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
40 public static ClassPath buildDefaultClassPath (Application app)
42 LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
43 for (Resource resource: app.getActiveCodeResources()) {
44 classPathEntries.add(resource.getFinalTarget());
46 return new ClassPath(classPathEntries);
50 * Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into
51 * a cache directory and then referencing them from there. This avoids problems with
52 * overwriting in-use classpath elements when the application is later updated. This also
53 * "garbage collects" expired caches if necessary.
55 public static ClassPath buildCachedClassPath (Application app) throws IOException
57 File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR);
59 // a negative value of code_cache_retention_days allows to clean up the cache forcefully
60 long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
61 if (retainMillis != 0L) {
62 GarbageCollector.collect(codeCacheDir, retainMillis);
65 ResourceCache cache = new ResourceCache(codeCacheDir);
66 LinkedHashSet<File> classPathEntries = new LinkedHashSet<>();
67 for (Resource resource: app.getActiveCodeResources()) {
68 String digest = app.getDigest(resource);
69 File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest);
70 classPathEntries.add(entry);
73 return new ClassPath(classPathEntries);
77 * Builds a {@link ClassPath} instance by first caching all native jars (indicated by
78 * nresource=[native jar]), unpacking them, and referencing the locations of each of the
79 * unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath}
81 * @param app used to determine native jars and related information.
82 * @param addCurrentLibraryPath if true, it adds the locations referenced by
83 * {@code System.getProperty("java.library.path")} as well.
84 * @return a classpath instance if at least one native resource was found and unpacked,
85 * {@code null} if no native resources were used by the application.
87 public static ClassPath buildLibsPath (Application app,
88 boolean addCurrentLibraryPath) throws IOException {
89 List<Resource> resources = app.getNativeResources();
90 if (resources.isEmpty()) {
94 LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
95 File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR);
96 ResourceCache cache = new ResourceCache(nativeCacheDir);
98 // negative value forces total garbage collection, 0 avoids garbage collection at all
99 long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
100 if (retainMillis != 0L) {
101 GarbageCollector.collectNative(nativeCacheDir, retainMillis);
104 for (Resource resource : resources) {
105 // Use untruncated cache subdirectory names to avoid overwriting issues when unpacking,
106 // in the off chance that two native jars share a directory AND contain files with the
108 String digest = app.getDigest(resource);
109 File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest);
110 File cachedParent = cachedFile.getParentFile();
111 File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked");
113 if (!unpackedIndicator.exists()) {
115 FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false);
116 unpackedIndicator.createNewFile();
117 } catch (IOException ioe) {
118 log.warning("Failed to unpack native jar",
119 "file", cachedFile.getAbsolutePath(), ioe);
120 // Keep going and unpack the other jars...
124 nativedirs.add(cachedFile.getParentFile());
127 if (addCurrentLibraryPath) {
128 for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) {
129 nativedirs.add(new File(path));
133 return new ClassPath(nativedirs);