diff --git a/src/java/com/threerings/getdown/data/Application.java b/src/java/com/threerings/getdown/data/Application.java index a37756c..4a3105e 100644 --- a/src/java/com/threerings/getdown/data/Application.java +++ b/src/java/com/threerings/getdown/data/Application.java @@ -49,6 +49,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.samskivert.io.StreamUtil; import com.samskivert.text.MessageUtil; @@ -277,6 +279,29 @@ public class Application } } + /** + * Returns a resource for a zip file containing a Java VM that can be downloaded to use in + * place of the installed VM (in the case where the VM that launched Getdown does not meet the + * application's version requirements) or null if no VM is available for this platform. + */ + public Resource getJavaVMResource () + { + if (StringUtil.isBlank(_javaLocation)) { + return null; + } + + String vmfile = LaunchUtil.LOCAL_JAVA_DIR + ".jar"; + try { + URL remote = new URL(createVAppBase(_targetVersion), _javaLocation); + return new Resource(vmfile, remote, getLocalPath(vmfile), true); + } catch (Exception e) { + Log.warning("Failed to create VM resource [vmfile=" + vmfile + ", appbase=" + _appbase + + ", tvers=" + _targetVersion + ", javaloc=" + _javaLocation + + ", error=" + e + "]."); + return null; + } + } + /** * Returns a resource that can be used to download an archive containing all files belonging to * the application. @@ -493,7 +518,34 @@ public class Application */ public boolean haveValidJavaVersion () { - return true; // TODO + // if we're doing no version checking, then yay! + if (_javaVersion <= 0) { + return true; + } + + // if we have a fully unpacked VM assume it is the right version (TODO: don't) + Resource vmjar = getJavaVMResource(); + if (vmjar != null && vmjar.isMarkedValid()) { + return true; + } + + // parse the version out of the java.version system property + String verstr = System.getProperty("java.version"); + Matcher m = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)_(\\d+)").matcher(verstr); + if (!m.matches()) { + // if we can't parse the java version we're in weird land and should probably just try + // our luck with what we've got rather than try to download a new jvm + Log.warning("Unable to parse VM version, hoping for the best [version=" + verstr + + ", needed=" + _javaVersion + "]."); + return true; + } + + int one = Integer.parseInt(m.group(1)); // will there ever be a two? + int major = Integer.parseInt(m.group(2)); + int minor = Integer.parseInt(m.group(3)); + int patch = Integer.parseInt(m.group(4)); + int version = patch + 100 * (minor + 100 * (major + 100 * one)); + return version >= _javaVersion; } /** @@ -548,7 +600,7 @@ public class Application ArrayList args = new ArrayList(); // reconstruct the path to the JVM - args.add(LaunchUtil.getJVMPath(_windebug)); + args.add(LaunchUtil.getJVMPath(_appdir, _windebug)); // add the classpath arguments args.add("-classpath"); diff --git a/src/java/com/threerings/getdown/data/Resource.java b/src/java/com/threerings/getdown/data/Resource.java index 2822426..6908ec9 100644 --- a/src/java/com/threerings/getdown/data/Resource.java +++ b/src/java/com/threerings/getdown/data/Resource.java @@ -152,11 +152,9 @@ public class Resource return false; } try { - return FileUtil.unpackJar( - new JarFile(_local), _local.getParentFile()); + return FileUtil.unpackJar(new JarFile(_local), _local.getParentFile()); } catch (IOException ioe) { - Log.warning("Failed to create JarFile from '" + - _local + "': " + ioe); + Log.warning("Failed to create JarFile from '" + _local + "': " + ioe); return false; } } diff --git a/src/java/com/threerings/getdown/launcher/Getdown.java b/src/java/com/threerings/getdown/launcher/Getdown.java index 161a3d6..b0a8016 100644 --- a/src/java/com/threerings/getdown/launcher/Getdown.java +++ b/src/java/com/threerings/getdown/launcher/Getdown.java @@ -340,10 +340,17 @@ 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++) { + // if we aren't running in a JVM that meets our version requirements, either + // complain or attempt to download and install the appropriate version + if (!_app.haveValidJavaVersion()) { + // download and install the necessary version of java, then loop back again and + // reverify everything; if we can't download java; we'll throw an exception + Log.info("Attempting to update Java VM..."); + updateJava(); + continue; + } + // make sure we have the desired version and that the metadata files are valid... setStatus("m.validating", -1, -1L, false); if (_app.verifyMetadata(this)) { @@ -397,6 +404,47 @@ public abstract class Getdown extends Thread setStatus(message, -1, -1L, true); } + /** + * Downloads and installs an Java VM bundled with the application. This is called if we are not + * running with the necessary Java version. + */ + protected void updateJava () + throws IOException + { + Resource vmjar = _app.getJavaVMResource(); + if (vmjar == null) { + throw new IOException("m.java_download_failed"); + } + + updateStatus("m.downloading_java"); + ArrayList list = new ArrayList(); + list.add(vmjar); + download(list); + + updateStatus("m.unpacking_java"); + if (!vmjar.unpack()) { + throw new IOException("m.java_unpack_failed"); + } + vmjar.markAsValid(); + + // Sun, why dost thou spite me? Java doesn't know anything about file permissions (and by + // extension then, neither does Jar), so on Joonix we have to hackily make java_vm/bin/java + // executable by execing chmod; a pox on their children! + if (RunAnywhere.isLinux()) { + String vmbin = LaunchUtil.LOCAL_JAVA_DIR + File.separator + "bin" + + File.separator + "java"; + String cmd = "chmod a+rx " + _app.getLocalPath(vmbin); + try { + Log.info("Please smack a Java engineer. Running: " + cmd); + Runtime.getRuntime().exec(cmd); + } catch (Exception e) { + Log.warning("Failed to mark VM binary as executable [cmd=" + cmd + + ", error=" + e + "]."); + // we should do something like tell the user or something but fucking fuck + } + } + } + /** * Called if the application is determined to be of an old version. */ @@ -480,8 +528,7 @@ public abstract class Getdown extends Thread } public void downloadFailed (Resource rsrc, Exception e) { - updateStatus( - MessageUtil.tcompose("m.failure", e.getMessage())); + updateStatus(MessageUtil.tcompose("m.failure", e.getMessage())); Log.warning("Download failed [rsrc=" + rsrc + "]."); Log.logStackTrace(e); synchronized (lock) { @@ -643,22 +690,20 @@ public abstract class Getdown extends Thread createInterface(false); } - 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); - } - if (percent >= 0) { - _status.setProgress(percent, remaining); - } + EventQueue.invokeLater(new Runnable() { + public void run () { + if (_status == null) { + Log.info("Dropping status '" + message + "'."); + return; } - }); - } + if (message != null) { + _status.setStatus(message); + } + if (percent >= 0) { + _status.setProgress(percent, remaining); + } + } + }); } /** diff --git a/src/java/com/threerings/getdown/messages.properties b/src/java/com/threerings/getdown/messages.properties index 5c5bcd6..b6bf685 100644 --- a/src/java/com/threerings/getdown/messages.properties +++ b/src/java/com/threerings/getdown/messages.properties @@ -32,6 +32,9 @@ m.proxy_port = Proxy port m.proxy_ok = OK m.proxy_cancel = Cancel +m.downloading_java = Downloading Java Virtual Machine... +m.unpacking_java = Unpacking Java Virtual Machine... + m.resolving = Resolving downloads... m.downloading = Downloading data... m.failure = Download failed: {0} @@ -49,6 +52,17 @@ m.updating_metadata = Downloading control files... m.init_failed = Our configuration file is missing or corrupt. Attempting \ to download a new copy... +m.java_download_failed = We were unable to automatically download the \ + necessary verson of Java for your computer.\n\n\ + Please go to www.java.com and download the latest version of \ + Java, then try running the application again. + +m.java_unpack_failed = We were unable to unpack an updated version of \ + Java. Please make sure you have at least 100 MB of free space on your \ + harddrive and try running the application again.\n\n\ + If that does not solve the problem, go to www.java.com and download and \ + install the latest version of Java and try again. + m.unable_to_repair = We were unable to download the necessary files after \ five attempts. You can try running the application again, but if it \ fails you may need to uninstall and reinstall. diff --git a/src/java/com/threerings/getdown/util/LaunchUtil.java b/src/java/com/threerings/getdown/util/LaunchUtil.java index becfd97..6f3b440 100644 --- a/src/java/com/threerings/getdown/util/LaunchUtil.java +++ b/src/java/com/threerings/getdown/util/LaunchUtil.java @@ -35,43 +35,40 @@ import com.threerings.getdown.Log; */ public class LaunchUtil { + /** The directory into which a local VM installation should be unpacked. */ + public static final String LOCAL_JAVA_DIR = "java_vm"; + /** * Reconstructs the path to the JVM used to launch this process. */ - public static String getJVMPath () + public static String getJVMPath (File appdir) { - return getJVMPath(false); + return getJVMPath(appdir, false); } /** * Reconstructs the path to the JVM used to launch this process. * - * @param windebug if true we will use java.exe instead of javaw.exe on - * Windows. + * @param windebug if true we will use java.exe instead of javaw.exe on Windows. */ - public static String getJVMPath (boolean windebug) + public static String getJVMPath (File appdir, boolean windebug) { - String apbase = System.getProperty("java.home") + - File.separator + "bin" + File.separator; - String apath = apbase + "java"; - if (new File(apath).exists()) { - return apath; + // first look in our application directory for an installed VM + String vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR).getPath(), windebug); + if (vmpath != null) { + return vmpath; } - if (!windebug) { - apath = apbase + "javaw.exe"; - if (new File(apath).exists()) { - return apath; - } + // then fall back to the VM in which we're already running + vmpath = checkJVMPath(System.getProperty("java.home"), windebug); + if (vmpath != null) { + return vmpath; } - apath = apbase + "java.exe"; - if (new File(apath).exists()) { - return apath; - } - - Log.warning("Unable to find java! [jhome=" + apbase + "]."); - return apbase + "java"; + // then throw up our hands and hope for the best + Log.warning("Unable to find java [appdir=" + appdir + + ", java.home=" + System.getProperty("java.home") + "]!"); + return "java"; } /** @@ -142,4 +139,30 @@ public class LaunchUtil return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1); } + + /** + * Checks whether a Java Virtual Machine can be located in the supplied path. + */ + protected static String checkJVMPath (String vmhome, boolean windebug) + { + String vmbase = vmhome + File.separator + "bin" + File.separator; + String vmpath = vmbase + "java"; + if (new File(vmpath).exists()) { + return vmpath; + } + + if (!windebug) { + vmpath = vmbase + "javaw.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + } + + vmpath = vmbase + "java.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + + return null; + } }