X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=blobdiff_plain;f=getdown%2Fsrc%2Fgetdown%2Fcore%2Fsrc%2Fmain%2Fjava%2Fcom%2Fthreerings%2Fgetdown%2Fdata%2FPathBuilder.java;fp=getdown%2Fsrc%2Fgetdown%2Fcore%2Fsrc%2Fmain%2Fjava%2Fcom%2Fthreerings%2Fgetdown%2Fdata%2FPathBuilder.java;h=b0a1dc9203302f75ed023bc7b0fa3b53c4a64c36;hp=0000000000000000000000000000000000000000;hb=ca8504cf9d10874dce9f07cf7a9d933853fe0dd0;hpb=775e7bc104584e88dddcea73fbf02c66f5200c16 diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java new file mode 100644 index 0000000..b0a1dc9 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java @@ -0,0 +1,135 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 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 classPathEntries = new LinkedHashSet(); + 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 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 resources = app.getNativeResources(); + if (resources.isEmpty()) { + return null; + } + + LinkedHashSet 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); + } +}