All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.getdown.data.PathBuilder Maven / Gradle / Ivy

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.data;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipFile;

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());
        }
        addClassPathDirectories(app, classPathEntries);
        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);
        }

        addClassPathDirectories(app, classPathEntries);

        return new ClassPath(classPathEntries);
    }

    private static void addClassPathDirectories (Application app, Set classPathEntries) {
        for (String cpdir : app.getClassPathDirectories()) {
            File cpfile = new File(cpdir);
            if (!cpfile.isAbsolute()) {
                cpfile = new File(app.getAppDir(), cpdir);
            }
            classPathEntries.add(cpfile);
        }
    }

    /**
     * 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 ZipFile(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);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy