diff --git a/core/src/main/java/com/threerings/getdown/Log.java b/core/src/main/java/com/threerings/getdown/Log.java
index 7538ea3..a953f5d 100644
--- a/core/src/main/java/com/threerings/getdown/Log.java
+++ b/core/src/main/java/com/threerings/getdown/Log.java
@@ -42,6 +42,14 @@ public class Log
*/
public void warning (Object message, Object... args) { doLog(2, message, args); }
+ /**
+ * Logs an error message.
+ *
+ * @param message the message to be logged.
+ * @param args a list of key/value pairs and an optional final Throwable.
+ */
+ public void error (Object message, Object... args) { doLog(3, message, args); }
+
protected void doLog (int levIdx, Object message, Object[] args) {
if (_impl.isLoggable(LEVELS[levIdx])) {
Throwable err = null;
diff --git a/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java b/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java
index f7ce8e7..a0dcf7b 100644
--- a/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java
+++ b/core/src/main/java/com/threerings/getdown/classpath/ClassPaths.java
@@ -48,7 +48,7 @@ public class ClassPaths
*/
public static ClassPath buildCachedClassPath (Application app) throws IOException
{
- File cacheDir = new File(app.getAppdir(), CACHE_DIR);
+ 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);
diff --git a/core/src/main/java/com/threerings/getdown/data/Application.java b/core/src/main/java/com/threerings/getdown/data/Application.java
index 54f4d54..02a29d4 100644
--- a/core/src/main/java/com/threerings/getdown/data/Application.java
+++ b/core/src/main/java/com/threerings/getdown/data/Application.java
@@ -233,45 +233,21 @@ public class Application
}
}
- /**
- * Creates an application instance with no signers.
- *
- * @see #Application(File, String, List, String[], String[])
- */
- public Application (File appdir, String appid)
- {
- this(appdir, appid, null, null, null);
- }
-
/**
* Creates an application instance which records the location of the getdown.txt
* configuration file from the supplied application directory.
*
- * @param appid usually null but a string identifier if a secondary application is desired to
- * be launched. That application will use {@code appid.class} and {@code appid.apparg} to
- * configure itself but all other parameters will be the same as the primary application.
- * @param signers a list of possible signers of this application. Used to verify the digest.
- * @param jvmargs additional arguments to pass on to launched jvms.
- * @param appargs additional arguments to pass on to launched application; these will be added
- * after the args in the getdown.txt file.
*/
- public Application (File appdir, String appid, List signers,
- String[] jvmargs, String[] appargs)
- {
- _appdir = appdir;
- _appid = appid;
- _signers = (signers == null) ? Collections.emptyList() : signers;
- _config = getLocalPath(appdir, CONFIG_FILE);
- _extraJvmArgs = (jvmargs == null) ? EMPTY_STRING_ARRAY : jvmargs;
- _extraAppArgs = (appargs == null) ? EMPTY_STRING_ARRAY : appargs;
+ public Application (EnvConfig envc) {
+ _envc = envc;
+ _config = getLocalPath(envc.appDir, CONFIG_FILE);
}
/**
* Returns the configured application directory.
*/
- public File getAppdir()
- {
- return _appdir;
+ public File getAppDir () {
+ return _envc.appDir;
}
/**
@@ -550,7 +526,7 @@ public class Application
}
// otherwise, issue a warning that we found no getdown file
else {
- log.info("Found no getdown.txt file", "appdir", _appdir);
+ log.info("Found no getdown.txt file", "appdir", getAppDir());
}
} catch (Exception e) {
log.warning("Failure reading config file", "file", config, e);
@@ -559,8 +535,8 @@ public class Application
// if we failed to read our config file, check for an appbase specified via a system
// property; we can use that to bootstrap ourselves back into operation
if (config == null) {
- String appbase = SysProps.appBase();
- log.info("Attempting to obtain 'appbase' from system property", "appbase", appbase);
+ String appbase = _envc.appBase;
+ log.info("Using 'appbase' from bootstrap config", "appbase", appbase);
Map cdata = new HashMap<>();
cdata.put("appbase", appbase);
config = new Config(cdata);
@@ -607,7 +583,7 @@ public class Application
}
}
- String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + ".");
+ String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
// determine our application class name (use app-specific class _if_ one is provided)
_class = config.getString("class");
@@ -708,9 +684,6 @@ public class Application
addAll(jvmargs, _jvmargs);
}
- // Add the launch specific JVM arguments
- addAll(_extraJvmArgs, _jvmargs);
-
// get the set of optimum JVM arguments
_optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
@@ -719,7 +692,7 @@ public class Application
addAll(appargs, _appargs);
// add the launch specific application arguments
- addAll(_extraAppArgs, _appargs);
+ _appargs.addAll(_envc.appArgs);
// look for custom arguments
fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
@@ -783,7 +756,7 @@ public class Application
*/
public File getLocalPath (String path)
{
- return getLocalPath(_appdir, path);
+ return getLocalPath(getAppDir(), path);
}
/**
@@ -806,7 +779,7 @@ public class Application
// if we have an unpacked VM, check the 'release' file for its version
Resource vmjar = getJavaVMResource();
if (vmjar != null && vmjar.isMarkedValid()) {
- File vmdir = new File(_appdir, LaunchUtil.LOCAL_JAVA_DIR);
+ File vmdir = new File(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR);
File relfile = new File(vmdir, "release");
if (!relfile.exists()) {
log.warning("Unpacked JVM missing 'release' file. Assuming valid version.");
@@ -925,7 +898,7 @@ public class Application
ArrayList args = new ArrayList<>();
// reconstruct the path to the JVM
- args.add(LaunchUtil.getJVMPath(_appdir, _windebug || optimum));
+ args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum));
// check whether we're using -jar mode or -classpath mode
boolean dashJarMode = MANIFEST_CLASS.equals(_class);
@@ -999,7 +972,7 @@ public class Application
String[] sargs = args.toArray(new String[args.size()]);
log.info("Running " + StringUtil.join(sargs, "\n "));
- return Runtime.getRuntime().exec(sargs, envp, _appdir);
+ return Runtime.getRuntime().exec(sargs, envp, getAppDir());
}
/**
@@ -1095,7 +1068,7 @@ public class Application
/** Replaces the application directory and version in any argument. */
protected String processArg (String arg)
{
- arg = arg.replace("%APPDIR%", _appdir.getAbsolutePath());
+ arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
arg = arg.replace("%VERSION%", String.valueOf(_version));
// if this argument contains %ENV.FOO% replace those with the associated values looked up
@@ -1135,7 +1108,7 @@ public class Application
// this will read in the contents of the digest file and validate itself
try {
- _digest = new Digest(_appdir, _strictComments);
+ _digest = new Digest(getAppDir(), _strictComments);
} catch (IOException ioe) {
log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery...");
}
@@ -1149,7 +1122,7 @@ public class Application
try {
status.updateStatus("m.checking");
downloadDigestFiles();
- _digest = new Digest(_appdir, _strictComments);
+ _digest = new Digest(getAppDir(), _strictComments);
if (!olddig.equals(_digest.getMetaDigest())) {
log.info("Unversioned digest changed. Revalidating...");
status.updateStatus("m.validating");
@@ -1167,7 +1140,7 @@ public class Application
if (_digest == null) {
status.updateStatus("m.updating_metadata");
downloadDigestFiles();
- _digest = new Digest(_appdir, _strictComments);
+ _digest = new Digest(getAppDir(), _strictComments);
}
// now verify the contents of our main config file
@@ -1178,7 +1151,7 @@ public class Application
// caller because there's nothing we can do to automatically recover
downloadConfigFile();
downloadDigestFiles();
- _digest = new Digest(_appdir, _strictComments);
+ _digest = new Digest(getAppDir(), _strictComments);
// revalidate everything if we end up downloading new metadata
clearValidationMarkers();
// if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage
@@ -1509,8 +1482,8 @@ public class Application
File target = downloadFile(path);
if (sigVersion > 0) {
- if (_signers.isEmpty()) {
- log.info("No signers, not verifying file", "path", path);
+ if (_envc.certs.isEmpty()) {
+ log.info("No signing certs, not verifying digest.txt", "path", path);
} else {
File signatureFile = downloadFile(path + SIGNATURE_SUFFIX);
@@ -1523,7 +1496,7 @@ public class Application
byte[] buffer = new byte[8192];
int length, validated = 0;
- for (Certificate cert : _signers) {
+ for (Certificate cert : _envc.certs) {
try (FileInputStream dataInput = new FileInputStream(target)) {
Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion));
sig.initVerify(cert);
@@ -1705,8 +1678,7 @@ public class Application
return new File(appdir, path);
}
- protected File _appdir;
- protected String _appid;
+ protected final EnvConfig _envc;
protected File _config;
protected Digest _digest;
@@ -1749,15 +1721,10 @@ public class Application
protected List _jvmargs = new ArrayList<>();
protected List _appargs = new ArrayList<>();
- protected String[] _extraJvmArgs;
- protected String[] _extraAppArgs;
-
protected String[] _optimumJvmArgs;
protected List _txtJvmArgs = new ArrayList<>();
- protected List _signers;
-
/** If a warning has been issued about not being able to set modtimes. */
protected boolean _warnedAboutSetLastModified;
diff --git a/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
new file mode 100644
index 0000000..96ec657
--- /dev/null
+++ b/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
@@ -0,0 +1,207 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2016 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+package com.threerings.getdown.data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+import com.threerings.getdown.util.StringUtil;
+
+/** Configuration that comes from our "environment" (command line args, sys props, etc.). */
+public final class EnvConfig {
+
+ /** Used to report problems or feedback by {@link #create}. */
+ public static final class Note {
+ public static enum Level { INFO, WARN, ERROR };
+ public static Note info (String msg) { return new Note(Level.INFO, msg); }
+ public static Note warn (String msg) { return new Note(Level.WARN, msg); }
+ public static Note error (String msg) { return new Note(Level.ERROR, msg); }
+ public final Level level;
+ public final String message;
+ public Note (Level level, String message) {
+ this.level = level;
+ this.message = message;
+ }
+ }
+
+ /**
+ * Creates an environment config, obtaining information (in order) from the following sources:
+ *
+ *
+ * - A {@code bootstrap.properties} file bundled with the jar.
+ * - System properties supplied to the JVM.
+ * - The supplied command line arguments ({@code argv}).
+ *
+ *
+ * If a later source supplies a configuration already provided by a prior source, a warning
+ * message will be logged to indicate the conflict, and the prior source will be used.
+ *
+ * @param notes a list into which notes are added, to be logged after the logging system has
+ * been initialized (which cannot happen until the appdir is known). If any {@code ERROR} notes
+ * are included, the app should terminate after reporting them.
+ * @return an env config instance, or {@code null} if no appdir could be located via any
+ * configuration source.
+ */
+ public static EnvConfig create (String[] argv, List notes) {
+ String appDir = null, appDirProv = null;
+ String appId = null, appIdProv = null;
+ String appBase = null, appBaseProv = null;
+
+ // start with bootstrap.properties config, if avaialble
+ try {
+ ResourceBundle bundle = ResourceBundle.getBundle("bootstrap");
+ if (bundle.containsKey("appdir")) {
+ appDir = bundle.getString("appdir");
+ appDir = appDir.replace(USER_HOME_KEY, System.getProperty("user.home"));
+ File appDirFile = new File(appDir);
+ if (!appDirFile.exists()) {
+ appDirFile.mkdirs();
+ }
+ appDirProv = "bootstrap.properties";
+ }
+ if (bundle.containsKey("appid")) {
+ appId = bundle.getString("appid");
+ appIdProv = "bootstrap.properties";
+ }
+ if (bundle.containsKey("appbase")) {
+ appBase = bundle.getString("appbase");
+ appBaseProv = "bootstrap.properties";
+ }
+ } catch (MissingResourceException e) {
+ // bootstrap.properties is optional; no need for a warning
+ }
+
+ // next seek config from system properties
+ String spropsAppDir = SysProps.appDir();
+ if (!StringUtil.isBlank(spropsAppDir)) {
+ if (appDir == null) {
+ appDir = spropsAppDir;
+ appDirProv = "system property";
+ } else {
+ notes.add(Note.warn("Ignoring 'appdir' system property, have appdir via '" +
+ appDirProv + "'"));
+ }
+ }
+ String spropsAppId = SysProps.appId();
+ if (!StringUtil.isBlank(spropsAppId)) {
+ if (appId == null) {
+ appId = spropsAppId;
+ appIdProv = "system property";
+ } else {
+ notes.add(Note.warn("Ignoring 'appid' system property, have appid via '" +
+ appIdProv + "'"));
+ }
+ }
+ String spropsAppBase = SysProps.appBase();
+ if (!StringUtil.isBlank(spropsAppBase)) {
+ if (appBase == null) {
+ appBase = spropsAppBase;
+ appBaseProv = "system property";
+ } else {
+ notes.add(Note.warn("Ignoring 'appbase' system property, have appbase via '" +
+ appBaseProv + "'"));
+ }
+ }
+
+ // finally obtain config from command line arguments
+ String argvAppDir = argv.length > 0 ? argv[0] : null;
+ if (!StringUtil.isBlank(argvAppDir)) {
+ if (appDir == null) {
+ appDir = argvAppDir;
+ appDirProv = "command line";
+ } else {
+ notes.add(Note.warn("Ignoring 'appdir' command line arg, have appdir via '" +
+ appDirProv + "'"));
+ }
+ }
+ String argvAppId = argv.length > 1 ? argv[1] : null;
+ if (!StringUtil.isBlank(spropsAppId)) {
+ if (appId == null) {
+ appId = argvAppId;
+ appIdProv = "command line";
+ } else {
+ notes.add(Note.warn("Ignoring 'appid' command line arg, have appid via '" +
+ appIdProv + "'"));
+ }
+ }
+
+ // ensure that we were able to fine an app dir
+ if (appDir == null) {
+ return null; // caller will report problem to user
+ }
+
+ // ensure that the appdir refers to a directory that exists
+ File appDirFile = new File(appDir);
+ if (!appDirFile.exists() || !appDirFile.isDirectory()) {
+ notes.add(Note.error("Invalid appdir '" + appDir + "'."));
+ return null;
+ }
+
+ notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir));
+ if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId));
+ if (appBase != null) notes.add(Note.info("Using appbase from " + appBaseProv + ": " +
+ appBase));
+
+ // pass along anything after the first tow args as extra app args
+ List appArgs = argv.length > 2 ?
+ Arrays.asList(argv).subList(2, argv.length-2) :
+ Collections.emptyList();
+
+ // load X.509 certificate if it exists
+ File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt");
+ List certs = new ArrayList<>();
+ if (crtFile.exists()) {
+ try (FileInputStream fis = new FileInputStream(crtFile)) {
+ X509Certificate certificate = (X509Certificate)
+ CertificateFactory.getInstance("X.509").generateCertificate(fis);
+ certs.add(certificate);
+ } catch (Exception e) {
+ notes.add(Note.error("Certificate error: " + e.getMessage()));
+ }
+ }
+
+ return new EnvConfig(appDirFile, appId, appBase, certs, appArgs);
+ }
+
+ /** The directory in which the application and metadata is stored. */
+ public final File appDir;
+
+ /** Either {@code null} or an identifier for a secondary application that should be
+ * launched. That app will use {@code appid.class} and {@code appid.apparg} to configure
+ * itself but all other parameters will be the same as the primary app. */
+ public final String appId;
+
+ /** Either {@code null} or fallback {@code appbase} to use if one cannot be read from a
+ * {@code getdown.txt} file during startup. */
+ public final String appBase;
+
+ /** Zero or more signing certificates used to verify the digest file. */
+ public final List certs;
+
+ /** Additional arguments to pass on to launched application. These will be added after the
+ * args in the getdown.txt file. */
+ public final List appArgs;
+
+ public EnvConfig (File appDir) {
+ this(appDir, null, null, Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private EnvConfig (File appDir, String appId, String appBase, List certs,
+ List appArgs) {
+ this.appDir = appDir;
+ this.appId = appId;
+ this.appBase = appBase;
+ this.certs = certs;
+ this.appArgs = appArgs;
+ }
+
+ private static final String USER_HOME_KEY = "${user.home}";
+}
diff --git a/core/src/main/java/com/threerings/getdown/data/SysProps.java b/core/src/main/java/com/threerings/getdown/data/SysProps.java
index 0d164b3..6da630e 100644
--- a/core/src/main/java/com/threerings/getdown/data/SysProps.java
+++ b/core/src/main/java/com/threerings/getdown/data/SysProps.java
@@ -28,8 +28,8 @@ public class SysProps
return System.getProperty("appid");
}
- /** Configures the appbase (in lieu of providing a skeleton getdown.txt, and as a last resort
- * fallback). Usage: {@code -Dappbase=URL}. */
+ /** Configures the bootstrap appbase (used in lieu of providing a skeleton getdown.txt, and as
+ * a last resort fallback). Usage: {@code -Dappbase=URL}. */
public static String appBase () {
return System.getProperty("appbase");
}
diff --git a/core/src/main/java/com/threerings/getdown/tools/Differ.java b/core/src/main/java/com/threerings/getdown/tools/Differ.java
index 189f0e6..87784f6 100644
--- a/core/src/main/java/com/threerings/getdown/tools/Differ.java
+++ b/core/src/main/java/com/threerings/getdown/tools/Differ.java
@@ -23,6 +23,7 @@ import java.security.MessageDigest;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Digest;
+import com.threerings.getdown.data.EnvConfig;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.StreamUtil;
@@ -58,13 +59,13 @@ public class Differ
", overs=" + overs + "].");
}
- Application oapp = new Application(ovdir, null);
+ Application oapp = new Application(new EnvConfig(ovdir));
oapp.init(false);
ArrayList orsrcs = new ArrayList<>();
orsrcs.addAll(oapp.getCodeResources());
orsrcs.addAll(oapp.getResources());
- Application napp = new Application(nvdir, null);
+ Application napp = new Application(new EnvConfig(nvdir));
napp.init(false);
ArrayList nrsrcs = new ArrayList<>();
nrsrcs.addAll(napp.getCodeResources());
diff --git a/core/src/main/java/com/threerings/getdown/tools/Digester.java b/core/src/main/java/com/threerings/getdown/tools/Digester.java
index e1fa2ab..9aad0cb 100644
--- a/core/src/main/java/com/threerings/getdown/tools/Digester.java
+++ b/core/src/main/java/com/threerings/getdown/tools/Digester.java
@@ -20,6 +20,7 @@ import java.util.List;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Digest;
+import com.threerings.getdown.data.EnvConfig;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.Base64;
@@ -73,7 +74,7 @@ public class Digester
System.out.println("Generating digest file '" + target + "'...");
// create our application and instruct it to parse its business
- Application app = new Application(appdir, null);
+ Application app = new Application(new EnvConfig(appdir));
app.init(false);
List rsrcs = new ArrayList<>();
diff --git a/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
index da74d1f..2ac66d4 100644
--- a/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
+++ b/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
@@ -381,7 +381,7 @@ public class JarDiff implements JarDiffCodes
public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException {
String thisName = null;
- Long crcL = new Long(entry.getCrc());
+ Long crcL = Long.valueOf(entry.getCrc());
// check if this jar contains files with the passed in entry's crc
if (_crcToEntryMap.containsKey(crcL)) {
// get the Linked List with files with the crc
@@ -416,7 +416,7 @@ public class JarDiff implements JarDiffCodes
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
long crc = entry.getCrc();
- Long crcL = new Long(crc);
+ Long crcL = Long.valueOf(crc);
if (_debug) {
System.out.println("\t" + entry.getName() + " CRC " + crc);
}
diff --git a/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java b/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java
index fcca643..be6b32b 100644
--- a/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java
+++ b/core/src/test/java/com/threerings/getdown/classpath/ClassPathsTest.java
@@ -28,7 +28,7 @@ public class ClassPathsTest
when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile);
when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile);
when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar));
- when(_application.getAppdir()).thenReturn(_appdir.getRoot());
+ when(_application.getAppDir()).thenReturn(_appdir.getRoot());
}
@Test public void shouldBuildDefaultClassPath () throws IOException
diff --git a/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java b/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java
index 1718107..89a1094 100644
--- a/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java
+++ b/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java
@@ -43,22 +43,11 @@ import ca.beq.util.win32.registry.RootKey;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.getdown.data.Application.UpdateInterface.Step;
-import com.threerings.getdown.data.Application;
-import com.threerings.getdown.data.Build;
-import com.threerings.getdown.data.Resource;
-import com.threerings.getdown.data.SysProps;
+import com.threerings.getdown.data.*;
import com.threerings.getdown.net.Downloader;
import com.threerings.getdown.net.HTTPDownloader;
import com.threerings.getdown.tools.Patcher;
-import com.threerings.getdown.util.Config;
-import com.threerings.getdown.util.ConnectionUtil;
-import com.threerings.getdown.util.FileUtil;
-import com.threerings.getdown.util.LaunchUtil;
-import com.threerings.getdown.util.MessageUtil;
-import com.threerings.getdown.util.ProgressAggregator;
-import com.threerings.getdown.util.ProgressObserver;
-import com.threerings.getdown.util.StringUtil;
-import com.threerings.getdown.util.VersionUtil;
+import com.threerings.getdown.util.*;
import static com.threerings.getdown.Log.log;
@@ -68,13 +57,7 @@ import static com.threerings.getdown.Log.log;
public abstract class Getdown extends Thread
implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
{
- public Getdown (File appDir, String appId)
- {
- this(appDir, appId, null, null, null);
- }
-
- public Getdown (File appDir, String appId, List signers,
- String[] jvmargs, String[] appargs)
+ public Getdown (EnvConfig envc)
{
super("Getdown");
try {
@@ -94,7 +77,7 @@ public abstract class Getdown extends Thread
} catch (Exception e) {
// welcome to hell, where java can't cope with a classpath that contains jars that live
// in a directory that contains a !, at least the same bug happens on all platforms
- String dir = appDir.toString();
+ String dir = envc.appDir.toString();
if (dir.equals(".")) {
dir = System.getProperty("user.dir");
}
@@ -103,7 +86,7 @@ public abstract class Getdown extends Thread
"contains the '!' character, this will trigger this error.";
fail(errmsg);
}
- _app = new Application(appDir, appId, signers, jvmargs, appargs);
+ _app = new Application(envc);
_startup = System.currentTimeMillis();
}
diff --git a/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java
index 356fac2..9c6a9fb 100644
--- a/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java
+++ b/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java
@@ -14,24 +14,18 @@ import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedOutputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import com.samskivert.swing.util.SwingUtil;
import com.threerings.getdown.data.Digest;
+import com.threerings.getdown.data.EnvConfig;
import com.threerings.getdown.data.SysProps;
import com.threerings.getdown.util.LaunchUtil;
import com.threerings.getdown.util.StringUtil;
@@ -42,11 +36,10 @@ import static com.threerings.getdown.Log.log;
*/
public class GetdownApp
{
-
- private static final String USER_HOME = "${user.home}";
-
- public static void main (String[] argv)
- {
+ /**
+ * The main entry point of the Getdown launcher application.
+ */
+ public static void main (String[] argv) {
try {
start(argv);
} catch (Exception e) {
@@ -61,58 +54,17 @@ public class GetdownApp
* @throws Exception if anything goes wrong starting Getdown.
*/
public static Getdown start (String[] argv) throws Exception {
- int aidx = 0;
- List args = Arrays.asList(argv);
-
- // check for app dir in a sysprop and then via argv
- String adarg = SysProps.appDir();
- if (StringUtil.isBlank(adarg)) {
- loadBootstrapResource();
- if (args.isEmpty() && SysProps.appDir() == null) {
- System.err.println("Usage: java -jar getdown.jar app_dir [app_id] [app args]");
- System.exit(-1);
- }
- if (!args.isEmpty()) {
- adarg = args.get(aidx++);
- } else {
- adarg = SysProps.appDir();
- }
- }
-
- // check for an app identifier in a sysprop and then via argv
- String appId = SysProps.appId();
- if (StringUtil.isBlank(appId) && aidx < args.size()) {
- appId = args.get(aidx++);
- }
-
- // pass along anything after that as app args
- String[] appArgs = (aidx >= args.size()) ? null :
- args.subList(aidx, args.size()).toArray(new String[0]);
-
- // ensure a valid directory was supplied
- File appDir = new File(adarg);
- if (!appDir.exists() || !appDir.isDirectory()) {
- log.warning("Invalid app_dir '" + adarg + "'.");
+ List notes = new ArrayList<>();
+ EnvConfig envc = EnvConfig.create(argv, notes);
+ if (envc == null) {
+ if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message);
+ else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]");
System.exit(-1);
}
- // load X.509 certificate if it exists
- File crtFile = new File(appDir, Digest.digestFile(Digest.VERSION) + ".crt");
- List crts = new ArrayList<>();
- if (crtFile.exists()) {
- try (FileInputStream fis = new FileInputStream(crtFile)) {
- X509Certificate certificate = (X509Certificate)
- CertificateFactory.getInstance("X.509").generateCertificate(fis);
- crts.add(certificate);
- } catch (Exception e) {
- log.warning("Certificate error: " + e.getMessage());
- System.exit(-1);
- }
- }
-
// pipe our output into a file in the application directory
if (!SysProps.noLogRedir()) {
- File logFile = new File(appDir, "launcher.log");
+ File logFile = new File(envc.appDir, "launcher.log");
try {
PrintStream logOut = new PrintStream(
new BufferedOutputStream(new FileOutputStream(logFile)), true);
@@ -123,6 +75,17 @@ public class GetdownApp
}
}
+ // report any notes from reading our env config, and abort if necessary
+ boolean abort = false;
+ for (EnvConfig.Note note : notes) {
+ switch (note.level) {
+ case INFO: log.info(note.message); break;
+ case WARN: log.warning(note.message); break;
+ case ERROR: log.error(note.message); abort = true; break;
+ }
+ }
+ if (abort) System.exit(-1);
+
// record a few things for posterity
log.info("------------------ VM Info ------------------");
log.info("-- OS Name: " + System.getProperty("os.name"));
@@ -135,7 +98,7 @@ public class GetdownApp
log.info("-- Cur dir: " + System.getProperty("user.dir"));
log.info("---------------------------------------------");
- Getdown app = new Getdown(appDir, appId, crts, null, appArgs) {
+ Getdown app = new Getdown(envc) {
@Override
protected Container createContainer () {
// create our user interface, and display it
@@ -270,25 +233,4 @@ public class GetdownApp
app.start();
return app;
}
-
- private static void loadBootstrapResource() {
- try {
- ResourceBundle bundle = ResourceBundle.getBundle("bootstrap");
- if (bundle.containsKey("appbase")) {
- System.setProperty("appbase", bundle.getString("appbase"));
- }
- if (bundle.containsKey("appdir")) {
- String appDir = bundle.getString("appdir");
- appDir = appDir.replace(USER_HOME, System.getProperty("user.home"));
- File appDirFile = new File(appDir);
- if (!appDirFile.exists()) {
- appDirFile.mkdirs();
- }
- System.setProperty("appdir", appDir);
- }
- log.info("bootstrap.properties found using:", "_appdir", System.getProperty("appdir"), "appbase", System.getProperty("appbase"));
- } catch (MissingResourceException e) {
- log.info("bootstrap.properties not found in resource bundle, starting without bootstrapping");
- }
- }
}