JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files
[jalview.git] / getdown / src / getdown / core / src / main / java / com / threerings / getdown / data / PathBuilder.java
1 //
2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
5
6 package com.threerings.getdown.data;
7
8 import java.io.File;
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;
14
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;
19
20 public class PathBuilder
21 {
22     /** Name of directory to store cached code files in. */
23     public static final String CODE_CACHE_DIR = ".cache";
24
25     /** Name of directory to store cached native resources in. */
26     public static final String NATIVE_CACHE_DIR = ".ncache";
27
28     /**
29      * Builds either a default or cached classpath based on {@code app}'s configuration.
30      */
31     public static ClassPath buildClassPath (Application app) throws IOException
32     {
33         return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app);
34     }
35
36     /**
37      * Builds a {@link ClassPath} instance for {@code app} using the code resources in place in
38      * the app directory.
39      */
40     public static ClassPath buildDefaultClassPath (Application app)
41     {
42         LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
43         for (Resource resource: app.getActiveCodeResources()) {
44             classPathEntries.add(resource.getFinalTarget());
45         }
46         return new ClassPath(classPathEntries);
47     }
48
49     /**
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.
54      */
55     public static ClassPath buildCachedClassPath (Application app) throws IOException
56     {
57         File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR);
58
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);
63         }
64
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);
71         }
72
73         return new ClassPath(classPathEntries);
74     }
75
76     /**
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}
80      *
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.
86      */
87     public static ClassPath buildLibsPath (Application app,
88                                            boolean addCurrentLibraryPath) throws IOException {
89         List<Resource> resources = app.getNativeResources();
90         if (resources.isEmpty()) {
91             return null;
92         }
93
94         LinkedHashSet<File> nativedirs = new LinkedHashSet<>();
95         File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR);
96         ResourceCache cache = new ResourceCache(nativeCacheDir);
97
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);
102         }
103
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
107             // same names
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");
112
113             if (!unpackedIndicator.exists()) {
114                 try {
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...
121                 }
122             }
123
124             nativedirs.add(cachedFile.getParentFile());
125         }
126
127         if (addCurrentLibraryPath) {
128             for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) {
129                 nativedirs.add(new File(path));
130             }
131         }
132
133         return new ClassPath(nativedirs);
134     }
135 }