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 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 Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
+11 -4
View File
@@ -25,8 +25,8 @@
<licenses> <licenses>
<license> <license>
<name>GNU Lesser General Public License (LGPL), Version 2.1</name> <name>The (New) BSD License</name>
<url>http://www.fsf.org/licensing/licenses/lgpl.txt</url> <url>http://www.opensource.org/licenses/bsd-license.php</url>
<distribution>repo</distribution> <distribution>repo</distribution>
</license> </license>
</licenses> </licenses>
@@ -90,6 +90,12 @@
<version>4.8.1</version> <version>4.8.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -109,8 +115,8 @@
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version> <version>3.3</version>
<configuration> <configuration>
<source>1.5</source> <source>1.6</source>
<target>1.5</target> <target>1.6</target>
<fork>true</fork> <fork>true</fork>
<showDeprecation>true</showDeprecation> <showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings> <showWarnings>true</showWarnings>
@@ -211,6 +217,7 @@
<Codebase>*</Codebase> <Codebase>*</Codebase>
<Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase> <Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
<Caller-Allowable-Codebase>*</Caller-Allowable-Codebase> <Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
<Trusted-Library>true</Trusted-Library>
</manifestEntries> </manifestEntries>
</archive> </archive>
</configuration> </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 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.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.*; import com.threerings.getdown.util.*;
@@ -232,6 +234,32 @@ public class Application
_extraAppArgs = (appargs == null) ? ArrayUtil.EMPTY_STRING : appargs; _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. * Returns a resource that refers to the application configuration file itself.
*/ */
@@ -260,6 +288,14 @@ public class Application
return _resources; 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 * Returns a list of all the active {@link Resource} objects used by this application (code and
* non-code). * non-code).
@@ -656,6 +692,11 @@ public class Application
// obtain a thread dump of the running JVM // obtain a thread dump of the running JVM
_windebug = getLocalPath("debug.txt").exists(); _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 // parse and return our application config
UpdateInterface ui = new UpdateInterface(); UpdateInterface ui = new UpdateInterface();
_name = ui.name = (String)cdata.get("ui.name"); _name = ui.name = (String)cdata.get("ui.name");
@@ -901,16 +942,11 @@ public class Application
boolean dashJarMode = MANIFEST_CLASS.equals(_class); boolean dashJarMode = MANIFEST_CLASS.equals(_class);
// add the -classpath arguments if we're not in -jar mode // add the -classpath arguments if we're not in -jar mode
StringBuilder cpbuf = new StringBuilder(); ClassPath classPath = ClassPaths.buildClassPath(this);
for (Resource rsrc : getActiveCodeResources()) {
if (cpbuf.length() > 0) {
cpbuf.append(File.pathSeparator);
}
cpbuf.append(rsrc.getFinalTarget().getAbsolutePath());
}
if (!dashJarMode) { if (!dashJarMode) {
args.add("-classpath"); 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 // 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 we're in -jar mode add those arguments, otherwise add the app class name
if (dashJarMode) { if (dashJarMode) {
args.add("-jar"); args.add("-jar");
args.add(cpbuf.toString()); args.add(classPath.asArgumentString());
} else { } else {
args.add(_class); args.add(_class);
} }
@@ -1007,20 +1043,13 @@ public class Application
/** /**
* Runs this application directly in the current VM. * 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 ClassPath classPath = ClassPaths.buildClassPath(this);
ArrayList<URL> jars = new ArrayList<URL>(); URL[] jarUrls = classPath.asUrls();
for (Resource rsrc : getActiveCodeResources()) {
try { // create custom class loader
jars.add(new URL("file", "", rsrc.getFinalTarget().getAbsolutePath())); URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) {
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
URLClassLoader loader = new URLClassLoader(
jars.toArray(new URL[jars.size()]),
ClassLoader.getSystemClassLoader()) {
@Override protected PermissionCollection getPermissions (CodeSource code) { @Override protected PermissionCollection getPermissions (CodeSource code) {
Permissions perms = new Permissions(); Permissions perms = new Permissions();
perms.add(new AllPermission()); perms.add(new AllPermission());
@@ -1030,7 +1059,7 @@ public class Application
Thread.currentThread().setContextClassLoader(loader); Thread.currentThread().setContextClassLoader(loader);
log.info("Configured URL class 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 // configure any system properties that we can
for (String jvmarg : _jvmargs) { for (String jvmarg : _jvmargs) {
@@ -1126,12 +1155,6 @@ public class Application
log.info("Verifying application: " + _vappbase); log.info("Verifying application: " + _vappbase);
log.info("Version: " + _version); log.info("Version: " + _version);
log.info("Class: " + _class); 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 // this will read in the contents of the digest file and validate itself
try { try {
@@ -1780,6 +1803,9 @@ public class Application
protected List<Resource> _codes = new ArrayList<Resource>(); protected List<Resource> _codes = new ArrayList<Resource>();
protected List<Resource> _resources = 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,AuxGroup> _auxgroups = new HashMap<String,AuxGroup>();
protected Map<String,Boolean> _auxactive = new HashMap<String,Boolean>(); protected Map<String,Boolean> _auxactive = new HashMap<String,Boolean>();
@@ -10,10 +10,8 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -95,6 +93,14 @@ public class Digest
return false; 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. * Creates a digest file at the specified location using the supplied list of resources.
*/ */
@@ -184,6 +184,19 @@ public class GetdownApp
System.exit(exitCode); 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; protected JFrame _frame;
}; };
app.start(); app.start();
@@ -142,8 +142,8 @@ public class ConfigUtil
// if we're checking qualifiers and the os doesn't match this qualifier, skip it // if we're checking qualifiers and the os doesn't match this qualifier, skip it
String quals = pair[1].substring(1, qidx); String quals = pair[1].substring(1, qidx);
if (osname != null && !checkQualifiers(quals, osname, osarch)) { 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]); "key", pair[0], "value", pair[1]);
continue; continue;
} }
// otherwise filter out the qualifier text // otherwise filter out the qualifier text
@@ -6,7 +6,10 @@
package com.threerings.getdown.util; package com.threerings.getdown.util;
import java.io.*; import java.io.*;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.jar.Pack200; import java.util.jar.Pack200;
@@ -121,4 +124,47 @@ public class FileUtil extends com.samskivert.util.FileUtil
StreamUtil.close(packedJarIn); 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.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import com.samskivert.io.StreamUtil;
import com.samskivert.util.RunAnywhere; import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
@@ -155,7 +154,7 @@ public class LaunchUtil
try { try {
// copy the moved file back to getdown-dop-new.jar so that we don't end up // copy the moved file back to getdown-dop-new.jar so that we don't end up
// downloading another copy next time // downloading another copy next time
StreamUtil.copy(new FileInputStream(curgd), new FileOutputStream(newgd)); FileUtil.copy(curgd, newgd);
} catch (IOException e) { } catch (IOException e) {
log.warning("Error copying updated Getdown back: " + 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 // that didn't work, let's try copying it
log.info("Attempting to upgrade by copying over " + curgd + "..."); log.info("Attempting to upgrade by copying over " + curgd + "...");
try { try {
StreamUtil.copy(new FileInputStream(newgd), new FileOutputStream(curgd)); FileUtil.copy(newgd, curgd);
} catch (IOException ioe) { } catch (IOException ioe) {
log.warning("Mayday! Brute force copy method also failed.", 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... to download a new copy...
m.java_download_failed = We were unable to automatically download the \ 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 \ Please go to www.java.com and download the latest version of \
Java, then try running the application again. Java, then try running the application again.
@@ -99,7 +99,7 @@ m.corrupt_digest_signature_error = We couldn't verify the application's digital
m.default_install_error = the support section of the website m.default_install_error = the support section of the website
m.another_getdown_running = Multiple instances of this application's \ m.another_getdown_running = Multiple instances of this application's \
installer are running. This one will stop and let another complete. installer are running. This one will stop and let another complete.
m.applet_stopped = Getdown's applet was told to stop working. m.applet_stopped = Getdown's applet was told to stop working.
@@ -66,7 +66,7 @@ Anwendung erneut starten.
m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \ m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \
entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \ 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\ \ starten.\n\n\ \
Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \ Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \
Javaversion herunterladen und installieren und dann erneut versuchen. Javaversion herunterladen und installieren und dann erneut versuchen.
@@ -113,10 +113,10 @@ m.applet_stopped = Die Anwendung wurde beendet.
# application/digest errors # application/digest errors
m.missing_appbase = In der Konfigurations-Datei fehlt die 'appbase'. m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'.
m.invalid_version = In der Konfigurations-Datei steht die falsche Version. m.invalid_version = In der Konfigurationsdatei steht die falsche Version.
m.invalid_appbase = In der Konfigurations-Datei steht die falsche 'appbase'. m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'.
m.missing_class = In der Konfigurations-Datei fehlt die Anwendungs-Klasse. m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse.
m.missing_code = Die Konfigurations-Datei enth\u00e4lt keine Code-Quellen. m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen.
m.invalid_digest_file = Die \u00dcbersichts-Datei ist ung\u00fcltig. 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; 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 () { public class SysPropsTest
long vers = SysProps.parseJavaVersion("java.version", "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?"); {
assert(vers > 1060000); @Test
public void shouldParseJavaVersion ()
{
long version = SysProps.parseJavaVersion(
"java.version", "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?");
assertTrue(version > 1060000);
}
long runVers = SysProps.parseJavaVersion("java.runtime.version", @Test
"(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?"); public void shouldParseJavaRuntimeVersion ()
assert(runVers > 106000000); {
} 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; package com.threerings.getdown.util;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.util.List; import java.util.List;
import org.junit.*; import org.junit.*;
import org.junit.rules.TemporaryFolder;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
@@ -27,4 +30,31 @@ public class FileUtilTest
assertEquals(linesBySplit[ii], lines.get(ii)); 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();
} }