From 7bfd596beb7bf9d121991a57341ba95080c42a62 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 6 Jun 2006 23:29:47 +0000 Subject: [PATCH] Added support for running Getdown as a signed applet as well as support for running our target application directly in the same JVM instead of forking off a new VM. --- build.xml | 17 ++ etc/getdown.pro | 8 + .../threerings/getdown/data/Application.java | 57 +++++ .../threerings/getdown/launcher/Getdown.java | 216 ++++++++---------- .../getdown/launcher/GetdownApp.java | 128 +++++++++++ .../getdown/launcher/GetdownApplet.java | 137 +++++++++++ 6 files changed, 442 insertions(+), 121 deletions(-) create mode 100644 src/java/com/threerings/getdown/launcher/GetdownApp.java create mode 100644 src/java/com/threerings/getdown/launcher/GetdownApplet.java diff --git a/build.xml b/build.xml index 5a3799b..037b661 100644 --- a/build.xml +++ b/build.xml @@ -6,6 +6,10 @@ + + + + @@ -82,5 +86,18 @@ classpath="lib/retroweaver-all-1.2.2.jar"/> + + + + + + + + + diff --git a/etc/getdown.pro b/etc/getdown.pro index 61cb424..e74f259 100644 --- a/etc/getdown.pro +++ b/etc/getdown.pro @@ -23,3 +23,11 @@ -keep public class com.threerings.getdown.launcher.Getdown { public static void main (java.lang.String[]); } + +-keep public class com.threerings.getdown.launcher.GetdownApp { + public static void main (java.lang.String[]); +} + +-keep public class com.threerings.getdown.launcher.GetdownApplet { + *; +} diff --git a/src/java/com/threerings/getdown/data/Application.java b/src/java/com/threerings/getdown/data/Application.java index 4cfbc1c..00bac5d 100644 --- a/src/java/com/threerings/getdown/data/Application.java +++ b/src/java/com/threerings/getdown/data/Application.java @@ -6,6 +6,12 @@ package com.threerings.getdown.data; import java.awt.Color; import java.awt.Rectangle; +import java.lang.reflect.Method; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.Permissions; + import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -17,6 +23,7 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; @@ -488,6 +495,54 @@ public class Application return Runtime.getRuntime().exec(sargs, null); } + /** + * Runs this application directly in the current VM. + */ + public void invokeDirect () + { + // create a custom class loader + ArrayList jars = new ArrayList(); + for (Resource rsrc : _codes) { + try { + jars.add( + new URL("file", "", rsrc.getLocal().getAbsolutePath())); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } + URLClassLoader loader = new URLClassLoader( + jars.toArray(new URL[jars.size()]), + ClassLoader.getSystemClassLoader()) { + protected PermissionCollection getPermissions (CodeSource code) { + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + return perms; + } + }; + + for (String jvmarg : _jvmargs) { + if (jvmarg.startsWith("-D")) { + jvmarg = processArg(jvmarg.substring(2)); + int eqidx = jvmarg.indexOf("="); + if (eqidx == -1) { + Log.warning("Bogus system property: '" + jvmarg + "'?"); + } else { + System.setProperty(jvmarg.substring(0, eqidx), + jvmarg.substring(eqidx+1)); + } + } + } + + try { + Class appclass = loader.loadClass(_class); + Method main = appclass.getMethod("main", SA_PROTO.getClass()); + String[] args = _appargs.toArray(new String[_appargs.size()]); + main.invoke(null, new Object[] { args }); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } + /** Replaces the application directory and version in any argument. */ protected String processArg (String arg) { @@ -813,4 +868,6 @@ public class Application protected ArrayList _jvmargs = new ArrayList(); protected ArrayList _appargs = new ArrayList(); + + protected static final String[] SA_PROTO = new String[0]; } diff --git a/src/java/com/threerings/getdown/launcher/Getdown.java b/src/java/com/threerings/getdown/launcher/Getdown.java index d0252d0..c601ce4 100644 --- a/src/java/com/threerings/getdown/launcher/Getdown.java +++ b/src/java/com/threerings/getdown/launcher/Getdown.java @@ -4,6 +4,7 @@ package com.threerings.getdown.launcher; import java.awt.BorderLayout; +import java.awt.Container; import java.awt.EventQueue; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -52,9 +53,15 @@ import com.threerings.getdown.util.ProgressObserver; * Manages the main control for the Getdown application updater and * deployment system. */ -public class Getdown extends Thread +public abstract class Getdown extends Thread implements Application.StatusDisplay { + public static void main (String[] args) + { + // legacy support + GetdownApp.main(args); + } + public Getdown (File appDir, String appId) { super("Getdown"); @@ -78,6 +85,19 @@ public class Getdown extends Thread _startup = System.currentTimeMillis(); } + /** + * This is used by the applet which always needs a user interface and wants + * to load it as soon as possible. + */ + public void preInit () + { + try { + _ifc = _app.init(true); + } catch (Exception e) { + Log.warning("Failed to preinit: " + e); + } + } + public void run () { // if we have no messages, just bail because we're hosed; the @@ -104,12 +124,10 @@ public class Getdown extends Thread getdown(); } else { // create a panel they can use to configure the proxy settings - _frame.getContentPane().removeAll(); - _frame.getContentPane().add( + _container = createContainer(); + _container.add( new ProxyPanel(this, _msgs), BorderLayout.CENTER); - _frame.pack(); - SwingUtil.centerWindow(_frame); - _frame.setVisible(true); + showContainer(); // allow them to close the window to abort the proxy // configuration _dead = true; @@ -164,9 +182,8 @@ public class Getdown extends Thread } // clear out our UI - _frame.dispose(); + disposeContainer(); _status = null; - _frame = null; // fire up a new thread new Thread(this).start(); @@ -477,40 +494,40 @@ public class Getdown extends Thread setStatus("m.launching", 100, -1L, false); try { - Process proc = _app.createProcess(); + if (invokeDirect()) { + _app.invokeDirect(); - // 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! - if (LaunchUtil.mustMonitorChildren()) { - // close our window if it's around - if (_frame != null) { - _frame.dispose(); + } 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! + if (LaunchUtil.mustMonitorChildren()) { + // close our window if it's around + disposeContainer(); _status = null; - _frame = null; + InputStream stderr = proc.getErrorStream(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(stderr)); + while (reader.readLine() != null) { + // nothing doing! + } + Log.info("Process exited: " + proc.waitFor()); } - Log.info("Sticking around to read stderr..."); - InputStream stderr = proc.getErrorStream(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(stderr)); - while (reader.readLine() != null) { - // nothing doing! - } - Log.info("Process exited: " + proc.waitFor()); } - // 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 (_frame != null && uptime < MIN_EXIST_TIME) { + if (_container != null && uptime < MIN_EXIST_TIME) { try { Thread.sleep(MIN_EXIST_TIME - uptime); } catch (Exception e) { } } - System.exit(0); + exit(0); } catch (Exception e) { Log.logStackTrace(e); @@ -523,7 +540,7 @@ public class Getdown extends Thread */ protected void createInterface (boolean force) { - if (_frame != null && !force) { + if (_container != null && !force) { return; } @@ -531,38 +548,26 @@ public class Getdown extends Thread BufferedImage bgimg = loadImage(_ifc.backgroundImage); BufferedImage barimg = loadImage(_ifc.progressImage); - // create our user interface, and display it - String title = StringUtil.isBlank(_ifc.name) ? "" : _ifc.name; - if (_frame == null) { - _frame = new JFrame(title); - _frame.addWindowListener(new WindowAdapter() { - public void windowClosing (WindowEvent evt) { - if (_dead) { - System.exit(0); - } else { - if (_abort == null) { - _abort = new AbortPanel(Getdown.this, _msgs); - } - _abort.pack(); - SwingUtil.centerWindow(_abort); - _abort.setVisible(true); - _abort.setState(JFrame.NORMAL); - _abort.requestFocus(); - } - } - }); - _frame.setResizable(false); - - } else { - _frame.setTitle(title); - _frame.getContentPane().removeAll(); - } - _frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + _container = createContainer(); _status = new StatusPanel(_msgs, _ifc, bgimg, barimg); - _frame.getContentPane().add(_status, BorderLayout.CENTER); - _frame.pack(); - SwingUtil.centerWindow(_frame); - _frame.setVisible(true); + _container.add(_status, BorderLayout.CENTER); + showContainer(); + } + + protected void handleWindowClose () + { + if (_dead) { + exit(0); + } else { + if (_abort == null) { + _abort = new AbortPanel(Getdown.this, _msgs); + } + _abort.pack(); + SwingUtil.centerWindow(_abort); + _abort.setVisible(true); + _abort.setState(JFrame.NORMAL); + _abort.requestFocus(); + } } protected void setStatus (final String message, final int percent, @@ -575,6 +580,10 @@ public class Getdown extends Thread if (_status != null) { EventQueue.invokeLater(new Runnable() { public void run () { + if (_status == null) { + Log.info("Dropping status '" + message + "'."); + return; + } if (message != null) { _status.setStatus(message); } @@ -601,67 +610,32 @@ public class Getdown extends Thread } } - public static void main (String[] args) - { - // maybe they specified the appdir in a system property - int aidx = 0; - String adarg = System.getProperty("appdir"); - // if not, check for a command line argument - if (StringUtil.isBlank(adarg)) { - if (args.length < 1) { - System.err.println( - "Usage: java -jar getdown.jar app_dir [app_id]"); - System.exit(-1); - } - adarg = args[aidx++]; - } + /** + * Creates the container in which our user interface will be displayed. + */ + protected abstract Container createContainer (); - // look for a specific app identifier - String appId = null; - if (args.length > aidx) { - appId = args[aidx++]; - } + /** + * Shows the container in which our user interface will be displayed. + */ + protected abstract void showContainer (); - // ensure a valid directory was supplied - File appDir = new File(adarg); - if (!appDir.exists() || !appDir.isDirectory()) { - Log.warning("Invalid app_dir '" + adarg + "'."); - System.exit(-1); - } + /** + * Disposes the container in which we have our user interface. + */ + protected abstract void disposeContainer (); - // pipe our output into a file in the application directory - if (System.getProperty("no_log_redir") == null) { - File log = new File(appDir, "launcher.log"); - try { - PrintStream logOut = new PrintStream( - new BufferedOutputStream(new FileOutputStream(log)), true); - System.setOut(logOut); - System.setErr(logOut); - } catch (IOException ioe) { - Log.warning("Unable to redirect output to '" + log + - "': " + ioe); - } - } + /** + * 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 abstract boolean invokeDirect (); - // 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("---------------------------------------------"); - - try { - Getdown app = new Getdown(appDir, appId); - app.start(); - } catch (Exception e) { - Log.logStackTrace(e); - } - } + /** + * Requests that Getdown exit. In applet mode this does nothing. + */ + protected abstract void exit (int exitCode); /** Used to pass progress on to our user interface. */ protected ProgressObserver _progobs = new ProgressObserver() { @@ -675,7 +649,7 @@ public class Getdown extends Thread new Application.UpdateInterface(); protected ResourceBundle _msgs; - protected JFrame _frame; + protected Container _container; protected StatusPanel _status; protected AbortPanel _abort; diff --git a/src/java/com/threerings/getdown/launcher/GetdownApp.java b/src/java/com/threerings/getdown/launcher/GetdownApp.java new file mode 100644 index 0000000..f50d70f --- /dev/null +++ b/src/java/com/threerings/getdown/launcher/GetdownApp.java @@ -0,0 +1,128 @@ +// +// $Id$ + +package com.threerings.getdown.launcher; + +import java.awt.Container; +import java.awt.event.WindowEvent; +import java.awt.event.WindowAdapter; +import javax.swing.JFrame; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.BufferedOutputStream; +import java.io.PrintStream; + +import com.samskivert.swing.util.SwingUtil; +import com.samskivert.util.StringUtil; + +import com.threerings.getdown.Log; + +/** + * Does something extraordinary. + */ +public class GetdownApp +{ + public static void main (String[] args) + { + // maybe they specified the appdir in a system property + int aidx = 0; + String adarg = System.getProperty("appdir"); + // if not, check for a command line argument + if (StringUtil.isBlank(adarg)) { + if (args.length < 1) { + System.err.println( + "Usage: java -jar getdown.jar app_dir [app_id]"); + System.exit(-1); + } + adarg = args[aidx++]; + } + + // look for a specific app identifier + String appId = null; + if (args.length > aidx) { + appId = args[aidx++]; + } + + // ensure a valid directory was supplied + File appDir = new File(adarg); + if (!appDir.exists() || !appDir.isDirectory()) { + Log.warning("Invalid app_dir '" + adarg + "'."); + System.exit(-1); + } + + // pipe our output into a file in the application directory + if (System.getProperty("no_log_redir") == null) { + File log = new File(appDir, "launcher.log"); + try { + PrintStream logOut = new PrintStream( + new BufferedOutputStream(new FileOutputStream(log)), true); + System.setOut(logOut); + System.setErr(logOut); + } catch (IOException ioe) { + Log.warning("Unable to redirect output to '" + log + + "': " + ioe); + } + } + + // 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("---------------------------------------------"); + + try { + Getdown app = new Getdown(appDir, appId) { + protected Container createContainer () { + // create our user interface, and display it + String title = + StringUtil.isBlank(_ifc.name) ? "" : _ifc.name; + if (_frame == null) { + _frame = new JFrame(title); + _frame.addWindowListener(new WindowAdapter() { + public void windowClosing (WindowEvent evt) { + handleWindowClose(); + } + }); + _frame.setResizable(false); + } else { + _frame.setTitle(title); + _frame.getContentPane().removeAll(); + } + _frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + return _frame.getContentPane(); + } + protected void showContainer () { + if (_frame != null) { + _frame.pack(); + SwingUtil.centerWindow(_frame); + _frame.setVisible(true); + } + } + protected void disposeContainer () { + if (_frame != null) { + _frame.dispose(); + _frame = null; + } + } + protected boolean invokeDirect () { + return false; + } + protected void exit (int exitCode) { + System.exit(exitCode); + } + protected JFrame _frame; + }; + app.start(); + } catch (Exception e) { + Log.logStackTrace(e); + } + } +} diff --git a/src/java/com/threerings/getdown/launcher/GetdownApplet.java b/src/java/com/threerings/getdown/launcher/GetdownApplet.java new file mode 100644 index 0000000..cb80680 --- /dev/null +++ b/src/java/com/threerings/getdown/launcher/GetdownApplet.java @@ -0,0 +1,137 @@ +// +// $Id$ + +package com.threerings.getdown.launcher; + +import java.awt.Container; + +import javax.swing.JApplet; +import javax.swing.SwingUtilities; + +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.File; +import java.io.PrintStream; + +import com.samskivert.util.RunAnywhere; +import com.samskivert.util.StringUtil; + +import com.threerings.getdown.Log; + +/** + * An applet that can be used to launch a Getdown application (when signed and + * given privileges). + */ +public class GetdownApplet extends JApplet +{ + @Override // documentation inherited + public void init () + { + // 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 appname = getParameter("appname"); + String appdir = System.getProperty("user.home") + + File.separator + root + File.separator + appname; + + // if our application directory does not exist, auto-create it + File appDir = new File(appdir); + if (!appDir.exists() || !appDir.isDirectory()) { + if (!appDir.mkdirs()) { + Log.warning("Unable to create app_dir '" + appdir + "'."); + // TODO: report error + return; + } + } + + // if our getdown.txt file does not exist, auto-create it + File gdfile = new File(appDir, "getdown.txt"); + if (!gdfile.exists()) { + String appbase = getParameter("appbase"); + if (StringUtil.isBlank(appbase)) { + Log.warning("Missing 'appbase' cannot auto-create " + + "application directory."); + // TODO: report + return; + } + try { + PrintStream out = new PrintStream(new FileOutputStream(gdfile)); + out.println("appbase = " + appbase); + out.close(); + } catch (IOException ioe) { + Log.warning("Failed to create '" + gdfile + "'."); + Log.logStackTrace(ioe); + // TODO: report + return; + } + } + + // 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("---------------------------------------------"); + + try { + _getdown = new Getdown(appDir, null) { + protected Container createContainer () { + getContentPane().removeAll(); + return getContentPane(); + } + protected void showContainer () { + invalidate(); + } + protected void disposeContainer () { + } + protected boolean invokeDirect () { + return true; + } + protected void exit (int exitCode) { + Log.info("Applet \"exiting\"."); + // don't exit as we're in an applet + } + }; + + SwingUtilities.invokeAndWait(new Runnable() { + public void run () { + _getdown.preInit(); + _getdown.createInterface(false); + } + }); + + } catch (Exception e) { + Log.logStackTrace(e); + } + } + + @Override // documentation inherited + public void start () + { + try { + _getdown.start(); + } catch (Exception e) { + Log.logStackTrace(e); + } + } + + @Override // documentation inherited + public void stop () + { + // TODO + } + + protected Getdown _getdown; +}