diff --git a/getdown/src/getdown/CHANGELOG.md b/getdown/src/getdown/CHANGELOG.md
index 098651eb1..ec7a923e1 100644
--- a/getdown/src/getdown/CHANGELOG.md
+++ b/getdown/src/getdown/CHANGELOG.md
@@ -1,16 +1,73 @@
# Getdown Releases
-## 1.8.3 - Unreleased
+## 1.8.7 - Unreleased
+
+* Reinstated env var support in `appbase` property.
+
+* Fixed issue with `myIpAddress()` in PAC proxy support.
+
+## 1.8.6 - June 4, 2019
+
+* Fixed issues with PAC proxy support: added `myIpAddress()`, fixed `dnsResolve()`, fixed crash
+ when detecting PAC proxy.
+
+* Reverted env var support in `appbase` property. It's causing problems that need to be
+ investigated.
+
+## 1.8.5 - May 29, 2019
+
+* Fixed issues with proxy information not getting properly passed through to app.
+ Via [#216](//github.com/threerings/getdown/pull/216).
+
+* `appbase` and `latest` properties in `getdown.txt` now process env var subtitutions.
+
+* Added support for [Proxy Auto-config](https://en.wikipedia.org/wiki/Proxy_auto-config) via PAC
+ files.
+
+* Proxy handling can now recover from credentials going out of date. It will detect the error and
+ ask for updated credentials.
+
+* Added `try_no_proxy` system property. This instructs Getdown to always first try to run without a
+ proxy, regardless of whether it has been configured to use a proxy in the past. And if it can run
+ without a proxy, it does so for that session, but retains the proxy config for future sessions in
+ which the proxy may again be needed.
+
+* Added `revalidate_policy` config to control when Getdown revalidates resources (by hashing them
+ and comparing that hash to the values in `digest.txt`). The default, `after_update`, only
+ validates resources after the app is updated. A new mode, `always`, validates resources prior to
+ every application launch.
+
+## 1.8.4 - May 14, 2019
+
+* Added `verify_timeout` config to allow customization of the default (60 second) timeout during
+ the resource verification process. Apparently in some pathological situations, this is needed.
+ Woe betide the users who have to stare at an unmoving progress bar for more than 60 seconds.
+ Via [#198](//github.com/threerings/getdown/pull/198)
+ and [901682d](//github.com/threerings/getdown/commit/901682d).
+
+* Added `java_local_dir` config to allow custom location for Java if `java_location` is specified.
+ Via [#206](//github.com/threerings/getdown/pull/206).
+
+* `messages_XX.properties` files are now all maintained in UTF-8 encoding and then converted to
+ escaped ISO-8859-1 during the build process.
+
+* Resources and unpacked resources now support `.zip` files as well as `.jar` files.
+ Via [#210](//github.com/threerings/getdown/pull/210).
+
+* Fixed issue when path to JVM contained spaces. Via [#214](//github.com/threerings/getdown/pull/214).
+
+## 1.8.3 - Apr 10, 2019
* Added support for `nresource` resources which must be jar files that contain native libraries.
Prior to launching the application, these resources will be unpacked and their contents added to
the `java.library.path` system property.
* When the app is updated to require a new version of the JVM, that JVM will be downloaded and used
- immediately during that app invocation (instead of one invocation later). Via PR#169.
+ immediately during that app invocation (instead of one invocation later).
+ Via [#169](//github.com/threerings/getdown/pull/169).
-* When a custom JVM is installed, old JVM files will be deleted prior to unpacking the new JVM. Via
- PR#170.
+* When a custom JVM is installed, old JVM files will be deleted prior to unpacking the new JVM.
+ Via [#170](//github.com/threerings/getdown/pull/170).
* Number of concurrent downloads now defaults to num-cores minus one. Though downloads are I/O
bound rather than CPU bound, this still turns out to be a decent default.
@@ -23,6 +80,14 @@
credentials supplied by the user. Otherwise they will be requested every time Getdown runs, which
is not a viable user experience.
+* The Getdown window can be now closed by pressing the `ESC` key.
+ Via [#191](//github.com/threerings/getdown/pull/191).
+
+* If no `appdir` is specified via the command line or system property, the current working
+ directory will be used as the `appdir`. Via [8d59367](//github.com/threerings/getdown/commit/8d59367)
+
+* A basic Russian translation has been added. Thanks [@sergiorussia](//github.com/sergiorussia)!
+
## 1.8.2 - Nov 27, 2018
* Fixed a data corruption bug introduced at last minute into 1.8.1 release. Oops.
diff --git a/getdown/src/getdown/ant/.project-MOVED b/getdown/src/getdown/ant/.project-MOVED
deleted file mode 100644
index 097cb89db..000000000
--- a/getdown/src/getdown/ant/.project-MOVED
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
- getdown-ant
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.m2e.core.maven2Builder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- org.eclipse.m2e.core.maven2Nature
-
-
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index e9441bb12..000000000
--- a/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/main/java=UTF-8
-encoding/=UTF-8
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 54e56721d..000000000
--- a/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs
deleted file mode 100644
index f897a7f1c..000000000
--- a/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-activeProfiles=
-eclipse.preferences.version=1
-resolveWorkspaceProjects=true
-version=1
diff --git a/getdown/src/getdown/ant/pom.xml b/getdown/src/getdown/ant/pom.xml
index f8231aa2e..a72d95d87 100644
--- a/getdown/src/getdown/ant/pom.xml
+++ b/getdown/src/getdown/ant/pom.xml
@@ -4,7 +4,7 @@
com.threerings.getdowngetdown
- 1.8.3-SNAPSHOT
+ 1.8.7-SNAPSHOTgetdown-ant
diff --git a/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java
index 48cc8d426..76212ae89 100644
--- a/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java
+++ b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java
@@ -7,16 +7,13 @@ package com.threerings.getdown.tools;
import java.io.File;
import java.io.IOException;
-
import java.security.GeneralSecurityException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
-import com.threerings.getdown.data.Digest;
-
/**
- * An ant task used to create a digest.txt for a Getdown
+ * An ant task used to create a {@code digest.txt} for a Getdown
* application deployment.
*/
public class DigesterTask extends Task
diff --git a/getdown/src/getdown/core/.project-MOVED b/getdown/src/getdown/core/.project-MOVED
deleted file mode 100644
index 177252f5f..000000000
--- a/getdown/src/getdown/core/.project-MOVED
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
- getdown-core
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.m2e.core.maven2Builder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- org.eclipse.m2e.core.maven2Nature
-
-
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs
deleted file mode 100644
index 0a9bbb889..000000000
--- a/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-eclipse.preferences.version=1
-encoding//src/it/java=UTF-8
-encoding//src/main/java=UTF-8
-encoding//src/test/java=UTF-8
-encoding//src/test/resources=UTF-8
-encoding/=UTF-8
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index 54e56721d..000000000
--- a/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,6 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
-org.eclipse.jdt.core.compiler.compliance=1.7
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=1.7
diff --git a/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs
deleted file mode 100644
index f897a7f1c..000000000
--- a/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs
+++ /dev/null
@@ -1,4 +0,0 @@
-activeProfiles=
-eclipse.preferences.version=1
-resolveWorkspaceProjects=true
-version=1
diff --git a/getdown/src/getdown/core/pom.xml b/getdown/src/getdown/core/pom.xml
index efec8b6ce..5e0f852ac 100644
--- a/getdown/src/getdown/core/pom.xml
+++ b/getdown/src/getdown/core/pom.xml
@@ -4,7 +4,7 @@
com.threerings.getdowngetdown
- 1.8.3-SNAPSHOT
+ 1.8.7-SNAPSHOTgetdown-core
@@ -34,7 +34,7 @@
Wildcards can be used (*.mycompany.com) and multiple values can be
separated by commas (app1.foo.com,app2.bar.com,app3.baz.com). -->
- jalview.org,*.jalview.org
+
diff --git a/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java
index 52b4b5ee3..d2ddaf272 100644
--- a/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java
+++ b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java
@@ -5,16 +5,16 @@
package com.threerings.getdown.tests;
-import java.io.File;
import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
-import org.junit.*;
-import static org.junit.Assert.*;
-
import com.threerings.getdown.tools.Digester;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
public class DigesterIT {
@@ -32,23 +32,23 @@ public class DigesterIT {
Files.delete(digest2);
assertEquals(Arrays.asList(
- "getdown.txt = 779c74fb4b251e18faf6e240a0667964",
+ "getdown.txt = 9c9b2494929c99d44ae51034d59e1a1b",
"testapp.jar = 404dafa55e78b25ec0e3a936357b1883",
"funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249",
"crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16",
"foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec",
"script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf",
- "digest.txt = f5561d85e4d80cc85883963897e58ff6"
+ "digest.txt = 11f9ba349cf9edacac4d72a3158447e5"
), digestLines);
assertEquals(Arrays.asList(
- "getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b",
+ "getdown.txt = 1efecfae2a189002a6658f17d162b1922c7bde978944949276dc038a0df2461f",
"testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0",
"funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
"crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b",
"foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062",
"script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988",
- "digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407"
+ "digest2.txt = 41eacdabda8909bdbbf61e4f980867f4003c16a12f6770e6fc619b6af100e05b"
), digest2Lines);
}
}
diff --git a/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt
index 3e0e5381a..ab0e47383 100644
--- a/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt
+++ b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt
@@ -13,6 +13,18 @@ apparg = %APPDIR%
# test the %env% mechanism
jvmarg = -Dusername=\%ENV.USER%
+# test various java_*** configs, they are not interesting for digester
+java_local_dir = jre
+java_max_version = 1089999
+java_min_version = [windows] 1080111
+java_min_version = [!windows] 1080192
+java_exact_version_required = [linux] true
+java_location = [linux-amd64] /files/java/java_linux_64.zip
+java_location = [linux-i386] /files/java/java_linux_32.zip
+java_location = [mac] /files/java/java_mac_64.zip
+java_location = [windows-amd64] /files/java/java_windows_64.zip
+java_location = [windows-x86] /files/java/java_windows_32.zip
+
strict_comments = true
resource = funny%test dir/some=file.txt
resource = crazyhashfile#txt
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java
index 13b99564a..da98c9031 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java
@@ -15,7 +15,7 @@ import java.util.logging.*;
/**
* A placeholder class that contains a reference to the log object used by the Getdown code.
*/
-public class Log
+public final class Log
{
public static class Shim {
/**
@@ -69,6 +69,13 @@ public class Log
/** We dispatch our log messages through this logging shim. */
public static final Shim log = new Shim();
+ /**
+ * Formats a message with key/value pairs. The pairs will be appended to the message as a
+ * comma separated list of {@code key=value} in square brackets.
+ * @param message the main log message.
+ * @param args the key/value pairs. Any trailing key with no value will be ignored.
+ * @return the formatted message, i.e. {@code Some log message [key=value, key=value]}.
+ */
public static String format (Object message, Object... args) {
if (args.length < 2) return String.valueOf(message);
StringBuilder buf = new StringBuilder(String.valueOf(message));
@@ -76,13 +83,13 @@ public class Log
buf.append(' ');
}
buf.append('[');
- for (int ii = 0; ii < args.length; ii += 2) {
+ for (int ii = 0, ll = args.length/2; ii < ll; ii += 1) {
if (ii > 0) {
buf.append(',').append(' ');
}
- buf.append(args[ii]).append('=');
+ buf.append(args[2*ii]).append('=');
try {
- buf.append(args[ii+1]);
+ buf.append(args[2*ii+1]);
} catch (Throwable t) {
buf.append("");
}
@@ -136,6 +143,5 @@ public class Log
protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD);
}
- protected static final String DATE_FORMAT = "{0,date} {0,time}";
protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE};
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java
index 67ea64575..7e01e87c5 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java
@@ -6,6 +6,8 @@
package com.threerings.getdown.cache;
import java.io.File;
+
+import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.FileUtil;
/**
@@ -55,9 +57,9 @@ public class GarbageCollector
if (subdirs != null) {
for (File dir : subdirs) {
if (dir.isDirectory()) {
- // Get all the native jars in the directory (there should only be one)
+ // Get all the native jars or zips in the directory (there should only be one)
for (File file : dir.listFiles()) {
- if (!file.getName().endsWith(".jar")) {
+ if (!Resource.isJar(file) && !Resource.isZip(file)) {
continue;
}
File cachedFile = getCachedFile(file);
@@ -94,6 +96,6 @@ public class GarbageCollector
private static File getCachedFile (File file)
{
return !isLastAccessedFile(file) ? file : new File(
- file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf(".")));
+ file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf('.')));
}
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java
index 0210e9a86..41f0c5f6d 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java
@@ -69,7 +69,7 @@ public class ResourceCache
private String getFileSuffix (File fileToCache) {
String fileName = fileToCache.getName();
- int index = fileName.lastIndexOf(".");
+ int index = fileName.lastIndexOf('.');
return index > -1 ? fileName.substring(index) : "";
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
index 0de5c8ac8..a93122553 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
@@ -22,24 +22,17 @@ import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.zip.GZIPInputStream;
-import com.sun.management.OperatingSystemMXBean;
-import java.lang.management.ManagementFactory;
-
-import jalview.bin.MemorySetting;
+import com.threerings.getdown.net.Connector;
import com.threerings.getdown.util.*;
// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
import com.threerings.getdown.util.Base64;
-import com.threerings.getdown.data.EnvConfig;
-import com.threerings.getdown.data.EnvConfig.Note;
-
import static com.threerings.getdown.Log.log;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
- * Parses and provide access to the information contained in the getdown.txt
+ * Parses and provide access to the information contained in the {@code getdown.txt}
* configuration file.
*/
public class Application
@@ -218,10 +211,10 @@ public class Application
* Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to
* take network actions.
*/
- public static interface StatusDisplay
+ public interface StatusDisplay
{
/** Requests that the specified status message be displayed. */
- public void updateStatus (String message);
+ void updateStatus (String message);
}
/**
@@ -239,19 +232,56 @@ public class Application
}
}
- /** The proxy that should be used to do HTTP downloads. This must be configured prior to using
- * the application instance. Yes this is a public mutable field, no I'm not going to create a
+ /**
+ * Reads the {@code getdown.txt} config file into a {@code Config} object and returns it.
+ */
+ public static Config readConfig (EnvConfig envc, boolean checkPlatform) throws IOException {
+ Config config = null;
+ File cfgfile = new File(envc.appDir, CONFIG_FILE);
+ Config.ParseOpts opts = Config.createOpts(checkPlatform);
+ try {
+ // if we have a configuration file, read the data from it
+ if (cfgfile.exists()) {
+ config = Config.parseConfig(cfgfile, opts);
+ }
+ // otherwise, try reading data from our backup config file; thanks to funny windows
+ // bullshit, we have to do this backup file fiddling in case we got screwed while
+ // updating getdown.txt during normal operation
+ else if ((cfgfile = new File(envc.appDir, Application.CONFIG_FILE + "_old")).exists()) {
+ config = Config.parseConfig(cfgfile, opts);
+ }
+ // otherwise, issue a warning that we found no getdown file
+ else {
+ log.info("Found no getdown.txt file", "appdir", envc.appDir);
+ }
+ } catch (Exception e) {
+ log.warning("Failure reading config file", "file", config, e);
+ }
+
+ // 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) {
+ log.info("Using 'appbase' from bootstrap config", "appbase", envc.appBase);
+ Map cdata = new HashMap<>();
+ cdata.put("appbase", envc.appBase);
+ config = new Config(cdata);
+ }
+
+ return config;
+ }
+
+ /** A helper that is used to do HTTP downloads. This must be configured prior to using the
+ * application instance. Yes this is a public mutable field, no I'm not going to create a
* getter and setter just to pretend like that's not the case. */
- public Proxy proxy = Proxy.NO_PROXY;
+ public Connector conn = Connector.DEFAULT;
/**
- * Creates an application instance which records the location of the getdown.txt
+ * Creates an application instance which records the location of the {@code getdown.txt}
* configuration file from the supplied application directory.
*
*/
public Application (EnvConfig envc) {
_envc = envc;
- _config = getLocalPath(envc.appDir, CONFIG_FILE);
}
/**
@@ -375,8 +405,7 @@ public class Application
*/
public List getActiveCodeResources ()
{
- ArrayList codes = new ArrayList<>();
- codes.addAll(getCodeResources());
+ List codes = new ArrayList<>(getCodeResources());
for (AuxGroup aux : getAuxGroups()) {
if (isAuxGroupActive(aux.name)) {
codes.addAll(aux.codes);
@@ -404,8 +433,7 @@ public class Application
*/
public List getActiveResources ()
{
- ArrayList rsrcs = new ArrayList<>();
- rsrcs.addAll(getResources());
+ List rsrcs = new ArrayList<>(getResources());
for (AuxGroup aux : getAuxGroups()) {
if (isAuxGroupActive(aux.name)) {
rsrcs.addAll(aux.rsrcs);
@@ -442,7 +470,15 @@ public class Application
}
/**
- * Returns a resource for a zip file containing a Java VM that can be downloaded to use in
+ * @return directory into which a local VM installation should be unpacked.
+ */
+ public File getJavaLocalDir ()
+ {
+ return _javaLocalDir;
+ }
+
+ /**
+ * @return 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.
*/
@@ -452,21 +488,16 @@ public class Application
return null;
}
- String extension = (_javaLocation.endsWith(".tgz"))?".tgz":".jar";
- String vmfile = LaunchUtil.LOCAL_JAVA_DIR + extension;
- log.info("vmfile is '"+vmfile+"'");
- System.out.println("vmfile is '"+vmfile+"'");
+ // take extension from java location
+ String vmfileExt = _javaLocation.substring(_javaLocation.lastIndexOf('.'));
+ String vmfile = _javaLocalDir.getName() + vmfileExt;
try {
URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation));
- log.info("Attempting to fetch jvm at "+remote.toString());
- System.out.println("Attempting to fetch jvm at "+remote.toString());
return new Resource(vmfile, remote, getLocalPath(vmfile),
EnumSet.of(Resource.Attr.UNPACK, Resource.Attr.CLEAN));
} catch (Exception e) {
log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase,
"tvers", _targetVersion, "javaloc", _javaLocation, "error", e);
- System.out.println("Failed to create VM resource: vmfile="+vmfile+", appbase="+_appbase+
- ", tvers="+_targetVersion+", javaloc="+_javaLocation+", error="+e);
return null;
}
}
@@ -547,88 +578,52 @@ public class Application
* @exception IOException thrown if there is an error reading the file or an error encountered
* during its parsing.
*/
- public Config init (boolean checkPlatform)
- throws IOException
+ public Config init (boolean checkPlatform) throws IOException
{
- Config config = null;
- File cfgfile = _config;
- Config.ParseOpts opts = Config.createOpts(checkPlatform);
- try {
- // if we have a configuration file, read the data from it
- if (cfgfile.exists()) {
- config = Config.parseConfig(_config, opts);
- }
- // otherwise, try reading data from our backup config file; thanks to funny windows
- // bullshit, we have to do this backup file fiddling in case we got screwed while
- // updating getdown.txt during normal operation
- else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) {
- config = Config.parseConfig(cfgfile, opts);
- }
- // otherwise, issue a warning that we found no getdown file
- else {
- log.info("Found no getdown.txt file", "appdir", getAppDir());
- }
- } catch (Exception e) {
- log.warning("Failure reading config file", "file", _config, e);
- }
-
- // see if there's an override config from locator file
- Config locatorConfig = createLocatorConfig(opts);
-
- // merge the locator file config into config (or replace config with)
- if (locatorConfig != null) {
- if (config == null || locatorConfig.getBoolean(LOCATOR_FILE_EXTENSION+"_replace")) {
- config = locatorConfig;
- } else {
- config.mergeConfig(locatorConfig, locatorConfig.getBoolean(LOCATOR_FILE_EXTENSION+"_merge"));
- }
- }
+ Config config = readConfig(_envc, checkPlatform);
+ initBase(config);
+ initJava(config);
+ initTracking(config);
+ initResources(config);
+ initArgs(config);
+ return config;
+ }
- // 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 = _envc.appBase;
- log.info("Using 'appbase' from bootstrap config", "appbase", appbase);
- Map cdata = new HashMap<>();
- cdata.put("appbase", appbase);
- config = new Config(cdata);
- }
+ /**
+ * Reads the basic config info from {@code config} into this instance. This includes things
+ * like the appbase and version.
+ */
+ public void initBase (Config config) throws IOException {
+ // first extract our version information
+ _version = config.getLong("version", -1L);
- // first determine our application base, this way if anything goes wrong later in the
+ // 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 = config.getString("appbase");
-
- // see if locatorConfig override
- if (locatorConfig != null && !StringUtil.isBlank(locatorConfig.getString("appbase"))) {
- _appbase = locatorConfig.getString("appbase");
- }
-
if (_appbase == null) {
throw new RuntimeException("m.missing_appbase");
}
- // check if we're overriding the domain in the appbase
- _appbase = SysProps.overrideAppbase(_appbase);
+ // check if we're overriding the domain in the appbase, and sub envvars
+ _appbase = resolveEnvVars(SysProps.overrideAppbase(_appbase));
// make sure there's a trailing slash
if (!_appbase.endsWith("/")) {
- _appbase = _appbase + "/";
+ _appbase += "/";
}
- // extract our version information
- _version = config.getLong("version", -1L);
-
// if we are a versioned deployment, create a versioned appbase
try {
_vappbase = createVAppBase(_version);
} catch (MalformedURLException mue) {
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
- throw (IOException) new IOException(err).initCause(mue);
+ throw new IOException(err, mue);
}
// check for a latest config URL
String latest = config.getString("latest");
if (latest != null) {
+ latest = processArg(latest);
if (latest.startsWith(_appbase)) {
latest = _appbase + latest.substring(_appbase.length());
} else {
@@ -641,20 +636,25 @@ public class Application
}
}
- String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
-
- // determine our application class name (use app-specific class _if_ one is provided)
- _class = config.getString("class");
- if (appPrefix.length() > 0) {
- _class = config.getString(appPrefix + "class", _class);
- }
- if (_class == null) {
- throw new IOException("m.missing_class");
- }
-
- // determine whether we want strict comments
+ // read some miscellaneous configurations
_strictComments = config.getBoolean("strict_comments");
+ _allowOffline = config.getBoolean("allow_offline");
+ _revalidatePolicy = config.getEnum(
+ "revalidate_policy", RevalidatePolicy.class, RevalidatePolicy.AFTER_UPDATE);
+ int tpSize = SysProps.threadPoolSize();
+ _maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads", tpSize));
+ _verifyTimeout = config.getInt("verify_timeout", 60);
+
+ // whether to cache code resources and launch from cache
+ _useCodeCache = config.getBoolean("use_code_cache");
+ _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
+ }
+ /**
+ * Reads the JVM requirements from {@code config} into this instance. This includes things like
+ * the min and max java version, location of a locally installed JRE, etc.
+ */
+ public void initJava (Config config) {
// check to see if we're using a custom java.version property and regex
_javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
_javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
@@ -669,14 +669,16 @@ public class Application
// check to see if we require a particular JVM version and have a supplied JVM
_javaExactVersionRequired = config.getBoolean("java_exact_version_required");
- // this is a little weird, but when we're run from the digester, we see a String[] which
- // contains java locations for all platforms which we can't grok, but the digester doesn't
- // need to know about that; when we're run in a real application there will be only one!
- Object javaloc = config.getRaw("java_location");
- if (javaloc instanceof String) {
- _javaLocation = (String)javaloc;
- }
+ _javaLocation = config.getString("java_location");
+
+ // used only in conjunction with java_location
+ _javaLocalDir = getLocalPath(config.getString("java_local_dir", LaunchUtil.LOCAL_JAVA_DIR));
+ }
+ /**
+ * Reads the install tracking info from {@code config} into this instance.
+ */
+ public void initTracking (Config config) {
// determine whether we have any tracking configuration
_trackingURL = config.getString("tracking_url");
@@ -701,14 +703,16 @@ public class Application
// Some app may need to generate google analytics code
_trackingGAHash = config.getString("tracking_ga_hash");
+ }
+ /**
+ * Reads the app resource info from {@code config} into this instance.
+ */
+ public void initResources (Config config) throws IOException {
// clear our arrays as we may be reinitializing
_codes.clear();
_resources.clear();
_auxgroups.clear();
- _jvmargs.clear();
- _appargs.clear();
- _txtJvmArgs.clear();
// parse our code resources
if (config.getMultiValue("code") == null &&
@@ -727,10 +731,10 @@ public class Application
// parse our auxiliary resource groups
for (String auxgroup : config.getList("auxgroups")) {
- ArrayList codes = new ArrayList<>();
+ List codes = new ArrayList<>();
parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
- ArrayList rsrcs = new ArrayList<>();
+ List rsrcs = new ArrayList<>();
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
@@ -738,87 +742,48 @@ public class Application
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
}
+ }
- // transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
- String[] jvmargs = config.getMultiValue("jvmarg");
- addAll(jvmargs, _jvmargs);
+ /**
+ * Reads the command line arg info from {@code config} into this instance.
+ */
+ public void initArgs (Config config) throws IOException {
+ _jvmargs.clear();
+ _appargs.clear();
+ _txtJvmArgs.clear();
+
+ String appPrefix = _envc.appId == null ? "" : (_envc.appId + ".");
+
+ // determine our application class name (use app-specific class _if_ one is provided)
+ _class = config.getString("class");
if (appPrefix.length() > 0) {
- jvmargs = config.getMultiValue(appPrefix + "jvmarg");
- addAll(jvmargs, _jvmargs);
+ _class = config.getString(appPrefix + "class", _class);
+ }
+ if (_class == null) {
+ throw new IOException("m.missing_class");
}
- // see if a percentage of physical memory option exists
- int jvmmempc = config.getInt("jvmmempc", -1);
- // app_id prefixed setting overrides
+ // transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
+ addAll(config.getMultiValue("jvmarg"), _jvmargs);
if (appPrefix.length() > 0) {
- jvmmempc = config.getInt(appPrefix + "jvmmempc", jvmmempc);
- }
- if (0 <= jvmmempc && jvmmempc <= 100) {
-
- long maxMemLong = -1;
-
- try
- {
- maxMemLong = MemorySetting.memPercent(jvmmempc);
- } catch (Exception e)
- {
- e.printStackTrace();
- } catch (Throwable t)
- {
- t.printStackTrace();
- }
-
- if (maxMemLong > 0)
- {
-
- String[] maxMemHeapArg = new String[]{"-Xmx"+Long.toString(maxMemLong)};
- // remove other max heap size arg
- ARG: for (int i = 0; i < _jvmargs.size(); i++) {
- if (_jvmargs.get(i) instanceof java.lang.String && _jvmargs.get(i).startsWith("-Xmx")) {
- _jvmargs.remove(i);
- }
- }
- addAll(maxMemHeapArg, _jvmargs);
-
- }
-
- } else if (jvmmempc != -1) {
- System.out.println("'jvmmempc' value must be in range 0 to 100 (read as '"+Integer.toString(jvmmempc)+"')");
+ addAll(config.getMultiValue(appPrefix + "jvmarg"), _jvmargs);
}
// get the set of optimum JVM arguments
_optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
// transfer our application arguments
- String[] appargs = config.getMultiValue(appPrefix + "apparg");
- addAll(appargs, _appargs);
+ addAll(config.getMultiValue(appPrefix + "apparg"), _appargs);
// add the launch specific application arguments
_appargs.addAll(_envc.appArgs);
-
+
// look for custom arguments
fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
- // determine whether we want to allow offline operation (defaults to false)
- _allowOffline = config.getBoolean("allow_offline");
-
- // 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();
-
- // whether to cache code resources and launch from cache
- _useCodeCache = config.getBoolean("use_code_cache");
- _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
-
- // maximum simultaneous downloads
- _maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads",
- SysProps.threadPoolSize()));
-
// extract some info used to configure our child process on macOS
_dockName = config.getString("ui.name");
_dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns");
-
- return config;
}
/**
@@ -847,8 +812,7 @@ 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.
*/
- public URL getRemoteURL (String path)
- throws MalformedURLException
+ public URL getRemoteURL (String path) throws MalformedURLException
{
return new URL(_vappbase, encodePath(path));
}
@@ -858,7 +822,7 @@ public class Application
*/
public File getLocalPath (String path)
{
- return getLocalPath(getAppDir(), path);
+ return new File(getAppDir(), path);
}
/**
@@ -881,8 +845,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(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR);
- File relfile = new File(vmdir, "release");
+ File relfile = new File(_javaLocalDir, "release");
if (!relfile.exists()) {
log.warning("Unpacked JVM missing 'release' file. Assuming valid version.");
return true;
@@ -939,7 +902,7 @@ public class Application
}
/**
- * Attempts to redownload the getdown.txt file based on information parsed from a
+ * Attempts to redownload the {@code getdown.txt} file based on information parsed from a
* previous call to {@link #init}.
*/
public void attemptRecovery (StatusDisplay status)
@@ -950,7 +913,7 @@ public class Application
}
/**
- * Downloads and replaces the getdown.txt and digest.txt files with
+ * Downloads and replaces the {@code getdown.txt} and {@code digest.txt} files with
* those for the target version of our application.
*/
public void updateMetadata ()
@@ -961,7 +924,7 @@ public class Application
_vappbase = createVAppBase(_targetVersion);
} catch (MalformedURLException mue) {
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
- throw (IOException) new IOException(err).initCause(mue);
+ throw new IOException(err, mue);
}
try {
@@ -1000,7 +963,7 @@ public class Application
ArrayList args = new ArrayList<>();
// reconstruct the path to the JVM
- args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum));
+ args.add(LaunchUtil.getJVMBinaryPath(_javaLocalDir, SysProps.debug() || optimum));
// check whether we're using -jar mode or -classpath mode
boolean dashJarMode = MANIFEST_CLASS.equals(_class);
@@ -1018,14 +981,8 @@ public class Application
args.add("-Xdock:name=" + _dockName);
}
- // pass along our proxy settings
- String proxyHost;
- if ((proxyHost = System.getProperty("http.proxyHost")) != null) {
- args.add("-Dhttp.proxyHost=" + proxyHost);
- args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort"));
- args.add("-Dhttps.proxyHost=" + proxyHost);
- args.add("-Dhttps.proxyPort=" + System.getProperty("http.proxyPort"));
- }
+ // forward our proxy settings
+ conn.addProxyArgs(args);
// add the marker indicating the app is running in getdown
args.add("-D" + Properties.GETDOWN + "=true");
@@ -1071,32 +1028,11 @@ public class Application
args.add(_class);
}
- // almost finally check the startup file arguments
- for (File f : _startupFiles) {
- _appargs.add(f.getAbsolutePath());
- break; // Only add one file to open
- }
-
- // check if one arg with recognised extension
- if ( _appargs.size() == 1 && _appargs.get(0) != null ) {
- String filename = _appargs.get(0);
- String ext = null;
- int j = filename.lastIndexOf('.');
- if (j > -1) {
- ext = filename.substring(j+1);
- }
- if (LOCATOR_FILE_EXTENSION.equals(ext.toLowerCase())) {
- // this file extension should have been dealt with in Getdown class
- } else {
- _appargs.add(0, "-open");
- }
- }
-
// finally add the application arguments
for (String string : _appargs) {
args.add(processArg(string));
}
-
+
String[] envp = createEnvironment();
String[] sargs = args.toArray(new String[args.size()]);
log.info("Running " + StringUtil.join(sargs, "\n "));
@@ -1156,7 +1092,7 @@ public class Application
for (String jvmarg : _jvmargs) {
if (jvmarg.startsWith("-D")) {
jvmarg = processArg(jvmarg.substring(2));
- int eqidx = jvmarg.indexOf("=");
+ int eqidx = jvmarg.indexOf('=');
if (eqidx == -1) {
log.warning("Bogus system property: '" + jvmarg + "'?");
} else {
@@ -1194,32 +1130,36 @@ public class Application
}
}
- /** Replaces the application directory and version in any argument. */
+ /** Replaces the application directory, version and env vars in any argument. */
protected String processArg (String arg)
{
arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
arg = arg.replace("%VERSION%", String.valueOf(_version));
+ arg = resolveEnvVars(arg);
+ return arg;
+ }
- // if this argument contains %ENV.FOO% replace those with the associated values looked up
- // from the environment
- if (arg.contains(ENV_VAR_PREFIX)) {
+ /** Resolves env var substitutions in {@code text}. */
+ protected String resolveEnvVars (String text) {
+ // if the text contains %ENV.FOO% replace it with FOO looked up in the environment
+ if (text.contains(ENV_VAR_PREFIX)) {
StringBuffer sb = new StringBuffer();
- Matcher matcher = ENV_VAR_PATTERN.matcher(arg);
+ Matcher matcher = ENV_VAR_PATTERN.matcher(text);
while (matcher.find()) {
String varName = matcher.group(1), varValue = System.getenv(varName);
String repValue = varValue == null ? "MISSING:"+varName : varValue;
matcher.appendReplacement(sb, Matcher.quoteReplacement(repValue));
}
matcher.appendTail(sb);
- arg = sb.toString();
+ return sb.toString();
+ } else {
+ return text;
}
-
- return arg;
}
/**
- * 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
+ * Loads the {@code digest.txt} file and verifies the contents of both that file and the
+ * {@code getdown.text} file. Then it loads the {@code version.txt} and decides
* whether or not the application needs to be updated or whether we can proceed to verification
* and execution.
*
@@ -1306,11 +1246,11 @@ public class Application
}
if (_latest != null) {
- try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream();
- InputStreamReader reader = new InputStreamReader(in, UTF_8);
- BufferedReader bin = new BufferedReader(reader)) {
- for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
- if (pair[0].equals("version")) {
+ try {
+ List vdata = Config.parsePairs(
+ new StringReader(conn.fetch(_latest)), Config.createOpts(false));
+ for (String[] pair : vdata) {
+ if ("version".equals(pair[0])) {
_targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
if (fileVersion != -1 && _targetVersion > fileVersion) {
// replace the file with the newest version
@@ -1404,7 +1344,10 @@ public class Application
while (completed[0] < rsrcs.size()) {
// we should be getting progress completion updates WAY more often than one every
// minute, so if things freeze up for 60 seconds, abandon ship
- Runnable action = actions.poll(60, TimeUnit.SECONDS);
+ Runnable action = actions.poll(_verifyTimeout, TimeUnit.SECONDS);
+ if (action == null) {
+ throw new IllegalStateException("m.verify_timeout");
+ }
action.run();
}
@@ -1415,14 +1358,14 @@ public class Application
unpacked.addAll(unpackedAsync);
long complete = System.currentTimeMillis();
- log.info("Verified resources", "count", rsrcs.size(), "size", (totalSize/1024) + "k",
- "duration", (complete-start) + "ms");
+ log.info("Verified resources", "count", rsrcs.size(), "alreadyValid", alreadyValid[0],
+ "size", (totalSize/1024) + "k", "duration", (complete-start) + "ms");
}
private void verifyResource (Resource rsrc, ProgressObserver obs, int[] alreadyValid,
Set unpacked,
Set toInstall, Set toDownload) {
- if (rsrc.isMarkedValid()) {
+ if (_revalidatePolicy != RevalidatePolicy.ALWAYS && rsrc.isMarkedValid()) {
if (alreadyValid != null) {
alreadyValid[0]++;
}
@@ -1513,7 +1456,7 @@ public class Application
protected URL createVAppBase (long version)
throws MalformedURLException
{
- String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version);
+ String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", String.valueOf(version));
return HostWhitelist.verify(new URL(url));
}
@@ -1530,8 +1473,7 @@ public class Application
/**
* Downloads a new copy of CONFIG_FILE.
*/
- protected void downloadConfigFile ()
- throws IOException
+ protected void downloadConfigFile () throws IOException
{
downloadControlFile(CONFIG_FILE, 0);
}
@@ -1673,8 +1615,7 @@ public class Application
* Download a path to a temporary file, returning a {@link File} instance with the path
* contents.
*/
- protected File downloadFile (String path)
- throws IOException
+ protected File downloadFile (String path) throws IOException
{
File target = getLocalPath(path + "_new");
@@ -1684,30 +1625,11 @@ public class Application
} catch (Exception e) {
log.warning("Requested to download invalid control file",
"appbase", _vappbase, "path", path, "error", e);
- throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e);
+ throw new IOException("Invalid path '" + path + "'.", e);
}
log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
-
- // stream the URL into our temporary file
- URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0);
- // we have to tell Java not to use caches here, otherwise it will cache any request for
- // same URL for the lifetime of this JVM (based on the URL string, not the URL object);
- // if the getdown.txt file, for example, changes in the meanwhile, we would never hear
- // about it; turning off caches is not a performance concern, because when Getdown asks
- // to download a file, it expects it to come over the wire, not from a cache
- uconn.setUseCaches(false);
- uconn.setRequestProperty("Accept-Encoding", "gzip");
- try (InputStream fin = uconn.getInputStream()) {
- String encoding = uconn.getContentEncoding();
- boolean gzip = "gzip".equalsIgnoreCase(encoding);
- try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
- try (FileOutputStream fout = new FileOutputStream(target)) {
- StreamUtil.copy(fin2, fout);
- }
- }
- }
-
+ conn.download(targetURL, target); // stream the URL into our temporary file
return target;
}
@@ -1721,9 +1643,7 @@ public class Application
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
protected static void addAll (String[] values, List target) {
if (values != null) {
- for (String value : values) {
- target.add(value);
- }
+ Collections.addAll(target, values);
}
}
@@ -1805,96 +1725,7 @@ public class Application
}
}
- protected File getLocalPath (File appdir, String path)
- {
- return new File(appdir, path);
- }
-
- public static void setStartupFilesFromParameterString(String p) {
- // multiple files *might* be passed in as space separated quoted filenames
- String q = "\"";
- if (!StringUtil.isBlank(p)) {
- String[] filenames;
- // split quoted params or treat as single string array
- if (p.startsWith(q) && p.endsWith(q)) {
- // this fails if, e.g.
- // p=q("stupidfilename\" " "otherfilename")
- // let's hope no-one ever ends a filename with '" '
- filenames = p.substring(q.length(),p.length()-q.length()).split(q+" "+q);
- } else {
- // single unquoted filename
- filenames = new String[]{p};
- }
-
- // check for locator file. Only allow one locator file to be double clicked (if multiple files opened, ignore locator files)
- String locatorFilename = filenames.length >= 1 ? filenames[0] : null;
- if (
- !StringUtil.isBlank(locatorFilename)
- && locatorFilename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)
- ) {
- setLocatorFile(locatorFilename);
- // remove the locator filename from the filenames array
- String[] otherFilenames = new String[filenames.length - 1];
- System.arraycopy(filenames, 1, otherFilenames, 0, otherFilenames.length);
- filenames = otherFilenames;
- }
-
- for (int i = 0; i < filenames.length; i++) {
- String filename = filenames[i];
- // skip any other locator files in a multiple file list
- if (! filename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)) {
- addStartupFile(filename);
- }
- }
- }
- }
-
- public static void setLocatorFile(String filename) {
- _locatorFile = new File(filename);
- }
-
- public static void addStartupFile(String filename) {
- _startupFiles.add(new File(filename));
- }
-
- private Config createLocatorConfig(Config.ParseOpts opts) {
- if (_locatorFile == null) {
- return null;
- }
-
- Config locatorConfig = null;
-
- try {
- Config tmpConfig = null;
- if (_locatorFile.exists()) {
- tmpConfig = Config.parseConfig(_locatorFile, opts);
- } else {
- log.warning("Given locator file does not exist", "file", _locatorFile);
- }
-
- // appbase is sanitised in HostWhitelist
- Map tmpData = new HashMap<>();
- for (Map.Entry entry : tmpConfig.getData().entrySet()) {
- String key = entry.getKey();
- Object value = entry.getValue();
- String mkey = key.indexOf('.') > -1 ? key.substring(key.indexOf('.') + 1) : key;
- if (Config.allowedReplaceKeys.contains(mkey) || Config.allowedMergeKeys.contains(mkey)) {
- tmpData.put(key, value);
- }
- }
- locatorConfig = new Config(tmpData);
-
- } catch (Exception e) {
- log.warning("Failure reading locator file", "file", _locatorFile, e);
- }
-
- log.info("Returning locatorConfig", locatorConfig);
-
- return locatorConfig;
- }
-
protected final EnvConfig _envc;
- protected File _config;
protected Digest _digest;
protected long _version = -1;
@@ -1906,7 +1737,6 @@ public class Application
protected String _dockName;
protected String _dockIconPath;
protected boolean _strictComments;
- protected boolean _windebug;
protected boolean _allowOffline;
protected int _maxConcDownloads;
@@ -1924,10 +1754,14 @@ public class Application
protected long _javaMinVersion, _javaMaxVersion;
protected boolean _javaExactVersionRequired;
protected String _javaLocation;
+ protected File _javaLocalDir;
protected List _codes = new ArrayList<>();
protected List _resources = new ArrayList<>();
+ protected int _verifyTimeout = 60;
+
+ protected RevalidatePolicy _revalidatePolicy = RevalidatePolicy.AFTER_UPDATE;
protected boolean _useCodeCache;
protected int _codeCacheRetentionDays;
@@ -1941,9 +1775,6 @@ public class Application
protected List _txtJvmArgs = new ArrayList<>();
- /** If a warning has been issued about not being able to set modtimes. */
- protected boolean _warnedAboutSetLastModified;
-
/** Locks gettingdown.lock in the app dir. Held the entire time updating is going on.*/
protected FileLock _lock;
@@ -1956,8 +1787,6 @@ public class Application
protected static final String ENV_VAR_PREFIX = "%ENV.";
protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%");
-
- protected static File _locatorFile;
- protected static List _startupFiles = new ArrayList<>();
- public static final String LOCATOR_FILE_EXTENSION = "jvl";
+
+ protected static enum RevalidatePolicy { ALWAYS, AFTER_UPDATE }
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java
index bc8d14052..e310a52a2 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java
@@ -21,7 +21,7 @@ import static com.threerings.getdown.Log.log;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
- * Manages the digest.txt file and the computing and processing of digests for an
+ * Manages the {@code digest.txt} file and the computing and processing of digests for an
* application.
*/
public class Digest
@@ -72,8 +72,7 @@ public class Digest
digests.put(rsrc, rsrc.computeDigest(fversion, md, null));
completed.add(rsrc);
} catch (Throwable t) {
- completed.add(new IOException("Error computing digest for: " + rsrc).
- initCause(t));
+ completed.add(new IOException("Error computing digest for: " + rsrc, t));
}
}
});
@@ -88,7 +87,7 @@ public class Digest
if (done instanceof IOException) {
throw (IOException)done;
} else if (done instanceof Resource) {
- pending.remove((Resource)done);
+ pending.remove(done);
} else {
throw new AssertionError("What is this? " + done);
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
index 57b8d8493..a14b02c63 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
@@ -7,22 +7,19 @@ package com.threerings.getdown.data;
import java.io.File;
import java.io.FileInputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import com.threerings.getdown.util.StringUtil;
-import com.threerings.getdown.data.Application;
/** 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 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); }
@@ -141,23 +138,11 @@ public final class EnvConfig {
appIdProv + "'"));
}
}
-
- int skipArgs = 2;
- // Look for locator file, pass to Application and remove from appArgs
- String argvLocatorFilename = argv.length > 2 ? argv[2] : null;
- if (
- !StringUtil.isBlank(argvLocatorFilename)
- && argvLocatorFilename.toLowerCase().endsWith("."+Application.LOCATOR_FILE_EXTENSION)
- ) {
- notes.add(Note.info("locatorFilename in args: '"+argv[2]+"'"));
- Application.setLocatorFile(argvLocatorFilename);
-
- skipArgs++;
- }
- // ensure that we were able to find an app dir
+ // if no appdir was provided, default to the current working directory
if (appDir == null) {
- return null; // caller will report problem to user
+ appDir = System.getProperty("user.dir");
+ appDirProv = "default (cwd)";
}
notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir));
@@ -187,9 +172,9 @@ public final class EnvConfig {
return null;
}
- // pass along anything after the first two (or three) args as extra app args
- List appArgs = argv.length > skipArgs ?
- Arrays.asList(argv).subList(skipArgs, argv.length) :
+ // pass along anything after the first two args as extra app args
+ List appArgs = argv.length > 2 ?
+ Arrays.asList(argv).subList(2, argv.length) :
Collections.emptyList();
// load X.509 certificate if it exists
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java
index b0a1dc920..57e9275be 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java
@@ -10,7 +10,7 @@ import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.jar.JarFile;
+import java.util.zip.ZipFile;
import com.threerings.getdown.cache.GarbageCollector;
import com.threerings.getdown.cache.ResourceCache;
@@ -112,7 +112,7 @@ public class PathBuilder
if (!unpackedIndicator.exists()) {
try {
- FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false);
+ FileUtil.unpackJar(new ZipFile(cachedFile), cachedParent, false);
unpackedIndicator.createNewFile();
} catch (IOException ioe) {
log.warning("Failed to unpack native jar",
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java
index adc2d4f21..d1ccba3ba 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java
@@ -7,25 +7,14 @@ package com.threerings.getdown.data;
import java.io.*;
import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.security.MessageDigest;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-import org.apache.commons.compress.archivers.ArchiveInputStream;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.ProgressObserver;
import com.threerings.getdown.util.StringUtil;
-
import static com.threerings.getdown.Log.log;
/**
@@ -34,7 +23,7 @@ import static com.threerings.getdown.Log.log;
public class Resource implements Comparable
{
/** Defines special attributes for resources. */
- public static enum Attr {
+ public enum Attr {
/** Indicates that the resource should be unpacked. */
UNPACK,
/** If present, when unpacking a resource, any directories created by the newly
@@ -46,7 +35,7 @@ public class Resource implements Comparable
PRELOAD,
/** Indicates that the resource is a jar containing native libs. */
NATIVE
- };
+ }
public static final EnumSet NORMAL = EnumSet.noneOf(Attr.class);
public static final EnumSet UNPACK = EnumSet.of(Attr.UNPACK);
@@ -66,29 +55,30 @@ public class Resource implements Comparable
byte[] buffer = new byte[DIGEST_BUFFER_SIZE];
int read;
- boolean isJar = isJar(target.getPath());
- boolean isPacked200Jar = isPacked200Jar(target.getPath());
+ boolean isZip = isJar(target) || isZip(target); // jar is a zip too
+ boolean isPacked200Jar = isPacked200Jar(target);
// if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic
// manner to properly correlate jardiff patched jars with their unpatched originals
- if (isJar || isPacked200Jar){
+ if (isPacked200Jar || isZip){
File tmpJarFile = null;
- JarFile jar = null;
+ ZipFile zip = null;
try {
- // if this is a compressed jar file, uncompress it to compute the jar file digest
+ // if this is a compressed zip file, uncompress it to compute the zip file digest
if (isPacked200Jar){
tmpJarFile = new File(target.getPath() + ".tmp");
+ tmpJarFile.deleteOnExit();
FileUtil.unpackPacked200Jar(target, tmpJarFile);
- jar = new JarFile(tmpJarFile);
+ zip = new ZipFile(tmpJarFile);
} else{
- jar = new JarFile(target);
+ zip = new ZipFile(target);
}
- List entries = Collections.list(jar.entries());
+ List extends ZipEntry> entries = Collections.list(zip.entries());
Collections.sort(entries, ENTRY_COMP);
int eidx = 0;
- for (JarEntry entry : entries) {
+ for (ZipEntry entry : entries) {
// old versions of the digest code skipped metadata
if (version < 2) {
if (entry.getName().startsWith("META-INF")) {
@@ -97,7 +87,7 @@ public class Resource implements Comparable
}
}
- try (InputStream in = jar.getInputStream(entry)) {
+ try (InputStream in = zip.getInputStream(entry)) {
while ((read = in.read(buffer)) != -1) {
md.update(buffer, 0, read);
}
@@ -107,11 +97,11 @@ public class Resource implements Comparable
}
} finally {
- if (jar != null) {
+ if (zip != null) {
try {
- jar.close();
+ zip.close();
} catch (IOException ioe) {
- log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe);
+ log.warning("Error closing", "path", target, "zip", zip, "error", ioe);
}
}
if (tmpJarFile != null) {
@@ -132,6 +122,34 @@ public class Resource implements Comparable
return StringUtil.hexlate(md.digest());
}
+ /**
+ * Returns whether {@code file} is a {@code zip} file.
+ */
+ public static boolean isZip (File file)
+ {
+ String path = file.getName();
+ return path.endsWith(".zip") || path.endsWith(".zip_new");
+ }
+
+ /**
+ * Returns whether {@code file} is a {@code jar} file.
+ */
+ public static boolean isJar (File file)
+ {
+ String path = file.getName();
+ return path.endsWith(".jar") || path.endsWith(".jar_new");
+ }
+
+ /**
+ * Returns whether {@code file} is a {@code jar.pack} file.
+ */
+ public static boolean isPacked200Jar (File file)
+ {
+ String path = file.getName();
+ return path.endsWith(".jar.pack") || path.endsWith(".jar.pack_new") ||
+ path.endsWith(".jar.pack.gz") || path.endsWith(".jar.pack.gz_new");
+ }
+
/**
* Creates a resource with the supplied remote URL and local path.
*/
@@ -141,17 +159,13 @@ public class Resource implements Comparable
_remote = remote;
_local = local;
_localNew = new File(local.toString() + "_new");
- String lpath = _local.getPath();
- _marker = new File(lpath + "v");
+ _marker = new File(_local.getPath() + "v");
_attrs = attrs;
- _isTgz = isTgz(lpath);
- _isJar = isJar(lpath);
- _isPacked200Jar = isPacked200Jar(lpath);
+ _isZip = isJar(local) || isZip(local);
+ _isPacked200Jar = isPacked200Jar(local);
boolean unpack = attrs.contains(Attr.UNPACK);
- if (unpack && _isJar) {
- _unpacked = _local.getParentFile();
- } else if(unpack && _isTgz) {
+ if (unpack && _isZip) {
_unpacked = _local.getParentFile();
} else if(unpack && _isPacked200Jar) {
String dotJar = ".jar", lname = _local.getName();
@@ -307,20 +321,13 @@ public class Resource implements Comparable
public void unpack () throws IOException
{
// sanity check
- if (!_isJar && !_isPacked200Jar && !_isTgz) {
- throw new IOException("Requested to unpack non-jar/tgz file '" + _local + "'.");
+ if (!_isZip && !_isPacked200Jar) {
+ throw new IOException("Requested to unpack non-jar file '" + _local + "'.");
}
- if (_isJar) {
- try (JarFile jar = new JarFile(_local)) {
+ if (_isZip) {
+ try (ZipFile jar = new ZipFile(_local)) {
FileUtil.unpackJar(jar, _unpacked, _attrs.contains(Attr.CLEAN));
}
- } else if (_isTgz) {
- try (InputStream fi = Files.newInputStream(_local.toPath());
- InputStream bi = new BufferedInputStream(fi);
- InputStream gzi = new GzipCompressorInputStream(bi);
- TarArchiveInputStream tgz = new TarArchiveInputStream(gzi)) {
- FileUtil.unpackTgz(tgz, _unpacked, _attrs.contains(Attr.CLEAN));
- }
} else {
FileUtil.unpackPacked200Jar(_local, _unpacked);
}
@@ -382,31 +389,15 @@ public class Resource implements Comparable
}
}
- protected static boolean isJar (String path)
- {
- return path.endsWith(".jar") || path.endsWith(".jar_new");
- }
-
- protected static boolean isTgz (String path)
- {
- return path.endsWith(".tgz") || path.endsWith(".tgz_new");
- }
-
- protected static boolean isPacked200Jar (String path)
- {
- return path.endsWith(".jar.pack") || path.endsWith(".jar.pack_new") ||
- path.endsWith(".jar.pack.gz")|| path.endsWith(".jar.pack.gz_new");
- }
-
protected String _path;
protected URL _remote;
protected File _local, _localNew, _marker, _unpacked;
protected EnumSet _attrs;
- protected boolean _isJar, _isPacked200Jar, _isTgz;
+ protected boolean _isZip, _isPacked200Jar;
/** Used to sort the entries in a jar file. */
- protected static final Comparator ENTRY_COMP = new Comparator() {
- @Override public int compare (JarEntry e1, JarEntry e2) {
+ protected static final Comparator ENTRY_COMP = new Comparator() {
+ @Override public int compare (ZipEntry e1, ZipEntry e2) {
return e1.getName().compareTo(e2.getName());
}
};
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java
index 0d96ecb71..b36d40021 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java
@@ -16,7 +16,7 @@ import com.threerings.getdown.util.VersionUtil;
* accessor so that it's easy to see all of the secret system property arguments that Getdown makes
* use of.
*/
-public class SysProps
+public final class SysProps
{
/** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */
public static String appDir () {
@@ -40,6 +40,14 @@ public class SysProps
return System.getProperty("no_log_redir") != null;
}
+ /** Used to debug Getdown's launching of an app. When set, it disables redirection of stdout
+ * and stderr into a log file, and on Windows it uses {@code java.exe} to launch the app so
+ * that its console output can be observed.
+ * Usage: {@code -Ddebug}. */
+ public static boolean debug () {
+ return System.getProperty("debug") != null;
+ }
+
/** Overrides the domain on {@code appbase}. Usage: {@code -Dappbase_domain=foo}. */
public static String appbaseDomain () {
return System.getProperty("appbase_domain");
@@ -101,6 +109,16 @@ public class SysProps
return Boolean.getBoolean("direct");
}
+ /** If true, Getdown will always try to connect without proxy settings even it a proxy is set
+ * in {@code proxy.txt}. If direct access is possible it will not clear {@code proxy.txt}, it
+ * will preserve the settings. This is to support cases where a user uses a workstation in two
+ * different networks, one with proxy the other one without. They should not be asked for
+ * proxy settings again each time they switch back to the proxy network.
+ * Usage: {@code -Dtry_no_proxy}. */
+ public static boolean tryNoProxyFirst () {
+ return Boolean.getBoolean("try_no_proxy");
+ }
+
/** Specifies the connection timeout (in seconds) to use when downloading control files from
* the server. This is chiefly useful when you are running in versionless mode and want Getdown
* to more quickly timeout its startup update check if the server with which it is
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java
index 6033e2f6e..2298d6099 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java
@@ -6,7 +6,13 @@
package com.threerings.getdown.net;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
import java.util.Collection;
import java.util.HashMap;
@@ -26,8 +32,13 @@ import static com.threerings.getdown.Log.log;
* implementors must take care to only execute thread-safe code or simply pass a message to the AWT
* thread, for example.
*/
-public abstract class Downloader
+public class Downloader
{
+ public Downloader (Connector conn)
+ {
+ _conn = conn;
+ }
+
/**
* Start the downloading process.
* @param resources the resources to download.
@@ -129,10 +140,31 @@ public abstract class Downloader
*/
protected void downloadFailed (Resource rsrc, Exception cause) {}
+ /**
+ * Called when a to-be-downloaded resource returns a 404 not found.
+ */
+ protected void resourceMissing (Resource rsrc) {}
+
/**
* Performs the protocol-specific portion of checking download size.
*/
- protected abstract long checkSize (Resource rsrc) throws IOException;
+ protected long checkSize (Resource rsrc) throws IOException {
+ URLConnection conn = _conn.open(rsrc.getRemote(), 0, 0);
+ try {
+ // if we're accessing our data via HTTP, we only need a HEAD request
+ if (conn instanceof HttpURLConnection) {
+ ((HttpURLConnection)conn).setRequestMethod("HEAD");
+ }
+ // if we get a satisfactory response code, return a size; ignore errors as we'll report
+ // those when we actually attempt to download the resource
+ int code = _conn.checkConnectStatus(conn);
+ return code == HttpURLConnection.HTTP_OK ? conn.getContentLength() : 0;
+
+ } finally {
+ // let it be known that we're done with this connection
+ conn.getInputStream().close();
+ }
+ }
/**
* Periodically called by the protocol-specific downloaders to update their progress. This
@@ -203,13 +235,68 @@ public abstract class Downloader
* protocol-specific code. This method should periodically check whether {@code _state} is set
* to aborted and abort any in-progress download if so.
*/
- protected abstract void download (Resource rsrc) throws IOException;
+ protected void download (Resource rsrc) throws IOException {
+ URLConnection conn = _conn.open(rsrc.getRemote(), 0, 0);
+ // make sure we got a satisfactory response code
+ int code = _conn.checkConnectStatus(conn);
+ if (code == HttpURLConnection.HTTP_NOT_FOUND) {
+ resourceMissing(rsrc);
+ } else if (code != HttpURLConnection.HTTP_OK) {
+ throw new IOException(
+ "Resource returned HTTP error " + rsrc.getRemote() + " [code=" + code + "]");
+ }
+
+ // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
+ // system property
+ if (true) {
+ // download the resource from the specified URL
+ long actualSize = conn.getContentLength();
+ log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
+ long currentSize = 0L;
+ byte[] buffer = new byte[4*4096];
+ try (InputStream in = conn.getInputStream();
+ FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
+
+ // TODO: look to see if we have a download info file
+ // containing info on potentially partially downloaded data;
+ // if so, use a "Range: bytes=HAVE-" header.
+
+ // read in the file data
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ // abort the download if the downloader is aborted
+ if (_state == State.ABORTED) {
+ break;
+ }
+ // write it out to our local copy
+ out.write(buffer, 0, read);
+ // note that we've downloaded some data
+ currentSize += read;
+ reportProgress(rsrc, currentSize, actualSize);
+ }
+ }
+
+ } else {
+ log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
+ File localNew = rsrc.getLocalNew();
+ try (ReadableByteChannel rbc = Channels.newChannel(conn.getInputStream());
+ FileOutputStream fos = new FileOutputStream(localNew)) {
+ // TODO: more work is needed here, transferFrom can fail to transfer the entire
+ // file, in which case it's not clear what we're supposed to do.. call it again?
+ // will it repeatedly fail?
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ reportProgress(rsrc, localNew.length(), localNew.length());
+ }
+ }
+ }
+
+ protected final Connector _conn;
/** The reported sizes of our resources. */
- protected Map _sizes = new HashMap<>();
+ protected final Map _sizes = new HashMap<>();
/** The bytes downloaded for each resource. */
- protected Map _downloaded = new HashMap<>();
+ protected final Map _downloaded = new HashMap<>();
/** The time at which the file transfer began. */
protected long _start;
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java
deleted file mode 100644
index a7a3287a9..000000000
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java
+++ /dev/null
@@ -1,115 +0,0 @@
-//
-// Getdown - application installer, patcher and launcher
-// Copyright (C) 2004-2018 Getdown authors
-// https://github.com/threerings/getdown/blob/master/LICENSE
-
-package com.threerings.getdown.net;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-
-import com.threerings.getdown.data.Resource;
-import com.threerings.getdown.util.ConnectionUtil;
-
-import static com.threerings.getdown.Log.log;
-
-/**
- * Implements downloading files over HTTP
- */
-public class HTTPDownloader extends Downloader
-{
- public HTTPDownloader (Proxy proxy)
- {
- _proxy = proxy;
- }
-
- @Override protected long checkSize (Resource rsrc) throws IOException
- {
- URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
- try {
- // if we're accessing our data via HTTP, we only need a HEAD request
- if (conn instanceof HttpURLConnection) {
- HttpURLConnection hcon = (HttpURLConnection)conn;
- hcon.setRequestMethod("HEAD");
- hcon.connect();
- // make sure we got a satisfactory response code
- if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new IOException("Unable to check up-to-date for " +
- rsrc.getRemote() + ": " + hcon.getResponseCode());
- }
- }
- return conn.getContentLength();
-
- } finally {
- // let it be known that we're done with this connection
- conn.getInputStream().close();
- }
- }
-
- @Override protected void download (Resource rsrc) throws IOException
- {
- // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a
- // system property
- if (true) {
- // download the resource from the specified URL
- URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0);
- conn.connect();
-
- // make sure we got a satisfactory response code
- if (conn instanceof HttpURLConnection) {
- HttpURLConnection hcon = (HttpURLConnection)conn;
- if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) {
- throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " +
- hcon.getResponseCode());
- }
- }
- long actualSize = conn.getContentLength();
- log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize);
- long currentSize = 0L;
- byte[] buffer = new byte[4*4096];
- try (InputStream in = conn.getInputStream();
- FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) {
-
- // TODO: look to see if we have a download info file
- // containing info on potentially partially downloaded data;
- // if so, use a "Range: bytes=HAVE-" header.
-
- // read in the file data
- int read;
- while ((read = in.read(buffer)) != -1) {
- // abort the download if the downloader is aborted
- if (_state == State.ABORTED) {
- break;
- }
- // write it out to our local copy
- out.write(buffer, 0, read);
- // note that we've downloaded some data
- currentSize += read;
- reportProgress(rsrc, currentSize, actualSize);
- }
- }
-
- } else {
- log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown");
- File localNew = rsrc.getLocalNew();
- try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream());
- FileOutputStream fos = new FileOutputStream(localNew)) {
- // TODO: more work is needed here, transferFrom can fail to transfer the entire
- // file, in which case it's not clear what we're supposed to do.. call it again?
- // will it repeatedly fail?
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- reportProgress(rsrc, localNew.length(), localNew.length());
- }
- }
- }
-
- protected final Proxy _proxy;
-}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java
index 22446ec0a..8c6d84160 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java
@@ -11,7 +11,7 @@ package com.threerings.getdown.spi;
public interface ProxyAuth
{
/** Credentials for a proxy server. */
- public static class Credentials {
+ class Credentials {
public final String username;
public final String password;
public Credentials (String username, String password) {
@@ -23,10 +23,10 @@ public interface ProxyAuth
/**
* Loads the credentials for the app installed in {@code appDir}.
*/
- public Credentials loadCredentials (String appDir);
+ Credentials loadCredentials (String appDir);
/**
* Encrypts and saves the credentials for the app installed in {@code appDir}.
*/
- public void saveCredentials (String appDir, String username, String password);
+ void saveCredentials (String appDir, String username, String password);
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java
index c2e740b6e..4f8e50ebd 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java
@@ -11,15 +11,14 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-
+import java.io.OutputStream;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Enumeration;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
+import java.util.List;
import java.util.zip.ZipEntry;
-
-import java.security.MessageDigest;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Digest;
@@ -39,8 +38,8 @@ public class Differ
/**
* Creates a single patch file that contains the differences between
* the two specified application directories. The patch file will be
- * created in the nvdir directory with name
- * patchV.dat where V is the old application version.
+ * created in the {@code nvdir} directory with name
+ * {@code patchV.dat} where V is the old application version.
*/
public void createDiff (File nvdir, File ovdir, boolean verbose)
throws IOException
@@ -61,13 +60,13 @@ public class Differ
Application oapp = new Application(new EnvConfig(ovdir));
oapp.init(false);
- ArrayList orsrcs = new ArrayList<>();
+ List orsrcs = new ArrayList<>();
orsrcs.addAll(oapp.getCodeResources());
orsrcs.addAll(oapp.getResources());
Application napp = new Application(new EnvConfig(nvdir));
napp.init(false);
- ArrayList nrsrcs = new ArrayList<>();
+ List nrsrcs = new ArrayList<>();
nrsrcs.addAll(napp.getCodeResources());
nrsrcs.addAll(napp.getResources());
@@ -91,15 +90,15 @@ public class Differ
}
}
- protected void createPatch (File patch, ArrayList orsrcs,
- ArrayList nrsrcs, boolean verbose)
+ protected void createPatch (File patch, List orsrcs,
+ List nrsrcs, boolean verbose)
throws IOException
{
int version = Digest.VERSION;
MessageDigest md = Digest.getMessageDigest(version);
try (FileOutputStream fos = new FileOutputStream(patch);
BufferedOutputStream buffered = new BufferedOutputStream(fos);
- JarOutputStream jout = new JarOutputStream(buffered)) {
+ ZipOutputStream jout = new ZipOutputStream(buffered)) {
// for each file in the new application, it either already exists
// in the old application, or it is new
@@ -172,13 +171,13 @@ public class Differ
throws IOException
{
File temp = File.createTempFile("differ", "jar");
- try (JarFile jar = new JarFile(target);
+ try (ZipFile jar = new ZipFile(target);
FileOutputStream tempFos = new FileOutputStream(temp);
BufferedOutputStream tempBos = new BufferedOutputStream(tempFos);
- JarOutputStream jout = new JarOutputStream(tempBos)) {
+ ZipOutputStream jout = new ZipOutputStream(tempBos)) {
byte[] buffer = new byte[4096];
- for (Enumeration< JarEntry > iter = jar.entries(); iter.hasMoreElements();) {
- JarEntry entry = iter.nextElement();
+ for (Enumeration extends ZipEntry> iter = jar.entries(); iter.hasMoreElements();) {
+ ZipEntry entry = iter.nextElement();
entry.setCompressedSize(-1);
jout.putNextEntry(entry);
try (InputStream in = jar.getInputStream(entry)) {
@@ -193,8 +192,7 @@ public class Differ
return temp;
}
- protected void jarDiff (File ofile, File nfile, JarOutputStream jout)
- throws IOException
+ protected void jarDiff (File ofile, File nfile, ZipOutputStream jout) throws IOException
{
JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false);
}
@@ -209,7 +207,7 @@ public class Differ
Differ differ = new Differ();
boolean verbose = false;
int aidx = 0;
- if (args[0].equals("-verbose")) {
+ if ("-verbose".equals(args[0])) {
verbose = true;
aidx++;
}
@@ -222,11 +220,10 @@ public class Differ
}
}
- protected static void pipe (File file, JarOutputStream jout)
- throws IOException
+ protected static void pipe (File file, OutputStream out) throws IOException
{
try (FileInputStream fin = new FileInputStream(file)) {
- StreamUtil.copy(fin, jout);
+ StreamUtil.copy(fin, out);
}
}
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java
index b04a6539b..ae61b4333 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java
@@ -23,6 +23,7 @@ import com.threerings.getdown.data.Digest;
import com.threerings.getdown.data.EnvConfig;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.util.Base64;
+import com.threerings.getdown.util.Config;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -74,8 +75,11 @@ public class Digester
System.out.println("Generating digest file '" + target + "'...");
// create our application and instruct it to parse its business
- Application app = new Application(new EnvConfig(appdir));
- app.init(false);
+ EnvConfig envc = new EnvConfig(appdir);
+ Application app = new Application(envc);
+ Config config = Application.readConfig(envc, false);
+ app.initBase(config);
+ app.initResources(config);
List rsrcs = new ArrayList<>();
rsrcs.add(app.getConfigResource());
@@ -86,6 +90,9 @@ public class Digester
rsrcs.addAll(ag.rsrcs);
}
+ // reinit app just to verify that getdown.txt has valid format
+ app.init(true);
+
// now generate the digest file
Digest.createDigest(version, rsrcs, target);
}
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
index 1cea0eacd..f0db8ac03 100644
--- a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java
@@ -41,15 +41,24 @@
package com.threerings.getdown.tools;
-import java.io.*;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
import java.util.*;
-import java.util.jar.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
- * JarDiff is able to create a jar file containing the delta between two jar files (old and new).
- * The delta jar file can then be applied to the old jar file to reconstruct the new jar file.
+ * JarDiff is able to create a zip file containing the delta between two jar or zip files (old
+ * and new). The delta file can then be applied to the old archive file to reconstruct the new
+ * archive file.
*
*
Refer to the JNLP spec for details on how this is done.
*
@@ -58,39 +67,37 @@ import static java.nio.charset.StandardCharsets.UTF_8;
public class JarDiff implements JarDiffCodes
{
private static final int DEFAULT_READ_SIZE = 2048;
- private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
- private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+ private static final byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+ private static final byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
// The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java
// and other stuff here. Instead, we use an explicit _debug flag.
private static boolean _debug;
/**
- * Creates a patch from the two passed in files, writing the result to os.
+ * Creates a patch from the two passed in files, writing the result to {@code os}.
*/
public static void createPatch (String oldPath, String newPath,
OutputStream os, boolean minimal) throws IOException
{
- try (JarFile2 oldJar = new JarFile2(oldPath);
- JarFile2 newJar = new JarFile2(newPath)) {
+ try (ZipFile2 oldArchive = new ZipFile2(oldPath);
+ ZipFile2 newArchive = new ZipFile2(newPath)) {
- HashMap moved = new HashMap<>();
- HashSet implicit = new HashSet<>();
- HashSet moveSrc = new HashSet<>();
- HashSet newEntries = new HashSet<>();
+ Map moved = new HashMap<>();
+ Set implicit = new HashSet<>();
+ Set moveSrc = new HashSet<>();
+ Set newEntries = new HashSet<>();
// FIRST PASS
- // Go through the entries in new jar and
- // determine which files are candidates for implicit moves
- // ( files that has the same filename and same content in old.jar
- // and new.jar )
- // and for files that cannot be implicitly moved, we will either
- // find out whether it is moved or new (modified)
- for (JarEntry newEntry : newJar) {
+ // Go through the entries in new archive and determine which files are candidates for
+ // implicit moves (files that have the same filename and same content in old and new)
+ // and for files that cannot be implicitly moved, we will either find out whether it is
+ // moved or new (modified)
+ for (ZipEntry newEntry : newArchive) {
String newname = newEntry.getName();
// Return best match of contents, will return a name match if possible
- String oldname = oldJar.getBestMatch(newJar, newEntry);
+ String oldname = oldArchive.getBestMatch(newArchive, newEntry);
if (oldname == null) {
// New or modified entry
if (_debug) {
@@ -101,7 +108,7 @@ public class JarDiff implements JarDiffCodes
// Content already exist - need to do a move
// Should do implicit move? Yes, if names are the same, and
- // no move command already exist from oldJar
+ // no move command already exist from oldArchive
if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
if (_debug) {
System.out.println(newname + " added to implicit set!");
@@ -117,12 +124,9 @@ public class JarDiff implements JarDiffCodes
// JarDiffPatcher also.
if (!minimal && (implicit.contains(oldname) ||
moveSrc.contains(oldname) )) {
-
// generate non-minimal jardiff
// for backward compatibility
-
if (_debug) {
-
System.out.println("NEW: "+ newname);
}
newEntries.add(newname);
@@ -153,8 +157,8 @@ public class JarDiff implements JarDiffCodes
// SECOND PASS: = - -
//