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"); - } - } }