Merge branch 'master' of https://github.com/threerings/getdown
# Conflicts: # core/src/main/java/com/threerings/getdown/data/Application.java
This commit is contained in:
@@ -5,6 +5,19 @@
|
|||||||
* Fixed issues with proxy information not getting properly passed through to app.
|
* Fixed issues with proxy information not getting properly passed through to app.
|
||||||
Via [#216](//github.com/threerings/getdown/pull/216).
|
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.
|
||||||
|
|
||||||
## 1.8.4 - May 14, 2019
|
## 1.8.4 - May 14, 2019
|
||||||
|
|
||||||
* Added `verify_timeout` config to allow customization of the default (60 second) timeout during
|
* Added `verify_timeout` config to allow customization of the default (60 second) timeout during
|
||||||
|
|||||||
@@ -7,16 +7,13 @@ package com.threerings.getdown.tools;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import org.apache.tools.ant.BuildException;
|
import org.apache.tools.ant.BuildException;
|
||||||
import org.apache.tools.ant.Task;
|
import org.apache.tools.ant.Task;
|
||||||
|
|
||||||
import com.threerings.getdown.data.Digest;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ant task used to create a <code>digest.txt</code> for a Getdown
|
* An ant task used to create a {@code digest.txt} for a Getdown
|
||||||
* application deployment.
|
* application deployment.
|
||||||
*/
|
*/
|
||||||
public class DigesterTask extends Task
|
public class DigesterTask extends Task
|
||||||
|
|||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.tests;
|
package com.threerings.getdown.tests;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
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.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import com.threerings.getdown.tools.Digester;
|
import com.threerings.getdown.tools.Digester;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class DigesterIT {
|
public class DigesterIT {
|
||||||
|
|
||||||
@@ -32,23 +32,23 @@ public class DigesterIT {
|
|||||||
Files.delete(digest2);
|
Files.delete(digest2);
|
||||||
|
|
||||||
assertEquals(Arrays.asList(
|
assertEquals(Arrays.asList(
|
||||||
"getdown.txt = 779c74fb4b251e18faf6e240a0667964",
|
"getdown.txt = 9c9b2494929c99d44ae51034d59e1a1b",
|
||||||
"testapp.jar = 404dafa55e78b25ec0e3a936357b1883",
|
"testapp.jar = 404dafa55e78b25ec0e3a936357b1883",
|
||||||
"funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249",
|
"funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249",
|
||||||
"crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16",
|
"crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16",
|
||||||
"foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec",
|
"foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec",
|
||||||
"script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf",
|
"script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf",
|
||||||
"digest.txt = f5561d85e4d80cc85883963897e58ff6"
|
"digest.txt = 11f9ba349cf9edacac4d72a3158447e5"
|
||||||
), digestLines);
|
), digestLines);
|
||||||
|
|
||||||
assertEquals(Arrays.asList(
|
assertEquals(Arrays.asList(
|
||||||
"getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b",
|
"getdown.txt = 1efecfae2a189002a6658f17d162b1922c7bde978944949276dc038a0df2461f",
|
||||||
"testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0",
|
"testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0",
|
||||||
"funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
|
"funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
|
||||||
"crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b",
|
"crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b",
|
||||||
"foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062",
|
"foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062",
|
||||||
"script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988",
|
"script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988",
|
||||||
"digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407"
|
"digest2.txt = 41eacdabda8909bdbbf61e4f980867f4003c16a12f6770e6fc619b6af100e05b"
|
||||||
), digest2Lines);
|
), digest2Lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,18 @@ apparg = %APPDIR%
|
|||||||
# test the %env% mechanism
|
# test the %env% mechanism
|
||||||
jvmarg = -Dusername=\%ENV.USER%
|
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
|
strict_comments = true
|
||||||
resource = funny%test dir/some=file.txt
|
resource = funny%test dir/some=file.txt
|
||||||
resource = crazyhashfile#txt
|
resource = crazyhashfile#txt
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.logging.*;
|
|||||||
/**
|
/**
|
||||||
* A placeholder class that contains a reference to the log object used by the Getdown code.
|
* 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 {
|
public static class Shim {
|
||||||
/**
|
/**
|
||||||
@@ -143,6 +143,5 @@ public class Log
|
|||||||
protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD);
|
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};
|
protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,6 @@ public class GarbageCollector
|
|||||||
private static File getCachedFile (File file)
|
private static File getCachedFile (File file)
|
||||||
{
|
{
|
||||||
return !isLastAccessedFile(file) ? file : new 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('.')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ public class ResourceCache
|
|||||||
|
|
||||||
private String getFileSuffix (File fileToCache) {
|
private String getFileSuffix (File fileToCache) {
|
||||||
String fileName = fileToCache.getName();
|
String fileName = fileToCache.getName();
|
||||||
int index = fileName.lastIndexOf(".");
|
int index = fileName.lastIndexOf('.');
|
||||||
|
|
||||||
return index > -1 ? fileName.substring(index) : "";
|
return index > -1 ? fileName.substring(index) : "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ package com.threerings.getdown.data;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -23,8 +22,8 @@ import java.util.*;
|
|||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
|
|
||||||
|
import com.threerings.getdown.net.Connector;
|
||||||
import com.threerings.getdown.util.*;
|
import com.threerings.getdown.util.*;
|
||||||
// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+
|
// 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.util.Base64;
|
||||||
@@ -33,7 +32,7 @@ import static com.threerings.getdown.Log.log;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses and provide access to the information contained in the <code>getdown.txt</code>
|
* Parses and provide access to the information contained in the {@code getdown.txt}
|
||||||
* configuration file.
|
* configuration file.
|
||||||
*/
|
*/
|
||||||
public class Application
|
public class Application
|
||||||
@@ -212,10 +211,10 @@ public class Application
|
|||||||
* Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to
|
* Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to
|
||||||
* take network actions.
|
* take network actions.
|
||||||
*/
|
*/
|
||||||
public static interface StatusDisplay
|
public interface StatusDisplay
|
||||||
{
|
{
|
||||||
/** Requests that the specified status message be displayed. */
|
/** Requests that the specified status message be displayed. */
|
||||||
public void updateStatus (String message);
|
void updateStatus (String message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,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<String, Object> 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. */
|
* 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 <code>getdown.txt</code>
|
* Creates an application instance which records the location of the {@code getdown.txt}
|
||||||
* configuration file from the supplied application directory.
|
* configuration file from the supplied application directory.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Application (EnvConfig envc) {
|
public Application (EnvConfig envc) {
|
||||||
_envc = envc;
|
_envc = envc;
|
||||||
_config = new File(envc.appDir, CONFIG_FILE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -369,8 +405,7 @@ public class Application
|
|||||||
*/
|
*/
|
||||||
public List<Resource> getActiveCodeResources ()
|
public List<Resource> getActiveCodeResources ()
|
||||||
{
|
{
|
||||||
ArrayList<Resource> codes = new ArrayList<>();
|
List<Resource> codes = new ArrayList<>(getCodeResources());
|
||||||
codes.addAll(getCodeResources());
|
|
||||||
for (AuxGroup aux : getAuxGroups()) {
|
for (AuxGroup aux : getAuxGroups()) {
|
||||||
if (isAuxGroupActive(aux.name)) {
|
if (isAuxGroupActive(aux.name)) {
|
||||||
codes.addAll(aux.codes);
|
codes.addAll(aux.codes);
|
||||||
@@ -398,8 +433,7 @@ public class Application
|
|||||||
*/
|
*/
|
||||||
public List<Resource> getActiveResources ()
|
public List<Resource> getActiveResources ()
|
||||||
{
|
{
|
||||||
ArrayList<Resource> rsrcs = new ArrayList<>();
|
List<Resource> rsrcs = new ArrayList<>(getResources());
|
||||||
rsrcs.addAll(getResources());
|
|
||||||
for (AuxGroup aux : getAuxGroups()) {
|
for (AuxGroup aux : getAuxGroups()) {
|
||||||
if (isAuxGroupActive(aux.name)) {
|
if (isAuxGroupActive(aux.name)) {
|
||||||
rsrcs.addAll(aux.rsrcs);
|
rsrcs.addAll(aux.rsrcs);
|
||||||
@@ -544,44 +578,22 @@ public class Application
|
|||||||
* @exception IOException thrown if there is an error reading the file or an error encountered
|
* @exception IOException thrown if there is an error reading the file or an error encountered
|
||||||
* during its parsing.
|
* during its parsing.
|
||||||
*/
|
*/
|
||||||
public Config init (boolean checkPlatform)
|
public Config init (boolean checkPlatform) throws IOException
|
||||||
throws IOException
|
|
||||||
{
|
{
|
||||||
Config config = null;
|
Config config = readConfig(_envc, checkPlatform);
|
||||||
File cfgfile = _config;
|
initBase(config);
|
||||||
Config.ParseOpts opts = Config.createOpts(checkPlatform);
|
initJava(config);
|
||||||
try {
|
initTracking(config);
|
||||||
// if we have a configuration file, read the data from it
|
initResources(config);
|
||||||
if (cfgfile.exists()) {
|
initArgs(config);
|
||||||
config = Config.parseConfig(_config, opts);
|
return config;
|
||||||
}
|
}
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<String, Object> cdata = new HashMap<>();
|
|
||||||
cdata.put("appbase", appbase);
|
|
||||||
config = new Config(cdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract our version information
|
|
||||||
_version = config.getLong("version", -1L);
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 determine our application base, this way if anything goes wrong later in the
|
// first determine our application base, this way if anything goes wrong later in the
|
||||||
// process, our caller can use the appbase to download a new configuration file
|
// process, our caller can use the appbase to download a new configuration file
|
||||||
_appbase = config.getString("appbase");
|
_appbase = config.getString("appbase");
|
||||||
@@ -589,25 +601,29 @@ public class Application
|
|||||||
throw new RuntimeException("m.missing_appbase");
|
throw new RuntimeException("m.missing_appbase");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we're overriding the domain in the appbase
|
// check if we're overriding the domain in the appbase, and sub envvars
|
||||||
_appbase = processArg(SysProps.overrideAppbase(_appbase));
|
_appbase = processArg(SysProps.overrideAppbase(_appbase));
|
||||||
|
|
||||||
// make sure there's a trailing slash
|
// make sure there's a trailing slash
|
||||||
if (!_appbase.endsWith("/")) {
|
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
|
// if we are a versioned deployment, create a versioned appbase
|
||||||
try {
|
try {
|
||||||
_vappbase = createVAppBase(_version);
|
_vappbase = createVAppBase(_version);
|
||||||
} catch (MalformedURLException mue) {
|
} catch (MalformedURLException mue) {
|
||||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
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
|
// check for a latest config URL
|
||||||
String latest = processArg(config.getString("latest"));
|
String latest = config.getString("latest");
|
||||||
if (latest != null) {
|
if (latest != null) {
|
||||||
|
latest = processArg(latest);
|
||||||
if (latest.startsWith(_appbase)) {
|
if (latest.startsWith(_appbase)) {
|
||||||
latest = _appbase + latest.substring(_appbase.length());
|
latest = _appbase + latest.substring(_appbase.length());
|
||||||
} else {
|
} else {
|
||||||
@@ -620,20 +636,28 @@ 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
|
// determine whether we want strict comments
|
||||||
_strictComments = config.getBoolean("strict_comments");
|
_strictComments = config.getBoolean("strict_comments");
|
||||||
|
|
||||||
|
// determine whether we want to allow offline operation (defaults to false)
|
||||||
|
_allowOffline = config.getBoolean("allow_offline");
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
|
||||||
|
_verifyTimeout = config.getInt("verify_timeout", 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
// check to see if we're using a custom java.version property and regex
|
||||||
_javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
|
_javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
|
||||||
_javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
|
_javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
|
||||||
@@ -648,17 +672,16 @@ public class Application
|
|||||||
// check to see if we require a particular JVM version and have a supplied JVM
|
// check to see if we require a particular JVM version and have a supplied JVM
|
||||||
_javaExactVersionRequired = config.getBoolean("java_exact_version_required");
|
_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
|
_javaLocation = config.getString("java_location");
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// used only in conjunction with java_location
|
// used only in conjunction with java_location
|
||||||
_javaLocalDir = getLocalPath(config.getString("java_local_dir", LaunchUtil.LOCAL_JAVA_DIR));
|
_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
|
// determine whether we have any tracking configuration
|
||||||
_trackingURL = config.getString("tracking_url");
|
_trackingURL = config.getString("tracking_url");
|
||||||
|
|
||||||
@@ -683,14 +706,16 @@ public class Application
|
|||||||
|
|
||||||
// Some app may need to generate google analytics code
|
// Some app may need to generate google analytics code
|
||||||
_trackingGAHash = config.getString("tracking_ga_hash");
|
_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
|
// clear our arrays as we may be reinitializing
|
||||||
_codes.clear();
|
_codes.clear();
|
||||||
_resources.clear();
|
_resources.clear();
|
||||||
_auxgroups.clear();
|
_auxgroups.clear();
|
||||||
_jvmargs.clear();
|
|
||||||
_appargs.clear();
|
|
||||||
_txtJvmArgs.clear();
|
|
||||||
|
|
||||||
// parse our code resources
|
// parse our code resources
|
||||||
if (config.getMultiValue("code") == null &&
|
if (config.getMultiValue("code") == null &&
|
||||||
@@ -709,10 +734,10 @@ public class Application
|
|||||||
|
|
||||||
// parse our auxiliary resource groups
|
// parse our auxiliary resource groups
|
||||||
for (String auxgroup : config.getList("auxgroups")) {
|
for (String auxgroup : config.getList("auxgroups")) {
|
||||||
ArrayList<Resource> codes = new ArrayList<>();
|
List<Resource> codes = new ArrayList<>();
|
||||||
parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
|
parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
|
||||||
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
|
parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
|
||||||
ArrayList<Resource> rsrcs = new ArrayList<>();
|
List<Resource> rsrcs = new ArrayList<>();
|
||||||
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
|
parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
|
||||||
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
|
parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs);
|
||||||
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
|
parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
|
||||||
@@ -720,21 +745,38 @@ public class Application
|
|||||||
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
|
parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs);
|
||||||
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
|
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
_class = config.getString(appPrefix + "class", _class);
|
||||||
|
}
|
||||||
|
if (_class == null) {
|
||||||
|
throw new IOException("m.missing_class");
|
||||||
|
}
|
||||||
|
|
||||||
// transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
|
// transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
|
||||||
String[] jvmargs = config.getMultiValue("jvmarg");
|
addAll(config.getMultiValue("jvmarg"), _jvmargs);
|
||||||
addAll(jvmargs, _jvmargs);
|
|
||||||
if (appPrefix.length() > 0) {
|
if (appPrefix.length() > 0) {
|
||||||
jvmargs = config.getMultiValue(appPrefix + "jvmarg");
|
addAll(config.getMultiValue(appPrefix + "jvmarg"), _jvmargs);
|
||||||
addAll(jvmargs, _jvmargs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the set of optimum JVM arguments
|
// get the set of optimum JVM arguments
|
||||||
_optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
|
_optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
|
||||||
|
|
||||||
// transfer our application arguments
|
// transfer our application arguments
|
||||||
String[] appargs = config.getMultiValue(appPrefix + "apparg");
|
addAll(config.getMultiValue(appPrefix + "apparg"), _appargs);
|
||||||
addAll(appargs, _appargs);
|
|
||||||
|
|
||||||
// add the launch specific application arguments
|
// add the launch specific application arguments
|
||||||
_appargs.addAll(_envc.appArgs);
|
_appargs.addAll(_envc.appArgs);
|
||||||
@@ -742,27 +784,13 @@ public class Application
|
|||||||
// look for custom arguments
|
// look for custom arguments
|
||||||
fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
|
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
|
// extract some info used to configure our child process on macOS
|
||||||
_dockName = config.getString("ui.name");
|
_dockName = config.getString("ui.name");
|
||||||
_dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns");
|
_dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns");
|
||||||
|
|
||||||
_verifyTimeout = config.getInt("verify_timeout", 60);
|
// look for a debug.txt file which causes us to run in java.exe on Windows so that we can
|
||||||
return config;
|
// obtain a thread dump of the running JVM
|
||||||
|
_windebug = getLocalPath("debug.txt").exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -791,8 +819,7 @@ public class Application
|
|||||||
* Returns a URL from which the specified path can be fetched. Our application base URL is
|
* Returns a URL from which the specified path can be fetched. Our application base URL is
|
||||||
* properly versioned and combined with the supplied path.
|
* properly versioned and combined with the supplied path.
|
||||||
*/
|
*/
|
||||||
public URL getRemoteURL (String path)
|
public URL getRemoteURL (String path) throws MalformedURLException
|
||||||
throws MalformedURLException
|
|
||||||
{
|
{
|
||||||
return new URL(_vappbase, encodePath(path));
|
return new URL(_vappbase, encodePath(path));
|
||||||
}
|
}
|
||||||
@@ -882,7 +909,7 @@ public class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to redownload the <code>getdown.txt</code> 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}.
|
* previous call to {@link #init}.
|
||||||
*/
|
*/
|
||||||
public void attemptRecovery (StatusDisplay status)
|
public void attemptRecovery (StatusDisplay status)
|
||||||
@@ -893,7 +920,7 @@ public class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads and replaces the <code>getdown.txt</code> and <code>digest.txt</code> files with
|
* Downloads and replaces the {@code getdown.txt} and {@code digest.txt} files with
|
||||||
* those for the target version of our application.
|
* those for the target version of our application.
|
||||||
*/
|
*/
|
||||||
public void updateMetadata ()
|
public void updateMetadata ()
|
||||||
@@ -904,7 +931,7 @@ public class Application
|
|||||||
_vappbase = createVAppBase(_targetVersion);
|
_vappbase = createVAppBase(_targetVersion);
|
||||||
} catch (MalformedURLException mue) {
|
} catch (MalformedURLException mue) {
|
||||||
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
String err = MessageUtil.tcompose("m.invalid_appbase", _appbase);
|
||||||
throw (IOException) new IOException(err).initCause(mue);
|
throw new IOException(err, mue);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -961,16 +988,8 @@ public class Application
|
|||||||
args.add("-Xdock:name=" + _dockName);
|
args.add("-Xdock:name=" + _dockName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass along our proxy settings
|
// forward our proxy settings
|
||||||
if (proxy.type() == Proxy.Type.HTTP && proxy.address() instanceof InetSocketAddress) {
|
conn.addProxyArgs(args);
|
||||||
InetSocketAddress proxyAddr = (InetSocketAddress) proxy.address();
|
|
||||||
String proxyHost = proxyAddr.getHostString();
|
|
||||||
int proxyPort = proxyAddr.getPort();
|
|
||||||
args.add("-Dhttp.proxyHost=" + proxyHost);
|
|
||||||
args.add("-Dhttp.proxyPort=" + proxyPort);
|
|
||||||
args.add("-Dhttps.proxyHost=" + proxyHost);
|
|
||||||
args.add("-Dhttps.proxyPort=" + proxyPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the marker indicating the app is running in getdown
|
// add the marker indicating the app is running in getdown
|
||||||
args.add("-D" + Properties.GETDOWN + "=true");
|
args.add("-D" + Properties.GETDOWN + "=true");
|
||||||
@@ -1080,7 +1099,7 @@ public class Application
|
|||||||
for (String jvmarg : _jvmargs) {
|
for (String jvmarg : _jvmargs) {
|
||||||
if (jvmarg.startsWith("-D")) {
|
if (jvmarg.startsWith("-D")) {
|
||||||
jvmarg = processArg(jvmarg.substring(2));
|
jvmarg = processArg(jvmarg.substring(2));
|
||||||
int eqidx = jvmarg.indexOf("=");
|
int eqidx = jvmarg.indexOf('=');
|
||||||
if (eqidx == -1) {
|
if (eqidx == -1) {
|
||||||
log.warning("Bogus system property: '" + jvmarg + "'?");
|
log.warning("Bogus system property: '" + jvmarg + "'?");
|
||||||
} else {
|
} else {
|
||||||
@@ -1121,10 +1140,6 @@ public class Application
|
|||||||
/** Replaces the application directory and version in any argument. */
|
/** Replaces the application directory and version in any argument. */
|
||||||
protected String processArg (String arg)
|
protected String processArg (String arg)
|
||||||
{
|
{
|
||||||
if (arg == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
|
arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath());
|
||||||
arg = arg.replace("%VERSION%", String.valueOf(_version));
|
arg = arg.replace("%VERSION%", String.valueOf(_version));
|
||||||
|
|
||||||
@@ -1146,8 +1161,8 @@ public class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the <code>digest.txt</code> file and verifies the contents of both that file and the
|
* Loads the {@code digest.txt} file and verifies the contents of both that file and the
|
||||||
* <code>getdown.text</code> file. Then it loads the <code>version.txt</code> and decides
|
* {@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
|
* whether or not the application needs to be updated or whether we can proceed to verification
|
||||||
* and execution.
|
* and execution.
|
||||||
*
|
*
|
||||||
@@ -1234,11 +1249,11 @@ public class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_latest != null) {
|
if (_latest != null) {
|
||||||
try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream();
|
try {
|
||||||
InputStreamReader reader = new InputStreamReader(in, UTF_8);
|
List<String[]> vdata = Config.parsePairs(
|
||||||
BufferedReader bin = new BufferedReader(reader)) {
|
new StringReader(conn.fetch(_latest)), Config.createOpts(false));
|
||||||
for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
|
for (String[] pair : vdata) {
|
||||||
if (pair[0].equals("version")) {
|
if ("version".equals(pair[0])) {
|
||||||
_targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
|
_targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
|
||||||
if (fileVersion != -1 && _targetVersion > fileVersion) {
|
if (fileVersion != -1 && _targetVersion > fileVersion) {
|
||||||
// replace the file with the newest version
|
// replace the file with the newest version
|
||||||
@@ -1444,7 +1459,7 @@ public class Application
|
|||||||
protected URL createVAppBase (long version)
|
protected URL createVAppBase (long version)
|
||||||
throws MalformedURLException
|
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));
|
return HostWhitelist.verify(new URL(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1461,8 +1476,7 @@ public class Application
|
|||||||
/**
|
/**
|
||||||
* Downloads a new copy of CONFIG_FILE.
|
* Downloads a new copy of CONFIG_FILE.
|
||||||
*/
|
*/
|
||||||
protected void downloadConfigFile ()
|
protected void downloadConfigFile () throws IOException
|
||||||
throws IOException
|
|
||||||
{
|
{
|
||||||
downloadControlFile(CONFIG_FILE, 0);
|
downloadControlFile(CONFIG_FILE, 0);
|
||||||
}
|
}
|
||||||
@@ -1604,8 +1618,7 @@ public class Application
|
|||||||
* Download a path to a temporary file, returning a {@link File} instance with the path
|
* Download a path to a temporary file, returning a {@link File} instance with the path
|
||||||
* contents.
|
* contents.
|
||||||
*/
|
*/
|
||||||
protected File downloadFile (String path)
|
protected File downloadFile (String path) throws IOException
|
||||||
throws IOException
|
|
||||||
{
|
{
|
||||||
File target = getLocalPath(path + "_new");
|
File target = getLocalPath(path + "_new");
|
||||||
|
|
||||||
@@ -1615,30 +1628,11 @@ public class Application
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warning("Requested to download invalid control file",
|
log.warning("Requested to download invalid control file",
|
||||||
"appbase", _vappbase, "path", path, "error", e);
|
"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 + "'.");
|
log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
|
||||||
|
conn.download(targetURL, target); // stream the URL into our temporary file
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,9 +1646,7 @@ public class Application
|
|||||||
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
|
/** Helper function to add all values in {@code values} (if non-null) to {@code target}. */
|
||||||
protected static void addAll (String[] values, List<String> target) {
|
protected static void addAll (String[] values, List<String> target) {
|
||||||
if (values != null) {
|
if (values != null) {
|
||||||
for (String value : values) {
|
Collections.addAll(target, values);
|
||||||
target.add(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1737,7 +1729,6 @@ public class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected final EnvConfig _envc;
|
protected final EnvConfig _envc;
|
||||||
protected File _config;
|
|
||||||
protected Digest _digest;
|
protected Digest _digest;
|
||||||
|
|
||||||
protected long _version = -1;
|
protected long _version = -1;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import static com.threerings.getdown.Log.log;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the <code>digest.txt</code> 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.
|
* application.
|
||||||
*/
|
*/
|
||||||
public class Digest
|
public class Digest
|
||||||
@@ -72,8 +72,7 @@ public class Digest
|
|||||||
digests.put(rsrc, rsrc.computeDigest(fversion, md, null));
|
digests.put(rsrc, rsrc.computeDigest(fversion, md, null));
|
||||||
completed.add(rsrc);
|
completed.add(rsrc);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
completed.add(new IOException("Error computing digest for: " + rsrc).
|
completed.add(new IOException("Error computing digest for: " + rsrc, t));
|
||||||
initCause(t));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -88,7 +87,7 @@ public class Digest
|
|||||||
if (done instanceof IOException) {
|
if (done instanceof IOException) {
|
||||||
throw (IOException)done;
|
throw (IOException)done;
|
||||||
} else if (done instanceof Resource) {
|
} else if (done instanceof Resource) {
|
||||||
pending.remove((Resource)done);
|
pending.remove(done);
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError("What is this? " + done);
|
throw new AssertionError("What is this? " + done);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public final class EnvConfig {
|
|||||||
|
|
||||||
/** Used to report problems or feedback by {@link #create}. */
|
/** Used to report problems or feedback by {@link #create}. */
|
||||||
public static final class Note {
|
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 info (String msg) { return new Note(Level.INFO, msg); }
|
||||||
public static Note warn (String msg) { return new Note(Level.WARN, msg); }
|
public static Note warn (String msg) { return new Note(Level.WARN, msg); }
|
||||||
public static Note error (String msg) { return new Note(Level.ERROR, msg); }
|
public static Note error (String msg) { return new Note(Level.ERROR, msg); }
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import static com.threerings.getdown.Log.log;
|
|||||||
public class Resource implements Comparable<Resource>
|
public class Resource implements Comparable<Resource>
|
||||||
{
|
{
|
||||||
/** Defines special attributes for resources. */
|
/** Defines special attributes for resources. */
|
||||||
public static enum Attr {
|
public enum Attr {
|
||||||
/** Indicates that the resource should be unpacked. */
|
/** Indicates that the resource should be unpacked. */
|
||||||
UNPACK,
|
UNPACK,
|
||||||
/** If present, when unpacking a resource, any directories created by the newly
|
/** If present, when unpacking a resource, any directories created by the newly
|
||||||
@@ -35,7 +35,7 @@ public class Resource implements Comparable<Resource>
|
|||||||
PRELOAD,
|
PRELOAD,
|
||||||
/** Indicates that the resource is a jar containing native libs. */
|
/** Indicates that the resource is a jar containing native libs. */
|
||||||
NATIVE
|
NATIVE
|
||||||
};
|
}
|
||||||
|
|
||||||
public static final EnumSet<Attr> NORMAL = EnumSet.noneOf(Attr.class);
|
public static final EnumSet<Attr> NORMAL = EnumSet.noneOf(Attr.class);
|
||||||
public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
|
public static final EnumSet<Attr> UNPACK = EnumSet.of(Attr.UNPACK);
|
||||||
|
|||||||
@@ -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
|
* accessor so that it's easy to see all of the secret system property arguments that Getdown makes
|
||||||
* use of.
|
* use of.
|
||||||
*/
|
*/
|
||||||
public class SysProps
|
public final class SysProps
|
||||||
{
|
{
|
||||||
/** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */
|
/** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */
|
||||||
public static String appDir () {
|
public static String appDir () {
|
||||||
@@ -101,6 +101,16 @@ public class SysProps
|
|||||||
return Boolean.getBoolean("direct");
|
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
|
/** 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
|
* 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
|
* to more quickly timeout its startup update check if the server with which it is
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
//
|
||||||
|
// 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.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import com.threerings.getdown.data.SysProps;
|
||||||
|
import com.threerings.getdown.util.Base64;
|
||||||
|
import com.threerings.getdown.util.StreamUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the process of making HTTP connections, using a proxy if necessary. Tracks and reports
|
||||||
|
* when proxy credentials are rejected.
|
||||||
|
*/
|
||||||
|
public class Connector {
|
||||||
|
|
||||||
|
/** The default connector uses no proxy. */
|
||||||
|
public static final Connector DEFAULT = new Connector(Proxy.NO_PROXY);
|
||||||
|
|
||||||
|
/** Tracks the state of a connector. If it fails for proxy-related reasons, it may transition
|
||||||
|
* to a need_proxy or need_proxy_auth state. */
|
||||||
|
public enum State { ACTIVE, NEED_PROXY, NEED_PROXY_AUTH }
|
||||||
|
|
||||||
|
/** The proxy used by this connector. */
|
||||||
|
public final Proxy proxy;
|
||||||
|
|
||||||
|
/** The current state of this connector. */
|
||||||
|
public State state = State.ACTIVE;
|
||||||
|
|
||||||
|
public Connector (Proxy proxy) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a connection to a URL, setting the authentication header if user info is present.
|
||||||
|
* @param proxy the proxy via which to perform HTTP connections.
|
||||||
|
* @param url the URL to which to open a connection.
|
||||||
|
* @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the
|
||||||
|
* connection. If {@code 0} is supplied, the connection timeout specified via system properties
|
||||||
|
* will be used instead.
|
||||||
|
* @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from
|
||||||
|
* the connection. If {@code 0} is supplied, the read timeout specified via system properties
|
||||||
|
* will be used instead.
|
||||||
|
*/
|
||||||
|
public URLConnection open (URL url, int connectTimeout, int readTimeout)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
URLConnection conn = url.openConnection(proxy);
|
||||||
|
|
||||||
|
// configure a connect timeout, if requested
|
||||||
|
int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
|
||||||
|
if (ctimeout > 0) {
|
||||||
|
conn.setConnectTimeout(ctimeout * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// configure a read timeout, if requested
|
||||||
|
int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout();
|
||||||
|
if (rtimeout > 0) {
|
||||||
|
conn.setReadTimeout(rtimeout * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If URL has a username:password@ before hostname, use HTTP basic auth
|
||||||
|
String userInfo = url.getUserInfo();
|
||||||
|
if (userInfo != null) {
|
||||||
|
// Remove any percent-encoding in the username/password
|
||||||
|
userInfo = URLDecoder.decode(userInfo, "UTF-8");
|
||||||
|
// Now base64 encode the auth info and make it a single line
|
||||||
|
String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT).
|
||||||
|
replaceAll("\\n","").replaceAll("\\r", "");
|
||||||
|
conn.setRequestProperty("Authorization", "Basic " + encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a connection to a http or https URL, setting the authentication header if user info is
|
||||||
|
* present. Throws a class cast exception if the connection returned is not the right type. See
|
||||||
|
* {@link #open} for parameter documentation.
|
||||||
|
*/
|
||||||
|
public HttpURLConnection openHttp (URL url, int connectTimeout, int readTimeout)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return (HttpURLConnection)open(url, connectTimeout, readTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads {@code url} into {@code target}.
|
||||||
|
*/
|
||||||
|
public void download (URL url, File target) throws IOException {
|
||||||
|
URLConnection conn = open(url, 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
|
||||||
|
conn.setUseCaches(false);
|
||||||
|
conn.setRequestProperty("Accept-Encoding", "gzip");
|
||||||
|
checkConnectOK(conn, "Unable to download " + url);
|
||||||
|
try (InputStream fin = conn.getInputStream()) {
|
||||||
|
String encoding = conn.getContentEncoding();
|
||||||
|
boolean gzip = "gzip".equalsIgnoreCase(encoding);
|
||||||
|
try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) {
|
||||||
|
try (FileOutputStream fout = new FileOutputStream(target)) {
|
||||||
|
StreamUtil.copy(fin2, fout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the data at {@code url} into a string.
|
||||||
|
*/
|
||||||
|
public String fetch (URL url) throws IOException {
|
||||||
|
URLConnection conn = open(url, 0, 0);
|
||||||
|
checkConnectOK(conn, "Unable to fetch " + url);
|
||||||
|
int size = conn.getContentLength();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(size > 0 ? size : 1024);
|
||||||
|
try (InputStream in = conn.getInputStream()) {
|
||||||
|
StreamUtil.copy(in, out);
|
||||||
|
}
|
||||||
|
return out.toString(UTF_8.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that {@code conn} returned an {@code OK} response code iff it is an HTTP connection.
|
||||||
|
* If the connection failed for proxy related reasons, this changes the state of this connector
|
||||||
|
* to reflect the needed proxy information.
|
||||||
|
*/
|
||||||
|
public void checkConnectOK (URLConnection conn, String errpre) throws IOException
|
||||||
|
{
|
||||||
|
// if it's not an HTTP connection, there's nothing to check
|
||||||
|
if (!(conn instanceof HttpURLConnection)) return;
|
||||||
|
|
||||||
|
int code = ((HttpURLConnection)conn).getResponseCode();
|
||||||
|
switch (code) {
|
||||||
|
case HttpURLConnection.HTTP_OK:
|
||||||
|
return;
|
||||||
|
case HttpURLConnection.HTTP_FORBIDDEN:
|
||||||
|
case HttpURLConnection.HTTP_USE_PROXY:
|
||||||
|
state = State.NEED_PROXY;
|
||||||
|
break;
|
||||||
|
case HttpURLConnection.HTTP_PROXY_AUTH:
|
||||||
|
state = State.NEED_PROXY_AUTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
throw new IOException(errpre + " [code=" + code + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds appropriate proxy args from this connector's configuration to the supplied list of
|
||||||
|
* command line args that will be used to launch the app.
|
||||||
|
*/
|
||||||
|
public void addProxyArgs (List<String> args) {
|
||||||
|
if (proxy.type() == Proxy.Type.HTTP && proxy.address() instanceof InetSocketAddress) {
|
||||||
|
InetSocketAddress proxyAddr = (InetSocketAddress) proxy.address();
|
||||||
|
String proxyHost = proxyAddr.getHostString();
|
||||||
|
int proxyPort = proxyAddr.getPort();
|
||||||
|
args.add("-Dhttp.proxyHost=" + proxyHost);
|
||||||
|
args.add("-Dhttp.proxyPort=" + proxyPort);
|
||||||
|
args.add("-Dhttps.proxyHost=" + proxyHost);
|
||||||
|
args.add("-Dhttps.proxyPort=" + proxyPort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,13 @@
|
|||||||
package com.threerings.getdown.net;
|
package com.threerings.getdown.net;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
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.Collection;
|
||||||
import java.util.HashMap;
|
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
|
* implementors must take care to only execute thread-safe code or simply pass a message to the AWT
|
||||||
* thread, for example.
|
* thread, for example.
|
||||||
*/
|
*/
|
||||||
public abstract class Downloader
|
public class Downloader
|
||||||
{
|
{
|
||||||
|
public Downloader (Connector conn)
|
||||||
|
{
|
||||||
|
_conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the downloading process.
|
* Start the downloading process.
|
||||||
* @param resources the resources to download.
|
* @param resources the resources to download.
|
||||||
@@ -132,7 +143,22 @@ public abstract class Downloader
|
|||||||
/**
|
/**
|
||||||
* Performs the protocol-specific portion of checking download size.
|
* 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");
|
||||||
|
}
|
||||||
|
// make sure we got a satisfactory response code
|
||||||
|
_conn.checkConnectOK(conn, "Unable to check up-to-date for " + rsrc.getRemote());
|
||||||
|
return conn.getContentLength();
|
||||||
|
|
||||||
|
} 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
|
* Periodically called by the protocol-specific downloaders to update their progress. This
|
||||||
@@ -203,13 +229,62 @@ public abstract class Downloader
|
|||||||
* protocol-specific code. This method should periodically check whether {@code _state} is set
|
* protocol-specific code. This method should periodically check whether {@code _state} is set
|
||||||
* to aborted and abort any in-progress download if so.
|
* 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
|
||||||
|
_conn.checkConnectOK(conn, "Unable to download resource " + rsrc.getRemote());
|
||||||
|
|
||||||
|
// 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. */
|
/** The reported sizes of our resources. */
|
||||||
protected Map<Resource, Long> _sizes = new HashMap<>();
|
protected final Map<Resource, Long> _sizes = new HashMap<>();
|
||||||
|
|
||||||
/** The bytes downloaded for each resource. */
|
/** The bytes downloaded for each resource. */
|
||||||
protected Map<Resource, Long> _downloaded = new HashMap<>();
|
protected final Map<Resource, Long> _downloaded = new HashMap<>();
|
||||||
|
|
||||||
/** The time at which the file transfer began. */
|
/** The time at which the file transfer began. */
|
||||||
protected long _start;
|
protected long _start;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ package com.threerings.getdown.spi;
|
|||||||
public interface ProxyAuth
|
public interface ProxyAuth
|
||||||
{
|
{
|
||||||
/** Credentials for a proxy server. */
|
/** Credentials for a proxy server. */
|
||||||
public static class Credentials {
|
class Credentials {
|
||||||
public final String username;
|
public final String username;
|
||||||
public final String password;
|
public final String password;
|
||||||
public Credentials (String username, 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}.
|
* 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}.
|
* 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,17 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.tools;
|
package com.threerings.getdown.tools;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
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.security.MessageDigest;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
@@ -31,8 +38,8 @@ public class Differ
|
|||||||
/**
|
/**
|
||||||
* Creates a single patch file that contains the differences between
|
* Creates a single patch file that contains the differences between
|
||||||
* the two specified application directories. The patch file will be
|
* the two specified application directories. The patch file will be
|
||||||
* created in the <code>nvdir</code> directory with name
|
* created in the {@code nvdir} directory with name
|
||||||
* <code>patchV.dat</code> where V is the old application version.
|
* {@code patchV.dat} where V is the old application version.
|
||||||
*/
|
*/
|
||||||
public void createDiff (File nvdir, File ovdir, boolean verbose)
|
public void createDiff (File nvdir, File ovdir, boolean verbose)
|
||||||
throws IOException
|
throws IOException
|
||||||
@@ -53,13 +60,13 @@ public class Differ
|
|||||||
|
|
||||||
Application oapp = new Application(new EnvConfig(ovdir));
|
Application oapp = new Application(new EnvConfig(ovdir));
|
||||||
oapp.init(false);
|
oapp.init(false);
|
||||||
ArrayList<Resource> orsrcs = new ArrayList<>();
|
List<Resource> orsrcs = new ArrayList<>();
|
||||||
orsrcs.addAll(oapp.getCodeResources());
|
orsrcs.addAll(oapp.getCodeResources());
|
||||||
orsrcs.addAll(oapp.getResources());
|
orsrcs.addAll(oapp.getResources());
|
||||||
|
|
||||||
Application napp = new Application(new EnvConfig(nvdir));
|
Application napp = new Application(new EnvConfig(nvdir));
|
||||||
napp.init(false);
|
napp.init(false);
|
||||||
ArrayList<Resource> nrsrcs = new ArrayList<>();
|
List<Resource> nrsrcs = new ArrayList<>();
|
||||||
nrsrcs.addAll(napp.getCodeResources());
|
nrsrcs.addAll(napp.getCodeResources());
|
||||||
nrsrcs.addAll(napp.getResources());
|
nrsrcs.addAll(napp.getResources());
|
||||||
|
|
||||||
@@ -83,8 +90,8 @@ public class Differ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createPatch (File patch, ArrayList<Resource> orsrcs,
|
protected void createPatch (File patch, List<Resource> orsrcs,
|
||||||
ArrayList<Resource> nrsrcs, boolean verbose)
|
List<Resource> nrsrcs, boolean verbose)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
int version = Digest.VERSION;
|
int version = Digest.VERSION;
|
||||||
@@ -200,7 +207,7 @@ public class Differ
|
|||||||
Differ differ = new Differ();
|
Differ differ = new Differ();
|
||||||
boolean verbose = false;
|
boolean verbose = false;
|
||||||
int aidx = 0;
|
int aidx = 0;
|
||||||
if (args[0].equals("-verbose")) {
|
if ("-verbose".equals(args[0])) {
|
||||||
verbose = true;
|
verbose = true;
|
||||||
aidx++;
|
aidx++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.threerings.getdown.data.Digest;
|
|||||||
import com.threerings.getdown.data.EnvConfig;
|
import com.threerings.getdown.data.EnvConfig;
|
||||||
import com.threerings.getdown.data.Resource;
|
import com.threerings.getdown.data.Resource;
|
||||||
import com.threerings.getdown.util.Base64;
|
import com.threerings.getdown.util.Base64;
|
||||||
|
import com.threerings.getdown.util.Config;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
@@ -74,8 +75,11 @@ public class Digester
|
|||||||
System.out.println("Generating digest file '" + target + "'...");
|
System.out.println("Generating digest file '" + target + "'...");
|
||||||
|
|
||||||
// create our application and instruct it to parse its business
|
// create our application and instruct it to parse its business
|
||||||
Application app = new Application(new EnvConfig(appdir));
|
EnvConfig envc = new EnvConfig(appdir);
|
||||||
app.init(false);
|
Application app = new Application(envc);
|
||||||
|
Config config = Application.readConfig(envc, false);
|
||||||
|
app.initBase(config);
|
||||||
|
app.initResources(config);
|
||||||
|
|
||||||
List<Resource> rsrcs = new ArrayList<>();
|
List<Resource> rsrcs = new ArrayList<>();
|
||||||
rsrcs.add(app.getConfigResource());
|
rsrcs.add(app.getConfigResource());
|
||||||
@@ -86,6 +90,9 @@ public class Digester
|
|||||||
rsrcs.addAll(ag.rsrcs);
|
rsrcs.addAll(ag.rsrcs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reinit app just to verify that getdown.txt has valid format
|
||||||
|
app.init(true);
|
||||||
|
|
||||||
// now generate the digest file
|
// now generate the digest file
|
||||||
Digest.createDigest(version, rsrcs, target);
|
Digest.createDigest(version, rsrcs, target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,15 +67,15 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||||||
public class JarDiff implements JarDiffCodes
|
public class JarDiff implements JarDiffCodes
|
||||||
{
|
{
|
||||||
private static final int DEFAULT_READ_SIZE = 2048;
|
private static final int DEFAULT_READ_SIZE = 2048;
|
||||||
private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
|
private static final byte[] newBytes = new byte[DEFAULT_READ_SIZE];
|
||||||
private static byte[] oldBytes = 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
|
// 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.
|
// and other stuff here. Instead, we use an explicit _debug flag.
|
||||||
private static boolean _debug;
|
private static boolean _debug;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a patch from the two passed in files, writing the result to <code>os</code>.
|
* Creates a patch from the two passed in files, writing the result to {@code os}.
|
||||||
*/
|
*/
|
||||||
public static void createPatch (String oldPath, String newPath,
|
public static void createPatch (String oldPath, String newPath,
|
||||||
OutputStream os, boolean minimal) throws IOException
|
OutputStream os, boolean minimal) throws IOException
|
||||||
@@ -83,10 +83,10 @@ public class JarDiff implements JarDiffCodes
|
|||||||
try (ZipFile2 oldArchive = new ZipFile2(oldPath);
|
try (ZipFile2 oldArchive = new ZipFile2(oldPath);
|
||||||
ZipFile2 newArchive = new ZipFile2(newPath)) {
|
ZipFile2 newArchive = new ZipFile2(newPath)) {
|
||||||
|
|
||||||
HashMap<String,String> moved = new HashMap<>();
|
Map<String,String> moved = new HashMap<>();
|
||||||
HashSet<String> implicit = new HashSet<>();
|
Set<String> implicit = new HashSet<>();
|
||||||
HashSet<String> moveSrc = new HashSet<>();
|
Set<String> moveSrc = new HashSet<>();
|
||||||
HashSet<String> newEntries = new HashSet<>();
|
Set<String> newEntries = new HashSet<>();
|
||||||
|
|
||||||
// FIRST PASS
|
// FIRST PASS
|
||||||
// Go through the entries in new archive and determine which files are candidates for
|
// Go through the entries in new archive and determine which files are candidates for
|
||||||
@@ -157,7 +157,7 @@ public class JarDiff implements JarDiffCodes
|
|||||||
|
|
||||||
// SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
|
// SECOND PASS: <deleted files> = <oldjarnames> - <implicitmoves> -
|
||||||
// <source of move commands> - <new or modified entries>
|
// <source of move commands> - <new or modified entries>
|
||||||
ArrayList<String> deleted = new ArrayList<>();
|
List<String> deleted = new ArrayList<>();
|
||||||
for (ZipEntry oldEntry : oldArchive) {
|
for (ZipEntry oldEntry : oldArchive) {
|
||||||
String oldName = oldEntry.getName();
|
String oldName = oldEntry.getName();
|
||||||
if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
|
if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
|
||||||
@@ -203,9 +203,9 @@ public class JarDiff implements JarDiffCodes
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the index file out to <code>jos</code>.
|
* Writes the index file out to {@code jos}.
|
||||||
* <code>oldEntries</code> gives the names of the files that were removed,
|
* {@code oldEntries} gives the names of the files that were removed,
|
||||||
* <code>movedMap</code> maps from the new name to the old name.
|
* {@code movedMap} maps from the new name to the old name.
|
||||||
*/
|
*/
|
||||||
private static void createIndex (ZipOutputStream jos, List<String> oldEntries,
|
private static void createIndex (ZipOutputStream jos, List<String> oldEntries,
|
||||||
Map<String,String> movedMap)
|
Map<String,String> movedMap)
|
||||||
@@ -286,7 +286,7 @@ public class JarDiff implements JarDiffCodes
|
|||||||
*/
|
*/
|
||||||
private static class ZipFile2 implements Iterable<ZipEntry>, Closeable
|
private static class ZipFile2 implements Iterable<ZipEntry>, Closeable
|
||||||
{
|
{
|
||||||
private ZipFile _archive;
|
private final ZipFile _archive;
|
||||||
private List<ZipEntry> _entries;
|
private List<ZipEntry> _entries;
|
||||||
private HashMap<String,ZipEntry> _nameToEntryMap;
|
private HashMap<String,ZipEntry> _nameToEntryMap;
|
||||||
private HashMap<Long,LinkedList<ZipEntry>> _crcToEntryMap;
|
private HashMap<Long,LinkedList<ZipEntry>> _crcToEntryMap;
|
||||||
@@ -384,7 +384,7 @@ public class JarDiff implements JarDiffCodes
|
|||||||
|
|
||||||
public String hasSameContent (ZipFile2 file, ZipEntry entry) throws IOException {
|
public String hasSameContent (ZipFile2 file, ZipEntry entry) throws IOException {
|
||||||
String thisName = null;
|
String thisName = null;
|
||||||
Long crcL = Long.valueOf(entry.getCrc());
|
Long crcL = entry.getCrc();
|
||||||
// check if this archive contains files with the passed in entry's crc
|
// check if this archive contains files with the passed in entry's crc
|
||||||
if (_crcToEntryMap.containsKey(crcL)) {
|
if (_crcToEntryMap.containsKey(crcL)) {
|
||||||
// get the Linked List with files with the crc
|
// get the Linked List with files with the crc
|
||||||
@@ -419,7 +419,7 @@ public class JarDiff implements JarDiffCodes
|
|||||||
while (entries.hasMoreElements()) {
|
while (entries.hasMoreElements()) {
|
||||||
ZipEntry entry = entries.nextElement();
|
ZipEntry entry = entries.nextElement();
|
||||||
long crc = entry.getCrc();
|
long crc = entry.getCrc();
|
||||||
Long crcL = Long.valueOf(crc);
|
Long crcL = crc;
|
||||||
if (_debug) {
|
if (_debug) {
|
||||||
System.out.println("\t" + entry.getName() + " CRC " + crc);
|
System.out.println("\t" + entry.getName() + " CRC " + crc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ public class JarDiffPatcher implements JarDiffCodes
|
|||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int length = path.length();
|
int length = path.length();
|
||||||
ArrayList<String> sub = new ArrayList<>();
|
List<String> sub = new ArrayList<>();
|
||||||
|
|
||||||
while (index < length) {
|
while (index < length) {
|
||||||
while (index < length && Character.isWhitespace
|
while (index < length && Character.isWhitespace
|
||||||
@@ -223,9 +223,8 @@ public class JarDiffPatcher implements JarDiffCodes
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if (index < length) {
|
if (index < length) {
|
||||||
int start = index;
|
int last = index;
|
||||||
int last = start;
|
StringBuilder subString = null;
|
||||||
String subString = null;
|
|
||||||
|
|
||||||
while (index < length) {
|
while (index < length) {
|
||||||
char aChar = path.charAt(index);
|
char aChar = path.charAt(index);
|
||||||
@@ -233,9 +232,9 @@ public class JarDiffPatcher implements JarDiffCodes
|
|||||||
path.charAt(index + 1) == ' ') {
|
path.charAt(index + 1) == ' ') {
|
||||||
|
|
||||||
if (subString == null) {
|
if (subString == null) {
|
||||||
subString = path.substring(last, index);
|
subString = new StringBuilder(path.substring(last, index));
|
||||||
} else {
|
} else {
|
||||||
subString += path.substring(last, index);
|
subString.append(path, last, index);
|
||||||
}
|
}
|
||||||
last = ++index;
|
last = ++index;
|
||||||
} else if (Character.isWhitespace(aChar)) {
|
} else if (Character.isWhitespace(aChar)) {
|
||||||
@@ -245,12 +244,14 @@ public class JarDiffPatcher implements JarDiffCodes
|
|||||||
}
|
}
|
||||||
if (last != index) {
|
if (last != index) {
|
||||||
if (subString == null) {
|
if (subString == null) {
|
||||||
subString = path.substring(last, index);
|
subString = new StringBuilder(path.substring(last, index));
|
||||||
} else {
|
} else {
|
||||||
subString += path.substring(last, index);
|
subString.append(path, last, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sub.add(subString);
|
if (subString != null) {
|
||||||
|
sub.add(subString.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sub;
|
return sub;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
|
|||||||
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
|
* href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a
|
||||||
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
|
* href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>.
|
||||||
*/
|
*/
|
||||||
public class Base64 {
|
public final class Base64 {
|
||||||
/**
|
/**
|
||||||
* Default values for encoder/decoder flags.
|
* Default values for encoder/decoder flags.
|
||||||
*/
|
*/
|
||||||
@@ -68,7 +68,7 @@ public class Base64 {
|
|||||||
// shared code
|
// shared code
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
|
|
||||||
/* package */ static abstract class Coder {
|
/* package */ abstract static class Coder {
|
||||||
public byte[] output;
|
public byte[] output;
|
||||||
public int op;
|
public int op;
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ public class Base64 {
|
|||||||
* Lookup table for turning bytes into their position in the
|
* Lookup table for turning bytes into their position in the
|
||||||
* Base64 alphabet.
|
* Base64 alphabet.
|
||||||
*/
|
*/
|
||||||
private static final int DECODE[] = {
|
private static final int[] DECODE = {
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||||
@@ -201,7 +201,7 @@ public class Base64 {
|
|||||||
* Decode lookup table for the "web safe" variant (RFC 3548
|
* Decode lookup table for the "web safe" variant (RFC 3548
|
||||||
* sec. 4) where - and _ replace + and /.
|
* sec. 4) where - and _ replace + and /.
|
||||||
*/
|
*/
|
||||||
private static final int DECODE_WEBSAFE[] = {
|
private static final int[] DECODE_WEBSAFE = {
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
|
||||||
@@ -236,7 +236,7 @@ public class Base64 {
|
|||||||
private int state; // state number (0 to 6)
|
private int state; // state number (0 to 6)
|
||||||
private int value;
|
private int value;
|
||||||
|
|
||||||
final private int[] alphabet;
|
private final int[] alphabet;
|
||||||
|
|
||||||
public Decoder(int flags, byte[] output) {
|
public Decoder(int flags, byte[] output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
@@ -541,7 +541,7 @@ public class Base64 {
|
|||||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||||
* into output bytes.
|
* into output bytes.
|
||||||
*/
|
*/
|
||||||
private static final byte ENCODE[] = {
|
private static final byte[] ENCODE = {
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
@@ -552,21 +552,21 @@ public class Base64 {
|
|||||||
* Lookup table for turning Base64 alphabet positions (6 bits)
|
* Lookup table for turning Base64 alphabet positions (6 bits)
|
||||||
* into output bytes.
|
* into output bytes.
|
||||||
*/
|
*/
|
||||||
private static final byte ENCODE_WEBSAFE[] = {
|
private static final byte[] ENCODE_WEBSAFE = {
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
|
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_',
|
||||||
};
|
};
|
||||||
|
|
||||||
final private byte[] tail;
|
private final byte[] tail;
|
||||||
/* package */ int tailLen;
|
/* package */ int tailLen;
|
||||||
private int count;
|
private int count;
|
||||||
|
|
||||||
final public boolean do_padding;
|
public final boolean do_padding;
|
||||||
final public boolean do_newline;
|
public final boolean do_newline;
|
||||||
final public boolean do_cr;
|
public final boolean do_cr;
|
||||||
final private byte[] alphabet;
|
private final byte[] alphabet;
|
||||||
|
|
||||||
public Encoder(int flags, byte[] output) {
|
public Encoder(int flags, byte[] output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
@@ -618,7 +618,7 @@ public class Base64 {
|
|||||||
((input[p++] & 0xff) << 8) |
|
((input[p++] & 0xff) << 8) |
|
||||||
(input[p++] & 0xff);
|
(input[p++] & 0xff);
|
||||||
tailLen = 0;
|
tailLen = 0;
|
||||||
};
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ package com.threerings.getdown.util;
|
|||||||
/**
|
/**
|
||||||
* Utilities for handling ARGB colors.
|
* Utilities for handling ARGB colors.
|
||||||
*/
|
*/
|
||||||
public class Color
|
public final class Color
|
||||||
{
|
{
|
||||||
public final static int CLEAR = 0x00000000;
|
public static final int CLEAR = 0x00000000;
|
||||||
public final static int WHITE = 0xFFFFFFFF;
|
public static final int WHITE = 0xFFFFFFFF;
|
||||||
public final static int BLACK = 0xFF000000;
|
public static final int BLACK = 0xFF000000;
|
||||||
|
|
||||||
public static float brightness (int argb) {
|
public static float brightness (int argb) {
|
||||||
// TODO: we're ignoring alpha here...
|
// TODO: we're ignoring alpha here...
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class Config
|
|||||||
*
|
*
|
||||||
* @param opts options that influence the parsing. See {@link #createOpts}.
|
* @param opts options that influence the parsing. See {@link #createOpts}.
|
||||||
*
|
*
|
||||||
* @return a list of <code>String[]</code> instances containing the key/value pairs in the
|
* @return a list of {@code String[]} instances containing the key/value pairs in the
|
||||||
* order they were parsed from the file.
|
* order they were parsed from the file.
|
||||||
*/
|
*/
|
||||||
public static List<String[]> parsePairs (File source, ParseOpts opts)
|
public static List<String[]> parsePairs (File source, ParseOpts opts)
|
||||||
@@ -83,7 +83,7 @@ public class Config
|
|||||||
List<String[]> pairs = new ArrayList<>();
|
List<String[]> pairs = new ArrayList<>();
|
||||||
for (String line : FileUtil.readLines(source)) {
|
for (String line : FileUtil.readLines(source)) {
|
||||||
// nix comments
|
// nix comments
|
||||||
int cidx = line.indexOf("#");
|
int cidx = line.indexOf('#');
|
||||||
if (opts.strictComments ? cidx == 0 : cidx != -1) {
|
if (opts.strictComments ? cidx == 0 : cidx != -1) {
|
||||||
line = line.substring(0, cidx);
|
line = line.substring(0, cidx);
|
||||||
}
|
}
|
||||||
@@ -97,7 +97,7 @@ public class Config
|
|||||||
// parse our key/value pair
|
// parse our key/value pair
|
||||||
String[] pair = new String[2];
|
String[] pair = new String[2];
|
||||||
// if we're biasing toward key, put all the extra = in the key rather than the value
|
// if we're biasing toward key, put all the extra = in the key rather than the value
|
||||||
int eidx = opts.biasToKey ? line.lastIndexOf("=") : line.indexOf("=");
|
int eidx = opts.biasToKey ? line.lastIndexOf('=') : line.indexOf('=');
|
||||||
if (eidx != -1) {
|
if (eidx != -1) {
|
||||||
pair[0] = line.substring(0, eidx).trim();
|
pair[0] = line.substring(0, eidx).trim();
|
||||||
pair[1] = line.substring(eidx+1).trim();
|
pair[1] = line.substring(eidx+1).trim();
|
||||||
@@ -108,7 +108,7 @@ public class Config
|
|||||||
|
|
||||||
// if the pair has an os qualifier, we need to process it
|
// if the pair has an os qualifier, we need to process it
|
||||||
if (pair[1].startsWith("[")) {
|
if (pair[1].startsWith("[")) {
|
||||||
int qidx = pair[1].indexOf("]");
|
int qidx = pair[1].indexOf(']');
|
||||||
if (qidx == -1) {
|
if (qidx == -1) {
|
||||||
log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]);
|
log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]);
|
||||||
continue; // omit the pair entirely
|
continue; // omit the pair entirely
|
||||||
@@ -351,7 +351,7 @@ public class Config
|
|||||||
protected static boolean checkQualifiers (String quals, String osname, String osarch)
|
protected static boolean checkQualifiers (String quals, String osname, String osarch)
|
||||||
{
|
{
|
||||||
if (quals.startsWith("!")) {
|
if (quals.startsWith("!")) {
|
||||||
if (quals.indexOf(",") != -1) { // sanity check
|
if (quals.contains(",")) { // sanity check
|
||||||
log.warning("Multiple qualifiers cannot be used when one of the qualifiers " +
|
log.warning("Multiple qualifiers cannot be used when one of the qualifiers " +
|
||||||
"is negative", "quals", quals);
|
"is negative", "quals", quals);
|
||||||
return false;
|
return false;
|
||||||
@@ -371,7 +371,7 @@ public class Config
|
|||||||
{
|
{
|
||||||
String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-");
|
String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-");
|
||||||
String os = bits[0], arch = (bits.length > 1) ? bits[1] : "";
|
String os = bits[0], arch = (bits.length > 1) ? bits[1] : "";
|
||||||
return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1);
|
return (osname.contains(os)) && (osarch.contains(arch));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<String, Object> _data;
|
private final Map<String, Object> _data;
|
||||||
|
|||||||
@@ -1,73 +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.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
|
|
||||||
import com.threerings.getdown.data.SysProps;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
public class ConnectionUtil
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Opens a connection to a URL, setting the authentication header if user info is present.
|
|
||||||
* @param proxy the proxy via which to perform HTTP connections.
|
|
||||||
* @param url the URL to which to open a connection.
|
|
||||||
* @param connectTimeout if {@code > 0} then a timeout, in seconds, to use when opening the
|
|
||||||
* connection. If {@code 0} is supplied, the connection timeout specified via system properties
|
|
||||||
* will be used instead.
|
|
||||||
* @param readTimeout if {@code > 0} then a timeout, in seconds, to use while reading data from
|
|
||||||
* the connection. If {@code 0} is supplied, the read timeout specified via system properties
|
|
||||||
* will be used instead.
|
|
||||||
*/
|
|
||||||
public static URLConnection open (Proxy proxy, URL url, int connectTimeout, int readTimeout)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
URLConnection conn = url.openConnection(proxy);
|
|
||||||
|
|
||||||
// configure a connect timeout, if requested
|
|
||||||
int ctimeout = connectTimeout > 0 ? connectTimeout : SysProps.connectTimeout();
|
|
||||||
if (ctimeout > 0) {
|
|
||||||
conn.setConnectTimeout(ctimeout * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure a read timeout, if requested
|
|
||||||
int rtimeout = readTimeout > 0 ? readTimeout : SysProps.readTimeout();
|
|
||||||
if (rtimeout > 0) {
|
|
||||||
conn.setReadTimeout(rtimeout * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If URL has a username:password@ before hostname, use HTTP basic auth
|
|
||||||
String userInfo = url.getUserInfo();
|
|
||||||
if (userInfo != null) {
|
|
||||||
// Remove any percent-encoding in the username/password
|
|
||||||
userInfo = URLDecoder.decode(userInfo, "UTF-8");
|
|
||||||
// Now base64 encode the auth info and make it a single line
|
|
||||||
String encoded = Base64.encodeToString(userInfo.getBytes(UTF_8), Base64.DEFAULT).
|
|
||||||
replaceAll("\\n","").replaceAll("\\r", "");
|
|
||||||
conn.setRequestProperty("Authorization", "Basic " + encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a connection to a http or https URL, setting the authentication header if user info is
|
|
||||||
* present. Throws a class cast exception if the connection returned is not the right type. See
|
|
||||||
* {@link #open} for parameter documentation.
|
|
||||||
*/
|
|
||||||
public static HttpURLConnection openHttp (
|
|
||||||
Proxy proxy, URL url, int connectTimeout, int readTimeout) throws IOException
|
|
||||||
{
|
|
||||||
return (HttpURLConnection)open(proxy, url, connectTimeout, readTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,19 +17,19 @@ import static com.threerings.getdown.Log.log;
|
|||||||
* Useful routines for launching Java applications from within other Java
|
* Useful routines for launching Java applications from within other Java
|
||||||
* applications.
|
* applications.
|
||||||
*/
|
*/
|
||||||
public class LaunchUtil
|
public final class LaunchUtil
|
||||||
{
|
{
|
||||||
/** The default directory into which a local VM installation should be unpacked. */
|
/** The default directory into which a local VM installation should be unpacked. */
|
||||||
public static final String LOCAL_JAVA_DIR = "java_vm";
|
public static final String LOCAL_JAVA_DIR = "java_vm";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes a <code>version.txt</code> file into the specified application directory and
|
* Writes a {@code version.txt} file into the specified application directory and
|
||||||
* attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly
|
* attempts to relaunch Getdown in that directory which will cause it to upgrade to the newly
|
||||||
* specified version and relaunch the application.
|
* specified version and relaunch the application.
|
||||||
*
|
*
|
||||||
* @param appdir the directory in which the application is installed.
|
* @param appdir the directory in which the application is installed.
|
||||||
* @param getdownJarName the name of the getdown jar file in the application directory. This is
|
* @param getdownJarName the name of the getdown jar file in the application directory. This is
|
||||||
* probably <code>getdown-pro.jar</code> or <code>getdown-retro-pro.jar</code> if you are using
|
* probably {@code getdown-pro.jar} or {@code getdown-retro-pro.jar} if you are using
|
||||||
* the results of the standard build.
|
* the results of the standard build.
|
||||||
* @param newVersion the new version to which Getdown will update when it is executed.
|
* @param newVersion the new version to which Getdown will update when it is executed.
|
||||||
* @param javaLocalDir the name of the directory (inside {@code appdir}) that contains a
|
* @param javaLocalDir the name of the directory (inside {@code appdir}) that contains a
|
||||||
@@ -41,7 +41,7 @@ public class LaunchUtil
|
|||||||
* after making this call as it will be upgraded and restarted. If false is returned, the
|
* after making this call as it will be upgraded and restarted. If false is returned, the
|
||||||
* application should tell the user that they must restart the application manually.
|
* application should tell the user that they must restart the application manually.
|
||||||
*
|
*
|
||||||
* @exception IOException thrown if we were unable to create the <code>version.txt</code> file
|
* @exception IOException thrown if we were unable to create the {@code version.txt} file
|
||||||
* in the supplied application directory. If the version.txt file cannot be created, restarting
|
* in the supplied application directory. If the version.txt file cannot be created, restarting
|
||||||
* Getdown will not cause the application to be upgraded, so the application will have to
|
* Getdown will not cause the application to be upgraded, so the application will have to
|
||||||
* resort to telling the user that it is in a bad way.
|
* resort to telling the user that it is in a bad way.
|
||||||
@@ -177,23 +177,23 @@ public class LaunchUtil
|
|||||||
public static boolean mustMonitorChildren ()
|
public static boolean mustMonitorChildren ()
|
||||||
{
|
{
|
||||||
String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
|
String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
|
||||||
return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1);
|
return (osname.contains("windows 98") || osname.contains("windows me"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we're running in a JVM that identifies its operating system as Windows.
|
* Returns true if we're running in a JVM that identifies its operating system as Windows.
|
||||||
*/
|
*/
|
||||||
public static final boolean isWindows () { return _isWindows; }
|
public static boolean isWindows () { return _isWindows; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we're running in a JVM that identifies its operating system as MacOS.
|
* Returns true if we're running in a JVM that identifies its operating system as MacOS.
|
||||||
*/
|
*/
|
||||||
public static final boolean isMacOS () { return _isMacOS; }
|
public static boolean isMacOS () { return _isMacOS; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if we're running in a JVM that identifies its operating system as Linux.
|
* Returns true if we're running in a JVM that identifies its operating system as Linux.
|
||||||
*/
|
*/
|
||||||
public static final boolean isLinux () { return _isLinux; }
|
public static boolean isLinux () { return _isLinux; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a Java Virtual Machine can be located in the supplied path.
|
* Checks whether a Java Virtual Machine can be located in the supplied path.
|
||||||
@@ -232,10 +232,9 @@ public class LaunchUtil
|
|||||||
try {
|
try {
|
||||||
String osname = System.getProperty("os.name");
|
String osname = System.getProperty("os.name");
|
||||||
osname = (osname == null) ? "" : osname;
|
osname = (osname == null) ? "" : osname;
|
||||||
_isWindows = (osname.indexOf("Windows") != -1);
|
_isWindows = (osname.contains("Windows"));
|
||||||
_isMacOS = (osname.indexOf("Mac OS") != -1 ||
|
_isMacOS = (osname.contains("Mac OS") || osname.contains("MacOS"));
|
||||||
osname.indexOf("MacOS") != -1);
|
_isLinux = (osname.contains("Linux"));
|
||||||
_isLinux = (osname.indexOf("Linux") != -1);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// can't grab system properties; we'll just pretend we're not on any of these OSes
|
// can't grab system properties; we'll just pretend we're not on any of these OSes
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
package com.threerings.getdown.util;
|
package com.threerings.getdown.util;
|
||||||
|
|
||||||
public class MessageUtil {
|
public final class MessageUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the provided string is tainted. See {@link #taint}. Null strings
|
* Returns whether or not the provided string is tainted. See {@link #taint}. Null strings
|
||||||
@@ -101,11 +101,11 @@ public class MessageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to escape single quotes so that they are not interpreted by <code>MessageFormat</code>.
|
* Used to escape single quotes so that they are not interpreted by {@code MessageFormat}.
|
||||||
* As we assume all single quotes are to be escaped, we cannot use the characters
|
* As we assume all single quotes are to be escaped, we cannot use the characters
|
||||||
* <code>{</code> and <code>}</code> in our translation strings, but this is a small price to
|
* <code>{</code> and <code>}</code> in our translation strings, but this is a small price to
|
||||||
* pay to have to differentiate between messages that will and won't eventually be parsed by a
|
* pay to have to differentiate between messages that will and won't eventually be parsed by a
|
||||||
* <code>MessageFormat</code> instance.
|
* {@code MessageFormat} instance.
|
||||||
*/
|
*/
|
||||||
public static String escape (String message)
|
public static String escape (String message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,5 +14,5 @@ public interface ProgressObserver
|
|||||||
* Informs the observer that we have completed the specified
|
* Informs the observer that we have completed the specified
|
||||||
* percentage of the process.
|
* percentage of the process.
|
||||||
*/
|
*/
|
||||||
public void progress (int percent);
|
void progress (int percent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
import static com.threerings.getdown.Log.log;
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
public class StreamUtil {
|
public final class StreamUtil {
|
||||||
/**
|
/**
|
||||||
* Convenient close for a stream. Use in a finally clause and love life.
|
* Convenient close for a stream. Use in a finally clause and love life.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ package com.threerings.getdown.util;
|
|||||||
|
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
public class StringUtil {
|
public final class StringUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the specified string could be a valid URL (contains no illegal characters)
|
* @return true if the specified string could be a valid URL (contains no illegal characters)
|
||||||
*/
|
*/
|
||||||
public static boolean couldBeValidUrl (String url)
|
public static boolean couldBeValidUrl (String url)
|
||||||
{
|
{
|
||||||
return url.matches("[A-Za-z0-9\\-\\._~:/\\?#\\[\\]@!$&'\\(\\)\\*\\+,;=%]+");
|
return url.matches("[A-Za-z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]+");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,7 +84,7 @@ public class StringUtil {
|
|||||||
source = source.replace(",,", "%COMMA%");
|
source = source.replace(",,", "%COMMA%");
|
||||||
|
|
||||||
// count up the number of tokens
|
// count up the number of tokens
|
||||||
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
|
while ((tpos = source.indexOf(',', tpos+1)) != -1) {
|
||||||
tcount++;
|
tcount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ public class StringUtil {
|
|||||||
tpos = -1; tcount = 0;
|
tpos = -1; tcount = 0;
|
||||||
|
|
||||||
// do the split
|
// do the split
|
||||||
while ((tpos = source.indexOf(",", tpos+1)) != -1) {
|
while ((tpos = source.indexOf(',', tpos+1)) != -1) {
|
||||||
tokens[tcount] = source.substring(tstart, tpos);
|
tokens[tcount] = source.substring(tstart, tpos);
|
||||||
tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
|
tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ",");
|
||||||
if (intern) {
|
if (intern) {
|
||||||
@@ -119,7 +119,7 @@ public class StringUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a string from the supplied bytes that is the HEX encoded representation of those
|
* Generates a string from the supplied bytes that is the HEX encoded representation of those
|
||||||
* bytes. Returns the empty string for a <code>null</code> or empty byte array.
|
* bytes. Returns the empty string for a {@code null} or empty byte array.
|
||||||
*
|
*
|
||||||
* @param bytes the bytes for which we want a string representation.
|
* @param bytes the bytes for which we want a string representation.
|
||||||
* @param count the number of bytes to stop at (which will be coerced into being {@code <=} the
|
* @param count the number of bytes to stop at (which will be coerced into being {@code <=} the
|
||||||
@@ -185,7 +185,7 @@ public class StringUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for the various <code>join</code> methods.
|
* Helper function for the various {@code join} methods.
|
||||||
*/
|
*/
|
||||||
protected static String join (Object[] values, String separator, boolean escape)
|
protected static String join (Object[] values, String separator, boolean escape)
|
||||||
{
|
{
|
||||||
@@ -201,6 +201,6 @@ public class StringUtil {
|
|||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used by {@link #hexlate} and {@link #unhexlate}. */
|
/** Used by {@link #hexlate}. */
|
||||||
protected static final String XLATE = "0123456789abcdef";
|
protected static final String XLATE = "0123456789abcdef";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||||||
/**
|
/**
|
||||||
* Version related utilities.
|
* Version related utilities.
|
||||||
*/
|
*/
|
||||||
public class VersionUtil
|
public final class VersionUtil
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Reads a version number from a file.
|
* Reads a version number from a file.
|
||||||
|
|||||||
@@ -38,6 +38,12 @@
|
|||||||
<version>1.0</version>
|
<version>1.0</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -189,6 +195,7 @@
|
|||||||
<lib>${java.home}/jmods/java.base.jmod</lib>
|
<lib>${java.home}/jmods/java.base.jmod</lib>
|
||||||
<lib>${java.home}/jmods/java.desktop.jmod</lib>
|
<lib>${java.home}/jmods/java.desktop.jmod</lib>
|
||||||
<lib>${java.home}/jmods/java.logging.jmod</lib>
|
<lib>${java.home}/jmods/java.logging.jmod</lib>
|
||||||
|
<lib>${java.home}/jmods/java.scripting.jmod</lib>
|
||||||
<lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
|
<lib>${java.home}/jmods/jdk.jsobject.jmod</lib>
|
||||||
</libs>
|
</libs>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public final class AbortPanel extends JFrame
|
|||||||
public void actionPerformed (ActionEvent e)
|
public void actionPerformed (ActionEvent e)
|
||||||
{
|
{
|
||||||
String cmd = e.getActionCommand();
|
String cmd = e.getActionCommand();
|
||||||
if (cmd.equals("ok")) {
|
if ("ok".equals(cmd)) {
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
} else {
|
} else {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|||||||
@@ -22,7 +22,15 @@ import java.io.InputStreamReader;
|
|||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
@@ -31,24 +39,44 @@ import javax.swing.JFrame;
|
|||||||
import javax.swing.JLayeredPane;
|
import javax.swing.JLayeredPane;
|
||||||
|
|
||||||
import com.samskivert.swing.util.SwingUtil;
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
import com.threerings.getdown.data.*;
|
|
||||||
import com.threerings.getdown.data.Application.UpdateInterface.Step;
|
import com.threerings.getdown.data.Application.UpdateInterface.Step;
|
||||||
|
import com.threerings.getdown.data.Application;
|
||||||
|
import com.threerings.getdown.data.Build;
|
||||||
|
import com.threerings.getdown.data.EnvConfig;
|
||||||
|
import com.threerings.getdown.data.Resource;
|
||||||
|
import com.threerings.getdown.data.SysProps;
|
||||||
|
import com.threerings.getdown.net.Connector;
|
||||||
import com.threerings.getdown.net.Downloader;
|
import com.threerings.getdown.net.Downloader;
|
||||||
import com.threerings.getdown.net.HTTPDownloader;
|
|
||||||
import com.threerings.getdown.tools.Patcher;
|
import com.threerings.getdown.tools.Patcher;
|
||||||
import com.threerings.getdown.util.*;
|
import com.threerings.getdown.util.Config;
|
||||||
|
import com.threerings.getdown.util.FileUtil;
|
||||||
|
import com.threerings.getdown.util.LaunchUtil;
|
||||||
|
import com.threerings.getdown.util.MessageUtil;
|
||||||
|
import com.threerings.getdown.util.ProgressAggregator;
|
||||||
|
import com.threerings.getdown.util.ProgressObserver;
|
||||||
|
import com.threerings.getdown.util.StringUtil;
|
||||||
|
import com.threerings.getdown.util.VersionUtil;
|
||||||
import static com.threerings.getdown.Log.log;
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the main control for the Getdown application updater and deployment system.
|
* Manages the main control for the Getdown application updater and deployment system.
|
||||||
*/
|
*/
|
||||||
public abstract class Getdown extends Thread
|
public abstract class Getdown
|
||||||
implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
|
implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Starts a thread to run Getdown and ultimately (hopefully) launch the target app.
|
||||||
|
*/
|
||||||
|
public static void run (final Getdown getdown) {
|
||||||
|
new Thread("Getdown") {
|
||||||
|
@Override public void run () {
|
||||||
|
getdown.run();
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
public Getdown (EnvConfig envc)
|
public Getdown (EnvConfig envc)
|
||||||
{
|
{
|
||||||
super("Getdown");
|
|
||||||
try {
|
try {
|
||||||
// If the silent property exists, install without bringing up any gui. If it equals
|
// If the silent property exists, install without bringing up any gui. If it equals
|
||||||
// launch, start the application after installing. Otherwise, just install and exit.
|
// launch, start the application after installing. Otherwise, just install and exit.
|
||||||
@@ -75,7 +103,7 @@ public abstract class Getdown extends Thread
|
|||||||
// welcome to hell, where java can't cope with a classpath that contains jars that live
|
// welcome to hell, where java can't cope with a classpath that contains jars that live
|
||||||
// in a directory that contains a !, at least the same bug happens on all platforms
|
// in a directory that contains a !, at least the same bug happens on all platforms
|
||||||
String dir = envc.appDir.toString();
|
String dir = envc.appDir.toString();
|
||||||
if (dir.equals(".")) {
|
if (".".equals(dir)) {
|
||||||
dir = System.getProperty("user.dir");
|
dir = System.getProperty("user.dir");
|
||||||
}
|
}
|
||||||
String errmsg = "The directory in which this application is installed:\n" + dir +
|
String errmsg = "The directory in which this application is installed:\n" + dir +
|
||||||
@@ -115,8 +143,28 @@ public abstract class Getdown extends Thread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void run ()
|
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
|
||||||
|
*/
|
||||||
|
public void configProxy (String host, String port, String username, String password)
|
||||||
|
{
|
||||||
|
log.info("User configured proxy", "host", host, "port", port);
|
||||||
|
ProxyUtil.configProxy(_app, host, port, username, password);
|
||||||
|
|
||||||
|
// clear out our UI
|
||||||
|
disposeContainer();
|
||||||
|
_container = null;
|
||||||
|
|
||||||
|
// fire up a new thread
|
||||||
|
run(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main entry point of Getdown: does some sanity checks and preparation, then delegates the
|
||||||
|
* actual getting down to {@link #getdown}. This is not called directly, but rather via the
|
||||||
|
* static {@code run} method as Getdown does its main work on a separate thread.
|
||||||
|
*/
|
||||||
|
protected void run ()
|
||||||
{
|
{
|
||||||
// if we have no messages, just bail because we're hosed; the error message will be
|
// if we have no messages, just bail because we're hosed; the error message will be
|
||||||
// displayed to the user already
|
// displayed to the user already
|
||||||
@@ -130,79 +178,27 @@ public abstract class Getdown extends Thread
|
|||||||
File instdir = _app.getLocalPath("");
|
File instdir = _app.getLocalPath("");
|
||||||
if (!instdir.canWrite()) {
|
if (!instdir.canWrite()) {
|
||||||
String path = instdir.getPath();
|
String path = instdir.getPath();
|
||||||
if (path.equals(".")) {
|
if (".".equals(path)) {
|
||||||
path = System.getProperty("user.dir");
|
path = System.getProperty("user.dir");
|
||||||
}
|
}
|
||||||
fail(MessageUtil.tcompose("m.readonly_error", path));
|
fail(MessageUtil.tcompose("m.readonly_error", path));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
_dead = false;
|
||||||
_dead = false;
|
// if we fail to detect a proxy, but we're allowed to run offline, then go ahead and
|
||||||
// if we fail to detect a proxy, but we're allowed to run offline, then go ahead and
|
// run the app anyway because we're prepared to cope with not being able to update
|
||||||
// run the app anyway because we're prepared to cope with not being able to update
|
if (detectProxy() || _app.allowOffline()) getdown();
|
||||||
if (detectProxy() || _app.allowOffline()) {
|
else requestProxyInfo(false);
|
||||||
getdown();
|
|
||||||
} else if (_silent) {
|
|
||||||
log.warning("Need a proxy, but we don't want to bother anyone. Exiting.");
|
|
||||||
} else {
|
|
||||||
// create a panel they can use to configure the proxy settings
|
|
||||||
_container = createContainer();
|
|
||||||
// allow them to close the window to abort the proxy configuration
|
|
||||||
_dead = true;
|
|
||||||
configureContainer();
|
|
||||||
ProxyPanel panel = new ProxyPanel(this, _msgs);
|
|
||||||
// set up any existing configured proxy
|
|
||||||
String[] hostPort = ProxyUtil.loadProxy(_app);
|
|
||||||
panel.setProxy(hostPort[0], hostPort[1]);
|
|
||||||
_container.add(panel, BorderLayout.CENTER);
|
|
||||||
showContainer();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warning("run() failed.", e);
|
|
||||||
String msg = e.getMessage();
|
|
||||||
if (msg == null) {
|
|
||||||
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
|
||||||
} else if (!msg.startsWith("m.")) {
|
|
||||||
// try to do something sensible based on the type of error
|
|
||||||
if (e instanceof FileNotFoundException) {
|
|
||||||
msg = MessageUtil.compose(
|
|
||||||
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
|
|
||||||
} else {
|
|
||||||
msg = MessageUtil.compose(
|
|
||||||
"m.init_error", MessageUtil.taint(msg), _ifc.installError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fail(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
|
|
||||||
*/
|
|
||||||
public void configProxy (String host, String port, String username, String password)
|
|
||||||
{
|
|
||||||
log.info("User configured proxy", "host", host, "port", port);
|
|
||||||
|
|
||||||
if (!StringUtil.isBlank(host)) {
|
|
||||||
ProxyUtil.configProxy(_app, host, port, username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear out our UI
|
|
||||||
disposeContainer();
|
|
||||||
_container = null;
|
|
||||||
|
|
||||||
// fire up a new thread
|
|
||||||
new Thread(this).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean detectProxy () {
|
protected boolean detectProxy () {
|
||||||
if (ProxyUtil.autoDetectProxy(_app)) {
|
boolean tryNoProxy = SysProps.tryNoProxyFirst();
|
||||||
|
if (!tryNoProxy && ProxyUtil.autoDetectProxy(_app)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise see if we actually need a proxy; first we have to initialize our application
|
// see if we actually need a proxy; first we have to initialize our application
|
||||||
// to get some sort of interface configuration and the appbase URL
|
// to get some sort of interface configuration and the appbase URL
|
||||||
log.info("Checking whether we need to use a proxy...");
|
log.info("Checking whether we need to use a proxy...");
|
||||||
try {
|
try {
|
||||||
@@ -211,14 +207,18 @@ public abstract class Getdown extends Thread
|
|||||||
// no worries
|
// no worries
|
||||||
}
|
}
|
||||||
updateStatus("m.detecting_proxy");
|
updateStatus("m.detecting_proxy");
|
||||||
if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) {
|
URL configURL = _app.getConfigResource().getRemote();
|
||||||
return false;
|
if (!ProxyUtil.canLoadWithoutProxy(configURL, tryNoProxy ? 2 : 5)) {
|
||||||
|
// if we didn't auto-detect proxy first thing, do auto-detect now
|
||||||
|
return tryNoProxy ? ProxyUtil.autoDetectProxy(_app) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we got through, so we appear not to require a proxy; make a blank proxy config so that
|
|
||||||
// we don't go through this whole detection process again next time
|
|
||||||
log.info("No proxy appears to be needed.");
|
log.info("No proxy appears to be needed.");
|
||||||
ProxyUtil.saveProxy(_app, null, null);
|
if (!tryNoProxy) {
|
||||||
|
// we got through, so we appear not to require a proxy; make a blank proxy config so
|
||||||
|
// that we don't go through this whole detection process again next time
|
||||||
|
ProxyUtil.saveProxy(_app, null, null);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +228,25 @@ public abstract class Getdown extends Thread
|
|||||||
_ifc = new Application.UpdateInterface(config);
|
_ifc = new Application.UpdateInterface(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void requestProxyInfo (boolean reinitAuth) {
|
||||||
|
if (_silent) {
|
||||||
|
log.warning("Need a proxy, but we don't want to bother anyone. Exiting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a panel they can use to configure the proxy settings
|
||||||
|
_container = createContainer();
|
||||||
|
// allow them to close the window to abort the proxy configuration
|
||||||
|
_dead = true;
|
||||||
|
configureContainer();
|
||||||
|
ProxyPanel panel = new ProxyPanel(this, _msgs, reinitAuth);
|
||||||
|
// set up any existing configured proxy
|
||||||
|
String[] hostPort = ProxyUtil.loadProxy(_app);
|
||||||
|
panel.setProxy(hostPort[0], hostPort[1]);
|
||||||
|
_container.add(panel, BorderLayout.CENTER);
|
||||||
|
showContainer();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads and installs (without verifying) any resources that are marked with a
|
* Downloads and installs (without verifying) any resources that are marked with a
|
||||||
* {@code PRELOAD} attribute.
|
* {@code PRELOAD} attribute.
|
||||||
@@ -258,7 +277,7 @@ public abstract class Getdown extends Thread
|
|||||||
protected void getdown ()
|
protected void getdown ()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// first parses our application deployment file
|
// first parse our application deployment file
|
||||||
try {
|
try {
|
||||||
readConfig(true);
|
readConfig(true);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
@@ -273,7 +292,7 @@ public abstract class Getdown extends Thread
|
|||||||
throw new MultipleGetdownRunning();
|
throw new MultipleGetdownRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the config modtime so a sleeping getdown will notice the change.
|
// update the config modtime so a sleeping getdown will notice the change
|
||||||
File config = _app.getLocalPath(Application.CONFIG_FILE);
|
File config = _app.getLocalPath(Application.CONFIG_FILE);
|
||||||
if (!config.setLastModified(System.currentTimeMillis())) {
|
if (!config.setLastModified(System.currentTimeMillis())) {
|
||||||
log.warning("Unable to set modtime on config file, will be unable to check for " +
|
log.warning("Unable to set modtime on config file, will be unable to check for " +
|
||||||
@@ -285,7 +304,7 @@ public abstract class Getdown extends Thread
|
|||||||
// Store the config modtime before waiting the delay amount of time
|
// Store the config modtime before waiting the delay amount of time
|
||||||
long lastConfigModtime = config.lastModified();
|
long lastConfigModtime = config.lastModified();
|
||||||
log.info("Waiting " + _delay + " minutes before beginning actual work.");
|
log.info("Waiting " + _delay + " minutes before beginning actual work.");
|
||||||
Thread.sleep(_delay * 60 * 1000);
|
TimeUnit.MINUTES.sleep(_delay);
|
||||||
if (lastConfigModtime < config.lastModified()) {
|
if (lastConfigModtime < config.lastModified()) {
|
||||||
log.warning("getdown.txt was modified while getdown was waiting.");
|
log.warning("getdown.txt was modified while getdown was waiting.");
|
||||||
throw new MultipleGetdownRunning();
|
throw new MultipleGetdownRunning();
|
||||||
@@ -334,11 +353,7 @@ public abstract class Getdown extends Thread
|
|||||||
|
|
||||||
if (toDownload.size() > 0) {
|
if (toDownload.size() > 0) {
|
||||||
// we have resources to download, also note them as to-be-installed
|
// we have resources to download, also note them as to-be-installed
|
||||||
for (Resource r : toDownload) {
|
_toInstallResources.addAll(toDownload);
|
||||||
if (!_toInstallResources.contains(r)) {
|
|
||||||
_toInstallResources.add(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// if any of our resources have already been marked valid this is not a
|
// if any of our resources have already been marked valid this is not a
|
||||||
@@ -422,24 +437,20 @@ public abstract class Getdown extends Thread
|
|||||||
throw new IOException("m.unable_to_repair");
|
throw new IOException("m.unable_to_repair");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warning("getdown() failed.", e);
|
// if we failed due to proxy errors, ask for proxy info
|
||||||
String msg = e.getMessage();
|
switch (_app.conn.state) {
|
||||||
if (msg == null) {
|
case NEED_PROXY:
|
||||||
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
requestProxyInfo(false);
|
||||||
} else if (!msg.startsWith("m.")) {
|
break;
|
||||||
// try to do something sensible based on the type of error
|
case NEED_PROXY_AUTH:
|
||||||
if (e instanceof FileNotFoundException) {
|
requestProxyInfo(true);
|
||||||
msg = MessageUtil.compose(
|
break;
|
||||||
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
|
default:
|
||||||
} else {
|
log.warning("getdown() failed.", e);
|
||||||
msg = MessageUtil.compose(
|
fail(e);
|
||||||
"m.init_error", MessageUtil.taint(msg), _ifc.installError);
|
_app.releaseLock();
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
// Since we're dead, clear off the 'time remaining' label along with displaying the
|
|
||||||
// error message
|
|
||||||
fail(msg);
|
|
||||||
_app.releaseLock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +536,7 @@ public abstract class Getdown extends Thread
|
|||||||
|
|
||||||
// lastly regenerate the .jsa dump file that helps Java to start up faster
|
// lastly regenerate the .jsa dump file that helps Java to start up faster
|
||||||
String vmpath = LaunchUtil.getJVMBinaryPath(javaLocalDir, false);
|
String vmpath = LaunchUtil.getJVMBinaryPath(javaLocalDir, false);
|
||||||
String[] command = new String[] { vmpath, "-Xshare:dump" };
|
String[] command = { vmpath, "-Xshare:dump" };
|
||||||
try {
|
try {
|
||||||
log.info("Regenerating classes.jsa for " + vmpath + "...");
|
log.info("Regenerating classes.jsa for " + vmpath + "...");
|
||||||
Runtime.getRuntime().exec(command);
|
Runtime.getRuntime().exec(command);
|
||||||
@@ -620,7 +631,7 @@ public abstract class Getdown extends Thread
|
|||||||
// create our user interface
|
// create our user interface
|
||||||
createInterfaceAsync(false);
|
createInterfaceAsync(false);
|
||||||
|
|
||||||
Downloader dl = new HTTPDownloader(_app.proxy) {
|
Downloader dl = new Downloader(_app.conn) {
|
||||||
@Override protected void resolvingDownloads () {
|
@Override protected void resolvingDownloads () {
|
||||||
updateStatus("m.resolving");
|
updateStatus("m.resolving");
|
||||||
}
|
}
|
||||||
@@ -732,7 +743,7 @@ public abstract class Getdown extends Thread
|
|||||||
long minshow = _ifc.minShowSeconds * 1000L;
|
long minshow = _ifc.minShowSeconds * 1000L;
|
||||||
if (_container != null && uptime < minshow) {
|
if (_container != null && uptime < minshow) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(minshow - uptime);
|
TimeUnit.MILLISECONDS.sleep(minshow - uptime);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,8 +859,23 @@ public abstract class Getdown extends Thread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fail (Exception e) {
|
||||||
|
String msg = e.getMessage();
|
||||||
|
if (msg == null) {
|
||||||
|
msg = MessageUtil.compose("m.unknown_error", _ifc.installError);
|
||||||
|
} else if (!msg.startsWith("m.")) {
|
||||||
|
// try to do something sensible based on the type of error
|
||||||
|
msg = MessageUtil.taint(msg);
|
||||||
|
msg = e instanceof FileNotFoundException ?
|
||||||
|
MessageUtil.compose("m.missing_resource", msg, _ifc.installError) :
|
||||||
|
MessageUtil.compose("m.init_error", msg, _ifc.installError);
|
||||||
|
}
|
||||||
|
// since we're dead, clear off the 'time remaining' label along with displaying the error
|
||||||
|
fail(msg);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the status to indicate getdown has failed for the reason in <code>message</code>.
|
* Update the status to indicate getdown has failed for the reason in {@code message}.
|
||||||
*/
|
*/
|
||||||
protected void fail (String message)
|
protected void fail (String message)
|
||||||
{
|
{
|
||||||
@@ -930,14 +956,14 @@ public abstract class Getdown extends Thread
|
|||||||
do {
|
do {
|
||||||
URL url = _app.getTrackingProgressURL(++_reportedProgress);
|
URL url = _app.getTrackingProgressURL(++_reportedProgress);
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
new ProgressReporter(url).start();
|
reportProgress(url);
|
||||||
}
|
}
|
||||||
} while (_reportedProgress <= progress);
|
} while (_reportedProgress <= progress);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
URL url = _app.getTrackingURL(event);
|
URL url = _app.getTrackingURL(event);
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
new ProgressReporter(url).start();
|
reportProgress(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1000,44 +1026,40 @@ public abstract class Getdown extends Thread
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Used to fetch a progress report URL. */
|
/** Used to fetch a progress report URL. */
|
||||||
protected class ProgressReporter extends Thread
|
protected void reportProgress (final URL url) {
|
||||||
{
|
Thread reporter = new Thread("Progress reporter") {
|
||||||
public ProgressReporter (URL url) {
|
public void run () {
|
||||||
setDaemon(true);
|
|
||||||
_url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run () {
|
|
||||||
try {
|
|
||||||
HttpURLConnection ucon = ConnectionUtil.openHttp(_app.proxy, _url, 0, 0);
|
|
||||||
|
|
||||||
// if we have a tracking cookie configured, configure the request with it
|
|
||||||
if (_app.getTrackingCookieName() != null &&
|
|
||||||
_app.getTrackingCookieProperty() != null) {
|
|
||||||
String val = System.getProperty(_app.getTrackingCookieProperty());
|
|
||||||
if (val != null) {
|
|
||||||
ucon.setRequestProperty("Cookie", _app.getTrackingCookieName() + "=" + val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now request our tracking URL and ensure that we get a non-error response
|
|
||||||
ucon.connect();
|
|
||||||
try {
|
try {
|
||||||
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
HttpURLConnection ucon = _app.conn.openHttp(url, 0, 0);
|
||||||
log.warning("Failed to report tracking event",
|
|
||||||
"url", _url, "rcode", ucon.getResponseCode());
|
// if we have a tracking cookie configured, configure the request with it
|
||||||
|
if (_app.getTrackingCookieName() != null &&
|
||||||
|
_app.getTrackingCookieProperty() != null) {
|
||||||
|
String val = System.getProperty(_app.getTrackingCookieProperty());
|
||||||
|
if (val != null) {
|
||||||
|
ucon.setRequestProperty(
|
||||||
|
"Cookie", _app.getTrackingCookieName() + "=" + val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
ucon.disconnect();
|
// now request our tracking URL and ensure that we get a non-error response
|
||||||
|
ucon.connect();
|
||||||
|
try {
|
||||||
|
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
log.warning("Failed to report tracking event",
|
||||||
|
"url", url, "rcode", ucon.getResponseCode());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ucon.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
log.warning("Failed to report tracking event", "url", url, "error", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
log.warning("Failed to report tracking event", "url", _url, "error", ioe);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
reporter.setDaemon(true);
|
||||||
protected URL _url;
|
reporter.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used to pass progress on to our user interface. */
|
/** Used to pass progress on to our user interface. */
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public class GetdownApp
|
|||||||
log.info("-- Cur dir: " + System.getProperty("user.dir"));
|
log.info("-- Cur dir: " + System.getProperty("user.dir"));
|
||||||
log.info("---------------------------------------------");
|
log.info("---------------------------------------------");
|
||||||
|
|
||||||
Getdown app = new Getdown(envc) {
|
Getdown getdown = new Getdown(envc) {
|
||||||
@Override
|
@Override
|
||||||
protected Container createContainer () {
|
protected Container createContainer () {
|
||||||
// create our user interface, and display it
|
// create our user interface, and display it
|
||||||
@@ -146,7 +146,7 @@ public class GetdownApp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_ifc.iconImages != null) {
|
if (_ifc.iconImages != null) {
|
||||||
ArrayList<Image> icons = new ArrayList<>();
|
List<Image> icons = new ArrayList<>();
|
||||||
for (String path : _ifc.iconImages) {
|
for (String path : _ifc.iconImages) {
|
||||||
Image img = loadImage(path);
|
Image img = loadImage(path);
|
||||||
if (img == null) {
|
if (img == null) {
|
||||||
@@ -190,7 +190,7 @@ public class GetdownApp
|
|||||||
String[] cmdarray;
|
String[] cmdarray;
|
||||||
if (LaunchUtil.isWindows()) {
|
if (LaunchUtil.isWindows()) {
|
||||||
String osName = System.getProperty("os.name", "");
|
String osName = System.getProperty("os.name", "");
|
||||||
if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) {
|
if (osName.contains("9") || osName.contains("Me")) {
|
||||||
cmdarray = new String[] {
|
cmdarray = new String[] {
|
||||||
"command.com", "/c", "start", "\"" + url + "\"" };
|
"command.com", "/c", "start", "\"" + url + "\"" };
|
||||||
} else {
|
} else {
|
||||||
@@ -226,8 +226,7 @@ public class GetdownApp
|
|||||||
// super.fail causes the UI to be created (if needed) on the next UI tick, so we
|
// super.fail causes the UI to be created (if needed) on the next UI tick, so we
|
||||||
// want to wait until that happens before we attempt to redecorate the window
|
// want to wait until that happens before we attempt to redecorate the window
|
||||||
EventQueue.invokeLater(new Runnable() {
|
EventQueue.invokeLater(new Runnable() {
|
||||||
@Override
|
@Override public void run () {
|
||||||
public void run() {
|
|
||||||
// if the frame was set to be undecorated, make window decoration available
|
// if the frame was set to be undecorated, make window decoration available
|
||||||
// to allow the user to close the window
|
// to allow the user to close the window
|
||||||
if (_frame != null && _frame.isUndecorated()) {
|
if (_frame != null && _frame.isUndecorated()) {
|
||||||
@@ -247,7 +246,7 @@ public class GetdownApp
|
|||||||
|
|
||||||
protected JFrame _frame;
|
protected JFrame _frame;
|
||||||
};
|
};
|
||||||
app.start();
|
Getdown.run(getdown);
|
||||||
return app;
|
return getdown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,16 @@ import static com.threerings.getdown.Log.log;
|
|||||||
*/
|
*/
|
||||||
public final class ProxyPanel extends JPanel implements ActionListener
|
public final class ProxyPanel extends JPanel implements ActionListener
|
||||||
{
|
{
|
||||||
public ProxyPanel (Getdown getdown, ResourceBundle msgs)
|
public ProxyPanel (Getdown getdown, ResourceBundle msgs, boolean updateAuth)
|
||||||
{
|
{
|
||||||
_getdown = getdown;
|
_getdown = getdown;
|
||||||
_msgs = msgs;
|
_msgs = msgs;
|
||||||
|
_updateAuth = updateAuth;
|
||||||
|
|
||||||
setLayout(new VGroupLayout());
|
setLayout(new VGroupLayout());
|
||||||
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
add(new SaneLabelField(get("m.configure_proxy")));
|
String title = get(updateAuth ? "m.update_proxy_auth" : "m.configure_proxy");
|
||||||
|
add(new SaneLabelField(title));
|
||||||
add(new Spacer(5, 5));
|
add(new Spacer(5, 5));
|
||||||
|
|
||||||
JPanel row = new JPanel(new GridLayout());
|
JPanel row = new JPanel(new GridLayout());
|
||||||
@@ -61,19 +63,20 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
|||||||
row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
|
row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST);
|
||||||
_useAuth = new JCheckBox();
|
_useAuth = new JCheckBox();
|
||||||
row.add(_useAuth);
|
row.add(_useAuth);
|
||||||
|
_useAuth.setSelected(updateAuth);
|
||||||
add(row);
|
add(row);
|
||||||
|
|
||||||
row = new JPanel(new GridLayout());
|
row = new JPanel(new GridLayout());
|
||||||
row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
|
row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST);
|
||||||
_username = new SaneTextField();
|
_username = new SaneTextField();
|
||||||
_username.setEnabled(false);
|
_username.setEnabled(updateAuth);
|
||||||
row.add(_username);
|
row.add(_username);
|
||||||
add(row);
|
add(row);
|
||||||
|
|
||||||
row = new JPanel(new GridLayout());
|
row = new JPanel(new GridLayout());
|
||||||
row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST);
|
row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST);
|
||||||
_password = new SanePasswordField();
|
_password = new SanePasswordField();
|
||||||
_password.setEnabled(false);
|
_password.setEnabled(updateAuth);
|
||||||
row.add(_password);
|
row.add(_password);
|
||||||
add(row);
|
add(row);
|
||||||
|
|
||||||
@@ -112,7 +115,13 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
|||||||
public void addNotify ()
|
public void addNotify ()
|
||||||
{
|
{
|
||||||
super.addNotify();
|
super.addNotify();
|
||||||
_host.requestFocusInWindow();
|
if (_updateAuth) {
|
||||||
|
// we are asking the user to update the credentials for an existing proxy
|
||||||
|
// configuration, so focus that instead of the proxy host config
|
||||||
|
_username.requestFocusInWindow();
|
||||||
|
} else {
|
||||||
|
_host.requestFocusInWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
@@ -131,7 +140,7 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
|||||||
public void actionPerformed (ActionEvent e)
|
public void actionPerformed (ActionEvent e)
|
||||||
{
|
{
|
||||||
String cmd = e.getActionCommand();
|
String cmd = e.getActionCommand();
|
||||||
if (cmd.equals("ok")) {
|
if ("ok".equals(cmd)) {
|
||||||
String user = null, pass = null;
|
String user = null, pass = null;
|
||||||
if (_useAuth.isSelected()) {
|
if (_useAuth.isSelected()) {
|
||||||
user = _username.getText();
|
user = _username.getText();
|
||||||
@@ -184,8 +193,9 @@ public final class ProxyPanel extends JPanel implements ActionListener
|
|||||||
return dim;
|
return dim;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Getdown _getdown;
|
protected final Getdown _getdown;
|
||||||
protected ResourceBundle _msgs;
|
protected final ResourceBundle _msgs;
|
||||||
|
protected final boolean _updateAuth;
|
||||||
|
|
||||||
protected JTextField _host;
|
protected JTextField _host;
|
||||||
protected JTextField _port;
|
protected JTextField _port;
|
||||||
|
|||||||
@@ -8,31 +8,41 @@ package com.threerings.getdown.launcher;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
import java.io.Reader;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.PasswordAuthentication;
|
import java.net.PasswordAuthentication;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
import javax.script.Bindings;
|
||||||
|
import javax.script.Invocable;
|
||||||
|
import javax.script.ScriptContext;
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
|
import javax.script.ScriptEngineManager;
|
||||||
|
|
||||||
import ca.beq.util.win32.registry.RegistryKey;
|
import ca.beq.util.win32.registry.RegistryKey;
|
||||||
import ca.beq.util.win32.registry.RegistryValue;
|
import ca.beq.util.win32.registry.RegistryValue;
|
||||||
import ca.beq.util.win32.registry.RootKey;
|
import ca.beq.util.win32.registry.RootKey;
|
||||||
|
|
||||||
import com.threerings.getdown.data.Application;
|
import com.threerings.getdown.data.Application;
|
||||||
|
import com.threerings.getdown.net.Connector;
|
||||||
import com.threerings.getdown.spi.ProxyAuth;
|
import com.threerings.getdown.spi.ProxyAuth;
|
||||||
import com.threerings.getdown.util.Config;
|
import com.threerings.getdown.util.Config;
|
||||||
import com.threerings.getdown.util.ConnectionUtil;
|
|
||||||
import com.threerings.getdown.util.LaunchUtil;
|
import com.threerings.getdown.util.LaunchUtil;
|
||||||
import com.threerings.getdown.util.StringUtil;
|
import com.threerings.getdown.util.StringUtil;
|
||||||
|
|
||||||
import static com.threerings.getdown.Log.log;
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
public class ProxyUtil {
|
public final class ProxyUtil {
|
||||||
|
|
||||||
public static boolean autoDetectProxy (Application app)
|
public static boolean autoDetectProxy (Application app)
|
||||||
{
|
{
|
||||||
@@ -57,25 +67,39 @@ public class ProxyUtil {
|
|||||||
RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
|
RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
|
||||||
for (Iterator<?> iter = r.values(); iter.hasNext(); ) {
|
for (Iterator<?> iter = r.values(); iter.hasNext(); ) {
|
||||||
RegistryValue value = (RegistryValue)iter.next();
|
RegistryValue value = (RegistryValue)iter.next();
|
||||||
if (value.getName().equals("ProxyEnable")) {
|
if ("ProxyEnable".equals(value.getName())) {
|
||||||
enabled = value.getStringValue().equals("1");
|
enabled = "1".equals(value.getStringValue());
|
||||||
}
|
}
|
||||||
if (value.getName().equals("ProxyServer")) {
|
if (value.getName().equals("ProxyServer")) {
|
||||||
String strval = value.getStringValue();
|
String[] hostPort = splitHostPort(value.getStringValue());
|
||||||
int cidx = strval.indexOf(":");
|
rhost = hostPort[0];
|
||||||
if (cidx != -1) {
|
rport = hostPort[1];
|
||||||
rport = strval.substring(cidx+1);
|
}
|
||||||
strval = strval.substring(0, cidx);
|
if (value.getName().equals("AutoConfigURL")) {
|
||||||
|
String acurl = value.getStringValue();
|
||||||
|
Reader acjs = new InputStreamReader(new URL(acurl).openStream());
|
||||||
|
// technically we should be returning all this info and trying each proxy
|
||||||
|
// in succession, but that's complexity we'll leave for another day
|
||||||
|
for (String proxy : findPACProxiesForURL(acjs, app.getRemoteURL(""))) {
|
||||||
|
if (proxy.startsWith("PROXY ")) {
|
||||||
|
String[] hostPort = splitHostPort(proxy.substring(6));
|
||||||
|
rhost = hostPort[0];
|
||||||
|
rport = hostPort[1];
|
||||||
|
// TODO: is this valid? Does AutoConfigURL imply proxy enabled?
|
||||||
|
enabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rhost = strval;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
host = rhost;
|
host = rhost;
|
||||||
port = rport;
|
port = rport;
|
||||||
} else {
|
} else {
|
||||||
log.info("Detected no proxy settings in the registry.");
|
log.info("Detected no proxy settings in the registry.");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
log.info("Failed to find proxy settings in Windows registry", "error", t);
|
log.info("Failed to find proxy settings in Windows registry", "error", t);
|
||||||
}
|
}
|
||||||
@@ -97,32 +121,31 @@ public class ProxyUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canLoadWithoutProxy (URL rurl)
|
public static boolean canLoadWithoutProxy (URL rurl, int timeoutSeconds)
|
||||||
{
|
{
|
||||||
log.info("Testing whether proxy is needed, via: " + rurl);
|
log.info("Attempting to fetch without proxy: " + rurl);
|
||||||
try {
|
try {
|
||||||
// try to make a HEAD request for this URL (use short connect and read timeouts)
|
URLConnection conn = Connector.DEFAULT.open(rurl, timeoutSeconds, timeoutSeconds);
|
||||||
URLConnection conn = ConnectionUtil.open(Proxy.NO_PROXY, rurl, 5, 5);
|
// if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy
|
||||||
if (conn instanceof HttpURLConnection) {
|
if (!(conn instanceof HttpURLConnection)) {
|
||||||
HttpURLConnection hcon = (HttpURLConnection)conn;
|
|
||||||
try {
|
|
||||||
hcon.setRequestMethod("HEAD");
|
|
||||||
hcon.connect();
|
|
||||||
// make sure we got a satisfactory response code
|
|
||||||
int rcode = hcon.getResponseCode();
|
|
||||||
if (rcode == HttpURLConnection.HTTP_PROXY_AUTH ||
|
|
||||||
rcode == HttpURLConnection.HTTP_FORBIDDEN) {
|
|
||||||
log.warning("Got an 'HTTP credentials needed' response", "code", rcode);
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
hcon.disconnect();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the appbase is not an HTTP/S URL (like file:), then we don't need a proxy
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// otherwise, try to make a HEAD request for this URL
|
||||||
|
HttpURLConnection hcon = (HttpURLConnection)conn;
|
||||||
|
try {
|
||||||
|
hcon.setRequestMethod("HEAD");
|
||||||
|
hcon.connect();
|
||||||
|
// make sure we got a satisfactory response code
|
||||||
|
int rcode = hcon.getResponseCode();
|
||||||
|
if (rcode == HttpURLConnection.HTTP_PROXY_AUTH ||
|
||||||
|
rcode == HttpURLConnection.HTTP_FORBIDDEN) {
|
||||||
|
log.warning("Got an 'HTTP credentials needed' response", "code", rcode);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
hcon.disconnect();
|
||||||
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
log.info("Failed to HEAD " + rurl + ": " + ioe);
|
log.info("Failed to HEAD " + rurl + ": " + ioe);
|
||||||
log.info("We probably need a proxy, but auto-detection failed.");
|
log.info("We probably need a proxy, but auto-detection failed.");
|
||||||
@@ -190,9 +213,14 @@ public class ProxyUtil {
|
|||||||
}
|
}
|
||||||
boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password);
|
boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password);
|
||||||
|
|
||||||
int pport = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port);
|
if (StringUtil.isBlank(host)) {
|
||||||
log.info("Using proxy", "host", host, "port", pport, "haveCreds", haveCreds);
|
log.info("Using no proxy");
|
||||||
app.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pport));
|
app.conn = new Connector(Proxy.NO_PROXY);
|
||||||
|
} else {
|
||||||
|
int pp = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port);
|
||||||
|
log.info("Using proxy", "host", host, "port", pp, "haveCreds", haveCreds);
|
||||||
|
app.conn = new Connector(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pp)));
|
||||||
|
}
|
||||||
|
|
||||||
if (haveCreds) {
|
if (haveCreds) {
|
||||||
final String fuser = username;
|
final String fuser = username;
|
||||||
@@ -205,6 +233,54 @@ public class ProxyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Resolver {
|
||||||
|
public String dnsResolve (String host) {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(host).toString();
|
||||||
|
} catch (UnknownHostException uhe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] findPACProxiesForURL (Reader pac, URL url) {
|
||||||
|
ScriptEngineManager manager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = manager.getEngineByName("javascript");
|
||||||
|
Bindings globals = engine.createBindings();
|
||||||
|
globals.put("resolver", new Resolver());
|
||||||
|
engine.setBindings(globals, ScriptContext.GLOBAL_SCOPE);
|
||||||
|
try {
|
||||||
|
URL utils = ProxyUtil.class.getResource("PacUtils.js");
|
||||||
|
if (utils == null) {
|
||||||
|
log.error("Unable to load PacUtils.js");
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
engine.eval(new InputStreamReader(utils.openStream()));
|
||||||
|
Object res = engine.eval(pac);
|
||||||
|
if (engine instanceof Invocable) {
|
||||||
|
Object[] args = new Object[] { url.toString(), url.getHost() };
|
||||||
|
res = ((Invocable) engine).invokeFunction("FindProxyForURL", args);
|
||||||
|
}
|
||||||
|
String[] proxies = res.toString().split(";");
|
||||||
|
for (int ii = 0; ii < proxies.length; ii += 1) {
|
||||||
|
proxies[ii] = proxies[ii].trim();
|
||||||
|
}
|
||||||
|
return proxies;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warning("Failed to resolve PAC proxy", e);
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] splitHostPort (String hostPort) {
|
||||||
|
int cidx = hostPort.indexOf(":");
|
||||||
|
if (cidx == -1) {
|
||||||
|
return new String[] { hostPort, null};
|
||||||
|
} else {
|
||||||
|
return new String[] { hostPort.substring(0, cidx), hostPort.substring(cidx+1) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static final String PROXY_REGISTRY =
|
protected static final String PROXY_REGISTRY =
|
||||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public final class RotatingBackgrounds
|
|||||||
{
|
{
|
||||||
public interface ImageLoader {
|
public interface ImageLoader {
|
||||||
/** Loads and returns the image with the supplied path. */
|
/** Loads and returns the image with the supplied path. */
|
||||||
public Image loadImage (String path);
|
Image loadImage (String path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,7 +35,7 @@ public final class RotatingBackgrounds
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sequence of images to be rotated through from <code>backgrounds</code>.
|
* Create a sequence of images to be rotated through from {@code backgrounds}.
|
||||||
*
|
*
|
||||||
* Each String in backgrounds should be the path to the image, a semicolon, and the minimum
|
* Each String in backgrounds should be the path to the image, a semicolon, and the minimum
|
||||||
* amount of time to display the image in seconds. Each image will be active for an equal
|
* amount of time to display the image in seconds. Each image will be active for an equal
|
||||||
|
|||||||
@@ -26,12 +26,9 @@ import com.samskivert.swing.Label;
|
|||||||
import com.samskivert.swing.LabelStyleConstants;
|
import com.samskivert.swing.LabelStyleConstants;
|
||||||
import com.samskivert.swing.util.SwingUtil;
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
import com.samskivert.util.Throttle;
|
import com.samskivert.util.Throttle;
|
||||||
|
|
||||||
import com.threerings.getdown.data.Application.UpdateInterface;
|
import com.threerings.getdown.data.Application.UpdateInterface;
|
||||||
import com.threerings.getdown.util.MessageUtil;
|
import com.threerings.getdown.util.MessageUtil;
|
||||||
import com.threerings.getdown.util.Rectangle;
|
import com.threerings.getdown.util.Rectangle;
|
||||||
import com.threerings.getdown.util.StringUtil;
|
|
||||||
|
|
||||||
import static com.threerings.getdown.Log.log;
|
import static com.threerings.getdown.Log.log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -344,7 +341,7 @@ public final class StatusPanel extends JComponent
|
|||||||
{
|
{
|
||||||
String msg = get(key);
|
String msg = get(key);
|
||||||
if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
|
if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
|
||||||
return key + String.valueOf(Arrays.asList(args));
|
return key + Arrays.asList(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Used by {@link #setStatus}, and {@link #setProgress}. */
|
/** Used by {@link #setStatus}, and {@link #setProgress}. */
|
||||||
|
|||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/**
|
||||||
|
* Extracted from Mozilla Firefox 3.0.4, \mozilla\netwerk\base\src\nsProxyAutoConfig.js
|
||||||
|
*/
|
||||||
|
function dnsDomainIs (host, domain) {
|
||||||
|
return (host.length >= domain.length &&
|
||||||
|
host.substring(host.length - domain.length) == domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dnsDomainLevels (host) {
|
||||||
|
return host.split('.').length-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_addr (ipchars) {
|
||||||
|
var bytes = ipchars.split('.');
|
||||||
|
var result = (((bytes[0] & 0xff) << 24) |
|
||||||
|
((bytes[1] & 0xff) << 16) |
|
||||||
|
((bytes[2] & 0xff) << 8) |
|
||||||
|
( bytes[3] & 0xff ));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dnsResolve (host) {
|
||||||
|
return resolver.dnsResolve(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInNet (addrOrHost, pattern, maskstr) {
|
||||||
|
var testRE = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
|
||||||
|
var test = testRE.exec(addrOrHost);
|
||||||
|
if (test == null) {
|
||||||
|
addrOrHost = dnsResolve(addrOrHost);
|
||||||
|
if (addrOrHost == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (test[1] > 255 || test[2] > 255 || test[3] > 255 || test[4] > 255) {
|
||||||
|
return false; // not an IP address
|
||||||
|
}
|
||||||
|
var host = convert_addr(addrOrHost);
|
||||||
|
var pat = convert_addr(pattern);
|
||||||
|
var mask = convert_addr(maskstr);
|
||||||
|
return ((host & mask) == (pat & mask));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainHostName (host) {
|
||||||
|
return (host.search('\\.') == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isResolvable (host) {
|
||||||
|
var ip = dnsResolve(host);
|
||||||
|
return (ip != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function localHostOrDomainIs (host, hostdom) {
|
||||||
|
return (host == hostdom) || (hostdom.lastIndexOf(host + '.', 0) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function shExpMatch (url, pattern) {
|
||||||
|
pattern = pattern.replace(/\./g, '\\.');
|
||||||
|
pattern = pattern.replace(/\*/g, '.*');
|
||||||
|
pattern = pattern.replace(/\?/g, '.');
|
||||||
|
var newRe = new RegExp('^'+pattern+'$');
|
||||||
|
return newRe.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wdays = {
|
||||||
|
SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6
|
||||||
|
};
|
||||||
|
|
||||||
|
var months = {
|
||||||
|
JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11
|
||||||
|
};
|
||||||
|
|
||||||
|
function weekdayRange () {
|
||||||
|
function getDay (weekday) {
|
||||||
|
if (weekday in wdays) {
|
||||||
|
return wdays[weekday];
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
var date = new Date();
|
||||||
|
var argc = arguments.length;
|
||||||
|
var wday;
|
||||||
|
if (argc < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (arguments[argc - 1] == 'GMT') {
|
||||||
|
argc--;
|
||||||
|
wday = date.getUTCDay();
|
||||||
|
} else {
|
||||||
|
wday = date.getDay();
|
||||||
|
}
|
||||||
|
var wd1 = getDay(arguments[0]);
|
||||||
|
var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;
|
||||||
|
return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateRange () {
|
||||||
|
function getMonth (name) {
|
||||||
|
if (name in months) {
|
||||||
|
return months[name];
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
var date = new Date();
|
||||||
|
var argc = arguments.length;
|
||||||
|
if (argc < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var isGMT = (arguments[argc - 1] == 'GMT');
|
||||||
|
if (isGMT) {
|
||||||
|
argc--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// function will work even without explict handling of this case
|
||||||
|
if (argc == 1) {
|
||||||
|
var tmp = parseInt(arguments[0]);
|
||||||
|
if (isNaN(tmp)) {
|
||||||
|
return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0]));
|
||||||
|
} else if (tmp < 32) {
|
||||||
|
return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);
|
||||||
|
} else {
|
||||||
|
return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var year = date.getFullYear();
|
||||||
|
var date1, date2;
|
||||||
|
date1 = new Date(year, 0, 1, 0, 0, 0);
|
||||||
|
date2 = new Date(year, 11, 31, 23, 59, 59);
|
||||||
|
var adjustMonth = false;
|
||||||
|
for (var i = 0; i < (argc >> 1); i++) {
|
||||||
|
var tmp = parseInt(arguments[i]);
|
||||||
|
if (isNaN(tmp)) {
|
||||||
|
var mon = getMonth(arguments[i]);
|
||||||
|
date1.setMonth(mon);
|
||||||
|
} else if (tmp < 32) {
|
||||||
|
adjustMonth = (argc <= 2);
|
||||||
|
date1.setDate(tmp);
|
||||||
|
} else {
|
||||||
|
date1.setFullYear(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = (argc >> 1); i < argc; i++) {
|
||||||
|
var tmp = parseInt(arguments[i]);
|
||||||
|
if (isNaN(tmp)) {
|
||||||
|
var mon = getMonth(arguments[i]);
|
||||||
|
date2.setMonth(mon);
|
||||||
|
} else if(tmp < 32) {
|
||||||
|
date2.setDate(tmp);
|
||||||
|
} else {
|
||||||
|
date2.setFullYear(tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (adjustMonth) {
|
||||||
|
date1.setMonth(date.getMonth());
|
||||||
|
date2.setMonth(date.getMonth());
|
||||||
|
}
|
||||||
|
if (isGMT) {
|
||||||
|
var tmp = date;
|
||||||
|
tmp.setFullYear(date.getUTCFullYear());
|
||||||
|
tmp.setMonth(date.getUTCMonth());
|
||||||
|
tmp.setDate(date.getUTCDate());
|
||||||
|
tmp.setHours(date.getUTCHours());
|
||||||
|
tmp.setMinutes(date.getUTCMinutes());
|
||||||
|
tmp.setSeconds(date.getUTCSeconds());
|
||||||
|
date = tmp;
|
||||||
|
}
|
||||||
|
return ((date1 <= date) && (date <= date2));
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ m.configure_proxy = <html>We were unable to connect to the application server to
|
|||||||
<p> Your computer may access the Internet through a proxy and we were unable to automatically \
|
<p> Your computer may access the Internet through a proxy and we were unable to automatically \
|
||||||
detect your proxy settings. If you know your proxy settings, you can enter them below.</html>
|
detect your proxy settings. If you know your proxy settings, you can enter them below.</html>
|
||||||
|
|
||||||
|
m.update_proxy_auth = <html>The stored proxy user/password is wrong or obsolete. \
|
||||||
|
<p>Please provide an updated user/password combination.</html>
|
||||||
|
|
||||||
m.proxy_extra = <html>If you are sure that you don't use a proxy then \
|
m.proxy_extra = <html>If you are sure that you don't use a proxy then \
|
||||||
perhaps there is a temporary Internet outage that is preventing us from \
|
perhaps there is a temporary Internet outage that is preventing us from \
|
||||||
communicating with the servers. In this case, you can cancel and try \
|
communicating with the servers. In this case, you can cancel and try \
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ m.configure_proxy = <html>Es konnte keine Verbindung zum Applikations-Server auf
|
|||||||
Wenn kein Proxy verwendet werden soll, löschen Sie bitte alle Einträge in den unten \
|
Wenn kein Proxy verwendet werden soll, löschen Sie bitte alle Einträge in den unten \
|
||||||
stehenden Feldern und klicken sie auf OK.</html>
|
stehenden Feldern und klicken sie auf OK.</html>
|
||||||
|
|
||||||
|
m.update_proxy_auth = <html>Gespeicherte Proxy User/Passwort Kombination ungültig oder obsolet. \
|
||||||
|
<p>Bitte geben Sie die korrekte User/Passwort Kombination ein.</html>
|
||||||
|
|
||||||
m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
|
m.proxy_extra = <html>Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \
|
||||||
an Ihren Administrator.</html>
|
an Ihren Administrator.</html>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ m.configure_proxy = <html>Impossibile collegarsi al server per \
|
|||||||
questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \
|
questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \
|
||||||
tue impostazioni del proxy, puoi inserirle di seguito.</html>
|
tue impostazioni del proxy, puoi inserirle di seguito.</html>
|
||||||
|
|
||||||
m.proxy_extra = <html>Se sei sicuro di non usare proxy \
|
m.update_proxy_auth = <html>Combinazione User/Password salvata per il proxy non è valida \
|
||||||
|
oppure obsoleta. \
|
||||||
|
<p>Perfavore inserire una combinazione User/Password valida.</html>
|
||||||
|
|
||||||
|
m.proxy_extra = <html>Se sei sicuro di non usare proxy \
|
||||||
potrebbe essere un problema di internet o di collegamento con il server. \
|
potrebbe essere un problema di internet o di collegamento con il server. \
|
||||||
In questo caso puoi annullare e ripetere l'installazione più tardi.</html>
|
In questo caso puoi annullare e ripetere l'installazione più tardi.</html>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// Getdown - application installer, patcher and launcher
|
||||||
|
// Copyright (C) 2004-2018 Getdown authors
|
||||||
|
// https://github.com/threerings/getdown/blob/master/LICENSE
|
||||||
|
|
||||||
|
package com.threerings.getdown.launcher;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ProxyUtilTest {
|
||||||
|
|
||||||
|
static String[] strs (String... strs) { return strs; }
|
||||||
|
|
||||||
|
static String[] findPAC (String code, String url) throws Exception {
|
||||||
|
return ProxyUtil.findPACProxiesForURL(new StringReader(code), new URL(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testPAC (String code, String url, String... expectedProxies) throws Exception {
|
||||||
|
assertArrayEquals(expectedProxies, findPAC(code, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void testPACProxy () throws Exception {
|
||||||
|
String EXAMPLE0 =
|
||||||
|
"function FindProxyForURL(url, host) {\n" +
|
||||||
|
" if (shExpMatch(host, '*.example.com')) { return 'DIRECT'; }\n" +
|
||||||
|
" if (isInNet(host, '10.0.0.0', '255.255.248.0')) {\n" +
|
||||||
|
" return 'PROXY fastproxy.example.com:8080';\n" +
|
||||||
|
" }\n" +
|
||||||
|
" return 'PROXY proxy.example.com:8080; DIRECT';\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
testPAC(EXAMPLE0, "http://test.example.com/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE0, "http://10.0.1.1/", "PROXY fastproxy.example.com:8080");
|
||||||
|
testPAC(EXAMPLE0, "http://chicken.gov/", "PROXY proxy.example.com:8080", "DIRECT");
|
||||||
|
|
||||||
|
String EXAMPLE1 =
|
||||||
|
"function FindProxyForURL(url, host) {" +
|
||||||
|
" if (isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) {" +
|
||||||
|
" return 'DIRECT';" +
|
||||||
|
" } else {" +
|
||||||
|
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';" +
|
||||||
|
" }" +
|
||||||
|
"}";
|
||||||
|
testPAC(EXAMPLE1, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||||
|
testPAC(EXAMPLE1, "http://www.mozilla.org/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE1, "http://foo.mozilla.org/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE1, "http://localhost/", "DIRECT");
|
||||||
|
|
||||||
|
String EXAMPLE2 =
|
||||||
|
" function FindProxyForURL(url, host) {\n" +
|
||||||
|
" if ((isPlainHostName(host) || dnsDomainIs(host, '.mozilla.org')) &&\n" +
|
||||||
|
" !localHostOrDomainIs(host, 'www.mozilla.org') &&\n" +
|
||||||
|
" !localHostOrDomainIs(host, 'merchant.mozilla.org')) {\n" +
|
||||||
|
" return 'DIRECT';\n" +
|
||||||
|
" } else {\n" +
|
||||||
|
" return 'PROXY w3proxy.mozilla.org:8080; DIRECT';\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }";
|
||||||
|
testPAC(EXAMPLE2, "http://test.example.com/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||||
|
testPAC(EXAMPLE2, "http://www.mozilla.org/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||||
|
testPAC(EXAMPLE2, "http://www/", "PROXY w3proxy.mozilla.org:8080", "DIRECT");
|
||||||
|
testPAC(EXAMPLE2, "http://foo.mozilla.org/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE2, "http://localhost/", "DIRECT");
|
||||||
|
|
||||||
|
String EXAMPLE3A =
|
||||||
|
"function FindProxyForURL(url, host) {\n" +
|
||||||
|
" if (isResolvable(host)) return 'DIRECT';\n" +
|
||||||
|
" else return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||||
|
"}";
|
||||||
|
testPAC(EXAMPLE3A, "http://www.mozilla.org/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE3A, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||||
|
|
||||||
|
String EXAMPLE3B =
|
||||||
|
"function FindProxyForURL(url, host) {\n" +
|
||||||
|
" if (isPlainHostName(host) ||\n" +
|
||||||
|
" dnsDomainIs(host, '.mydomain.com') ||\n" +
|
||||||
|
" isResolvable(host)) {\n" +
|
||||||
|
" return 'DIRECT';\n" +
|
||||||
|
" } else {\n" +
|
||||||
|
" return 'PROXY proxy.mydomain.com:8080';\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
testPAC(EXAMPLE3B, "http://plain/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE3B, "http://foo.mydomain.com/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE3B, "http://www.mozilla.org/", "DIRECT");
|
||||||
|
testPAC(EXAMPLE3B, "http://doesnotexist.mozilla.org/", "PROXY proxy.mydomain.com:8080");
|
||||||
|
|
||||||
|
// example 4
|
||||||
|
// function FindProxyForURL(url, host) {
|
||||||
|
// if (isInNet(host, '198.95.0.0', '255.255.0.0')) return 'DIRECT';
|
||||||
|
// else return 'PROXY proxy.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
// function FindProxyForURL(url, host) {
|
||||||
|
// if (isPlainHostName(host) ||
|
||||||
|
// dnsDomainIs(host, '.mydomain.com') ||
|
||||||
|
// isInNet(host, '198.95.0.0', '255.255.0.0')) {
|
||||||
|
// return 'DIRECT';
|
||||||
|
// } else {
|
||||||
|
// return 'PROXY proxy.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// example 5
|
||||||
|
// function FindProxyForURL(url, host) {
|
||||||
|
// if (isPlainHostName(host) || dnsDomainIs(host, '.mydomain.com')) return 'DIRECT';
|
||||||
|
// else if (shExpMatch(host, '*.com')) return 'PROXY proxy1.mydomain.com:8080; ' +
|
||||||
|
// 'PROXY proxy4.mydomain.com:8080';
|
||||||
|
// else if (shExpMatch(host, '*.edu')) return 'PROXY proxy2.mydomain.com:8080; ' +
|
||||||
|
// 'PROXY proxy4.mydomain.com:8080';
|
||||||
|
// else return 'PROXY proxy3.mydomain.com:8080; ' +
|
||||||
|
// 'PROXY proxy4.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// example 6
|
||||||
|
// function FindProxyForURL(url, host) {
|
||||||
|
// if (url.substring(0, 5) == 'http:') {
|
||||||
|
// return 'PROXY http-proxy.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
// else if (url.substring(0, 4) == 'ftp:') {
|
||||||
|
// return 'PROXY ftp-proxy.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
// else if (url.substring(0, 7) == 'gopher:') {
|
||||||
|
// return 'PROXY gopher-proxy.mydomain.com:8080';
|
||||||
|
// }
|
||||||
|
// else if (url.substring(0, 6) == 'https:' ||
|
||||||
|
// url.substring(0, 6) == 'snews:') {
|
||||||
|
// return 'PROXY security-proxy.mydomain.com:8080';
|
||||||
|
// } else {
|
||||||
|
// return 'DIRECT';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user