Issue #64 introduced a code cache to support multiple app instances
Whenever 'use_code_cache' is set to 'true' the application's code resources are copied to a cache directory prior to launching the application. This is done in order to support multiple open application instances of different versions.
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
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;
|
||||||
|
import com.threerings.getdown.util.file.FileWalker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instead of building the class path from the application directory, this class path builder puts
|
||||||
|
* the files that make up the class path into a cache and assembles the class path from the cache
|
||||||
|
* directory.
|
||||||
|
*/
|
||||||
|
public class CacheBasedClassPathBuilder extends ClassPathBuilderBase
|
||||||
|
{
|
||||||
|
static final String CACHE_DIR = ".cache";
|
||||||
|
|
||||||
|
public CacheBasedClassPathBuilder(Application application)
|
||||||
|
{
|
||||||
|
super(application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassPath buildClassPath () throws IOException
|
||||||
|
{
|
||||||
|
File cacheDir = new File(_application.getAppdir(), CACHE_DIR);
|
||||||
|
|
||||||
|
// a negative value of code_cache_retention_days allows to clean up the cache forcefully
|
||||||
|
if (_application.getCodeCacheRetentionDays() <= 0) {
|
||||||
|
runGarbageCollection(cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceCache cache = new ResourceCache(cacheDir);
|
||||||
|
|
||||||
|
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>();
|
||||||
|
|
||||||
|
for (Resource resource: _application.getActiveCodeResources()) {
|
||||||
|
classPathEntries.add(
|
||||||
|
new ClassPathElement(
|
||||||
|
cache.cacheFile(resource.getFinalTarget(), _application.getDigest(resource))));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_application.getCodeCacheRetentionDays() > 0) {
|
||||||
|
runGarbageCollection(cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassPath(classPathEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runGarbageCollection(File cacheDir) {
|
||||||
|
GarbageCollector gc = new GarbageCollector(
|
||||||
|
new FileWalker(cacheDir), _application.getCodeCacheRetentionDays(), TimeUnit.DAYS);
|
||||||
|
|
||||||
|
gc.collectGarbage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
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<ClassPathElement> classPathEntries)
|
||||||
|
{
|
||||||
|
this._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 (ClassPathElement 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 (ClassPathElement entry : _classPathEntries) {
|
||||||
|
urls[i++] = entry.getURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ClassPathElement> getClassPathEntries ()
|
||||||
|
{
|
||||||
|
return _classPathEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Set<ClassPathElement> _classPathEntries;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class path builder compiles the class path of the application to be launched.
|
||||||
|
*/
|
||||||
|
public interface ClassPathBuilder {
|
||||||
|
/**
|
||||||
|
* Builds and returns the class path of the application to be launched.
|
||||||
|
*/
|
||||||
|
ClassPath buildClassPath () throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for {@link ClassPathBuilder class path builders}.
|
||||||
|
*/
|
||||||
|
abstract class ClassPathBuilderBase implements ClassPathBuilder {
|
||||||
|
public ClassPathBuilderBase(Application _application) {
|
||||||
|
this._application = _application;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Application _application;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link ClassPathBuilder class path builders}.
|
||||||
|
*/
|
||||||
|
public abstract class ClassPathBuilderFactory {
|
||||||
|
private ClassPathBuilderFactory () {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a class path builder that is able to compile the class path of the supplied
|
||||||
|
* {@link Application application}.
|
||||||
|
*/
|
||||||
|
public static ClassPathBuilder create (Application application)
|
||||||
|
{
|
||||||
|
return application.isUseCodeCache()
|
||||||
|
? new CacheBasedClassPathBuilder(application)
|
||||||
|
: new DefaultClassPathBuilder(application);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an element of an applications class path.
|
||||||
|
*/
|
||||||
|
public class ClassPathElement
|
||||||
|
{
|
||||||
|
public ClassPathElement (File _file)
|
||||||
|
{
|
||||||
|
this._file = _file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the code resource.
|
||||||
|
*/
|
||||||
|
public File getFile ()
|
||||||
|
{
|
||||||
|
return _file;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the elements absolute path on the file system.
|
||||||
|
*/
|
||||||
|
public String getAbsolutePath ()
|
||||||
|
{
|
||||||
|
return _file.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the file URL of this class path element.
|
||||||
|
*/
|
||||||
|
public URL getURL ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return _file.toURI().toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new IllegalStateException("URL of file is illegal: " + getAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final File _file;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
import com.threerings.getdown.data.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default class path builder assembles the class path from the code resources to be found in
|
||||||
|
* the application directory.
|
||||||
|
*/
|
||||||
|
public class DefaultClassPathBuilder extends ClassPathBuilderBase
|
||||||
|
{
|
||||||
|
public DefaultClassPathBuilder(Application _application) {
|
||||||
|
super(_application);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClassPath buildClassPath ()
|
||||||
|
{
|
||||||
|
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>();
|
||||||
|
|
||||||
|
for (Resource resource: _application.getActiveCodeResources()) {
|
||||||
|
classPathEntries.add(new ClassPathElement(resource.getFinalTarget()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassPath(classPathEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.threerings.getdown.classpath.cache;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.threerings.getdown.util.file.FileVisitor;
|
||||||
|
import com.threerings.getdown.util.file.FileWalker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects elements in the {@link ResourceCache cache} which became unused and deletes them
|
||||||
|
* afterwards.
|
||||||
|
*/
|
||||||
|
public class GarbageCollector
|
||||||
|
{
|
||||||
|
public GarbageCollector (
|
||||||
|
FileWalker _fileWalker, int retentionPeriod, TimeUnit retentionPeriodTimeUnit)
|
||||||
|
{
|
||||||
|
this._fileWalker = _fileWalker;
|
||||||
|
this._retentionMillis = retentionPeriodTimeUnit.toMillis(retentionPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect and delete the garbage in the cache.
|
||||||
|
*/
|
||||||
|
public void collectGarbage ()
|
||||||
|
{
|
||||||
|
_fileWalker.walkTree(new FileVisitor() {
|
||||||
|
@Override
|
||||||
|
public void visit(File file)
|
||||||
|
{
|
||||||
|
collect(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collect (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)) {
|
||||||
|
lastAccessedFile.delete();
|
||||||
|
cachedFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
File folder = file.getParentFile();
|
||||||
|
|
||||||
|
if (folder.list().length == 0) {
|
||||||
|
folder.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldDelete(File lastAccessedFile) {
|
||||||
|
return System.currentTimeMillis() - lastAccessedFile.lastModified() > _retentionMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getLastAccessedFile (File file)
|
||||||
|
{
|
||||||
|
if (isLastAccessedFile(file)) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(
|
||||||
|
file.getParentFile(),
|
||||||
|
file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLastAccessedFile (File file)
|
||||||
|
{
|
||||||
|
return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getCachedFile (File file)
|
||||||
|
{
|
||||||
|
if (!isLastAccessedFile(file)) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(
|
||||||
|
file.getParentFile(),
|
||||||
|
file.getName().substring(0, file.getName().lastIndexOf(".")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private final FileWalker _fileWalker;
|
||||||
|
private final long _retentionMillis;
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -5,9 +5,21 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.data;
|
package com.threerings.getdown.data;
|
||||||
|
|
||||||
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -16,27 +28,46 @@ import java.net.URLConnection;
|
|||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
import java.nio.channels.OverlappingFileLockException;
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
import java.security.*;
|
import java.security.AllPermission;
|
||||||
|
import java.security.CodeSource;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.PermissionCollection;
|
||||||
|
import java.security.Permissions;
|
||||||
|
import java.security.Signature;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.swing.JApplet;
|
import javax.swing.JApplet;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
|
||||||
import com.samskivert.io.StreamUtil;
|
import com.samskivert.io.StreamUtil;
|
||||||
import com.samskivert.text.MessageUtil;
|
import com.samskivert.text.MessageUtil;
|
||||||
import com.samskivert.util.ArrayUtil;
|
import com.samskivert.util.ArrayUtil;
|
||||||
import com.samskivert.util.RandomUtil;
|
import com.samskivert.util.RandomUtil;
|
||||||
import com.samskivert.util.RunAnywhere;
|
import com.samskivert.util.RunAnywhere;
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
import com.threerings.getdown.classpath.ClassPath;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import com.threerings.getdown.classpath.ClassPathBuilderFactory;
|
||||||
|
|
||||||
import com.threerings.getdown.launcher.RotatingBackgrounds;
|
import com.threerings.getdown.launcher.RotatingBackgrounds;
|
||||||
import com.threerings.getdown.util.*;
|
import com.threerings.getdown.util.ConfigUtil;
|
||||||
|
import com.threerings.getdown.util.ConnectionUtil;
|
||||||
import static com.threerings.getdown.Log.log;
|
import com.threerings.getdown.util.FileUtil;
|
||||||
|
import com.threerings.getdown.util.LaunchUtil;
|
||||||
|
import com.threerings.getdown.util.ProgressAggregator;
|
||||||
|
import com.threerings.getdown.util.ProgressObserver;
|
||||||
|
import com.threerings.getdown.util.VersionUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and provide access to the information contained in the <code>getdown.txt</code>
|
* Parses and provide access to the information contained in the <code>getdown.txt</code>
|
||||||
@@ -226,10 +257,10 @@ public class Application
|
|||||||
{
|
{
|
||||||
_appdir = appdir;
|
_appdir = appdir;
|
||||||
_appid = appid;
|
_appid = appid;
|
||||||
_signers = (signers == null) ? Collections.<Certificate>emptyList() : signers;
|
_signers = signers == null ? Collections.<Certificate>emptyList() : signers;
|
||||||
_config = getLocalPath(CONFIG_FILE);
|
_config = getLocalPath(CONFIG_FILE);
|
||||||
_extraJvmArgs = (jvmargs == null) ? ArrayUtil.EMPTY_STRING : jvmargs;
|
_extraJvmArgs = jvmargs == null ? ArrayUtil.EMPTY_STRING : jvmargs;
|
||||||
_extraAppArgs = (appargs == null) ? ArrayUtil.EMPTY_STRING : appargs;
|
_extraAppArgs = appargs == null ? ArrayUtil.EMPTY_STRING : appargs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,7 +383,7 @@ public class Application
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String infix = (auxgroup == null) ? "" : ("-" + auxgroup);
|
String infix = auxgroup == null ? "" : "-" + auxgroup;
|
||||||
String pfile = "patch" + infix + _version + ".dat";
|
String pfile = "patch" + infix + _version + ".dat";
|
||||||
try {
|
try {
|
||||||
URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile));
|
URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile));
|
||||||
@@ -514,7 +545,7 @@ public class Application
|
|||||||
|
|
||||||
// if we are a versioned deployment, create a versioned appbase
|
// if we are a versioned deployment, create a versioned appbase
|
||||||
try {
|
try {
|
||||||
_vappbase = (_version < 0) ? new URL(_appbase) : createVAppBase(_version);
|
_vappbase = _version < 0 ? new URL(_appbase) : createVAppBase(_version);
|
||||||
} catch (MalformedURLException mue) {
|
} catch (MalformedURLException mue) {
|
||||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
||||||
throw (IOException) new IOException(err).initCause(mue);
|
throw (IOException) new IOException(err).initCause(mue);
|
||||||
@@ -531,7 +562,7 @@ public class Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + ".");
|
String appPrefix = StringUtil.isBlank(_appid) ? "" : _appid + ".";
|
||||||
|
|
||||||
// determine our application class name
|
// determine our application class name
|
||||||
_class = (String)cdata.get(appPrefix + "class");
|
_class = (String)cdata.get(appPrefix + "class");
|
||||||
@@ -656,6 +687,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");
|
||||||
@@ -673,9 +709,9 @@ public class Application
|
|||||||
}
|
}
|
||||||
// and now ui.background can refer to the background color, but fall back to black
|
// and now ui.background can refer to the background color, but fall back to black
|
||||||
// or white, depending on the brightness of the progressText
|
// or white, depending on the brightness of the progressText
|
||||||
Color defaultBackground = (.5f < Color.RGBtoHSB(
|
Color defaultBackground = .5f < Color.RGBtoHSB(
|
||||||
ui.progressText.getRed(), ui.progressText.getGreen(), ui.progressText.getBlue(),
|
ui.progressText.getRed(), ui.progressText.getGreen(), ui.progressText.getBlue(),
|
||||||
null)[2])
|
null)[2]
|
||||||
? Color.BLACK
|
? Color.BLACK
|
||||||
: Color.WHITE;
|
: Color.WHITE;
|
||||||
ui.background = parseColor(cdata, "ui.background", defaultBackground);
|
ui.background = parseColor(cdata, "ui.background", defaultBackground);
|
||||||
@@ -690,7 +726,7 @@ public class Application
|
|||||||
|
|
||||||
// On an installation error, where do we point the user.
|
// On an installation error, where do we point the user.
|
||||||
String installError = parseUrl(cdata, "ui.install_error", null);
|
String installError = parseUrl(cdata, "ui.install_error", null);
|
||||||
ui.installError = (installError == null) ?
|
ui.installError = installError == null ?
|
||||||
"m.default_install_error" : MessageUtil.taint(installError);
|
"m.default_install_error" : MessageUtil.taint(installError);
|
||||||
|
|
||||||
// the patch notes bits
|
// the patch notes bits
|
||||||
@@ -802,8 +838,8 @@ public class Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean minVersionOK = (_javaMinVersion == 0) || (version >= _javaMinVersion);
|
boolean minVersionOK = _javaMinVersion == 0 || version >= _javaMinVersion;
|
||||||
boolean maxVersionOK = (_javaMaxVersion == 0) || (version <= _javaMaxVersion);
|
boolean maxVersionOK = _javaMaxVersion == 0 || version <= _javaMaxVersion;
|
||||||
return minVersionOK && maxVersionOK;
|
return minVersionOK && maxVersionOK;
|
||||||
|
|
||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException re) {
|
||||||
@@ -901,16 +937,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 = ClassPathBuilderFactory.create(this).buildClassPath();
|
||||||
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 +991,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);
|
||||||
}
|
}
|
||||||
@@ -1006,20 +1037,17 @@ public class Application
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs this application directly in the current VM.
|
* Runs this application directly in the current VM.
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void invokeDirect (JApplet applet)
|
public void invokeDirect (JApplet applet) throws IOException
|
||||||
{
|
{
|
||||||
// create a custom class loader
|
ClassPath classPath = ClassPathBuilderFactory.create(this).buildClassPath();
|
||||||
ArrayList<URL> jars = new ArrayList<URL>();
|
|
||||||
for (Resource rsrc : getActiveCodeResources()) {
|
URL[] jarUrls = classPath.asUrls();
|
||||||
try {
|
|
||||||
jars.add(new URL("file", "", rsrc.getFinalTarget().getAbsolutePath()));
|
// create custom class loader
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
URLClassLoader loader = new URLClassLoader(
|
URLClassLoader loader = new URLClassLoader(
|
||||||
jars.toArray(new URL[jars.size()]),
|
jarUrls,
|
||||||
ClassLoader.getSystemClassLoader()) {
|
ClassLoader.getSystemClassLoader()) {
|
||||||
@Override protected PermissionCollection getPermissions (CodeSource code) {
|
@Override protected PermissionCollection getPermissions (CodeSource code) {
|
||||||
Permissions perms = new Permissions();
|
Permissions perms = new Permissions();
|
||||||
@@ -1030,7 +1058,9 @@ 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 +1156,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 {
|
||||||
@@ -1145,7 +1169,7 @@ public class Application
|
|||||||
if (_version == -1) {
|
if (_version == -1) {
|
||||||
// make a note of the old meta-digest, if this changes we need to revalidate all of our
|
// make a note of the old meta-digest, if this changes we need to revalidate all of our
|
||||||
// resources as one or more of them have also changed
|
// resources as one or more of them have also changed
|
||||||
String olddig = (_digest == null) ? "" : _digest.getMetaDigest();
|
String olddig = _digest == null ? "" : _digest.getMetaDigest();
|
||||||
try {
|
try {
|
||||||
status.updateStatus("m.checking");
|
status.updateStatus("m.checking");
|
||||||
downloadDigestFile();
|
downloadDigestFile();
|
||||||
@@ -1299,7 +1323,7 @@ public class Application
|
|||||||
failures.add(rsrc);
|
failures.add(rsrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (failures.size() == 0) ? null : failures;
|
return failures.size() == 0 ? null : failures;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1357,6 +1381,24 @@ public class Application
|
|||||||
return _version;
|
return _version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the application should cache code resources prior to launching the
|
||||||
|
* application.
|
||||||
|
*/
|
||||||
|
public boolean isUseCodeCache ()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a versioned application base URL for the specified version.
|
* Creates a versioned application base URL for the specified version.
|
||||||
*/
|
*/
|
||||||
@@ -1602,7 +1644,7 @@ public class Application
|
|||||||
{
|
{
|
||||||
String value = (String)cdata.get(name);
|
String value = (String)cdata.get(name);
|
||||||
Rectangle rect = parseRect(name, value);
|
Rectangle rect = parseRect(name, value);
|
||||||
return (rect == null) ? def : rect;
|
return rect == null ? def : rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
|
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
|
||||||
@@ -1647,7 +1689,7 @@ public class Application
|
|||||||
{
|
{
|
||||||
String value = (String)cdata.get(name);
|
String value = (String)cdata.get(name);
|
||||||
Color color = parseColor(value);
|
Color color = parseColor(value);
|
||||||
return (color == null) ? def : color;
|
return color == null ? def : color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1670,7 +1712,7 @@ public class Application
|
|||||||
protected String[] parseList (Map<String, Object> cdata, String name)
|
protected String[] parseList (Map<String, Object> cdata, String name)
|
||||||
{
|
{
|
||||||
String value = (String)cdata.get(name);
|
String value = (String)cdata.get(name);
|
||||||
return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value);
|
return value == null ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1746,6 +1788,19 @@ public class Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the digest of the given {@code resource}.
|
||||||
|
*/
|
||||||
|
public String getDigest (Resource resource)
|
||||||
|
{
|
||||||
|
return _digest.getDigest(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getAppdir()
|
||||||
|
{
|
||||||
|
return _appdir;
|
||||||
|
}
|
||||||
|
|
||||||
protected File _appdir;
|
protected File _appdir;
|
||||||
protected String _appid;
|
protected String _appid;
|
||||||
protected File _config;
|
protected File _config;
|
||||||
@@ -1780,6 +1835,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>();
|
||||||
|
|
||||||
|
|||||||
@@ -5,27 +5,24 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.data;
|
package com.threerings.getdown.data;
|
||||||
|
|
||||||
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
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;
|
||||||
|
|
||||||
import com.samskivert.io.StreamUtil;
|
import com.samskivert.io.StreamUtil;
|
||||||
import com.samskivert.text.MessageUtil;
|
import com.samskivert.text.MessageUtil;
|
||||||
import com.samskivert.util.StringUtil;
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
import com.threerings.getdown.util.ConfigUtil;
|
import com.threerings.getdown.util.ConfigUtil;
|
||||||
import com.threerings.getdown.util.ProgressObserver;
|
import com.threerings.getdown.util.ProgressObserver;
|
||||||
|
|
||||||
import static com.threerings.getdown.Log.log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an
|
* Manages the <code>digest.txt</code> file and the computing and processing of MD5 digests for an
|
||||||
* application.
|
* application.
|
||||||
@@ -148,6 +145,14 @@ public class Digest
|
|||||||
data.append(path).append(" = ").append(digest).append("\n");
|
data.append(path).append(" = ").append(digest).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the digest of the given {@code resource}.
|
||||||
|
*/
|
||||||
|
public String getDigest (Resource resource)
|
||||||
|
{
|
||||||
|
return _digests.get(resource.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
protected HashMap<String, String> _digests = new HashMap<String, String>();
|
protected HashMap<String, String> _digests = new HashMap<String, String>();
|
||||||
protected String _metaDigest = "";
|
protected String _metaDigest = "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,15 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.util;
|
package com.threerings.getdown.util;
|
||||||
|
|
||||||
import java.io.*;
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.jar.JarOutputStream;
|
import java.util.jar.JarOutputStream;
|
||||||
@@ -14,8 +22,6 @@ import java.util.zip.GZIPInputStream;
|
|||||||
|
|
||||||
import com.samskivert.io.StreamUtil;
|
import com.samskivert.io.StreamUtil;
|
||||||
|
|
||||||
import static com.threerings.getdown.Log.log;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File related utilities.
|
* File related utilities.
|
||||||
*/
|
*/
|
||||||
@@ -109,7 +115,6 @@ public class FileUtil extends com.samskivert.util.FileUtil
|
|||||||
Pack200.Unpacker unpacker = Pack200.newUnpacker();
|
Pack200.Unpacker unpacker = Pack200.newUnpacker();
|
||||||
unpacker.unpack(packedJarIn, jarOutputStream);
|
unpacker.unpack(packedJarIn, jarOutputStream);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.warning("Failed to unpack packed 200 jar file", "jar", packedJar, "error", e);
|
log.warning("Failed to unpack packed 200 jar file", "jar", packedJar, "error", e);
|
||||||
return false;
|
return false;
|
||||||
@@ -120,4 +125,22 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.threerings.getdown.util.file;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface FileVisitor {
|
||||||
|
void visit(File file);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.threerings.getdown.util.file;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively walks over files and folders. A {@link FileVisitor visitor} is informed for each path
|
||||||
|
* traversed.
|
||||||
|
*/
|
||||||
|
public class FileWalker
|
||||||
|
{
|
||||||
|
public FileWalker (File start)
|
||||||
|
{
|
||||||
|
this._start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks over the directory tree with the provided {@code visitor}.
|
||||||
|
*/
|
||||||
|
public void walkTree (FileVisitor visitor)
|
||||||
|
{
|
||||||
|
Deque<File> stack = new ArrayDeque<File>(Arrays.asList(_start.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final File _start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import com.threerings.getdown.util.FileUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link CacheBasedClassPathBuilderTest}.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class CacheBasedClassPathBuilderTest extends ClassPathBuilderTestBase
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldBuildCacheBasedClassPath () 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(),
|
||||||
|
CacheBasedClassPathBuilder.CACHE_DIR, "fi", "first.jar");
|
||||||
|
|
||||||
|
File secondCachedJarFile = FileUtil.newFile(
|
||||||
|
_appdir.getRoot(),
|
||||||
|
CacheBasedClassPathBuilder.CACHE_DIR, "se", "second.jar");
|
||||||
|
|
||||||
|
String expectedClassPath = firstCachedJarFile.getAbsolutePath()
|
||||||
|
+ File.pathSeparator
|
||||||
|
+ secondCachedJarFile.getAbsolutePath();
|
||||||
|
|
||||||
|
ClassPath classPath = new CacheBasedClassPathBuilder(_application).buildClassPath();
|
||||||
|
|
||||||
|
assertEquals(expectedClassPath, classPath.asArgumentString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks wether the correct {@link ClassPathBuilder class path builders} are created.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class ClassPathBuilderFactoryTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldCreateDefaultClassPathBuilder ()
|
||||||
|
{
|
||||||
|
when(_application.isUseCodeCache()).thenReturn(Boolean.FALSE);
|
||||||
|
|
||||||
|
assertTrue(ClassPathBuilderFactory.create(_application) instanceof DefaultClassPathBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCacheClassPathBuilder ()
|
||||||
|
{
|
||||||
|
when(_application.isUseCodeCache()).thenReturn(Boolean.TRUE);
|
||||||
|
|
||||||
|
assertTrue(ClassPathBuilderFactory.create(_application) instanceof CacheBasedClassPathBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Application _application;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
import com.threerings.getdown.data.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for {@link ClassPathBuilder} tests.
|
||||||
|
*/
|
||||||
|
public abstract class ClassPathBuilderTestBase {
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
protected Application _application;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
protected Resource _firstJar;
|
||||||
|
|
||||||
|
protected File _firstJarFile;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
protected Resource _secondJar;
|
||||||
|
|
||||||
|
protected File _secondJarFile;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder _appdir = new TemporaryFolder();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
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.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ClassPath}.
|
||||||
|
*/
|
||||||
|
public class ClassPathTest
|
||||||
|
{
|
||||||
|
@Before
|
||||||
|
public void createJarsAndSetupClassPath () throws IOException
|
||||||
|
{
|
||||||
|
_firstJar = _folder.newFile("a.jar");
|
||||||
|
_secondJar = _folder.newFile("b.jar");
|
||||||
|
|
||||||
|
LinkedHashSet<ClassPathElement> classPathEntries = new LinkedHashSet<ClassPathElement>();
|
||||||
|
|
||||||
|
classPathEntries.add(new ClassPathElement(_firstJar));
|
||||||
|
classPathEntries.add(new ClassPathElement(_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;
|
||||||
|
private File _secondJar;
|
||||||
|
|
||||||
|
private ClassPath _classPath;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.threerings.getdown.classpath;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the {@link DefaultClassPathBuilder}.
|
||||||
|
*/
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class DefaultClassPathBuilderTest extends ClassPathBuilderTestBase
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldBuildClassPath () throws IOException
|
||||||
|
{
|
||||||
|
ClassPath classPath = new DefaultClassPathBuilder(_application).buildClassPath();
|
||||||
|
|
||||||
|
String expectedClassPath = _firstJarFile.getAbsolutePath()
|
||||||
|
+ File.pathSeparator
|
||||||
|
+ _secondJarFile.getAbsolutePath();
|
||||||
|
|
||||||
|
assertEquals(expectedClassPath, classPath.asArgumentString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package com.threerings.getdown.classpath.cache;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import com.threerings.getdown.util.file.FileWalker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 collector = new GarbageCollector(
|
||||||
|
new FileWalker(_folder.getRoot()), -1, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
collector.collectGarbage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached ()
|
||||||
|
{
|
||||||
|
GarbageCollector collector = new GarbageCollector(
|
||||||
|
new FileWalker(_folder.getRoot()), 1, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
collector.collectGarbage();
|
||||||
|
|
||||||
|
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,81 @@
|
|||||||
|
package com.threerings.getdown.classpath.cache;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
{
|
||||||
|
File lastAccessedFile = new File(
|
||||||
|
cacheFile().getParentFile(),
|
||||||
|
"abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
|
||||||
|
|
||||||
|
assertTrue(lastAccessedFile.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotCacheTheSameFile () throws Exception
|
||||||
|
{
|
||||||
|
File cachedFile = cacheFile();
|
||||||
|
|
||||||
|
TimeUnit.MILLISECONDS.sleep(5);
|
||||||
|
|
||||||
|
// caching it another time
|
||||||
|
File sameCachedFile = cacheFile();
|
||||||
|
|
||||||
|
assertEquals(cachedFile.lastModified(), sameCachedFile.lastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRememberWhenFileWasRequested () throws Exception
|
||||||
|
{
|
||||||
|
cacheFile();
|
||||||
|
|
||||||
|
TimeUnit.MILLISECONDS.sleep(5);
|
||||||
|
|
||||||
|
// caching it another time
|
||||||
|
File sameCachedFile = cacheFile();
|
||||||
|
File lastAccessed = new File(
|
||||||
|
sameCachedFile.getParentFile(),
|
||||||
|
sameCachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX);
|
||||||
|
|
||||||
|
assertTrue(lastAccessed.lastModified() > sameCachedFile.lastModified());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder _folder = new TemporaryFolder();
|
||||||
|
|
||||||
|
private File _fileToCache;
|
||||||
|
private ResourceCache _cache;
|
||||||
|
}
|
||||||
@@ -5,26 +5,49 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.util;
|
package com.threerings.getdown.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
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.Rule;
|
||||||
import static org.junit.Assert.*;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests {@link FileUtil}.
|
* Tests {@link FileUtil}.
|
||||||
*/
|
*/
|
||||||
public class FileUtilTest
|
public class FileUtilTest
|
||||||
{
|
{
|
||||||
@Test public void testReadLines () throws IOException
|
@Test
|
||||||
|
public void testReadLines () throws IOException
|
||||||
{
|
{
|
||||||
String data = "This is a test\nof a file with\na few lines\n";
|
String data = "This is a test\nof a file with\na few lines\n";
|
||||||
List<String> lines = FileUtil.readLines(new StringReader(data));
|
List<String> lines = FileUtil.readLines(new StringReader(data));
|
||||||
String[] linesBySplit = data.split("\n");
|
String[] linesBySplit = data.split("\n");
|
||||||
assertEquals(linesBySplit.length, lines.size());
|
assertEquals(linesBySplit.length, lines.size());
|
||||||
for (int ii = 0; ii < lines.size(); ii++) {
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
assertEquals(linesBySplit[ii], lines.get(ii));
|
assertEquals(linesBySplit[i], lines.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder _folder = new TemporaryFolder();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.threerings.getdown.util.file;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class under test: {@link FileWalker}.
|
||||||
|
*/
|
||||||
|
public class FileWalkerTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException
|
||||||
|
{
|
||||||
|
_folder.newFile("a.txt");
|
||||||
|
new File(_folder.newFolder("b"), "b.txt").createNewFile();
|
||||||
|
|
||||||
|
CountingVisitor visitor = new CountingVisitor();
|
||||||
|
new FileWalker(_folder.getRoot()).walkTree(visitor);
|
||||||
|
|
||||||
|
assertEquals(3, visitor.fileCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CountingVisitor implements FileVisitor
|
||||||
|
{
|
||||||
|
int fileCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(File file) {
|
||||||
|
fileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder _folder = new TemporaryFolder();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user