This commit is contained in:
Saeid Nourian
2016-10-11 15:59:14 -04:00
20 changed files with 726 additions and 59 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
Getdown - application installer, patcher and launcher
Copyright (C) 2004-2014 Three Rings Design, Inc.
Copyright (C) 2004-2016 Getdown authors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
+11 -4
View File
@@ -25,8 +25,8 @@
<licenses>
<license>
<name>GNU Lesser General Public License (LGPL), Version 2.1</name>
<url>http://www.fsf.org/licensing/licenses/lgpl.txt</url>
<name>The (New) BSD License</name>
<url>http://www.opensource.org/licenses/bsd-license.php</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -90,6 +90,12 @@
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
@@ -109,8 +115,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<source>1.6</source>
<target>1.6</target>
<fork>true</fork>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
@@ -211,6 +217,7 @@
<Codebase>*</Codebase>
<Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
<Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
<Trusted-Library>true</Trusted-Library>
</manifestEntries>
</archive>
</configuration>
@@ -0,0 +1,71 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Represents the class path and it's elements of the application to be launched. The class path can
* either be represented as an {@link #asArgumentString() argument string} for the java command line
* or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}.
*/
public class ClassPath
{
public ClassPath (LinkedHashSet<File> classPathEntries)
{
_classPathEntries = Collections.unmodifiableSet(classPathEntries);
}
/**
* Returns the class path as an java command line argument string, e.g.
*
* <pre>
* /path/to/a.jar:/path/to/b.jar
* </pre>
*/
public String asArgumentString ()
{
StringBuilder builder = new StringBuilder();
String delimiter = "";
for (File entry: _classPathEntries) {
builder.append(delimiter).append(entry.getAbsolutePath());
delimiter = File.pathSeparator;
}
return builder.toString();
}
/**
* Returns the class path entries as an array of URLs to be used for example by an
* {@link URLClassLoader}.
*/
public URL[] asUrls ()
{
URL[] urls = new URL[_classPathEntries.size()];
int i = 0;
for (File entry : _classPathEntries) {
urls[i++] = getURL(entry);
}
return urls;
}
public Set<File> getClassPathEntries ()
{
return _classPathEntries;
}
private static URL getURL (File file)
{
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException("URL of file is illegal: " + file.getAbsolutePath(), e);
}
}
private final Set<File> _classPathEntries;
}
@@ -0,0 +1,76 @@
//
// 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
{
static final String CACHE_DIR = ".cache";
/**
* Builds either a default or cached classpath based on {@code app}'s configuration.
*/
public static ClassPath buildClassPath (Application app) throws IOException
{
return app.useCodeCache() ? buildDefaultClassPath(app) : buildCachedClassPath(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 cacheDir = new File(app.getAppdir(), CACHE_DIR);
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
if (app.getCodeCacheRetentionDays() <= 0) {
runGarbageCollection(app, cacheDir);
}
ResourceCache cache = new ResourceCache(cacheDir);
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
for (Resource resource: app.getActiveCodeResources()) {
File entry = cache.cacheFile(resource.getFinalTarget(), app.getDigest(resource));
classPathEntries.add(entry);
}
if (app.getCodeCacheRetentionDays() > 0) {
runGarbageCollection(app, cacheDir);
}
return new ClassPath(classPathEntries);
}
private static void runGarbageCollection (Application app, File cacheDir)
{
long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays());
GarbageCollector.collect(cacheDir, retainMillis);
}
}
@@ -0,0 +1,61 @@
package com.threerings.getdown.classpath.cache;
import java.io.File;
import com.threerings.getdown.util.FileUtil;
/**
* Collects elements in the {@link ResourceCache cache} which became unused and deletes them
* afterwards.
*/
public class GarbageCollector
{
/**
* Collect and delete the garbage in the cache.
*/
public static void collect (File cacheDir, final long retentionPeriodMillis)
{
FileUtil.walkTree(cacheDir, new FileUtil.Visitor() {
@Override public void visit (File file) {
File cachedFile = getCachedFile(file);
File lastAccessedFile = getLastAccessedFile(file);
if (!cachedFile.exists() || !lastAccessedFile.exists()) {
if (cachedFile.exists()) {
cachedFile.delete();
} else {
lastAccessedFile.delete();
}
} else if (shouldDelete(lastAccessedFile, retentionPeriodMillis)) {
lastAccessedFile.delete();
cachedFile.delete();
}
File folder = file.getParentFile();
if (folder.list().length == 0) {
folder.delete();
}
}
});
}
private static boolean shouldDelete (File lastAccessedFile, long retentionMillis)
{
return System.currentTimeMillis() - lastAccessedFile.lastModified() > retentionMillis;
}
private static File getLastAccessedFile (File file)
{
return isLastAccessedFile(file) ? file : new File(
file.getParentFile(), file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
}
private static boolean isLastAccessedFile (File file)
{
return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
}
private static File getCachedFile (File file)
{
return !isLastAccessedFile(file) ? file : new File(
file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")));
}
}
@@ -0,0 +1,73 @@
package com.threerings.getdown.classpath.cache;
import java.io.File;
import java.io.IOException;
import com.threerings.getdown.util.FileUtil;
/**
* Maintains a cache of code resources. The cache allows multiple application instances of different
* versions to open at the same time.
*/
public class ResourceCache
{
public ResourceCache (File _cacheDir) throws IOException
{
this._cacheDir = _cacheDir;
createDirectoryIfNecessary(_cacheDir);
}
private void createDirectoryIfNecessary (File dir) throws IOException
{
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("unable to create directory: " + dir.getAbsolutePath());
}
}
/**
* Caches the given file under it's {@code digest}.
*
* @return the cached file
*/
public File cacheFile (File fileToCache, String digest) throws IOException
{
File cacheLocation = new File(_cacheDir, digest.substring(0, 2));
createDirectoryIfNecessary(cacheLocation);
File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache));
File lastAccessedFile = new File(
cacheLocation, cachedFile.getName() + LAST_ACCESSED_FILE_SUFFIX);
if (!cachedFile.exists()) {
createNewFile(cachedFile);
FileUtil.copy(fileToCache, cachedFile);
}
if (lastAccessedFile.exists()) {
lastAccessedFile.setLastModified(System.currentTimeMillis());
} else {
createNewFile(lastAccessedFile);
}
return cachedFile;
}
private void createNewFile (File fileToCreate) throws IOException
{
if (!fileToCreate.exists() && !fileToCreate.createNewFile()) {
throw new IOException("unable to create new file: " + fileToCreate.getAbsolutePath());
}
}
private String getFileSuffix (File fileToCache) {
String fileName = fileToCache.getName();
int index = fileName.lastIndexOf(".");
return index > -1 ? fileName.substring(index) : "";
}
private final File _cacheDir;
static final String LAST_ACCESSED_FILE_SUFFIX = ".lastAccessed";
}
@@ -33,6 +33,8 @@ import com.samskivert.util.StringUtil;
import org.apache.commons.codec.binary.Base64;
import com.threerings.getdown.classpath.ClassPaths;
import com.threerings.getdown.classpath.ClassPath;
import com.threerings.getdown.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.*;
@@ -232,6 +234,32 @@ public class Application
_extraAppArgs = (appargs == null) ? ArrayUtil.EMPTY_STRING : appargs;
}
/**
* Returns the configured application directory.
*/
public File getAppdir()
{
return _appdir;
}
/**
* Returns whether the application should cache code resources prior to launching the
* application.
*/
public boolean useCodeCache ()
{
return _useCodeCache;
}
/**
* Returns the number of days a cached code resource is allowed to stay unused before it
* becomes eligible for deletion.
*/
public int getCodeCacheRetentionDays ()
{
return _codeCacheRetentionDays;
}
/**
* Returns a resource that refers to the application configuration file itself.
*/
@@ -260,6 +288,14 @@ public class Application
return _resources;
}
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digest.getDigest(resource);
}
/**
* Returns a list of all the active {@link Resource} objects used by this application (code and
* non-code).
@@ -656,6 +692,11 @@ public class Application
// obtain a thread dump of the running JVM
_windebug = getLocalPath("debug.txt").exists();
// whether to cache code resources and launch from cache
_useCodeCache = Boolean.parseBoolean((String) cdata.get("use_code_cache"));
_codeCacheRetentionDays = cdata.containsKey("code_cache_retention_days") ?
Integer.parseInt((String) cdata.get("use_code_cache")) : 7;
// parse and return our application config
UpdateInterface ui = new UpdateInterface();
_name = ui.name = (String)cdata.get("ui.name");
@@ -901,16 +942,11 @@ public class Application
boolean dashJarMode = MANIFEST_CLASS.equals(_class);
// add the -classpath arguments if we're not in -jar mode
StringBuilder cpbuf = new StringBuilder();
for (Resource rsrc : getActiveCodeResources()) {
if (cpbuf.length() > 0) {
cpbuf.append(File.pathSeparator);
}
cpbuf.append(rsrc.getFinalTarget().getAbsolutePath());
}
ClassPath classPath = ClassPaths.buildClassPath(this);
if (!dashJarMode) {
args.add("-classpath");
args.add(cpbuf.toString());
args.add(classPath.asArgumentString());
}
// we love our Mac users, so we do nice things to preserve our application identity
@@ -960,7 +996,7 @@ public class Application
// if we're in -jar mode add those arguments, otherwise add the app class name
if (dashJarMode) {
args.add("-jar");
args.add(cpbuf.toString());
args.add(classPath.asArgumentString());
} else {
args.add(_class);
}
@@ -1007,20 +1043,13 @@ public class Application
/**
* Runs this application directly in the current VM.
*/
public void invokeDirect (JApplet applet)
public void invokeDirect (JApplet applet) throws IOException
{
// create a custom class loader
ArrayList<URL> jars = new ArrayList<URL>();
for (Resource rsrc : getActiveCodeResources()) {
try {
jars.add(new URL("file", "", rsrc.getFinalTarget().getAbsolutePath()));
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
URLClassLoader loader = new URLClassLoader(
jars.toArray(new URL[jars.size()]),
ClassLoader.getSystemClassLoader()) {
ClassPath classPath = ClassPaths.buildClassPath(this);
URL[] jarUrls = classPath.asUrls();
// create custom class loader
URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) {
@Override protected PermissionCollection getPermissions (CodeSource code) {
Permissions perms = new Permissions();
perms.add(new AllPermission());
@@ -1030,7 +1059,7 @@ public class Application
Thread.currentThread().setContextClassLoader(loader);
log.info("Configured URL class loader:");
for (URL url : jars) log.info(" " + url);
for (URL url : jarUrls) log.info(" " + url);
// configure any system properties that we can
for (String jvmarg : _jvmargs) {
@@ -1126,12 +1155,6 @@ public class Application
log.info("Verifying application: " + _vappbase);
log.info("Version: " + _version);
log.info("Class: " + _class);
// log.info("Code: " +
// StringUtil.toString(getCodeResources().iterator()));
// log.info("Resources: " +
// StringUtil.toString(getActiveResources().iterator()));
// log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator()));
// log.info("App Args: " + StringUtil.toString(_appargs.iterator()));
// this will read in the contents of the digest file and validate itself
try {
@@ -1780,6 +1803,9 @@ public class Application
protected List<Resource> _codes = new ArrayList<Resource>();
protected List<Resource> _resources = new ArrayList<Resource>();
protected boolean _useCodeCache;
protected int _codeCacheRetentionDays;
protected Map<String,AuxGroup> _auxgroups = new HashMap<String,AuxGroup>();
protected Map<String,Boolean> _auxactive = new HashMap<String,Boolean>();
@@ -10,10 +10,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
@@ -95,6 +93,14 @@ public class Digest
return false;
}
/**
* Returns the digest of the given {@code resource}.
*/
public String getDigest (Resource resource)
{
return _digests.get(resource.getPath());
}
/**
* Creates a digest file at the specified location using the supplied list of resources.
*/
@@ -184,6 +184,19 @@ public class GetdownApp
System.exit(exitCode);
}
}
@Override
protected void fail (String message) {
// if the frame was set to be undecorated, make window decoration available
// to allow the user to close the window
if (_frame != null && _frame.isUndecorated()) {
_frame.dispose();
_frame.setUndecorated(false);
showContainer();
}
super.fail(message);
}
protected JFrame _frame;
};
app.start();
@@ -142,7 +142,7 @@ public class ConfigUtil
// if we're checking qualifiers and the os doesn't match this qualifier, skip it
String quals = pair[1].substring(1, qidx);
if (osname != null && !checkQualifiers(quals, osname, osarch)) {
log.info("Skipping", "quals", quals, "osname", osname, "osarch", osarch,
log.debug("Skipping", "quals", quals, "osname", osname, "osarch", osarch,
"key", pair[0], "value", pair[1]);
continue;
}
@@ -6,7 +6,10 @@
package com.threerings.getdown.util;
import java.io.*;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
@@ -121,4 +124,47 @@ public class FileUtil extends com.samskivert.util.FileUtil
StreamUtil.close(packedJarIn);
}
}
/**
* Copies the given {@code source} file to the given {@code target}.
*/
public static void copy (File source, File target) throws IOException {
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(source);
out = new FileOutputStream(target);
StreamUtil.copy(in, out);
} finally {
StreamUtil.close(in);
StreamUtil.close(out);
}
}
/**
* Used by {@link #walkTree}.
*/
public interface Visitor
{
void visit (File file);
}
/**
* Walks all files in {@code root}, calling {@code visitor} on each file in the tree.
*/
public static void walkTree (File root, Visitor visitor)
{
Deque<File> stack = new ArrayDeque<File>(Arrays.asList(root.listFiles()));
while (!stack.isEmpty()) {
File currentFile = stack.pop();
if (currentFile.exists()) {
visitor.visit(currentFile);
if (currentFile.isDirectory()) {
for (File file: currentFile.listFiles()) {
stack.push(file);
}
}
}
}
}
}
@@ -10,7 +10,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
@@ -155,7 +154,7 @@ public class LaunchUtil
try {
// copy the moved file back to getdown-dop-new.jar so that we don't end up
// downloading another copy next time
StreamUtil.copy(new FileInputStream(curgd), new FileOutputStream(newgd));
FileUtil.copy(curgd, newgd);
} catch (IOException e) {
log.warning("Error copying updated Getdown back: " + e);
}
@@ -172,7 +171,7 @@ public class LaunchUtil
// that didn't work, let's try copying it
log.info("Attempting to upgrade by copying over " + curgd + "...");
try {
StreamUtil.copy(new FileInputStream(newgd), new FileOutputStream(curgd));
FileUtil.copy(newgd, curgd);
} catch (IOException ioe) {
log.warning("Mayday! Brute force copy method also failed.", ioe);
}
@@ -56,7 +56,7 @@ m.init_failed = Our configuration file is missing or corrupt. Attempting \
to download a new copy...
m.java_download_failed = We were unable to automatically download the \
necessary verson of Java for your computer.\n\n\
necessary version of Java for your computer.\n\n\
Please go to www.java.com and download the latest version of \
Java, then try running the application again.
@@ -66,7 +66,7 @@ Anwendung erneut starten.
m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \
entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \
Festplatte frei sind und versuche dann, die Anwendung erneut zu \
Festplatte frei sind und versuche dann die Anwendung erneut zu \
starten.\n\n\ \
Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \
Javaversion herunterladen und installieren und dann erneut versuchen.
@@ -113,10 +113,10 @@ m.applet_stopped = Die Anwendung wurde beendet.
# application/digest errors
m.missing_appbase = In der Konfigurations-Datei fehlt die 'appbase'.
m.invalid_version = In der Konfigurations-Datei steht die falsche Version.
m.invalid_appbase = In der Konfigurations-Datei steht die falsche 'appbase'.
m.missing_class = In der Konfigurations-Datei fehlt die Anwendungs-Klasse.
m.missing_code = Die Konfigurations-Datei enth\u00e4lt keine Code-Quellen.
m.invalid_digest_file = Die \u00dcbersichts-Datei ist ung\u00fcltig.
m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'.
m.invalid_version = In der Konfigurationsdatei steht die falsche Version.
m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'.
m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse.
m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen.
m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig.
@@ -0,0 +1,49 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedHashSet;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link ClassPath}.
*/
public class ClassPathTest
{
@Before public void createJarsAndSetupClassPath () throws IOException
{
_firstJar = _folder.newFile("a.jar");
_secondJar = _folder.newFile("b.jar");
LinkedHashSet<File> classPathEntries = new LinkedHashSet<File>();
classPathEntries.add(_firstJar);
classPathEntries.add(_secondJar);
_classPath = new ClassPath(classPathEntries);
}
@Test public void shouldCreateValidArgumentString ()
{
assertEquals(
_firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(),
_classPath.asArgumentString());
}
@Test public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException
{
URL[] actualUrls = _classPath.asUrls();
assertEquals(_firstJar, new File(actualUrls[0].toURI()));
assertEquals(_secondJar, new File(actualUrls[1].toURI()));
}
@Rule public TemporaryFolder _folder = new TemporaryFolder();
private File _firstJar, _secondJar;
private ClassPath _classPath;
}
@@ -0,0 +1,68 @@
package com.threerings.getdown.classpath;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
@RunWith(MockitoJUnitRunner.class)
public class ClassPathsTest
{
@Before public void setupFilesAndResources () throws IOException
{
_firstJarFile = _appdir.newFile("a.jar");
_secondJarFile = _appdir.newFile("b.jar");
when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile);
when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile);
when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar));
when(_application.getAppdir()).thenReturn(_appdir.getRoot());
}
@Test public void shouldBuildDefaultClassPath () throws IOException
{
ClassPath classPath = ClassPaths.buildDefaultClassPath(_application);
String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator +
_secondJarFile.getAbsolutePath();
assertEquals(expectedClassPath, classPath.asArgumentString());
}
@Test public void shouldBuildCachedClassPath () throws IOException
{
when(_application.getDigest(_firstJar)).thenReturn("first");
when(_application.getDigest(_secondJar)).thenReturn("second");
when(_application.getCodeCacheRetentionDays()).thenReturn(1);
File firstCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "fi", "first.jar");
File secondCachedJarFile = FileUtil.newFile(
_appdir.getRoot(), ClassPaths.CACHE_DIR, "se", "second.jar");
String expectedClassPath = firstCachedJarFile.getAbsolutePath() + File.pathSeparator +
secondCachedJarFile.getAbsolutePath();
ClassPath classPath = ClassPaths.buildCachedClassPath(_application);
assertEquals(expectedClassPath, classPath.asArgumentString());
}
@Mock protected Application _application;
@Mock protected Resource _firstJar;
@Mock protected Resource _secondJar;
protected File _firstJarFile, _secondJarFile;
@Rule public TemporaryFolder _appdir = new TemporaryFolder();
}
@@ -0,0 +1,66 @@
package com.threerings.getdown.classpath.cache;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
/**
* Validates that cache garbage is collected and deleted correctly.
*/
public class GarbageCollectorTest
{
@Before public void setupFiles () throws IOException
{
_cachedFile = _folder.newFile("abc123.jar");
_lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
}
@Test public void shouldDeleteCacheEntryIfRetentionPeriodIsReached ()
{
gcNow();
assertFalse(_cachedFile.exists());
assertFalse(_lastAccessedFile.exists());
}
@Test public void shouldDeleteCacheFolderIfFolderIsEmpty ()
{
gcNow();
assertFalse(_folder.getRoot().exists());
}
private void gcNow() {
GarbageCollector.collect(_folder.getRoot(), -1);
}
@Test public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached ()
{
GarbageCollector.collect(_folder.getRoot(), TimeUnit.DAYS.toMillis(1));
assertTrue(_cachedFile.exists());
assertTrue(_lastAccessedFile.exists());
}
@Test public void shouldDeleteCachedFileIfLastAccessedFileIsMissing ()
{
assumeTrue(_lastAccessedFile.delete());
gcNow();
assertFalse(_cachedFile.exists());
}
@Test public void shouldDeleteLastAccessedFileIfCachedFileIsMissing ()
{
assumeTrue(_cachedFile.delete());
gcNow();
assertFalse(_lastAccessedFile.exists());
}
@Rule public TemporaryFolder _folder = new TemporaryFolder();
private File _cachedFile;
private File _lastAccessedFile;
}
@@ -0,0 +1,67 @@
package com.threerings.getdown.classpath.cache;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
/**
* Asserts the correct functionality of the {@link ResourceCache}.
*/
public class ResourceCacheTest
{
@Before public void setupCache () throws IOException {
_fileToCache = _folder.newFile("filetocache.jar");
_cache = new ResourceCache(_folder.newFolder(".cache"));
}
@Test public void shouldCacheFile () throws IOException
{
assertEquals("abc123.jar", cacheFile().getName());
}
private File cacheFile() throws IOException
{
return _cache.cacheFile(_fileToCache, "abc123");
}
@Test public void shouldTrackFileUsage () throws IOException
{
String name = "abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
File lastAccessedFile = new File(cacheFile().getParentFile(), name);
assertTrue(lastAccessedFile.exists());
}
@Test public void shouldNotCacheTheSameFile () throws Exception
{
File cachedFile = cacheFile();
cachedFile.setLastModified(YESTERDAY);
long expectedLastModified = cachedFile.lastModified();
// caching it another time
File sameCachedFile = cacheFile();
assertEquals(expectedLastModified, sameCachedFile.lastModified());
}
@Test public void shouldRememberWhenFileWasRequested () throws Exception
{
File cachedFile = cacheFile();
String name = cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX;
File lastAccessedFile = new File(cachedFile.getParentFile(), name);
lastAccessedFile.setLastModified(YESTERDAY);
long lastAccessed = lastAccessedFile.lastModified();
// caching it another time
cacheFile();
assertTrue(lastAccessedFile.lastModified() > lastAccessed);
}
@Rule public TemporaryFolder _folder = new TemporaryFolder();
private File _fileToCache;
private ResourceCache _cache;
private static final long YESTERDAY = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
}
@@ -5,16 +5,25 @@
package com.threerings.getdown.data;
import org.junit.*;
import org.junit.Test;
public class SysPropsTest {
import static org.junit.Assert.assertTrue;
@Test public void testParseJavaVersion () {
long vers = SysProps.parseJavaVersion("java.version", "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?");
assert(vers > 1060000);
public class SysPropsTest
{
@Test
public void shouldParseJavaVersion ()
{
long version = SysProps.parseJavaVersion(
"java.version", "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?");
assertTrue(version > 1060000);
}
long runVers = SysProps.parseJavaVersion("java.runtime.version",
"(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?");
assert(runVers > 106000000);
@Test
public void shouldParseJavaRuntimeVersion ()
{
long version = SysProps.parseJavaVersion(
"java.runtime.version", "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?");
assertTrue(version > 106000000);
}
}
@@ -5,11 +5,14 @@
package com.threerings.getdown.util;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*;
/**
@@ -27,4 +30,31 @@ public class FileUtilTest
assertEquals(linesBySplit[ii], lines.get(ii));
}
}
@Test public void shouldCopyFile () throws IOException
{
File source = _folder.newFile("source.txt");
File target = new File(_folder.getRoot(), "target.txt");
assertFalse(target.exists());
FileUtil.copy(source, target);
assertTrue(target.exists());
}
@Test public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException
{
_folder.newFile("a.txt");
new File(_folder.newFolder("b"), "b.txt").createNewFile();
class CountingVisitor implements FileUtil.Visitor {
int fileCount = 0;
@Override public void visit(File file) {
fileCount++;
}
}
CountingVisitor visitor = new CountingVisitor();
FileUtil.walkTree(_folder.getRoot(), visitor);
assertEquals(3, visitor.fileCount);
}
@Rule public TemporaryFolder _folder = new TemporaryFolder();
}