diff --git a/src/java/com/threerings/getdown/data/Application.java b/src/java/com/threerings/getdown/data/Application.java index 39812fb..5c1da21 100644 --- a/src/java/com/threerings/getdown/data/Application.java +++ b/src/java/com/threerings/getdown/data/Application.java @@ -64,8 +64,8 @@ import com.threerings.getdown.util.MetaProgressObserver; import com.threerings.getdown.util.ProgressObserver; /** - * Parses and provide access to the information contained in the - * getdown.txt configuration file. + * Parses and provide access to the information contained in the getdown.txt + * configuration file. */ public class Application { @@ -75,12 +75,11 @@ public class Application /** The name of our target version file. */ public static final String VERSION_FILE = "version.txt"; - /** System properties that are prefixed with this string will be - * passed through to our application (minus this prefix). */ + /** System properties that are prefixed with this string will be passed through to our + * application (minus this prefix). */ public static final String PROP_PASSTHROUGH_PREFIX = "app."; - /** Used to communicate information about the UI displayed when - * updating the application. */ + /** Used to communicate information about the UI displayed when updating the application. */ public static class UpdateInterface { /** The human readable name of this application. */ @@ -116,16 +115,17 @@ public class Application /** Generates a string representation of this instance. */ public String toString () { - return "[name=" + name + ", bg=" + backgroundImage + - ", pi=" + progressImage + ", prect=" + progress + - ", pt=" + progressText + ", pb=" + progressBar + - ", srect=" + status + ", st=" + statusText + - ", shadow=" + textShadow + ", err=" + installError + "]"; + return "[name=" + name + ", bg=" + backgroundImage + ", pi=" + progressImage + + ", prect=" + progress + ", pt=" + progressText + ", pb=" + progressBar + + ", srect=" + status + ", st=" + statusText + ", shadow=" + textShadow + + ", err=" + installError + "]"; } } - /** Used by {@link #verifyMetadata} to communicate status in - * circumstances where it needs to take network actions. */ + /** + * Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to + * take network actions. + */ public static interface StatusDisplay { /** Requests that the specified status message be displayed. */ @@ -133,15 +133,13 @@ public class Application } /** - * Creates an application instance which records the location of the - * getdown.txt configuration file from the supplied - * application directory. + * 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 * - * appid.class and appid.apparg to configure - * itself but all other parameters will be the same as the primary - * application. + * @param appid usually null but a string identifier if a secondary application is desired to + * be launched. That application will use appid.class and + * appid.apparg to configure itself but all other parameters will be the same as + * the primary application. */ public Application (File appdir, String appid) { @@ -151,8 +149,8 @@ public class Application } /** - * Indicates whether or not we support downloading of our resources using - * the Bittorrent protocol. + * Indicates whether or not we support downloading of our resources using the Bittorrent + * protocol. */ public boolean getUseTorrent () { @@ -160,8 +158,7 @@ public class Application } /** - * Returns a resource that refers to the application configuration - * file itself. + * Returns a resource that refers to the application configuration file itself. */ public Resource getConfigResource () { @@ -173,8 +170,7 @@ public class Application } /** - * Returns a list of the code {@link Resource} objects used by this - * application. + * Returns a list of the code {@link Resource} objects used by this application. */ public List getCodeResources () { @@ -182,8 +178,7 @@ public class Application } /** - * Returns a list of the non-code {@link Resource} objects used by - * this application. + * Returns a list of the non-code {@link Resource} objects used by this application. */ public List getResources () { @@ -191,8 +186,7 @@ public class Application } /** - * Returns a list of all the {@link Resource} objects used by - * this application. + * Returns a list of all the {@link Resource} objects used by this application. */ public List getAllResources () { @@ -203,10 +197,9 @@ public class Application } /** - * Returns a list of all auxiliary resource groups defined by the - * application. An auxiliary resource group is a collection of resource - * files that are not downloaded unless a group token file is present in - * the application directory. + * Returns a list of all auxiliary resource groups defined by the application. An auxiliary + * resource group is a collection of resource files that are not downloaded unless a group + * token file is present in the application directory. */ public List getAuxGroups () { @@ -214,16 +207,16 @@ public class Application } /** - * Returns true if the specified auxgroup has been "activated", false if - * not. Non-activated groups should be ignored, activated groups should be - * downloaded and patched along with the main resources. + * Returns true if the specified auxgroup has been "activated", false if not. Non-activated + * groups should be ignored, activated groups should be downloaded and patched along with the + * main resources. */ public boolean isAuxGroupActive (String auxgroup) { Boolean active = _auxactive.get(auxgroup); if (active == null) { - // TODO: compare the contents with the MD5 hash of the auxgroup - // name and the client's machine ident + // TODO: compare the contents with the MD5 hash of the auxgroup name and the client's + // machine ident active = getLocalPath(auxgroup + ".dat").exists(); _auxactive.put(auxgroup, active); } @@ -231,9 +224,9 @@ public class Application } /** - * Returns a list of the non-code {@link Resource} objects included in the - * specified auxiliary resource group. If the group does not exist or has - * no resources, an empty list will be returned. + * Returns a list of the non-code {@link Resource} objects included in the specified auxiliary + * resource group. If the group does not exist or has no resources, an empty list will be + * returned. */ public List getResources (String group) { @@ -242,8 +235,7 @@ public class Application } /** - * Returns all non-code resources and all resources from active auxiliary - * resource groups. + * Returns all non-code resources and all resources from active auxiliary resource groups. */ public List getActiveResources () { @@ -258,19 +250,17 @@ public class Application } /** - * Returns a resource that can be used to download a patch file that - * will bring this application from its current version to the target - * version. + * Returns a resource that can be used to download a patch file that will bring this + * application from its current version to the target version. * - * @param auxgroup the auxiliary resource group for which a patch resource - * is desired or null for the main application patch resource. + * @param auxgroup the auxiliary resource group for which a patch resource is desired or null + * for the main application patch resource. */ public Resource getPatchResource (String auxgroup) { if (_targetVersion <= _version) { - Log.warning("Requested patch resource for up-to-date or " + - "non-versioned application [cvers=" + _version + - ", tvers=" + _targetVersion + "]."); + Log.warning("Requested patch resource for up-to-date or non-versioned application " + + "[cvers=" + _version + ", tvers=" + _targetVersion + "]."); return null; } @@ -288,8 +278,8 @@ public class Application } /** - * Returns a resource that can be used to download an archive containing - * all files belonging to the application. + * Returns a resource that can be used to download an archive containing all files belonging to + * the application. */ public Resource getFullResource () { @@ -306,17 +296,15 @@ public class Application } /** - * Instructs the application to parse its getdown.txt - * configuration and prepare itself for operation. The application - * base URL will be parsed first so that if there are errors - * discovered later, the caller can use the application base to - * download a new config.txt file and try again. + * Instructs the application to parse its getdown.txt configuration and prepare + * itself for operation. The application base URL will be parsed first so that if there are + * errors discovered later, the caller can use the application base to download a new + * config.txt file and try again. * - * @return a configured UpdateInterface instance that will be used to - * configure the update UI. + * @return a configured UpdateInterface instance that will be used to configure the update UI. * - * @exception IOException thrown if there is an error reading the file - * or an error encountered during its parsing. + * @exception IOException thrown if there is an error reading the file or an error encountered + * during its parsing. */ public UpdateInterface init (boolean checkPlatform) throws IOException @@ -326,9 +314,8 @@ public class Application try { cdata = ConfigUtil.parseConfig(_config, checkPlatform); } catch (FileNotFoundException fnfe) { - // thanks to funny windows bullshit, we have to do this backup - // file fiddling in case we got screwed while updating our - // very critical getdown config file + // thanks to funny windows bullshit, we have to do this backup file fiddling in case we + // got screwed while updating our very critical getdown config file File cbackup = getLocalPath(CONFIG_FILE + "_old"); if (cbackup.exists()) { cdata = ConfigUtil.parseConfig(cbackup, checkPlatform); @@ -337,9 +324,8 @@ public class Application } } - // first determine our application base, this way if anything goes - // wrong later in the process, our caller can use the appbase to - // download a new configuration file + // first determine our application base, this way if anything goes wrong later in the + // process, our caller can use the appbase to download a new configuration file _appbase = (String)cdata.get("appbase"); if (_appbase == null) { throw new IOException("m.missing_appbase"); @@ -380,6 +366,18 @@ public class Application throw new IOException("m.missing_class"); } + // check to see if we require a particular JVM version and have a supplied JVM + vstr = (String)cdata.get("java_version"); + if (vstr != null) { + try { + _javaVersion = Integer.parseInt(vstr); + } catch (Exception e) { + String err = MessageUtil.tcompose("m.invalid_java_version", vstr); + throw (IOException) new IOException(err).initCause(e); + } + } + _javaLocation = (String)cdata.get("java_location"); + // clear our arrays as we may be reinitializing _codes.clear(); _resources.clear(); @@ -428,8 +426,7 @@ public class Application if (file.exists()) { try { List args = ConfigUtil.parsePairs(file, false); - for (Iterator iter = args.iterator(); - iter.hasNext();) { + for (Iterator iter = args.iterator(); iter.hasNext();) { String[] pair = iter.next(); _jvmargs.add(pair[0] + "=" + pair[1]); } @@ -439,21 +436,18 @@ public class Application } // determine whether or not we should be using bit torrent - _useTorrent = (cdata.get("torrent") != null) || - (System.getProperty("torrent") != null); + _useTorrent = (cdata.get("torrent") != null) || (System.getProperty("torrent") != null); - // look for a debug.txt file which causes us to run in java.exe on - // Windows so that we can obtain a thread dump of the running JVM + // look for a debug.txt file which causes us to run in java.exe on Windows so that we can + // obtain a thread dump of the running JVM _windebug = getLocalPath("debug.txt").exists(); // parse and return our application config UpdateInterface ui = new UpdateInterface(); _name = ui.name = (String)cdata.get("ui.name"); ui.progress = parseRect(cdata, "ui.progress", ui.progress); - ui.progressText = parseColor( - cdata, "ui.progress_text", ui.progressText); - ui.progressBar = parseColor( - cdata, "ui.progress_bar", ui.progressBar); + ui.progressText = parseColor(cdata, "ui.progress_text", ui.progressText); + ui.progressBar = parseColor(cdata, "ui.progress_bar", ui.progressBar); ui.status = parseRect(cdata, "ui.status", ui.status); ui.statusText = parseColor(cdata, "ui.status_text", ui.statusText); ui.textShadow = parseColor(cdata, "ui.text_shadow", ui.textShadow); @@ -475,9 +469,8 @@ public class Application } /** - * Returns a URL from which the specified path can be fetched. Our - * application base URL is properly versioned and combined with the - * supplied path. + * Returns a URL from which the specified path can be fetched. Our application base URL is + * properly versioned and combined with the supplied path. */ public URL getRemoteURL (String path) throws MalformedURLException @@ -494,8 +487,18 @@ public class Application } /** - * Attempts to redownload the getdown.txt file based on - * information parsed from a previous call to {@link #init}. + * Returns true if we either have no version requirement, are running in a JVM that meets our + * version requirements or have what appears to be a version of the JVM that meets our + * requirements. + */ + public boolean haveValidJavaVersion () + { + return true; // TODO + } + + /** + * Attempts to redownload the getdown.txt file based on information parsed from a + * previous call to {@link #init}. */ public void attemptRecovery (StatusDisplay status) throws IOException @@ -505,9 +508,8 @@ public class Application } /** - * Downloads and replaces the getdown.txt and - * digest.txt files with those for the target version of - * our application. + * Downloads and replaces the getdown.txt and digest.txt files with + * those for the target version of our application. */ public void updateMetadata () throws IOException @@ -520,10 +522,9 @@ public class Application throw (IOException) new IOException(err).initCause(mue); } - // now re-download our control files; we download the digest first - // so that if it fails, our config file will still reference the - // old version and re-running the updater will start the whole - // process over again + // now re-download our control files; we download the digest first so that if it fails, our + // config file will still reference the old version and re-running the updater will start + // the whole process over again downloadControlFile(Digest.DIGEST_FILE); downloadControlFile(CONFIG_FILE); } @@ -553,11 +554,9 @@ public class Application args.add("-classpath"); args.add(cpbuf.toString()); - // 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 if (RunAnywhere.isMacOS()) { - args.add("-Xdock:icon=" + _appdir.getAbsolutePath() + - "/../desktop.icns"); + args.add("-Xdock:icon=" + _appdir.getAbsolutePath() + "/../desktop.icns"); args.add("-Xdock:name=" + _name); } @@ -565,8 +564,7 @@ public class Application String proxyHost; if ((proxyHost = System.getProperty("http.proxyHost")) != null) { args.add("-Dhttp.proxyHost=" + proxyHost); - args.add("-Dhttp.proxyPort=" + - System.getProperty("http.proxyPort")); + args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort")); } // pass along any pass-through arguments @@ -607,8 +605,7 @@ public class Application ArrayList jars = new ArrayList(); for (Resource rsrc : _codes) { try { - jars.add( - new URL("file", "", rsrc.getLocal().getAbsolutePath())); + jars.add(new URL("file", "", rsrc.getLocal().getAbsolutePath())); } catch (Exception e) { e.printStackTrace(System.err); } @@ -631,8 +628,7 @@ public class Application if (eqidx == -1) { Log.warning("Bogus system property: '" + jvmarg + "'?"); } else { - System.setProperty(jvmarg.substring(0, eqidx), - jvmarg.substring(eqidx+1)); + System.setProperty(jvmarg.substring(0, eqidx), jvmarg.substring(eqidx+1)); } } } @@ -655,8 +651,7 @@ public class Application Method main; try { // first see if the class has a special applet-aware main - main = appclass.getMethod( - "main", JApplet.class, SA_PROTO.getClass()); + main = appclass.getMethod("main", JApplet.class, SA_PROTO.getClass()); main.invoke(null, new Object[] { applet, args }); } catch (NoSuchMethodException nsme) { main = appclass.getMethod("main", SA_PROTO.getClass()); @@ -676,17 +671,16 @@ public class Application } /** - * Loads the digest.txt file and verifies the contents of - * both that file and the getdown.text file. Then it - * loads the version.txt and decides whether or not the - * application needs to be updated or whether we can proceed to - * verification and execution. + * Loads the digest.txt file and verifies the contents of both that file and the + * getdown.text file. Then it loads the version.txt and decides + * whether or not the application needs to be updated or whether we can proceed to verification + * and execution. * - * @return true if the application needs to be updated, false if it is - * up to date and can be verified and executed. + * @return true if the application needs to be updated, false if it is up to date and can be + * verified and executed. * - * @exception IOException thrown if we encounter an unrecoverable - * error while verifying the metadata. + * @exception IOException thrown if we encounter an unrecoverable error while verifying the + * metadata. */ public boolean verifyMetadata (StatusDisplay status) throws IOException @@ -701,21 +695,18 @@ public class Application // Log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator())); // Log.info("App Args: " + StringUtil.toString(_appargs.iterator())); - // create our digester which 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 { _digest = new Digest(_appdir); } catch (IOException ioe) { - Log.info("Failed to load digest: " + ioe.getMessage() + ". " + - "Attempting recovery..."); + Log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery..."); } - // if we have no version, then we are running in unversioned mode - // so we need to download our digest.txt file on every invocation + // if we have no version, then we are running in unversioned mode so we need to download + // our digest.txt file on every invocation if (_version == -1) { - // 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 + // 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 String olddig = (_digest == null) ? "" : _digest.getMetaDigest(); try { status.updateStatus("m.checking"); @@ -732,10 +723,9 @@ public class Application } } - // regardless of whether we're versioned, if we failed to read the - // digest from disk, try to redownload the digest file and give it - // another good college try; this time we allow exceptions to - // propagate up to the caller as there is nothing else we can do + // regardless of whether we're versioned, if we failed to read the digest from disk, try to + // redownload the digest file and give it another good college try; this time we allow + // exceptions to propagate up to the caller as there is nothing else we can do if (_digest == null) { status.updateStatus("m.updating_metadata"); downloadControlFile(Digest.DIGEST_FILE); @@ -746,16 +736,14 @@ public class Application Resource crsrc = getConfigResource(); if (!_digest.validateResource(crsrc, null)) { status.updateStatus("m.updating_metadata"); - // attempt to redownload both of our metadata files; again we - // pass errors up to our caller because there's nothing we can - // do to automatically recover + // attempt to redownload both of our metadata files; again we pass errors up to our + // caller because there's nothing we can do to automatically recover downloadControlFile(CONFIG_FILE); downloadControlFile(Digest.DIGEST_FILE); _digest = new Digest(_appdir); // revalidate everything if we end up downloading new metadata clearValidationMarkers(); - // if the new copy validates, reinitialize ourselves; - // otherwise report baffling hoseage + // if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage if (_digest.validateResource(crsrc, null)) { init(true); } else { @@ -767,15 +755,13 @@ public class Application // start by assuming we are happy with our version _targetVersion = _version; - // if we are a versioned application, read in the contents of the - // version.txt file + // if we are a versioned application, read in the contents of the version.txt file if (_version != -1) { File vfile = getLocalPath(VERSION_FILE); FileInputStream fin = null; try { fin = new FileInputStream(vfile); - BufferedReader bin = new BufferedReader( - new InputStreamReader(fin)); + BufferedReader bin = new BufferedReader(new InputStreamReader(fin)); String vstr = bin.readLine(); if (!StringUtil.isBlank(vstr)) { _targetVersion = Long.parseLong(vstr); @@ -792,11 +778,9 @@ public class Application } /** - * Verifies the code and media resources associated with this - * application. A list of resources that do not exist or fail the - * verification process will be returned. If all resources are ready - * to go, null will be returned and the application is considered - * ready to run. + * Verifies the code and media resources associated with this application. A list of resources + * that do not exist or fail the verification process will be returned. If all resources are + * ready to go, null will be returned and the application is considered ready to run. */ public List verifyResources (ProgressObserver obs) { @@ -830,8 +814,8 @@ public class Application } } catch (Exception e) { - Log.info("Failure validating resource [rsrc=" + rsrc + - ", error=" + e + "]. Requesting redownload..."); + Log.info("Failure validating resource [rsrc=" + rsrc + ", error=" + e + "]. " + + "Requesting redownload..."); } finally { mpobs.progress(100); @@ -856,12 +840,12 @@ public class Application protected URL createVAppBase (long version) throws MalformedURLException { - return new URL( - StringUtil.replace(_appbase, "%VERSION%", "" + version)); + return new URL(StringUtil.replace(_appbase, "%VERSION%", "" + version)); } - /** Clears all validation marker files for the resources in the - * supplied iterator. */ + /** + * Clears all validation marker files for the resources in the supplied iterator. + */ protected void clearValidationMarkers (Iterator iter) { while (iter.hasNext()) { @@ -870,9 +854,8 @@ public class Application } /** - * Downloads a new copy of the specified control file and, if the - * download is successful, moves it over the old file on the - * filesystem. + * Downloads a new copy of the specified control file and, if the download is successful, moves + * it over the old file on the filesystem. */ protected void downloadControlFile (String path) throws IOException @@ -882,15 +865,12 @@ public class Application try { targetURL = getRemoteURL(path); } catch (Exception e) { - Log.warning("Requested to download invalid control file " + - "[appbase=" + _vappbase + ", path=" + path + - ", error=" + e + "]."); - String msg = "Invalid path '" + path + "'."; - throw (IOException) new IOException(msg).initCause(e); + Log.warning("Requested to download invalid control file [appbase=" + _vappbase + + ", path=" + path + ", error=" + e + "]."); + throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e); } - Log.info("Attempting to refetch '" + path + "' from '" + - targetURL + "'."); + Log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'."); // stream the URL into our temporary file InputStream fin = null; @@ -904,12 +884,10 @@ public class Application StreamUtil.close(fout); } - // Windows is a wonderful operating system, it won't let you - // rename a file overtop of another one; thus to avoid running the - // risk of getting royally fucked, we have to do this complicated - // backup bullshit; this way if the shit hits the fan before we - // get the new copy into place, we should be able to read from the - // backup copy; yay! + // Windows is a wonderful operating system, it won't let you rename a file overtop of + // another one; thus to avoid running the risk of getting royally fucked, we have to do + // this complicated backup bullshit; this way if the shit hits the fan before we get the + // new copy into place, we should be able to read from the backup copy; yay! File original = getLocalPath(path); if (RunAnywhere.isWindows() && original.exists()) { File backup = getLocalPath(path + "_old"); @@ -917,16 +895,14 @@ public class Application Log.warning("Failed to delete " + backup + "."); } if (!original.renameTo(backup)) { - Log.warning("Failed to move " + original + " to backup. " + - "We will likely fail to replace it with " + - target + "."); + Log.warning("Failed to move " + original + " to backup. We will likely fail " + + "to replace it with " + target + "."); } } // now attempt to replace the current file with the new one if (!target.renameTo(original)) { - throw new IOException( - "Failed to rename(" + target + ", " + original + ")"); + throw new IOException("Failed to rename(" + target + ", " + original + ")"); } } @@ -934,14 +910,12 @@ public class Application protected Resource createResource (String path, boolean unpack) throws MalformedURLException { - return new Resource( - path, getRemoteURL(path), getLocalPath(path), unpack); + return new Resource(path, getRemoteURL(path), getLocalPath(path), unpack); } /** Used to parse resources with the specfied name. */ - protected void parseResources ( - HashMap cdata, String name, boolean unpack, - ArrayList list) + protected void parseResources (HashMap cdata, String name, boolean unpack, + ArrayList list) { String[] rsrcs = ConfigUtil.getMultiValue(cdata, name); if (rsrcs == null) { @@ -957,8 +931,7 @@ public class Application } /** Used to parse rectangle specifications from the config file. */ - protected Rectangle parseRect ( - HashMap cdata, String name, Rectangle def) + protected Rectangle parseRect (HashMap cdata, String name, Rectangle def) { String value = (String)cdata.get(name); if (!StringUtil.isBlank(value)) { @@ -966,24 +939,21 @@ public class Application if (v != null && v.length == 4) { return new Rectangle(v[0], v[1], v[2], v[3]); } else { - Log.warning("Ignoring invalid '" + name + "' config '" + - value + "'."); + Log.warning("Ignoring invalid '" + name + "' config '" + value + "'."); } } return def; } /** Used to parse color specifications from the config file. */ - protected Color parseColor ( - HashMap cdata, String name, Color def) + protected Color parseColor (HashMap cdata, String name, Color def) { String value = (String)cdata.get(name); if (!StringUtil.isBlank(value)) { try { return new Color(Integer.parseInt(value, 16)); } catch (Exception e) { - Log.warning("Ignoring invalid '" + name + "' config '" + - value + "'."); + Log.warning("Ignoring invalid '" + name + "' config '" + value + "'."); } } return def; @@ -993,8 +963,7 @@ public class Application protected String[] parseList (HashMap cdata, String name) { String value = (String)cdata.get(name); - return (value == null) ? new String[0] : - StringUtil.parseStringArray(value); + return (value == null) ? new String[0] : StringUtil.parseStringArray(value); } protected File _appdir; @@ -1011,14 +980,16 @@ public class Application protected boolean _windebug; protected boolean _useTorrent = false; + protected int _javaVersion; + protected String _javaLocation; + protected ArrayList _codes = new ArrayList(); protected ArrayList _resources = new ArrayList(); protected ArrayList _auxgroups = new ArrayList(); protected HashMap> _auxrsrcs = new HashMap>(); - protected HashMap _auxactive = - new HashMap(); + protected HashMap _auxactive = new HashMap(); protected ArrayList _jvmargs = new ArrayList(); protected ArrayList _appargs = new ArrayList(); diff --git a/src/java/com/threerings/getdown/launcher/Getdown.java b/src/java/com/threerings/getdown/launcher/Getdown.java index 722c754..161a3d6 100644 --- a/src/java/com/threerings/getdown/launcher/Getdown.java +++ b/src/java/com/threerings/getdown/launcher/Getdown.java @@ -68,8 +68,7 @@ import com.threerings.getdown.util.LaunchUtil; import com.threerings.getdown.util.ProgressObserver; /** - * Manages the main control for the Getdown application updater and - * deployment system. + * Manages the main control for the Getdown application updater and deployment system. */ public abstract class Getdown extends Thread implements Application.StatusDisplay @@ -86,16 +85,14 @@ public abstract class Getdown extends Thread try { _msgs = ResourceBundle.getBundle("com.threerings.getdown.messages"); } 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 + // 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(); if (dir.equals(".")) { dir = System.getProperty("user.dir"); } - String errmsg = "The directory in which this application is " + - "installed:\n" + dir + "\nis invalid. The directory " + - "must not contain the '!' character. Please reinstall."; + String errmsg = "The directory in which this application is installed:\n" + dir + + "\nis invalid. The directory must not contain the '!' character. Please reinstall."; updateStatus(errmsg); _dead = true; } @@ -104,8 +101,8 @@ public abstract class Getdown extends Thread } /** - * This is used by the applet which always needs a user interface and wants - * to load it as soon as possible. + * This is used by the applet which always needs a user interface and wants to load it as soon + * as possible. */ public void preInit () { @@ -120,8 +117,8 @@ public abstract class Getdown extends Thread public void run () { - // if we have no messages, just bail because we're hosed; the - // error message will be displayed to the user already + // if we have no messages, just bail because we're hosed; the error message will be + // displayed to the user already if (_msgs == null) { return; } @@ -145,11 +142,9 @@ public abstract class Getdown extends Thread } else { // create a panel they can use to configure the proxy settings _container = createContainer(); - _container.add( - new ProxyPanel(this, _msgs), BorderLayout.CENTER); + _container.add(new ProxyPanel(this, _msgs), BorderLayout.CENTER); showContainer(); - // allow them to close the window to abort the proxy - // configuration + // allow them to close the window to abort the proxy configuration _dead = true; } @@ -161,11 +156,11 @@ public abstract class Getdown extends Thread } else if (!msg.startsWith("m.")) { // try to do something sensible based on the type of error if (e instanceof FileNotFoundException) { - msg = MessageUtil.compose("m.missing_resource", - MessageUtil.taint(msg), _ifc.installError); + msg = MessageUtil.compose( + "m.missing_resource", MessageUtil.taint(msg), _ifc.installError); } else { - msg = MessageUtil.compose("m.init_error", - MessageUtil.taint(msg), _ifc.installError); + msg = MessageUtil.compose( + "m.init_error", MessageUtil.taint(msg), _ifc.installError); } } updateStatus(msg); @@ -174,13 +169,11 @@ public abstract class Getdown extends Thread } /** - * Configures our proxy settings (called by {@link ProxyPanel}) and - * fires up the launcher. + * Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher. */ public void configureProxy (String host, String port) { - Log.info("User configured proxy [host=" + host + - ", port=" + port + "]."); + Log.info("User configured proxy [host=" + host + ", port=" + port + "]."); // if we're provided with valid values, create a proxy.txt file if (!StringUtil.isBlank(host)) { @@ -193,8 +186,7 @@ public abstract class Getdown extends Thread } pout.close(); } catch (IOException ioe) { - Log.warning("Error creating proxy file '" + pfile + - "': " + ioe); + Log.warning("Error creating proxy file '" + pfile + "': " + ioe); } // also configure them in the JVM @@ -212,8 +204,8 @@ public abstract class Getdown extends Thread /** * Reads and/or autodetects our proxy settings. * - * @return true if we should proceed with running the launcher, false - * if we need to wait for the user to enter proxy settings. + * @return true if we should proceed with running the launcher, false if we need to wait for + * the user to enter proxy settings. */ protected boolean detectProxy () { @@ -228,8 +220,7 @@ public abstract class Getdown extends Thread String host = null, port = null; boolean enabled = false; RegistryKey.initialize(); - RegistryKey r = new RegistryKey( - RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY); + RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY); for (Iterator iter = r.values(); iter.hasNext(); ) { RegistryValue value = (RegistryValue)iter.next(); if (value.getName().equals("ProxyEnable")) { @@ -254,8 +245,7 @@ public abstract class Getdown extends Thread } } catch (Throwable t) { - Log.info("Failed to find proxy settings in Windows registry " + - "[error=" + t + "]."); + Log.info("Failed to find proxy settings in Windows registry [error=" + t + "]."); } } @@ -264,18 +254,15 @@ public abstract class Getdown extends Thread if (pfile.exists()) { try { HashMap pconf = ConfigUtil.parseConfig(pfile, false); - setProxyProperties((String)pconf.get("host"), - (String)pconf.get("port")); + setProxyProperties((String)pconf.get("host"), (String)pconf.get("port")); return true; - } catch (IOException ioe) { Log.warning("Failed to read '" + pfile + "': " + ioe); } } - // otherwise see if we actually need a proxy; first we have to - // initialize our application to get some sort of interface - // configuration and the appbase URL + // otherwise see if we actually need a proxy; first we have to initialize our application + // to get some sort of interface configuration and the appbase URL Log.info("Checking whether we need to use a proxy..."); try { _ifc = _app.init(true); @@ -287,26 +274,23 @@ public abstract class Getdown extends Thread URL rurl = _app.getConfigResource().getRemote(); try { // try to make a HEAD request for this URL - HttpURLConnection ucon = (HttpURLConnection) - rurl.openConnection(); + HttpURLConnection ucon = (HttpURLConnection)rurl.openConnection(); ucon.setRequestMethod("HEAD"); ucon.connect(); // make sure we got a satisfactory response code if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) { - Log.warning("Got a non-200 response but assuming we're OK " + - "because we got something... [url=" + rurl + - ", rsp=" + ucon.getResponseCode() + "]."); + Log.warning("Got a non-200 response but assuming we're OK because we got " + + "something... [url=" + rurl + ", rsp=" + ucon.getResponseCode() + "]."); } - // we got through, so we appear not to require a proxy; make a - // blank proxy config and get on gettin' down + // we got through, so we appear not to require a proxy; make a blank proxy config and + // get on gettin' down Log.info("No proxy appears to be needed."); try { pfile.createNewFile(); } catch (IOException ioe) { - Log.warning("Failed to create blank proxy file '" + - pfile + "': " + ioe); + Log.warning("Failed to create blank proxy file '" + pfile + "': " + ioe); } return true; @@ -334,8 +318,7 @@ public abstract class Getdown extends Thread } /** - * Does the actual application validation, update and launching - * business. + * Does the actual application validation, update and launching business. */ protected void getdown () { @@ -357,9 +340,11 @@ public abstract class Getdown extends Thread createInterface(true); } + // if we aren't running in a JVM that meets our version requirements, either complain + // or attempt to download and install the appropriate version + for (int ii = 0; ii < MAX_LOOPS; ii++) { - // make sure we have the desired version and that the - // metadata files are valid... + // make sure we have the desired version and that the metadata files are valid... setStatus("m.validating", -1, -1L, false); if (_app.verifyMetadata(this)) { Log.info("Application requires update."); @@ -394,11 +379,11 @@ public abstract class Getdown extends Thread } else if (!msg.startsWith("m.")) { // try to do something sensible based on the type of error if (e instanceof FileNotFoundException) { - msg = MessageUtil.compose("m.missing_resource", - MessageUtil.taint(msg), _ifc.installError); + msg = MessageUtil.compose( + "m.missing_resource", MessageUtil.taint(msg), _ifc.installError); } else { - msg = MessageUtil.compose("m.init_error", msg, - MessageUtil.taint(msg), _ifc.installError); + msg = MessageUtil.compose( + "m.init_error", msg, MessageUtil.taint(msg), _ifc.installError); } } updateStatus(msg); @@ -445,8 +430,7 @@ public abstract class Getdown extends Thread for (Resource prsrc : list) { try { Patcher patcher = new Patcher(); - patcher.patch(prsrc.getLocal().getParentFile(), - prsrc.getLocal(), _progobs); + patcher.patch(prsrc.getLocal().getParentFile(), prsrc.getLocal(), _progobs); } catch (Exception e) { Log.warning("Failed to apply patch [prsrc=" + prsrc + "]."); Log.logStackTrace(e); @@ -460,9 +444,9 @@ public abstract class Getdown extends Thread } } - // if the patch resource is null, that means something was booched - // in the application, so we skip the patching process but update - // the metadata which will result in a "brute force" upgrade + // if the patch resource is null, that means something was booched in the application, so + // we skip the patching process but update the metadata which will result in a "brute + // force" upgrade // finally update our metadata files... _app.updateMetadata(); @@ -471,8 +455,7 @@ public abstract class Getdown extends Thread } /** - * Called if the application is determined to require resource - * downloads. + * Called if the application is determined to require resource downloads. */ protected void download (List resources) { @@ -510,23 +493,19 @@ public abstract class Getdown extends Thread // assume we're going to use an HTTP downloader Downloader dl = new HTTPDownloader(resources, obs); - // if torrent downloading is enabled and we are downloading the right - // set of resources (a single patch file or the entire app from - // scratch), then use a torrent downloader instead. - // Because many of our installers also bundle background.png, - // and might bundle more required files, we need to allow a - // 'fudge factor' threshhold for determining at which point it is - // faster to torrent, and at which point we should use HTTP. + // if torrent downloading is enabled and we are downloading the right set of resources (a + // single patch file or the entire app from scratch), then use a torrent downloader + // instead. Because many of our installers also bundle background.png, and might bundle + // more required files, we need to allow a 'fudge factor' threshhold for determining at + // which point it is faster to torrent, and at which point we should use HTTP. if (_app.getUseTorrent()) { - int verifiedResources = _app.getAllResources().size() - - resources.size(); + int verifiedResources = _app.getAllResources().size() - resources.size(); if (verifiedResources <= MAX_TORRENT_VERIFIED_RESOURCES) { ArrayList full = new ArrayList(); full.add(_app.getFullResource()); full.addAll(resources); dl = new TorrentDownloader(full, obs); - } else if (resources.size() == 1 && - resources.get(0).getPath().startsWith("patch")) { + } else if (resources.size() == 1 && resources.get(0).getPath().startsWith("patch")) { dl = new TorrentDownloader(resources, obs); } } @@ -543,8 +522,7 @@ public abstract class Getdown extends Thread } /** - * Called to launch the application if everything is determined to be - * ready to go. + * Called to launch the application if everything is determined to be ready to go. */ protected void launch () { @@ -557,28 +535,27 @@ public abstract class Getdown extends Thread } else { Process proc = _app.createProcess(); - // on Windows 98 and ME we need to stick around and read the - // output of stderr lest the process fill its output buffer and - // choke, yay! + // on Windows 98 and ME we need to stick around and read the output of stderr lest + // the process fill its output buffer and choke, yay! final InputStream stderr = proc.getErrorStream(); if (LaunchUtil.mustMonitorChildren()) { // close our window if it's around disposeContainer(); _status = null; - BufferedReader reader = new BufferedReader( - new InputStreamReader(stderr)); + BufferedReader reader = new BufferedReader(new InputStreamReader(stderr)); while (reader.readLine() != null) { // nothing doing! } Log.info("Process exited: " + proc.waitFor()); + } else { - // spawn a daemon thread that will catch the early bits of - // stderr in case the launch fails + // spawn a daemon thread that will catch the early bits of stderr in case the + // launch fails Thread t = new Thread() { public void run () { try { - BufferedReader reader = new BufferedReader( - new InputStreamReader(stderr)); + BufferedReader reader = + new BufferedReader(new InputStreamReader(stderr)); String line; while ((line = reader.readLine()) != null) { Log.warning(line); @@ -593,9 +570,9 @@ public abstract class Getdown extends Thread } } - // if we have a UI open and we haven't been around for at least 5 - // seconds, don't stick a fork in ourselves straight away but give - // our lovely user a chance to see what we're doing + // if we have a UI open and we haven't been around for at least 5 seconds, don't stick + // a fork in ourselves straight away but give our lovely user a chance to see what + // we're doing long uptime = System.currentTimeMillis() - _startup; if (_container != null && uptime < MIN_EXIST_TIME) { try { @@ -611,8 +588,8 @@ public abstract class Getdown extends Thread } /** - * Creates our user interface, which we avoid doing unless we actually - * have to update something. + * Creates our user interface, which we avoid doing unless we actually have to update + * something. */ protected void createInterface (boolean force) { @@ -627,8 +604,7 @@ public abstract class Getdown extends Thread _status = new StatusPanel(_msgs); _container.add(_status, BorderLayout.CENTER); } - _status.init(_ifc, getBackgroundImage(), - getProgressImage()); + _status.init(_ifc, getBackgroundImage(), getProgressImage()); showContainer(); } }); @@ -660,8 +636,8 @@ public abstract class Getdown extends Thread } } - protected void setStatus (final String message, final int percent, - final long remaining, boolean createUI) + protected void setStatus ( + final String message, final int percent, final long remaining, boolean createUI) { if (_status == null && createUI) { createInterface(false); @@ -712,8 +688,7 @@ public abstract class Getdown extends Thread imgpath = _app.getLocalPath(path); return ImageIO.read(imgpath); } catch (IOException ioe2) { - Log.warning("Failed to load image [path=" + imgpath + - ", error=" + ioe2 + "]."); + Log.warning("Failed to load image [path=" + imgpath + ", error=" + ioe2 + "]."); return null; } } @@ -734,9 +709,8 @@ public abstract class Getdown extends Thread protected abstract void disposeContainer (); /** - * If this method returns true we will run the application in the same JVM, - * otherwise we will fork off a new JVM. Some options are not supported if - * we do not fork off a new JVM. + * If this method returns true we will run the application in the same JVM, otherwise we will + * fork off a new JVM. Some options are not supported if we do not fork off a new JVM. */ protected boolean invokeDirect () { @@ -744,8 +718,8 @@ public abstract class Getdown extends Thread } /** - * Provides access to the applet that we'll pass on to our application when - * we're in "invoke direct" mode. + * Provides access to the applet that we'll pass on to our application when we're in "invoke + * direct" mode. */ protected JApplet getApplet () { @@ -765,8 +739,7 @@ public abstract class Getdown extends Thread }; protected Application _app; - protected Application.UpdateInterface _ifc = - new Application.UpdateInterface(); + protected Application.UpdateInterface _ifc = new Application.UpdateInterface(); protected ResourceBundle _msgs; protected Container _container; @@ -776,10 +749,7 @@ public abstract class Getdown extends Thread protected boolean _dead; protected long _startup; - /** - * The maximum number of resources that can be already present for - * bittorrent to be used. - */ + /** The maximum number of resources that can be already present for bittorrent to be used. */ protected static final int MAX_TORRENT_VERIFIED_RESOURCES = 1; protected static final int MAX_LOOPS = 5; diff --git a/src/java/com/threerings/getdown/launcher/GetdownApplet.java b/src/java/com/threerings/getdown/launcher/GetdownApplet.java index 8b20381..c5f780d 100644 --- a/src/java/com/threerings/getdown/launcher/GetdownApplet.java +++ b/src/java/com/threerings/getdown/launcher/GetdownApplet.java @@ -54,23 +54,8 @@ public class GetdownApplet extends JApplet @Override // documentation inherited public void init () { - // Getdown absolutely requires full read/write permissions to the system. If we don't - // have this, then we need to not do anything unsafe, and display a message to the user - // telling them they need to (groan) close out of the web browser entirely and re-launch - // the browser, go to our site, and accept the certificate. - SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - try { - sm.checkWrite("getdown"); - sm.checkPropertiesAccess(); - } catch (SecurityException se) { - Log.warning("Signed applet rejected by user [se=" + se + "]."); - _permissioned = false; - } - } - - // First off, verify that we are not being hijacked to execute - // malicious code in the name of the signer. + // verify that we are not being hijacked to execute malicious code in the name of the + // signer String appbase = getParameter("appbase"); String appname = getParameter("appname"); String imgpath = getParameter("bgimage"); @@ -83,127 +68,39 @@ public class GetdownApplet extends JApplet if (imgpath == null) { imgpath = ""; } - String params = appbase + appname + imgpath; - String signature = getParameter("signature"); - if (signature == null) { - signature = ""; - } - Object[] signers = GetdownApplet.class.getSigners(); - if (signers.length == 0) { - _safe = true; - } - for (Object signer : signers) { - if (!_safe && signer instanceof Certificate) { - Certificate cert = (Certificate)signer; - try { - Signature sig = Signature.getInstance("SHA1withRSA"); - sig.initVerify(cert); - sig.update(params.getBytes()); - if (sig.verify(Base64.decodeBase64( - signature.getBytes()))) { - _safe = true; - } - } catch (GeneralSecurityException gse) { - // ignore the error - the default is to not launch. - } - } - } - - if (!_safe) { - Log.warning("Signed getdown invoked on unsigned application; " + - "aborting installation."); - } - - // Pass through properties parameter. - String properties = getParameter("properties"); - if (properties != null && _permissioned) { - String[] proparray = properties.split(" "); - for (String property : proparray) { - String key = property.substring(property.indexOf("-D") + 2, - property.indexOf("=")); - String value = property.substring(property.indexOf("=") + 1); - System.setProperty(key, value); - } - } - - // when run from an applet, we install - String root; - if (RunAnywhere.isWindows()) { - root = "Application Data"; - } else if (RunAnywhere.isMacOS()) { - root = "Library" + File.separator + "Application Support"; - } else /* isLinux() or something wacky */ { - root = ".getdown"; - } - - String appdir = root + File.separator + appname; - if (_permissioned) { - appdir = System.getProperty("user.home") + File.separator + appdir; - } - - // if our application directory does not exist, auto-create it - File appDir = new File(appdir); - if (_permissioned && (!appDir.exists() || !appDir.isDirectory())) { - if (!appDir.mkdirs()) { - Log.warning("Unable to create app_dir '" + appdir + "'."); - // TODO: report error - return; - } - } - - // if an installer.txt file is desired, create that - String inststr = getParameter("installer"); - if (_permissioned && !StringUtil.isBlank(inststr)) { - File infile = new File(appDir, "installer.txt"); - if (!infile.exists()) { - writeToFile(infile, inststr); - } - } - - // if our getdown.txt file does not exist, auto-create it - if (_permissioned) { - File gdfile = new File(appDir, "getdown.txt"); - if (!gdfile.exists()) { - if (StringUtil.isBlank(appbase)) { - Log.warning("Missing 'appbase' cannot auto-create " + - "application directory."); - // TODO: report - return; - } - if (!writeToFile(gdfile, "appbase = " + appbase)) { - // TODO: report the error - return; - } - } - } - - // if a background image was specified, grabbit + File appdir = null; try { - if (!StringUtil.isBlank(imgpath)) { - _bgimage = getImage(new URL(getDocumentBase(), imgpath)); - } - } catch (Exception e) { - Log.info("Failed to load background image [path=" + imgpath + "]."); - Log.logStackTrace(e); - } + appdir = initGetdown(appbase, appname, imgpath); - // record a few things for posterity - Log.info("------------------ VM Info ------------------"); - Log.info("-- OS Name: " + System.getProperty("os.name")); - Log.info("-- OS Arch: " + System.getProperty("os.arch")); - Log.info("-- OS Vers: " + System.getProperty("os.version")); - Log.info("-- Java Vers: " + System.getProperty("java.version")); - if (_permissioned) { + // if a background image was specified, grabbit + try { + if (!StringUtil.isBlank(imgpath)) { + _bgimage = getImage(new URL(getDocumentBase(), imgpath)); + } + } catch (Exception e) { + Log.info("Failed to load background image [path=" + imgpath + "]."); + Log.logStackTrace(e); + } + + // record a few things for posterity + Log.info("------------------ VM Info ------------------"); + Log.info("-- OS Name: " + System.getProperty("os.name")); + Log.info("-- OS Arch: " + System.getProperty("os.arch")); + Log.info("-- OS Vers: " + System.getProperty("os.version")); + Log.info("-- Java Vers: " + System.getProperty("java.version")); Log.info("-- Java Home: " + System.getProperty("java.home")); Log.info("-- User Name: " + System.getProperty("user.name")); Log.info("-- User Home: " + System.getProperty("user.home")); Log.info("-- Cur dir: " + System.getProperty("user.dir")); + Log.info("---------------------------------------------"); + + } catch (Exception e) { + _errmsg = e.getMessage(); } - Log.info("---------------------------------------------"); try { - _getdown = new Getdown(appDir, null) { + _getdown = new Getdown(appdir, null) { protected Container createContainer () { getContentPane().removeAll(); return getContentPane(); @@ -227,14 +124,13 @@ public class GetdownApplet extends JApplet protected void exit (int exitCode) { // don't exit as we're in an applet } - @Override // documentation inherited - protected void setStatus (final String message, final int percent, - final long remaining, boolean createUI) + @Override protected void setStatus ( + final String message, final int percent, final long remaining, boolean createUI) { super.setStatus(message, percent, remaining, createUI); try { - JSObject.getWindow(GetdownApplet.this).call("getdownStatus", - new Object[] {message, percent, remaining}); + JSObject.getWindow(GetdownApplet.this).call( + "getdownStatus", new Object[] { message, percent, remaining }); } catch (JSException jse) { // don't sweat it. } @@ -252,18 +148,14 @@ public class GetdownApplet extends JApplet @Override // documentation inherited public void start () { - if (!_safe) { - _getdown.updateStatus("m.corrupt_param_signature_error"); - return; - } - if (!_permissioned) { - _getdown.updateStatus("m.insufficient_permissions_error"); - return; - } - try { - _getdown.start(); - } catch (Exception e) { - Log.logStackTrace(e); + if (_errmsg != null) { + _getdown.updateStatus(_errmsg); + } else { + try { + _getdown.start(); + } catch (Exception e) { + Log.logStackTrace(e); + } } } @@ -273,6 +165,110 @@ public class GetdownApplet extends JApplet // TODO } + /** + * Does all the fiddly initialization of Getdown and throws an exception if something goes + * horribly wrong. If an exception is thrown we will abort the whole process and display an + * error message to the user. + */ + protected File initGetdown (String appbase, String appname, String imgpath) + throws Exception + { + // getdown requires full read/write permissions to the system; if we don't have this, then + // we need to not do anything unsafe, and display a message to the user telling them they + // need to (groan) close out of the web browser entirely and re-launch the browser, go to + // our site, and accept the certificate + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + sm.checkWrite("getdown"); + sm.checkPropertiesAccess(); + } catch (SecurityException se) { + Log.warning("Signed applet rejected by user [se=" + se + "]."); + throw new Exception("m.insufficient_permissions_error"); + } + } + + + Object[] signers = GetdownApplet.class.getSigners(); + if (signers.length == 0) { + Log.info("No signers, not verifying param signature."); + + } else { + String signature = getParameter("signature"); + if (signature == null) { + signature = ""; + } + String params = appbase + appname + imgpath; + for (Object signer : signers) { + if (signer instanceof Certificate) { + Certificate cert = (Certificate)signer; + try { + Signature sig = Signature.getInstance("SHA1withRSA"); + sig.initVerify(cert); + sig.update(params.getBytes()); + if (!sig.verify(Base64.decodeBase64(signature.getBytes()))) { + throw new Exception("m.corrupt_param_signature_error"); + } + } catch (GeneralSecurityException gse) { + throw new Exception("m.corrupt_param_signature_error"); + } + } + } + } + + // pass through properties parameters + String properties = getParameter("properties"); + if (properties != null) { + String[] proparray = properties.split(" "); + for (String property : proparray) { + String key = property.substring(property.indexOf("-D") + 2, property.indexOf("=")); + String value = property.substring(property.indexOf("=") + 1); + System.setProperty(key, value); + } + } + + // when run from an applet, we install to the user's home directory + String root; + if (RunAnywhere.isWindows()) { + root = "Application Data"; + } else if (RunAnywhere.isMacOS()) { + root = "Library" + File.separator + "Application Support"; + } else /* isLinux() or something wacky */ { + root = ".getdown"; + } + File appdir = new File(System.getProperty("user.home") + File.separator + root + + File.separator + appname); + + // if our application directory does not exist, auto-create it + if (!appdir.exists() || !appdir.isDirectory()) { + if (!appdir.mkdirs()) { + throw new Exception("m.create_appdir_failed"); + } + } + + // if an installer.txt file is desired, create that + String inststr = getParameter("installer"); + if (!StringUtil.isBlank(inststr)) { + File infile = new File(appdir, "installer.txt"); + if (!infile.exists()) { + writeToFile(infile, inststr); + } + } + + // if our getdown.txt file does not exist, auto-create it + File gdfile = new File(appdir, "getdown.txt"); + if (!gdfile.exists()) { + if (StringUtil.isBlank(appbase)) { + throw new Exception("m.missing_appbase"); + } + if (!writeToFile(gdfile, "appbase = " + appbase)) { + throw new Exception("m.create_getdown_failed"); + } + } + + return appdir; + } + /** * Creates the specified file and writes the supplied contents to it. */ @@ -290,15 +286,12 @@ public class GetdownApplet extends JApplet } } + /** Handles all the actual getting down. */ protected Getdown _getdown; + + /** A background image drawn to make things look purdy. */ protected Image _bgimage; - /** - * Getdown will refuse to initialize if the jar is signed but the - * parameters are not validated to prevent malicious code from being run. - */ - protected boolean _safe = false; - - /** Whether Getdown has been trusted by the user with system access */ - protected boolean _permissioned = true; + /** An error encountered during initialization. */ + protected String _errmsg; } diff --git a/src/java/com/threerings/getdown/launcher/StatusPanel.java b/src/java/com/threerings/getdown/launcher/StatusPanel.java index 4c70906..1dd8bc9 100644 --- a/src/java/com/threerings/getdown/launcher/StatusPanel.java +++ b/src/java/com/threerings/getdown/launcher/StatusPanel.java @@ -118,7 +118,7 @@ public class StatusPanel extends JComponent { status = xlate(status); _newlab = new Label(status, _ifc.statusText, _font); - _newlab.setTargetWidth(_ifc.status.width); + _newlab.setTargetWidth(Math.min(_ifc.status.width, getWidth() - _ifc.status.x*2)); if (_ifc.textShadow != null) { _newlab.setAlternateColor(_ifc.textShadow); _newlab.setStyle(Label.SHADOW); diff --git a/src/java/com/threerings/getdown/messages.properties b/src/java/com/threerings/getdown/messages.properties index 1273c1e..5c5bcd6 100644 --- a/src/java/com/threerings/getdown/messages.properties +++ b/src/java/com/threerings/getdown/messages.properties @@ -64,14 +64,20 @@ m.readonly_error = The directory in which this application is installed: \ \n{0}\nis read-only. Please install the applicaton into a directory where \ you have write access. -m.missing_resource = The application has failed to launch due to a \ - missing resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such problems. +m.missing_resource = The application has failed to launch due to a missing \ + resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \ + problems. -m.insufficient_permissions_error = \n\n\n\n\n\n\nYou did not accept the application's digital signature. \ - \nPlease quit and re-open your web browser and attempt to \nre-launch the application, \ - making sure that you accept \nthe application's digital signature. -m.corrupt_param_signature_error = We couldn't verify the application's digital signature.\n\ - Please check that you are launching the application from \nthe application's website. +m.insufficient_permissions_error = You did not accept this application's \ + digital signature. If you want to run the application, you will need to accept \ + its digital signature.\n\nTo do so, you will need to quit your web browser, \ + restart it, and return to this web page to relaunch the application. When the \ + security dialog is shown, click the button to accept the digital signature \ + and grant this application the privileges it needs to run. + +m.corrupt_param_signature_error = We couldn't verify the application's digital \ + signature.\nPlease check that you are launching the application from\nthe \ + correct website. m.default_install_error = the support section of the website