From: Ben Soares Date: Mon, 15 Apr 2019 09:25:36 +0000 (+0100) Subject: JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files X-Git-Tag: Release_2_11_0~17^2~7^2~37 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=f517e5ea31f1749617ac191137891cf87111550b;p=jalview.git JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files --- diff --git a/getdown/src/getdown b/getdown/src/getdown deleted file mode 160000 index 0e2f116..0000000 --- a/getdown/src/getdown +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e2f11698da3c6b9ffd9ce7a1317ff42b96a1dbf diff --git a/getdown/src/getdown/.project b/getdown/src/getdown/.project new file mode 100644 index 0000000..ccd1d40 --- /dev/null +++ b/getdown/src/getdown/.project @@ -0,0 +1,17 @@ + + + getdown + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/getdown/src/getdown/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/.travis.yml b/getdown/src/getdown/.travis.yml new file mode 100644 index 0000000..32f2196 --- /dev/null +++ b/getdown/src/getdown/.travis.yml @@ -0,0 +1,11 @@ +language: java +sudo: false +script: "mvn -B clean verify" + +cache: + directories: + - '$HOME/.m2/repository' + +jdk: + - openjdk7 + - oraclejdk8 diff --git a/getdown/src/getdown/AUTHORS b/getdown/src/getdown/AUTHORS new file mode 100644 index 0000000..5f389d0 --- /dev/null +++ b/getdown/src/getdown/AUTHORS @@ -0,0 +1,12 @@ +# +# This is the official list of the AUTHORS of Getdown for copyright purposes. +# +# This is not the full list of contributors, see +# https://github.com/threerings/getdown/graphs/contributors +# for the full list of contributors. +# +# Contributors assign copyright of their work to the authors listed in the this file to keep life +# simple. + +Michael Bayne +Ray Greenwell diff --git a/getdown/src/getdown/CHANGELOG.md b/getdown/src/getdown/CHANGELOG.md new file mode 100644 index 0000000..098651e --- /dev/null +++ b/getdown/src/getdown/CHANGELOG.md @@ -0,0 +1,180 @@ +# Getdown Releases + +## 1.8.3 - Unreleased + +* Added support for `nresource` resources which must be jar files that contain native libraries. + Prior to launching the application, these resources will be unpacked and their contents added to + the `java.library.path` system property. + +* When the app is updated to require a new version of the JVM, that JVM will be downloaded and used + immediately during that app invocation (instead of one invocation later). Via PR#169. + +* When a custom JVM is installed, old JVM files will be deleted prior to unpacking the new JVM. Via + PR#170. + +* Number of concurrent downloads now defaults to num-cores minus one. Though downloads are I/O + bound rather than CPU bound, this still turns out to be a decent default. + +* Avoid checking for proxy config if `https.proxyHost` is set. This matches existing behavior when + `http.proxyHost` is set. + +* Added support for proxy authentication. A deployment must also use the + `com.threerings.getdown.spi.ProxyAuth` service provider interface to persist the proxy + credentials supplied by the user. Otherwise they will be requested every time Getdown runs, which + is not a viable user experience. + +## 1.8.2 - Nov 27, 2018 + +* Fixed a data corruption bug introduced at last minute into 1.8.1 release. Oops. + +## 1.8.1 - Nov 26, 2018 + +* If both an `appbase` and `appdir` are provided via some means (bootstrap properties file, system + property, etc.) and the app dir does not yet exist, Getdown will create it. + +* Added `max_concurrent_downloads` setting to `getdown.txt`. Controls what you would expect. + Defaults to two. + +* `bootstrap.properties` can now contain system properties which will be set prior to running + Getdown. They must be prefixed by `sys.`: for example `sys.silent = true` will set the `silent` + system property to `true`. + +* If Getdown is run in a headless JVM, it will avoid showing a UI but will attempt to install and + launch the application anyhow. Note that passing `-Dsilent` will override this behavior (because + in silent mode the default is only to install the app, not also launch it). + +* Fixed issue with `appid` not being properly used when specified via command line arg. + +* Fixed issue with running Getdown on single CPU systems (or virtual systems). It was attempting to + create a thread pool of size zero, which failed. + +* Fixed issue with backslashes (or other regular expression escape characters) in environment + variables being substituted into app arguments. + +## 1.8.0 - Oct 19, 2018 + +* Added support for manually specifying the thread pool size via `-Dthread_pool_size`. Also reduced + the default thread pool size to `num_cpus-1` from `num_cpus`. + +* Added support for bundling a `bootstrap.properties` file with the Getdown jar file, which can + specify defaults for `appdir`, `appbase` and `appid`. + +* Added support for a host URL whitelist. Getdown can be custom built to refuse to operate with any + URL that does not match the built-time-specified whitelist. See `core/pom.xml` for details. + +* Removed the obsolete support for running Getdown in a signed applet. Applets are no longer + supported by any widely used browser. + +* Split the project into multiple Maven modules. See the notes on [migrating from 1.7 to 1.8] for + details. + +* A wide variety of small cleanups resulting from a security review generously performed by a + prospective user. This includes various uses of deterministic locales and encodings instead of + the platform default locale/encoding, in cases where platform/locale-specific behavior is not + desired or needed. + +* Made use of `appid` fall back to main app class if no `appid`-specific class is specified. + +* Added support for marking resources as executable (via `xresource`). + +* Fixed issue where entire tracking URL was being URL encoded. + +* Changed translations to avoid the use of the term 'game'. Use 'app' instead. + +## 1.7.1 - Jun 6, 2018 + +* Made it possible to use `appbase_domain` with `https` URLs. + +* Fixed issue with undecorated splash window being unclosable if failures happen early in + initialization process. (#57) + +* Added support for transparent splash window. (#92) + +* Fixed problem with unpacked code resources (`ucode`) and `pack.gz` files. (#95) + +* Changed default Java version regex to support new Java 9+ version formats. (#93) + +* Ensure correct signature algorithm is used for each version of digest files. (#91) + +* Use more robust delete in all cases where Getdown needs to delete files. This should fix issues + with lingering files on Windows (where sometimes delete fails spuriously). + +## 1.7.0 - Dec 12, 2017 + +* Fixed issue with `Digester` thread pool not being shutdown. (#89) + +* Fixed resource unpacking, which was broken by earlier change introducing resource installation + (downloading to `_new` files and then renaming into place). (#88) + +* The connect and read timeouts specified by system properties are now used for all the various + connections made by Getdown. + +* Proxy detection now uses a 5 second connect/read timeout, to avoid stalling for a long time in + certain problematic network conditions. + +* Getdown is now built against JDK 1.7 and requires JDK 1.7 (or newer) to run. Use the latest + Getdown 1.6.x release if you need to support Java 1.6. + +## 1.6.4 - Sep 17, 2017 + +* `digest.txt` (and `digest2.txt`) computation now uses parallel jobs. Each resource to be verified + is a single job and the jobs are doled out to a thread pool with #CPUs threads. This allows large + builds to proceed faster as most dev machines have more than one core. + +* Resource verification is now performed in parallel (similar to the `digest.txt` computation, each + resource is a job farmed out to a thread pool). For large installations on multi-core machines, + this speeds up the verification phase of an installation or update. + +* Socket reads now have a 30 second default timeout. This can be changed by passing + `-Dread_timeout=N` (where N is seconds) to the JVM running Getdown. + +* Fixed issue with failing to install a downloaded and validated `_new` file. + +* Added support for "strict comments". In this mode, Getdown only treats `#` as starting a comment + if it appears in column zero. This allows `#` to occur on the right hand side of configuration + values (like in file names). To enable, put `strict_comments = true` in your `getdown.txt` file. + +## 1.6.3 - Apr 23, 2017 + +* Fixed error parsing `cache_retention_days`. (#82) + +* Fixed error with new code cache. (9e23a426) + +## 1.6.2 - Feb 12, 2017 + +* Fixed issue with installing local JVM, caused by new resource installation process. (#78) + +* Local JVM now uses absolute path to avoid issues with cwd. + +* Added `override_appbase` system property. This enables a Getdown app that normally talks to some + download server to be installed in such a way that it instead talks to some other download + server. + +## 1.6.1 - Feb 12, 2017 + +* Fix issues with URL path encoding when downloading resources. (84af080b0) + +* Parsing `digest.txt` changed to allow `=` to appear in the filename. In `getdown.txt` we split on + the first `=` because `=` never appears in a key but may appear in a value. But in `digest.txt` + the format is `filename = hash` and `=` never appears in the hash but may appear in the filename, + so there we want to split on the _last_ `=` not the first. + +* Fixed bug with progress tracking and reporting. (256e0933) + +* Fix executable permissions on `jspawnhelper`. (#74) + +## 1.6 - Nov 5, 2016 + +* This release and all those before it are considered ancient history. Check the commit history for + more details on what was in each of these releases. + +## 1.0 - Sep 21, 2010 + +* The first Maven release of Getdown. + +## 0.1 - July 19, 2004 + +* The first production use of Getdown (on https://www.puzzlepirates.com which is miraculously still + operational as of 2018 when this changelog was created). + +[migrating from 1.7 to 1.8]: https://github.com/threerings/getdown/wiki/Migrate17to18 diff --git a/getdown/src/getdown/LICENSE b/getdown/src/getdown/LICENSE new file mode 100644 index 0000000..0d9b255 --- /dev/null +++ b/getdown/src/getdown/LICENSE @@ -0,0 +1,24 @@ +Getdown - application installer, patcher and launcher + +Copyright (C) 2004-2016 Getdown authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. diff --git a/getdown/src/getdown/README.md b/getdown/src/getdown/README.md new file mode 100644 index 0000000..7059c61 --- /dev/null +++ b/getdown/src/getdown/README.md @@ -0,0 +1,111 @@ +## What is it? + +Getdown (yes, it's the funky stuff) is a system for deploying Java applications to end-user +computers, as well as keeping those applications up to date. + +It was designed as a replacement for [Java Web Start](https://docs.oracle.com/javase/8/docs/technotes/guides/javaws/) +due to limitations in Java Web Start's architecture which are outlined in the +[rationale](https://github.com/threerings/getdown/wiki/Rationale) section. + +Note: Getdown was designed *in 2004* as an alternative to Java Web Start, because of design choices +made by JWS that were problematic to the use cases its authors had. It is _not_ a drop-in +replacement for JWS, aimed to help the developers left in the lurch by the deprecation of JWS in +Java 9. It may still be a viable alternative for developers looking to replace JWS, but don't +expect to find feature parity with JWS. + +## How do I use it? + +A tutorial and more detailed specification are available from the [Documentation] page. Questions +can be posted to the [OOO Libs Google group]. + +Note that because one can not rely on users having a JRE installed, you must create a custom +installer for each platform that you plan to support (Windows, macOS, Linux) that installs a JRE, +the Getdown launcher jar file, a stub configuration file that identifies the URL at which your real +app manifest is hosted, and whatever the appropiate "desktop integration" is that provides an icon +the user can click on. We have some details on the +[installers](https://github.com/threerings/getdown/wiki/Installers) documentation page, though it +is unfortunately not very detailed. + +## How does it work? + +The main design and operation of Getdown is detailed on the +[design](https://github.com/threerings/getdown/wiki/Design) page. You can also browse the +[javadoc documentation] and [source code] if you're interested in implementation details. + +## Where can I see it in action? + +Getdown was originally written by developers at [OOO] for the deployment of their Java-based +massively multiplayer games. Try out any of the following games to see it in action: + + * [Puzzle Pirates](https://www.puzzlepirates.com/) - OOO + * [Spiral Knights](https://www.spiralknights.com/) - OOO + +Getdown is implemented in Java, and is designed to deploy and update JVM-based applications. While +it would be technically feasible to use Getdown to deploy non-JVM-based applications, it is not +currently supported and it is unlikely that the overhead of bundling a JVM just to run Getdown +would be worth it if the JVM were not also being used to run the target application. + +## Release notes + +See [CHANGELOG.md](CHANGELOG.md) for release notes. + +## Obtaining Getdown + +Getdown will likely need to be integrated into your build. We have separate instructions for +[build integration]. You can also download the individual jar files from Maven Central if needed. +Getdown is comprised of three Maven artifacts (jar files), though you probably only need the first +one: + + * [getdown-launcher](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-launcher) + contains minified (via Proguard) code that you actually run to update and launch your app. It + also contains the tools needed to build a Getdown app distribution. + + * [getdown-core](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-core) contains the + core logic for downloading, verifying, patching and launching an app as well as the core logic + for creating an app distribution. It does not contain any user interface code. You would only + use this artifact if you were planning to integrate Getdown directly into your app. + + * [getdown-ant](http://repo2.maven.org/maven2/com/threerings/getdown/getdown-ant) contains an Ant + task for building a Getdown app distribution. See the [build integration] instructions for + details. + +You can also: + + * [Check out the code](https://github.com/threerings/getdown) and build it yourself. + * Browse the [source code] online. + * View the [javadoc documentation] online. + +## JVM Version Requirements + + * Getdown version 1.8.x requires Java 7 VM or newer. + * Getdown version 1.7.x requires Java 7 VM or newer. + * Getdown version 1.6.x requires Java 6 VM or newer. + * Getdown version 1.5 and earlier requires Java 5 VM or newer. + +## Migrating from Getdown 1.7 to Getdown 1.8 + +See [this document](https://github.com/threerings/getdown/wiki/Migrating-from-1.7-to-1.8) on the +changes needed to migrate from Getdown 1.7 to 1.8. + +## Building + +Getdown is built with Maven in the standard ways. Invoke the following commands, for fun and +profit: + +``` +% mvn compile # builds the classes +% mvn test # builds and runs the unit tests +% mvn package # builds and creates jar file +% mvn install # builds, jars and installs in your local Maven repository +``` + +## Discussion + +Feel free to pop over to the [OOO Libs Google Group] to ask questions and get (and give) answers. + +[Documentation]: https://github.com/threerings/getdown/wiki +[OOO Libs Google group]: http://groups.google.com/group/ooo-libs +[source code]: https://github.com/threerings/getdown/tree/master/src/main/java/com/threerings/getdown/launcher +[javadoc documentation]: https://threerings.github.com/getdown/apidocs/ +[OOO]: https://en.wikipedia.org/wiki/Three_Rings_Design +[build integration]: https://github.com/threerings/getdown/wiki/Build-Integration diff --git a/getdown/src/getdown/ant/.project b/getdown/src/getdown/ant/.project new file mode 100644 index 0000000..097cb89 --- /dev/null +++ b/getdown/src/getdown/ant/.project @@ -0,0 +1,23 @@ + + + getdown-ant + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/ant/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/ant/pom.xml b/getdown/src/getdown/ant/pom.xml new file mode 100644 index 0000000..f8231aa --- /dev/null +++ b/getdown/src/getdown/ant/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-ant + jar + Getdown Ant Task + An Ant task for building Getdown app distributions + + + + com.threerings.getdown + getdown-core + ${project.version} + + + org.apache.ant + ant + 1.7.1 + provided + + + diff --git a/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java new file mode 100644 index 0000000..48cc8d4 --- /dev/null +++ b/getdown/src/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java @@ -0,0 +1,94 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.IOException; + +import java.security.GeneralSecurityException; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +import com.threerings.getdown.data.Digest; + +/** + * An ant task used to create a digest.txt for a Getdown + * application deployment. + */ +public class DigesterTask extends Task +{ + /** + * Sets the application directory. + */ + public void setAppdir (File appdir) + { + _appdir = appdir; + } + + /** + * Sets the digest signing keystore. + */ + public void setKeystore (File path) + { + _storepath = path; + } + + /** + * Sets the keystore decryption key. + */ + public void setStorepass (String password) + { + _storepass = password; + } + + /** + * Sets the private key alias. + */ + public void setAlias (String alias) + { + _storealias = alias; + } + + /** + * Performs the actual work of the task. + */ + @Override + public void execute () throws BuildException + { + // make sure appdir is set + if (_appdir == null) { + throw new BuildException("Must specify the path to the application directory " + + "via the 'appdir' attribute."); + } + + // make sure _storepass and _keyalias are set, if _storepath is set + if (_storepath != null && (_storepass == null || _storealias == null)) { + throw new BuildException( + "Must specify both a keystore password and a private key alias."); + } + + try { + Digester.createDigests(_appdir, _storepath, _storepass, _storealias); + } catch (IOException ioe) { + throw new BuildException("Error creating digest: " + ioe.getMessage(), ioe); + } catch (GeneralSecurityException gse) { + throw new BuildException("Error creating signature: " + gse.getMessage(), gse); + } + } + + /** The application directory in which we're creating a digest file. */ + protected File _appdir; + + /** The path to the keystore we'll use to sign the digest file, if any. */ + protected File _storepath; + + /** The decryption key for the keystore. */ + protected String _storepass; + + /** The private key alias. */ + protected String _storealias; +} diff --git a/getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000..ff501eb Binary files /dev/null and b/getdown/src/getdown/ant/target/getdown-ant-1.8.3-SNAPSHOT.jar differ diff --git a/getdown/src/getdown/ant/target/maven-archiver/pom.properties b/getdown/src/getdown/ant/target/maven-archiver/pom.properties new file mode 100644 index 0000000..60817f2 --- /dev/null +++ b/getdown/src/getdown/ant/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Fri Apr 05 14:07:51 BST 2019 +version=1.8.3-SNAPSHOT +groupId=com.threerings.getdown +artifactId=getdown-ant diff --git a/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..2ad1bd7 --- /dev/null +++ b/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1 @@ +com/threerings/getdown/tools/DigesterTask.class diff --git a/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..f3127b7 --- /dev/null +++ b/getdown/src/getdown/ant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1 @@ +/Users/bsoares/git/getdown2/getdown/ant/src/main/java/com/threerings/getdown/tools/DigesterTask.java diff --git a/getdown/src/getdown/bin/differ b/getdown/src/getdown/bin/differ new file mode 100755 index 0000000..f48ed89 --- /dev/null +++ b/getdown/src/getdown/bin/differ @@ -0,0 +1,4 @@ +#!/bin/sh + +# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar +java -classpath */getdown*.jar com.threerings.getdown.tools.Differ "$@" diff --git a/getdown/src/getdown/bin/patcher b/getdown/src/getdown/bin/patcher new file mode 100755 index 0000000..e09f67d --- /dev/null +++ b/getdown/src/getdown/bin/patcher @@ -0,0 +1,4 @@ +#!/bin/sh + +# we'll magically try to match dist/getdown.jar or target/getdown-1.x-SNAPSHOT.jar +java -classpath */getdown*.jar com.threerings.getdown.tools.Patcher "$@" diff --git a/getdown/src/getdown/core/.project b/getdown/src/getdown/core/.project new file mode 100644 index 0000000..177252f --- /dev/null +++ b/getdown/src/getdown/core/.project @@ -0,0 +1,23 @@ + + + getdown-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..0a9bbb8 --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/it/java=UTF-8 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/core/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/core/pom.xml b/getdown/src/getdown/core/pom.xml new file mode 100644 index 0000000..e564bf6 --- /dev/null +++ b/getdown/src/getdown/core/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-core + jar + Getdown Core + Core Getdown functionality + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.22.0 + test + + + + + + + + + + + + .. + LICENSE + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.5 + + + add-test-source + process-resources + + add-test-source + + + + src/it/java + + + + + + + + maven-antrun-plugin + 1.8 + + + gen-build + generate-sources + + + + + + + + + + + + + + + + run + + + + + + + maven-clean-plugin + 3.1.0 + + + + ${project.build.sourceDirectory}/ + + com/threerings/getdown/data/Build.java + + false + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.0 + + + + integration-test + verify + + + + + false + + + + + + diff --git a/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java new file mode 100644 index 0000000..52b4b5e --- /dev/null +++ b/getdown/src/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java @@ -0,0 +1,54 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tests; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.util.Arrays; +import java.util.List; + +import org.junit.*; +import static org.junit.Assert.*; + +import com.threerings.getdown.tools.Digester; + +public class DigesterIT { + + @Test + public void testDigester () throws Exception { + Path appdir = Paths.get("src/it/resources/testapp"); + Digester.createDigests(appdir.toFile(), null, null, null); + + Path digest = appdir.resolve("digest.txt"); + List digestLines = Files.readAllLines(digest, StandardCharsets.UTF_8); + Files.delete(digest); + + Path digest2 = appdir.resolve("digest2.txt"); + List digest2Lines = Files.readAllLines(digest2, StandardCharsets.UTF_8); + Files.delete(digest2); + + assertEquals(Arrays.asList( + "getdown.txt = 779c74fb4b251e18faf6e240a0667964", + "testapp.jar = 404dafa55e78b25ec0e3a936357b1883", + "funny%test dir/some=file.txt = d8e8fca2dc0f896fd7cb4cb0031ba249", + "crazyhashfile#txt = f29d23fd5ab1781bd8d0760b3a516f16", + "foo.jar = 46ca4cc9079d9d019bb30cd21ebbc1ec", + "script.sh = f66e8ea25598e67e99c47d9b0b2a2cdf", + "digest.txt = f5561d85e4d80cc85883963897e58ff6" + ), digestLines); + + assertEquals(Arrays.asList( + "getdown.txt = 4f0c657895c3c3a35fa55bf5951c64fa9b0694f8fc685af3f1d8635c639e066b", + "testapp.jar = c9cb1906afbf48f8654b416c3f831046bd3752a76137e5bf0a9af2f790bf48e0", + "funny%test dir/some=file.txt = f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", + "crazyhashfile#txt = 6816889f922de38f145db215a28ad7c5e1badf7354b5cdab225a27486789fa3b", + "foo.jar = ea188b872e0496debcbe00aaadccccb12a8aa9b025bb62c130cd3d9b8540b062", + "script.sh = cca1c5c7628d9bf7533f655a9cfa6573d64afb8375f81960d1d832dc5135c988", + "digest2.txt = 70b442c9f56660561921da3368e1a206f05c379182fab3062750b7ddcf303407" + ), digest2Lines); + } +} diff --git a/getdown/src/getdown/core/src/it/resources/testapp/background.png b/getdown/src/getdown/core/src/it/resources/testapp/background.png new file mode 100644 index 0000000..ff6a6ee Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/background.png differ diff --git a/getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt b/getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt new file mode 100644 index 0000000..33bc373 --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/crazyhashfile#txt @@ -0,0 +1 @@ +Hello crazy world. diff --git a/getdown/src/getdown/core/src/it/resources/testapp/foo.jar b/getdown/src/getdown/core/src/it/resources/testapp/foo.jar new file mode 100644 index 0000000..d040c01 Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/foo.jar differ diff --git a/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt b/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/funny%test dir/some=file.txt @@ -0,0 +1 @@ +test diff --git a/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt new file mode 100644 index 0000000..3e0e538 --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/getdown.txt @@ -0,0 +1,28 @@ +# where our app is hosted on the internets +appbase = http://notused.com/testapp + +# the jar file that contains our code +code = testapp.jar + +# the main entry point of our app +class = com.threerings.testapp.TestApp + +# we pass the appdir to our app so that it can upgrade getdown +apparg = %APPDIR% + +# test the %env% mechanism +jvmarg = -Dusername=\%ENV.USER% + +strict_comments = true +resource = funny%test dir/some=file.txt +resource = crazyhashfile#txt +uresource = foo.jar +xresource = script.sh + +ui.name = Getdown Test App +ui.background_image = background.png +ui.progress = 17, 321, 458, 22 +ui.progress_bar = 336600 +ui.progress_text = FFFFFF +ui.status = 57, 245, 373, 68 +ui.status_text = 000000 diff --git a/getdown/src/getdown/core/src/it/resources/testapp/script.sh b/getdown/src/getdown/core/src/it/resources/testapp/script.sh new file mode 100644 index 0000000..e3a1aba --- /dev/null +++ b/getdown/src/getdown/core/src/it/resources/testapp/script.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "Hello world!" diff --git a/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar b/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar new file mode 100644 index 0000000..fe9de02 Binary files /dev/null and b/getdown/src/getdown/core/src/it/resources/testapp/testapp.jar differ diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java new file mode 100644 index 0000000..13b9956 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/Log.java @@ -0,0 +1,141 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.FieldPosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.*; + +/** + * A placeholder class that contains a reference to the log object used by the Getdown code. + */ +public class Log +{ + public static class Shim { + /** + * Logs a debug message. + * + * @param message the message to be logged. + * @param args a list of key/value pairs and an optional final Throwable. + */ + public void debug (Object message, Object... args) { doLog(0, message, args); } + + /** + * Logs an info message. + * + * @param message the message to be logged. + * @param args a list of key/value pairs and an optional final Throwable. + */ + public void info (Object message, Object... args) { doLog(1, message, args); } + + /** + * Logs a warning message. + * + * @param message the message to be logged. + * @param args a list of key/value pairs and an optional final Throwable. + */ + public void warning (Object message, Object... args) { doLog(2, message, args); } + + /** + * Logs an error message. + * + * @param message the message to be logged. + * @param args a list of key/value pairs and an optional final Throwable. + */ + public void error (Object message, Object... args) { doLog(3, message, args); } + + protected void doLog (int levIdx, Object message, Object[] args) { + if (_impl.isLoggable(LEVELS[levIdx])) { + Throwable err = null; + int nn = args.length; + if (message instanceof Throwable) { + err = (Throwable)message; + } else if (nn % 2 == 1 && (args[nn - 1] instanceof Throwable)) { + err = (Throwable)args[--nn]; + } + _impl.log(LEVELS[levIdx], format(message, args), err); + } + } + + protected final Logger _impl = Logger.getLogger("com.threerings.getdown"); + } + + /** We dispatch our log messages through this logging shim. */ + public static final Shim log = new Shim(); + + public static String format (Object message, Object... args) { + if (args.length < 2) return String.valueOf(message); + StringBuilder buf = new StringBuilder(String.valueOf(message)); + if (buf.length() > 0) { + buf.append(' '); + } + buf.append('['); + for (int ii = 0; ii < args.length; ii += 2) { + if (ii > 0) { + buf.append(',').append(' '); + } + buf.append(args[ii]).append('='); + try { + buf.append(args[ii+1]); + } catch (Throwable t) { + buf.append(""); + } + } + return buf.append(']').toString(); + } + + static { + Formatter formatter = new OneLineFormatter(); + Logger logger = LogManager.getLogManager().getLogger(""); + for (Handler handler : logger.getHandlers()) { + handler.setFormatter(formatter); + } + } + + protected static class OneLineFormatter extends Formatter { + @Override public String format (LogRecord record) { + StringBuffer buf = new StringBuffer(); + + // append the timestamp + _date.setTime(record.getMillis()); + _format.format(_date, buf, _fpos); + + // append the log level + buf.append(" "); + buf.append(record.getLevel().getLocalizedName()); + buf.append(" "); + + // append the message itself + buf.append(formatMessage(record)); + buf.append(System.lineSeparator()); + + // if an exception was also provided, append that + if (record.getThrown() != null) { + try { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } catch (Exception ex) { + buf.append("Format failure:").append(ex); + } + } + + return buf.toString(); + } + + protected Date _date = new Date(); + protected SimpleDateFormat _format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss:SSS"); + protected FieldPosition _fpos = new FieldPosition(SimpleDateFormat.DATE_FIELD); + } + + protected static final String DATE_FORMAT = "{0,date} {0,time}"; + protected static final Level[] LEVELS = {Level.FINE, Level.INFO, Level.WARNING, Level.SEVERE}; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java new file mode 100644 index 0000000..67ea645 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java @@ -0,0 +1,99 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import com.threerings.getdown.util.FileUtil; + +/** + * Collects elements in the {@link ResourceCache cache} which became unused and deletes them + * afterwards. + */ +public class GarbageCollector +{ + /** + * Collect and delete the garbage in the cache. + */ + public static void collect (File cacheDir, final long retentionPeriodMillis) + { + FileUtil.walkTree(cacheDir, new FileUtil.Visitor() { + @Override public void visit (File file) { + File cachedFile = getCachedFile(file); + File lastAccessedFile = getLastAccessedFile(file); + if (!cachedFile.exists() || !lastAccessedFile.exists()) { + if (cachedFile.exists()) { + FileUtil.deleteHarder(cachedFile); + } else { + FileUtil.deleteHarder(lastAccessedFile); + } + } else if (shouldDelete(lastAccessedFile, retentionPeriodMillis)) { + FileUtil.deleteHarder(lastAccessedFile); + FileUtil.deleteHarder(cachedFile); + } + + File folder = file.getParentFile(); + if (folder != null) { + String[] children = folder.list(); + if (children != null && children.length == 0) { + FileUtil.deleteHarder(folder); + } + } + } + }); + } + + /** + * Collect and delete garbage in the native cache. It tries to find a jar file with a matching + * last modified file, and deletes the entire directory accordingly. + */ + public static void collectNative (File cacheDir, final long retentionPeriodMillis) + { + File[] subdirs = cacheDir.listFiles(); + if (subdirs != null) { + for (File dir : subdirs) { + if (dir.isDirectory()) { + // Get all the native jars in the directory (there should only be one) + for (File file : dir.listFiles()) { + if (!file.getName().endsWith(".jar")) { + continue; + } + File cachedFile = getCachedFile(file); + File lastAccessedFile = getLastAccessedFile(file); + if (!cachedFile.exists() || !lastAccessedFile.exists() || + shouldDelete(lastAccessedFile, retentionPeriodMillis)) { + FileUtil.deleteDirHarder(dir); + } + } + } else { + // @TODO There shouldn't be any loose files in native/ but if there are then + // what? Delete them? file.delete(); + } + } + } + } + + private static boolean shouldDelete (File lastAccessedFile, long retentionMillis) + { + return System.currentTimeMillis() - lastAccessedFile.lastModified() > retentionMillis; + } + + private static File getLastAccessedFile (File file) + { + return isLastAccessedFile(file) ? file : new File( + file.getParentFile(), file.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + private static boolean isLastAccessedFile (File file) + { + return file.getName().endsWith(ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + private static File getCachedFile (File file) + { + return !isLastAccessedFile(file) ? file : new File( + file.getParentFile(), file.getName().substring(0, file.getName().lastIndexOf("."))); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java new file mode 100644 index 0000000..0210e9a --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java @@ -0,0 +1,80 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; + +import com.threerings.getdown.util.FileUtil; + +/** + * Maintains a cache of code resources. The cache allows multiple application instances of different + * versions to open at the same time. + */ +public class ResourceCache +{ + public ResourceCache (File _cacheDir) throws IOException + { + this._cacheDir = _cacheDir; + createDirectoryIfNecessary(_cacheDir); + } + + private void createDirectoryIfNecessary (File dir) throws IOException + { + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("unable to create directory: " + dir.getAbsolutePath()); + } + } + + /** + * Caches the given file under its {@code digest}. + * @param fileToCache file to cache. + * @param cacheSubdir the subdirectory of the cache directory in which to store the cached + * file. Usually either {@code digest} or a prefix of {@code digest}. + * @param digest a crypto digest of the cached files contents. + * @return the cached file. + */ + public File cacheFile (File fileToCache, String cacheSubdir, String digest) throws IOException + { + File cacheLocation = new File(_cacheDir, cacheSubdir); + createDirectoryIfNecessary(cacheLocation); + + File cachedFile = new File(cacheLocation, digest + getFileSuffix(fileToCache)); + File lastAccessedFile = new File( + cacheLocation, cachedFile.getName() + LAST_ACCESSED_FILE_SUFFIX); + + if (!cachedFile.exists()) { + createNewFile(cachedFile); + FileUtil.copy(fileToCache, cachedFile); + } + + if (lastAccessedFile.exists()) { + lastAccessedFile.setLastModified(System.currentTimeMillis()); + } else { + createNewFile(lastAccessedFile); + } + + return cachedFile; + } + + private void createNewFile (File fileToCreate) throws IOException + { + if (!fileToCreate.exists() && !fileToCreate.createNewFile()) { + throw new IOException("unable to create new file: " + fileToCreate.getAbsolutePath()); + } + } + + private String getFileSuffix (File fileToCache) { + String fileName = fileToCache.getName(); + int index = fileName.lastIndexOf("."); + + return index > -1 ? fileName.substring(index) : ""; + } + + private final File _cacheDir; + + static final String LAST_ACCESSED_FILE_SUFFIX = ".lastAccessed"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java new file mode 100644 index 0000000..501407c --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java @@ -0,0 +1,1826 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.security.*; +import java.security.cert.Certificate; +import java.util.*; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; + + +import com.threerings.getdown.util.*; +// avoid ambiguity with java.util.Base64 which we can't use as it's 1.8+ +import com.threerings.getdown.util.Base64; + +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Parses and provide access to the information contained in the getdown.txt + * configuration file. + */ +public class Application +{ + /** The name of our configuration file. */ + public static final String CONFIG_FILE = "getdown.txt"; + + /** The name of our target version file. */ + public static final String VERSION_FILE = "version.txt"; + + /** System properties that are prefixed with this string will be passed through to our + * application (minus this prefix). */ + public static final String PROP_PASSTHROUGH_PREFIX = "app."; + + /** Suffix used for control file signatures. */ + public static final String SIGNATURE_SUFFIX = ".sig"; + + /** A special classname that means 'use -jar code.jar' instead of a classname. */ + public static final String MANIFEST_CLASS = "manifest"; + + /** Used to communicate information about the UI displayed when updating the application. */ + public static final class UpdateInterface + { + /** + * The major steps involved in updating, along with some arbitrary percentages + * assigned to them, to mark global progress. + */ + public enum Step + { + UPDATE_JAVA(10), + VERIFY_METADATA(15, 65, 95), + DOWNLOAD(40), + PATCH(60), + VERIFY_RESOURCES(70, 97), + REDOWNLOAD_RESOURCES(90), + UNPACK(98), + LAUNCH(99); + + /** What is the final percent value for this step? */ + public final List defaultPercents; + + /** Enum constructor. */ + Step (int... percents) + { + this.defaultPercents = intsToList(percents); + } + } + + /** The human readable name of this application. */ + public final String name; + + /** A background color, just in case. */ + public final int background; + + /** Background image specifiers for `RotatingBackgrounds`. */ + public final List rotatingBackgrounds; + + /** The error background image for `RotatingBackgrounds`. */ + public final String errorBackground; + + /** The paths (relative to the appdir) of images for the window icon. */ + public final List iconImages; + + /** The path (relative to the appdir) to a single background image. */ + public final String backgroundImage; + + /** The path (relative to the appdir) to the progress bar image. */ + public final String progressImage; + + /** The dimensions of the progress bar. */ + public final Rectangle progress; + + /** The color of the progress text. */ + public final int progressText; + + /** The color of the progress bar. */ + public final int progressBar; + + /** The dimensions of the status display. */ + public final Rectangle status; + + /** The color of the status text. */ + public final int statusText; + + /** The color of the text shadow. */ + public final int textShadow; + + /** Where to point the user for help with install errors. */ + public final String installError; + + /** The dimensions of the patch notes button. */ + public final Rectangle patchNotes; + + /** The patch notes URL. */ + public final String patchNotesUrl; + + /** Whether window decorations are hidden for the UI. */ + public final boolean hideDecorations; + + /** Whether progress text should be hidden or not. */ + public final boolean hideProgressText; + + /** The minimum number of seconds to display the GUI. This is to prevent the GUI from + * flashing up on the screen and immediately disappearing, which can be confusing to the + * user. */ + public final int minShowSeconds; + + /** The global percentages for each step. A step may have more than one, and + * the lowest reasonable one is used if a step is revisited. */ + public final Map> stepPercentages; + + /** Generates a string representation of this instance. */ + @Override + public String toString () + { + return "[name=" + name + ", bg=" + background + ", bg=" + backgroundImage + + ", pi=" + progressImage + ", prect=" + progress + ", pt=" + progressText + + ", pb=" + progressBar + ", srect=" + status + ", st=" + statusText + + ", shadow=" + textShadow + ", err=" + installError + ", nrect=" + patchNotes + + ", notes=" + patchNotesUrl + ", stepPercentages=" + stepPercentages + + ", hideProgressText" + hideProgressText + ", minShow=" + minShowSeconds + "]"; + } + + public UpdateInterface (Config config) + { + this.name = config.getString("ui.name"); + this.progress = config.getRect("ui.progress", new Rectangle(5, 5, 300, 15)); + this.progressText = config.getColor("ui.progress_text", Color.BLACK); + this.hideProgressText = config.getBoolean("ui.hide_progress_text"); + this.minShowSeconds = config.getInt("ui.min_show_seconds", 5); + this.progressBar = config.getColor("ui.progress_bar", 0x6699CC); + this.status = config.getRect("ui.status", new Rectangle(5, 25, 500, 100)); + this.statusText = config.getColor("ui.status_text", Color.BLACK); + this.textShadow = config.getColor("ui.text_shadow", Color.CLEAR); + this.hideDecorations = config.getBoolean("ui.hide_decorations"); + this.backgroundImage = config.getString("ui.background_image"); + // default to black or white bg color, depending on the brightness of the progressText + int defaultBackground = (0.5f < Color.brightness(this.progressText)) ? + Color.BLACK : Color.WHITE; + this.background = config.getColor("ui.background", defaultBackground); + this.progressImage = config.getString("ui.progress_image"); + this.rotatingBackgrounds = stringsToList( + config.getMultiValue("ui.rotating_background")); + this.iconImages = stringsToList(config.getMultiValue("ui.icon")); + this.errorBackground = config.getString("ui.error_background"); + + // On an installation error, where do we point the user. + String installError = config.getUrl("ui.install_error", null); + this.installError = (installError == null) ? + "m.default_install_error" : MessageUtil.taint(installError); + + // the patch notes bits + this.patchNotes = config.getRect("ui.patch_notes", new Rectangle(5, 50, 112, 26)); + this.patchNotesUrl = config.getUrl("ui.patch_notes_url", null); + + // step progress percentage (defaults and then customized values) + EnumMap> stepPercentages = new EnumMap<>(Step.class); + for (Step step : Step.values()) { + stepPercentages.put(step, step.defaultPercents); + } + for (UpdateInterface.Step step : UpdateInterface.Step.values()) { + String spec = config.getString("ui.percents." + step.name()); + if (spec != null) { + try { + stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec))); + } catch (Exception e) { + log.warning("Failed to parse percentages for " + step + ": " + spec); + } + } + } + this.stepPercentages = Collections.unmodifiableMap(stepPercentages); + } + } + + /** + * Used by {@link #verifyMetadata} to communicate status in circumstances where it needs to + * take network actions. + */ + public static interface StatusDisplay + { + /** Requests that the specified status message be displayed. */ + public void updateStatus (String message); + } + + /** + * Contains metadata for an auxiliary resource group. + */ + public static class AuxGroup { + public final String name; + public final List codes; + public final List rsrcs; + + public AuxGroup (String name, List codes, List rsrcs) { + this.name = name; + this.codes = Collections.unmodifiableList(codes); + this.rsrcs = Collections.unmodifiableList(rsrcs); + } + } + + /** 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 + * getter and setter just to pretend like that's not the case. */ + public Proxy proxy = Proxy.NO_PROXY; + + /** + * Creates an application instance which records the location of the getdown.txt + * configuration file from the supplied application directory. + * + */ + public Application (EnvConfig envc) { + _envc = envc; + _config = getLocalPath(envc.appDir, CONFIG_FILE); + } + + /** + * Returns the configured application directory. + */ + public File getAppDir () { + return _envc.appDir; + } + + /** + * Returns whether the application should cache code resources prior to launching the + * application. + */ + public boolean useCodeCache () + { + return _useCodeCache; + } + + /** + * Returns the number of days a cached code resource is allowed to stay unused before it + * becomes eligible for deletion. + */ + public int getCodeCacheRetentionDays () + { + return _codeCacheRetentionDays; + } + + /** + * Returns the configured maximum concurrent downloads. Used to cap simultaneous downloads of + * app files from its hosting server. + */ + public int maxConcurrentDownloads () { + return _maxConcDownloads; + } + + /** + * Returns a resource that refers to the application configuration file itself. + */ + public Resource getConfigResource () + { + try { + return createResource(CONFIG_FILE, Resource.NORMAL); + } catch (Exception e) { + throw new RuntimeException("Invalid appbase '" + _vappbase + "'.", e); + } + } + + /** + * Returns a list of the code {@link Resource} objects used by this application. + */ + public List getCodeResources () + { + return _codes; + } + + /** + * Returns a list of the non-code {@link Resource} objects used by this application. + */ + public List getResources () + { + return _resources; + } + + /** + * Returns the digest of the given {@code resource}. + */ + public String getDigest (Resource resource) + { + return _digest.getDigest(resource); + } + + /** + * Returns a list of all the active {@link Resource} objects used by this application (code and + * non-code). + */ + public List getAllActiveResources () + { + List allResources = new ArrayList<>(); + allResources.addAll(getActiveCodeResources()); + allResources.addAll(getActiveResources()); + return allResources; + } + + /** + * Returns the auxiliary resource group with the specified name, or null. + */ + public AuxGroup getAuxGroup (String name) + { + return _auxgroups.get(name); + } + + /** + * Returns the set of all auxiliary resource groups defined by the application. An auxiliary + * resource group is a collection of resource files that are not downloaded unless a group + * token file is present in the application directory. + */ + public Iterable getAuxGroups () + { + return _auxgroups.values(); + } + + /** + * Returns true if the specified auxgroup has been "activated", false if not. Non-activated + * groups should be ignored, activated groups should be downloaded and patched along with the + * main resources. + */ + public boolean isAuxGroupActive (String auxgroup) + { + Boolean active = _auxactive.get(auxgroup); + if (active == null) { + // TODO: compare the contents with the MD5 hash of the auxgroup name and the client's + // machine ident + active = getLocalPath(auxgroup + ".dat").exists(); + _auxactive.put(auxgroup, active); + } + return active; + } + + /** + * Returns all main code resources and all code resources from active auxiliary resource groups. + */ + public List getActiveCodeResources () + { + ArrayList codes = new ArrayList<>(); + codes.addAll(getCodeResources()); + for (AuxGroup aux : getAuxGroups()) { + if (isAuxGroupActive(aux.name)) { + codes.addAll(aux.codes); + } + } + return codes; + } + + /** + * Returns all resources indicated to contain native library files (.dll, .so, etc.). + */ + public List getNativeResources () + { + List natives = new ArrayList<>(); + for (Resource resource: _resources) { + if (resource.isNative()) { + natives.add(resource); + } + } + return natives; + } + + /** + * Returns all non-code resources and all resources from active auxiliary resource groups. + */ + public List getActiveResources () + { + ArrayList rsrcs = new ArrayList<>(); + rsrcs.addAll(getResources()); + for (AuxGroup aux : getAuxGroups()) { + if (isAuxGroupActive(aux.name)) { + rsrcs.addAll(aux.rsrcs); + } + } + return rsrcs; + } + + /** + * Returns a resource that can be used to download a patch file that will bring this + * application from its current version to the target version. + * + * @param auxgroup the auxiliary resource group for which a patch resource is desired or null + * for the main application patch resource. + */ + public Resource getPatchResource (String auxgroup) + { + if (_targetVersion <= _version) { + log.warning("Requested patch resource for up-to-date or non-versioned application", + "cvers", _version, "tvers", _targetVersion); + return null; + } + + String infix = (auxgroup == null) ? "" : ("-" + auxgroup); + String pfile = "patch" + infix + _version + ".dat"; + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(pfile)); + return new Resource(pfile, remote, getLocalPath(pfile), Resource.NORMAL); + } catch (Exception e) { + log.warning("Failed to create patch resource path", + "pfile", pfile, "appbase", _appbase, "tvers", _targetVersion, "error", e); + return null; + } + } + + /** + * Returns a resource for a zip file containing a Java VM that can be downloaded to use in + * place of the installed VM (in the case where the VM that launched Getdown does not meet the + * application's version requirements) or null if no VM is available for this platform. + */ + public Resource getJavaVMResource () + { + if (StringUtil.isBlank(_javaLocation)) { + return null; + } + + String vmfile = LaunchUtil.LOCAL_JAVA_DIR + ".jar"; + log.info("vmfile is '"+vmfile+"'"); + System.out.println("vmfile is '"+vmfile+"'"); + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(_javaLocation)); + log.info("Attempting to fetch jvm at "+remote.toString()); + System.out.println("Attempting to fetch jvm at "+remote.toString()); + return new Resource(vmfile, remote, getLocalPath(vmfile), + EnumSet.of(Resource.Attr.UNPACK, Resource.Attr.CLEAN)); + } catch (Exception e) { + log.warning("Failed to create VM resource", "vmfile", vmfile, "appbase", _appbase, + "tvers", _targetVersion, "javaloc", _javaLocation, "error", e); + System.out.println("Failed to create VM resource: vmfile="+vmfile+", appbase="+_appbase+ + ", tvers="+_targetVersion+", javaloc="+_javaLocation+", error="+e); + return null; + } + } + + /** + * Returns a resource that can be used to download an archive containing all files belonging to + * the application. + */ + public Resource getFullResource () + { + String file = "full"; + try { + URL remote = new URL(createVAppBase(_targetVersion), encodePath(file)); + return new Resource(file, remote, getLocalPath(file), Resource.NORMAL); + } catch (Exception e) { + log.warning("Failed to create full resource path", + "file", file, "appbase", _appbase, "tvers", _targetVersion, "error", e); + return null; + } + } + + /** + * Returns the URL to use to report an initial download event. Returns null if no tracking + * start URL was configured for this application. + * + * @param event the event to be reported: start, jvm_start, jvm_complete, complete. + */ + public URL getTrackingURL (String event) + { + try { + String suffix = _trackingURLSuffix == null ? "" : _trackingURLSuffix; + String ga = getGATrackingCode(); + return _trackingURL == null ? null : + HostWhitelist.verify(new URL(_trackingURL + encodePath(event + suffix + ga))); + } catch (MalformedURLException mue) { + log.warning("Invalid tracking URL", "path", _trackingURL, "event", event, "error", mue); + return null; + } + } + + /** + * Returns the URL to request to report that we have reached the specified percentage of our + * initial download. Returns null if no tracking request was configured for the specified + * percentage. + */ + public URL getTrackingProgressURL (int percent) + { + if (_trackingPcts == null || !_trackingPcts.contains(percent)) { + return null; + } + return getTrackingURL("pct" + percent); + } + + /** + * Returns the name of our tracking cookie or null if it was not set. + */ + public String getTrackingCookieName () + { + return _trackingCookieName; + } + + /** + * Returns the name of our tracking cookie system property or null if it was not set. + */ + public String getTrackingCookieProperty () + { + return _trackingCookieProperty; + } + + /** + * Instructs the application to parse its {@code getdown.txt} configuration and prepare itself + * for operation. The application base URL will be parsed first so that if there are errors + * discovered later, the caller can use the application base to download a new {@code + * getdown.txt} file and try again. + * + * @return a {@code Config} instance that contains information from the config file. + * + * @exception IOException thrown if there is an error reading the file or an error encountered + * during its parsing. + */ + public Config init (boolean checkPlatform) + throws IOException + { + Config config = null; + File cfgfile = _config; + Config.ParseOpts opts = Config.createOpts(checkPlatform); + try { + // if we have a configuration file, read the data from it + if (cfgfile.exists()) { + config = Config.parseConfig(_config, opts); + } + // otherwise, try reading data from our backup config file; thanks to funny windows + // bullshit, we have to do this backup file fiddling in case we got screwed while + // updating getdown.txt during normal operation + else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) { + config = Config.parseConfig(cfgfile, opts); + } + // otherwise, issue a warning that we found no getdown file + else { + log.info("Found no getdown.txt file", "appdir", getAppDir()); + } + } catch (Exception e) { + log.warning("Failure reading config file", "file", config, e); + } + + // if we failed to read our config file, check for an appbase specified via a system + // property; we can use that to bootstrap ourselves back into operation + if (config == null) { + String appbase = _envc.appBase; + log.info("Using 'appbase' from bootstrap config", "appbase", appbase); + Map cdata = new HashMap<>(); + cdata.put("appbase", appbase); + config = new Config(cdata); + } + + // first determine our application base, this way if anything goes wrong later in the + // process, our caller can use the appbase to download a new configuration file + _appbase = config.getString("appbase"); + if (_appbase == null) { + throw new RuntimeException("m.missing_appbase"); + } + + // check if we're overriding the domain in the appbase + _appbase = SysProps.overrideAppbase(_appbase); + + // make sure there's a trailing slash + if (!_appbase.endsWith("/")) { + _appbase = _appbase + "/"; + } + + // extract our version information + _version = config.getLong("version", -1L); + + // if we are a versioned deployment, create a versioned appbase + try { + _vappbase = createVAppBase(_version); + } catch (MalformedURLException mue) { + String err = MessageUtil.tcompose("m.invalid_appbase", _appbase); + throw (IOException) new IOException(err).initCause(mue); + } + + // check for a latest config URL + String latest = config.getString("latest"); + if (latest != null) { + if (latest.startsWith(_appbase)) { + latest = _appbase + latest.substring(_appbase.length()); + } else { + latest = SysProps.replaceDomain(latest); + } + try { + _latest = HostWhitelist.verify(new URL(latest)); + } catch (MalformedURLException mue) { + log.warning("Invalid URL for latest attribute.", mue); + } + } + + 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 + _strictComments = config.getBoolean("strict_comments"); + + // check to see if we're using a custom java.version property and regex + _javaVersionProp = config.getString("java_version_prop", _javaVersionProp); + _javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex); + + // check to see if we require a particular JVM version and have a supplied JVM + _javaMinVersion = config.getLong("java_version", _javaMinVersion); + // we support java_min_version as an alias of java_version; it better expresses the check + // that's going on and better mirrors java_max_version + _javaMinVersion = config.getLong("java_min_version", _javaMinVersion); + // check to see if we require a particular max JVM version and have a supplied JVM + _javaMaxVersion = config.getLong("java_max_version", _javaMaxVersion); + // check to see if we require a particular JVM version and have a supplied JVM + _javaExactVersionRequired = config.getBoolean("java_exact_version_required"); + + // this is a little weird, but when we're run from the digester, we see a String[] which + // contains java locations for all platforms which we can't grok, but the digester doesn't + // need to know about that; when we're run in a real application there will be only one! + Object javaloc = config.getRaw("java_location"); + if (javaloc instanceof String) { + _javaLocation = (String)javaloc; + } + + // determine whether we have any tracking configuration + _trackingURL = config.getString("tracking_url"); + + // check for tracking progress percent configuration + String trackPcts = config.getString("tracking_percents"); + if (!StringUtil.isBlank(trackPcts)) { + _trackingPcts = new HashSet<>(); + for (int pct : StringUtil.parseIntArray(trackPcts)) { + _trackingPcts.add(pct); + } + } else if (!StringUtil.isBlank(_trackingURL)) { + _trackingPcts = new HashSet<>(); + _trackingPcts.add(50); + } + + // Check for tracking cookie configuration + _trackingCookieName = config.getString("tracking_cookie_name"); + _trackingCookieProperty = config.getString("tracking_cookie_property"); + + // Some app may need an extra suffix added to the tracking URL + _trackingURLSuffix = config.getString("tracking_url_suffix"); + + // Some app may need to generate google analytics code + _trackingGAHash = config.getString("tracking_ga_hash"); + + // clear our arrays as we may be reinitializing + _codes.clear(); + _resources.clear(); + _auxgroups.clear(); + _jvmargs.clear(); + _appargs.clear(); + _txtJvmArgs.clear(); + + // parse our code resources + if (config.getMultiValue("code") == null && + config.getMultiValue("ucode") == null) { + throw new IOException("m.missing_code"); + } + parseResources(config, "code", Resource.NORMAL, _codes); + parseResources(config, "ucode", Resource.UNPACK, _codes); + + // parse our non-code resources + parseResources(config, "resource", Resource.NORMAL, _resources); + parseResources(config, "uresource", Resource.UNPACK, _resources); + parseResources(config, "xresource", Resource.EXEC, _resources); + parseResources(config, "presource", Resource.PRELOAD, _resources); + parseResources(config, "nresource", Resource.NATIVE, _resources); + + // parse our auxiliary resource groups + for (String auxgroup : config.getList("auxgroups")) { + ArrayList codes = new ArrayList<>(); + parseResources(config, auxgroup + ".code", Resource.NORMAL, codes); + parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes); + ArrayList rsrcs = new ArrayList<>(); + parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs); + parseResources(config, auxgroup + ".xresource", Resource.EXEC, rsrcs); + parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs); + parseResources(config, auxgroup + ".presource", Resource.PRELOAD, rsrcs); + parseResources(config, auxgroup + ".nresource", Resource.NATIVE, rsrcs); + _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); + } + + // transfer our JVM arguments (we include both "global" args and app_id-prefixed args) + String[] jvmargs = config.getMultiValue("jvmarg"); + addAll(jvmargs, _jvmargs); + if (appPrefix.length() > 0) { + jvmargs = config.getMultiValue(appPrefix + "jvmarg"); + addAll(jvmargs, _jvmargs); + } + + // see if a percentage of physical memory option exists + int jvmmempc = config.getInt("jvmmempc", -1); + // app_id prefixed setting overrides + if (appPrefix.length() > 0) { + jvmmempc = config.getInt(appPrefix + "jvmmempc", jvmmempc); + } + if (0 <= jvmmempc && jvmmempc <= 100) { + final Object o = ManagementFactory.getOperatingSystemMXBean(); + + try { + if (o instanceof OperatingSystemMXBean) { + final OperatingSystemMXBean osb = (OperatingSystemMXBean) o; + long physicalMem = osb.getTotalPhysicalMemorySize(); + long requestedMem = physicalMem*jvmmempc/100; + String[] maxMemHeapArg = new String[]{"-Xmx"+Long.toString(requestedMem)}; + // remove other max heap size arg + ARG: for (int i = 0; i < _jvmargs.size(); i++) { + if (_jvmargs.get(i) instanceof java.lang.String && _jvmargs.get(i).startsWith("-Xmx")) { + _jvmargs.remove(i); + } + } + addAll(maxMemHeapArg, _jvmargs); + + } + } + catch (NoClassDefFoundError e) { + // com.sun.management.OperatingSystemMXBean doesn't exist in this JVM + System.out.println("No com.sun.management.OperatingSystemMXBean. Cannot use 'jvmmempc'."); + } + } else if (jvmmempc != -1) { + System.out.println("'jvmmempc' value must be in range 0 to 100 (read as '"+Integer.toString(jvmmempc)+"')"); + } + + // get the set of optimum JVM arguments + _optimumJvmArgs = config.getMultiValue("optimum_jvmarg"); + + // transfer our application arguments + String[] appargs = config.getMultiValue(appPrefix + "apparg"); + addAll(appargs, _appargs); + + // add the launch specific application arguments + _appargs.addAll(_envc.appArgs); + + // look for custom arguments + fillAssignmentListFromPairs("extra.txt", _txtJvmArgs); + + // determine whether we want to allow offline operation (defaults to false) + _allowOffline = config.getBoolean("allow_offline"); + + // look for a debug.txt file which causes us to run in java.exe on Windows so that we can + // obtain a thread dump of the running JVM + _windebug = getLocalPath("debug.txt").exists(); + + // whether to cache code resources and launch from cache + _useCodeCache = config.getBoolean("use_code_cache"); + _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7); + + // maximum simultaneous downloads + _maxConcDownloads = Math.max(1, config.getInt("max_concurrent_downloads", + SysProps.threadPoolSize())); + + // extract some info used to configure our child process on macOS + _dockName = config.getString("ui.name"); + _dockIconPath = config.getString("ui.mac_dock_icon", "../desktop.icns"); + + return config; + } + + /** + * Adds strings of the form pair0=pair1 to collector for each pair parsed out of pairLocation. + */ + protected void fillAssignmentListFromPairs (String pairLocation, List collector) + { + File pairFile = getLocalPath(pairLocation); + if (pairFile.exists()) { + try { + List args = Config.parsePairs(pairFile, Config.createOpts(false)); + for (String[] pair : args) { + if (pair[1].length() == 0) { + collector.add(pair[0]); + } else { + collector.add(pair[0] + "=" + pair[1]); + } + } + } catch (Throwable t) { + log.warning("Failed to parse '" + pairFile + "': " + t); + } + } + } + + /** + * Returns a URL from which the specified path can be fetched. Our application base URL is + * properly versioned and combined with the supplied path. + */ + public URL getRemoteURL (String path) + throws MalformedURLException + { + return new URL(_vappbase, encodePath(path)); + } + + /** + * Returns the local path to the specified resource. + */ + public File getLocalPath (String path) + { + return getLocalPath(getAppDir(), path); + } + + /** + * Returns true if we either have no version requirement, are running in a JVM that meets our + * version requirements or have what appears to be a version of the JVM that meets our + * requirements. + */ + public boolean haveValidJavaVersion () + { + // if we're doing no version checking, then yay! + if (_javaMinVersion == 0 && _javaMaxVersion == 0) return true; + + try { + // parse the version out of the java.version (or custom) system property + long version = SysProps.parseJavaVersion(_javaVersionProp, _javaVersionRegex); + + log.info("Checking Java version", "current", version, + "wantMin", _javaMinVersion, "wantMax", _javaMaxVersion); + + // if we have an unpacked VM, check the 'release' file for its version + Resource vmjar = getJavaVMResource(); + if (vmjar != null && vmjar.isMarkedValid()) { + File vmdir = new File(getAppDir(), LaunchUtil.LOCAL_JAVA_DIR); + File relfile = new File(vmdir, "release"); + if (!relfile.exists()) { + log.warning("Unpacked JVM missing 'release' file. Assuming valid version."); + return true; + } + + long vmvers = VersionUtil.readReleaseVersion(relfile, _javaVersionRegex); + if (vmvers == 0L) { + log.warning("Unable to read version from 'release' file. Assuming valid."); + return true; + } + + version = vmvers; + log.info("Checking version of unpacked JVM [vers=" + version + "]."); + } + + if (_javaExactVersionRequired) { + if (version == _javaMinVersion) return true; + else { + log.warning("An exact Java VM version is required.", "current", version, + "required", _javaMinVersion); + return false; + } + } + + boolean minVersionOK = (_javaMinVersion == 0) || (version >= _javaMinVersion); + boolean maxVersionOK = (_javaMaxVersion == 0) || (version <= _javaMaxVersion); + return minVersionOK && maxVersionOK; + + } catch (RuntimeException re) { + // if we can't parse the java version we're in weird land and should probably just try + // our luck with what we've got rather than try to download a new jvm + log.warning("Unable to parse VM version, hoping for the best", + "error", re, "needed", _javaMinVersion); + return true; + } + } + + /** + * Checks whether the app has a set of "optimum" JVM args that we wish to try first, detecting + * whether the launch is successful and, if necessary, trying again without the optimum + * arguments. + */ + public boolean hasOptimumJvmArgs () + { + return _optimumJvmArgs != null; + } + + /** + * Returns true if the app should attempt to run even if we have no Internet connection. + */ + public boolean allowOffline () + { + return _allowOffline; + } + + /** + * Attempts to redownload the getdown.txt file based on information parsed from a + * previous call to {@link #init}. + */ + public void attemptRecovery (StatusDisplay status) + throws IOException + { + status.updateStatus("m.updating_metadata"); + downloadConfigFile(); + } + + /** + * Downloads and replaces the getdown.txt and digest.txt files with + * those for the target version of our application. + */ + public void updateMetadata () + throws IOException + { + try { + // update our versioned application base with the target version + _vappbase = createVAppBase(_targetVersion); + } catch (MalformedURLException mue) { + String err = MessageUtil.tcompose("m.invalid_appbase", _appbase); + throw (IOException) new IOException(err).initCause(mue); + } + + try { + // now re-download our control files; we download the digest first so that if it fails, + // our config file will still reference the old version and re-running the updater will + // start the whole process over again + downloadDigestFiles(); + downloadConfigFile(); + + } catch (IOException ex) { + // if we are allowing offline execution, we want to allow the application to run in its + // current form rather than aborting the entire process; to do this, we delete the + // version.txt file and "trick" Getdown into thinking that it just needs to validate + // the application as is; next time the app runs when connected to the internet, it + // will have to rediscover that it needs updating and reattempt to update itself + if (_allowOffline) { + log.warning("Failed to update digest files. Attempting offline operaton.", ex); + if (!FileUtil.deleteHarder(getLocalPath(VERSION_FILE))) { + log.warning("Deleting version.txt failed. This probably isn't going to work."); + } + } else { + throw ex; + } + } + } + + /** + * Invokes the process associated with this application definition. + * + * @param optimum whether or not to include the set of optimum arguments (as opposed to falling + * back). + */ + public Process createProcess (boolean optimum) + throws IOException + { + ArrayList args = new ArrayList<>(); + + // reconstruct the path to the JVM + args.add(LaunchUtil.getJVMPath(getAppDir(), _windebug || optimum)); + + // check whether we're using -jar mode or -classpath mode + boolean dashJarMode = MANIFEST_CLASS.equals(_class); + + // add the -classpath arguments if we're not in -jar mode + ClassPath classPath = PathBuilder.buildClassPath(this); + if (!dashJarMode) { + args.add("-classpath"); + args.add(classPath.asArgumentString()); + } + + // we love our Mac users, so we do nice things to preserve our application identity + if (LaunchUtil.isMacOS()) { + args.add("-Xdock:icon=" + getLocalPath(_dockIconPath).getAbsolutePath()); + args.add("-Xdock:name=" + _dockName); + } + + // pass along our proxy settings + String proxyHost; + if ((proxyHost = System.getProperty("http.proxyHost")) != null) { + args.add("-Dhttp.proxyHost=" + proxyHost); + args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort")); + args.add("-Dhttps.proxyHost=" + proxyHost); + args.add("-Dhttps.proxyPort=" + System.getProperty("http.proxyPort")); + } + + // add the marker indicating the app is running in getdown + args.add("-D" + Properties.GETDOWN + "=true"); + + // set the native library path if we have native resources + // @TODO optional getdown.txt parameter to set addCurrentLibraryPath to true or false? + ClassPath javaLibPath = PathBuilder.buildLibsPath(this, true); + if (javaLibPath != null) { + args.add("-Djava.library.path=" + javaLibPath.asArgumentString()); + } + + // pass along any pass-through arguments + for (Map.Entry entry : System.getProperties().entrySet()) { + String key = (String)entry.getKey(); + if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) { + key = key.substring(PROP_PASSTHROUGH_PREFIX.length()); + args.add("-D" + key + "=" + entry.getValue()); + } + } + + // add the JVM arguments + for (String string : _jvmargs) { + args.add(processArg(string)); + } + + // add the optimum arguments if requested and available + if (optimum && _optimumJvmArgs != null) { + for (String string : _optimumJvmArgs) { + args.add(processArg(string)); + } + } + + // add the arguments from extra.txt (after the optimum ones, in case they override them) + for (String string : _txtJvmArgs) { + args.add(processArg(string)); + } + + // if we're in -jar mode add those arguments, otherwise add the app class name + if (dashJarMode) { + args.add("-jar"); + args.add(classPath.asArgumentString()); + } else { + args.add(_class); + } + + // finally add the application arguments + for (String string : _appargs) { + args.add(processArg(string)); + } + + String[] envp = createEnvironment(); + String[] sargs = args.toArray(new String[args.size()]); + log.info("Running " + StringUtil.join(sargs, "\n ")); + + return Runtime.getRuntime().exec(sargs, envp, getAppDir()); + } + + /** + * If the application provided environment variables, combine those with the current + * environment and return that in a style usable for {@link Runtime#exec(String, String[])}. + * If the application didn't provide any environment variables, null is returned to just use + * the existing environment. + */ + protected String[] createEnvironment () + { + List envvar = new ArrayList<>(); + fillAssignmentListFromPairs("env.txt", envvar); + if (envvar.isEmpty()) { + log.info("Didn't find any custom environment variables, not setting any."); + return null; + } + + List envAssignments = new ArrayList<>(); + for (String assignment : envvar) { + envAssignments.add(processArg(assignment)); + } + for (Map.Entry environmentEntry : System.getenv().entrySet()) { + envAssignments.add(environmentEntry.getKey() + "=" + environmentEntry.getValue()); + } + String[] envp = envAssignments.toArray(new String[envAssignments.size()]); + log.info("Environment " + StringUtil.join(envp, "\n ")); + return envp; + } + + /** + * Runs this application directly in the current VM. + */ + public void invokeDirect () throws IOException + { + ClassPath classPath = PathBuilder.buildClassPath(this); + URL[] jarUrls = classPath.asUrls(); + + // create custom class loader + URLClassLoader loader = new URLClassLoader(jarUrls, ClassLoader.getSystemClassLoader()) { + @Override protected PermissionCollection getPermissions (CodeSource code) { + Permissions perms = new Permissions(); + perms.add(new AllPermission()); + return perms; + } + }; + Thread.currentThread().setContextClassLoader(loader); + + log.info("Configured URL class loader:"); + for (URL url : jarUrls) log.info(" " + url); + + // configure any system properties that we can + for (String jvmarg : _jvmargs) { + if (jvmarg.startsWith("-D")) { + jvmarg = processArg(jvmarg.substring(2)); + int eqidx = jvmarg.indexOf("="); + if (eqidx == -1) { + log.warning("Bogus system property: '" + jvmarg + "'?"); + } else { + System.setProperty(jvmarg.substring(0, eqidx), jvmarg.substring(eqidx+1)); + } + } + } + + // pass along any pass-through arguments + Map passProps = new HashMap<>(); + for (Map.Entry entry : System.getProperties().entrySet()) { + String key = (String)entry.getKey(); + if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) { + key = key.substring(PROP_PASSTHROUGH_PREFIX.length()); + passProps.put(key, (String)entry.getValue()); + } + } + // we can't set these in the above loop lest we get a ConcurrentModificationException + for (Map.Entry entry : passProps.entrySet()) { + System.setProperty(entry.getKey(), entry.getValue()); + } + + // prepare our app arguments + String[] args = new String[_appargs.size()]; + for (int ii = 0; ii < args.length; ii++) args[ii] = processArg(_appargs.get(ii)); + + try { + log.info("Loading " + _class); + Class appclass = loader.loadClass(_class); + Method main = appclass.getMethod("main", EMPTY_STRING_ARRAY.getClass()); + log.info("Invoking main({" + StringUtil.join(args, ", ") + "})"); + main.invoke(null, new Object[] { args }); + } catch (Exception e) { + log.warning("Failure invoking app main", e); + } + } + + /** Replaces the application directory and version in any argument. */ + protected String processArg (String arg) + { + arg = arg.replace("%APPDIR%", getAppDir().getAbsolutePath()); + arg = arg.replace("%VERSION%", String.valueOf(_version)); + + // if this argument contains %ENV.FOO% replace those with the associated values looked up + // from the environment + if (arg.contains(ENV_VAR_PREFIX)) { + StringBuffer sb = new StringBuffer(); + Matcher matcher = ENV_VAR_PATTERN.matcher(arg); + while (matcher.find()) { + String varName = matcher.group(1), varValue = System.getenv(varName); + String repValue = varValue == null ? "MISSING:"+varName : varValue; + matcher.appendReplacement(sb, Matcher.quoteReplacement(repValue)); + } + matcher.appendTail(sb); + arg = sb.toString(); + } + + return arg; + } + + /** + * Loads the digest.txt file and verifies the contents of both that file and the + * getdown.text file. Then it loads the version.txt and decides + * whether or not the application needs to be updated or whether we can proceed to verification + * and execution. + * + * @return true if the application needs to be updated, false if it is up to date and can be + * verified and executed. + * + * @exception IOException thrown if we encounter an unrecoverable error while verifying the + * metadata. + */ + public boolean verifyMetadata (StatusDisplay status) + throws IOException + { + log.info("Verifying application: " + _vappbase); + log.info("Version: " + _version); + log.info("Class: " + _class); + + // this will read in the contents of the digest file and validate itself + try { + _digest = new Digest(getAppDir(), _strictComments); + } catch (IOException ioe) { + log.info("Failed to load digest: " + ioe.getMessage() + ". Attempting recovery..."); + } + + // if we have no version, then we are running in unversioned mode so we need to download + // our digest.txt file on every invocation + if (_version == -1) { + // make a note of the old meta-digest, if this changes we need to revalidate all of our + // resources as one or more of them have also changed + String olddig = (_digest == null) ? "" : _digest.getMetaDigest(); + try { + status.updateStatus("m.checking"); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + if (!olddig.equals(_digest.getMetaDigest())) { + log.info("Unversioned digest changed. Revalidating..."); + status.updateStatus("m.validating"); + clearValidationMarkers(); + } + } catch (IOException ioe) { + log.warning("Failed to refresh non-versioned digest: " + + ioe.getMessage() + ". Proceeding..."); + } + } + + // regardless of whether we're versioned, if we failed to read the digest from disk, try to + // redownload the digest file and give it another good college try; this time we allow + // exceptions to propagate up to the caller as there is nothing else we can do + if (_digest == null) { + status.updateStatus("m.updating_metadata"); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + } + + // now verify the contents of our main config file + Resource crsrc = getConfigResource(); + if (!_digest.validateResource(crsrc, null)) { + status.updateStatus("m.updating_metadata"); + // attempt to redownload both of our metadata files; again we pass errors up to our + // caller because there's nothing we can do to automatically recover + downloadConfigFile(); + downloadDigestFiles(); + _digest = new Digest(getAppDir(), _strictComments); + // revalidate everything if we end up downloading new metadata + clearValidationMarkers(); + // if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage + if (_digest.validateResource(crsrc, null)) { + init(true); + } else { + log.warning(CONFIG_FILE + " failed to validate even after redownloading. " + + "Blindly forging onward."); + } + } + + // start by assuming we are happy with our version + _targetVersion = _version; + + // if we are a versioned application, read in the contents of the version.txt file + // and/or check the latest config URL for a newer version + if (_version != -1) { + File vfile = getLocalPath(VERSION_FILE); + long fileVersion = VersionUtil.readVersion(vfile); + if (fileVersion != -1) { + _targetVersion = fileVersion; + } + + if (_latest != null) { + try (InputStream in = ConnectionUtil.open(proxy, _latest, 0, 0).getInputStream(); + InputStreamReader reader = new InputStreamReader(in, UTF_8); + BufferedReader bin = new BufferedReader(reader)) { + for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) { + if (pair[0].equals("version")) { + _targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion); + if (fileVersion != -1 && _targetVersion > fileVersion) { + // replace the file with the newest version + try (FileOutputStream fos = new FileOutputStream(vfile); + PrintStream out = new PrintStream(fos)) { + out.println(_targetVersion); + } + } + break; + } + } + } catch (Exception e) { + log.warning("Unable to retrieve version from latest config file.", e); + } + } + } + + // finally let the caller know if we need an update + return _version != _targetVersion; + } + + /** + * Verifies the code and media resources associated with this application. A list of resources + * that do not exist or fail the verification process will be returned. If all resources are + * ready to go, null will be returned and the application is considered ready to run. + * + * @param obs a progress observer that will be notified of verification progress. NOTE: this + * observer may be called from arbitrary threads, so if you update a UI based on calls to it, + * you have to take care to get back to your UI thread. + * @param alreadyValid if non-null a 1 element array that will have the number of "already + * validated" resources filled in. + * @param unpacked a set to populate with unpacked resources. + * @param toInstall a list into which to add resources that need to be installed. + * @param toDownload a list into which to add resources that need to be downloaded. + */ + public void verifyResources ( + ProgressObserver obs, int[] alreadyValid, Set unpacked, + Set toInstall, Set toDownload) + throws InterruptedException + { + // resources are verified on background threads supplied by the thread pool, and progress + // is reported by posting runnable actions to the actions queue which is processed by the + // main (UI) thread + ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize()); + final BlockingQueue actions = new LinkedBlockingQueue(); + final int[] completed = new int[1]; + + long start = System.currentTimeMillis(); + + // obtain the sizes of the resources to validate + List rsrcs = getAllActiveResources(); + long[] sizes = new long[rsrcs.size()]; + long totalSize = 0; + for (int ii = 0; ii < sizes.length; ii++) { + totalSize += sizes[ii] = rsrcs.get(ii).getLocal().length(); + } + final ProgressObserver fobs = obs; + // as long as we forward aggregated progress updates to the UI thread, having multiple + // threads update a progress aggregator is "mostly" thread-safe + final ProgressAggregator pagg = new ProgressAggregator(new ProgressObserver() { + public void progress (final int percent) { + actions.add(new Runnable() { + public void run () { + fobs.progress(percent); + } + }); + } + }, sizes); + + final int[] fAlreadyValid = alreadyValid; + final Set toInstallAsync = new ConcurrentSkipListSet<>(toInstall); + final Set toDownloadAsync = new ConcurrentSkipListSet<>(); + final Set unpackedAsync = new ConcurrentSkipListSet<>(); + + for (int ii = 0; ii < sizes.length; ii++) { + final Resource rsrc = rsrcs.get(ii); + final int index = ii; + exec.execute(new Runnable() { + public void run () { + verifyResource(rsrc, pagg.startElement(index), fAlreadyValid, + unpackedAsync, toInstallAsync, toDownloadAsync); + actions.add(new Runnable() { + public void run () { + completed[0] += 1; + } + }); + } + }); + } + + while (completed[0] < rsrcs.size()) { + // we should be getting progress completion updates WAY more often than one every + // minute, so if things freeze up for 60 seconds, abandon ship + Runnable action = actions.poll(60, TimeUnit.SECONDS); + action.run(); + } + + exec.shutdown(); + + toInstall.addAll(toInstallAsync); + toDownload.addAll(toDownloadAsync); + unpacked.addAll(unpackedAsync); + + long complete = System.currentTimeMillis(); + log.info("Verified resources", "count", rsrcs.size(), "size", (totalSize/1024) + "k", + "duration", (complete-start) + "ms"); + } + + private void verifyResource (Resource rsrc, ProgressObserver obs, int[] alreadyValid, + Set unpacked, + Set toInstall, Set toDownload) { + if (rsrc.isMarkedValid()) { + if (alreadyValid != null) { + alreadyValid[0]++; + } + obs.progress(100); + return; + } + + try { + if (_digest.validateResource(rsrc, obs)) { + // if the resource has a _new file, add it to to-install list + if (rsrc.getLocalNew().exists()) { + toInstall.add(rsrc); + return; + } + rsrc.applyAttrs(); + unpacked.add(rsrc); + rsrc.markAsValid(); + return; + } + + } catch (Exception e) { + log.info("Failure verifying resource. Requesting redownload...", + "rsrc", rsrc, "error", e); + + } finally { + obs.progress(100); + } + toDownload.add(rsrc); + } + + /** + * Unpacks the resources that require it (we know that they're valid). + * + * @param unpacked a set of resources to skip because they're already unpacked. + */ + public void unpackResources (ProgressObserver obs, Set unpacked) + throws InterruptedException + { + List rsrcs = getActiveResources(); + + // remove resources that we don't want to unpack + for (Iterator it = rsrcs.iterator(); it.hasNext(); ) { + Resource rsrc = it.next(); + if (!rsrc.shouldUnpack() || unpacked.contains(rsrc)) { + it.remove(); + } + } + + // obtain the sizes of the resources to unpack + long[] sizes = new long[rsrcs.size()]; + for (int ii = 0; ii < sizes.length; ii++) { + sizes[ii] = rsrcs.get(ii).getLocal().length(); + } + + ProgressAggregator pagg = new ProgressAggregator(obs, sizes); + for (int ii = 0; ii < sizes.length; ii++) { + Resource rsrc = rsrcs.get(ii); + ProgressObserver pobs = pagg.startElement(ii); + try { + rsrc.unpack(); + } catch (IOException ioe) { + log.warning("Failure unpacking resource", "rsrc", rsrc, ioe); + } + pobs.progress(100); + } + } + + /** + * Clears all validation marker files. + */ + public void clearValidationMarkers () + { + clearValidationMarkers(getAllActiveResources().iterator()); + } + + /** + * Returns the version number for the application. Should only be called after successful + * return of verifyMetadata. + */ + public long getVersion () + { + return _version; + } + + /** + * Creates a versioned application base URL for the specified version. + */ + protected URL createVAppBase (long version) + throws MalformedURLException + { + String url = version < 0 ? _appbase : _appbase.replace("%VERSION%", "" + version); + return HostWhitelist.verify(new URL(url)); + } + + /** + * Clears all validation marker files for the resources in the supplied iterator. + */ + protected void clearValidationMarkers (Iterator iter) + { + while (iter.hasNext()) { + iter.next().clearMarker(); + } + } + + /** + * Downloads a new copy of CONFIG_FILE. + */ + protected void downloadConfigFile () + throws IOException + { + downloadControlFile(CONFIG_FILE, 0); + } + + /** + * @return true if gettingdown.lock was unlocked, already locked by this application or if + * we're not locking at all. + */ + public synchronized boolean lockForUpdates () + { + if (_lock != null && _lock.isValid()) { + return true; + } + try { + _lockChannel = new RandomAccessFile(getLocalPath("gettingdown.lock"), "rw").getChannel(); + } catch (FileNotFoundException e) { + log.warning("Unable to create lock file", "message", e.getMessage(), e); + return false; + } + try { + _lock = _lockChannel.tryLock(); + } catch (IOException e) { + log.warning("Unable to create lock", "message", e.getMessage(), e); + return false; + } catch (OverlappingFileLockException e) { + log.warning("The lock is held elsewhere in this JVM", e); + return false; + } + log.info("Able to lock for updates: " + (_lock != null)); + return _lock != null; + } + + /** + * Release gettingdown.lock + */ + public synchronized void releaseLock () + { + if (_lock != null) { + log.info("Releasing lock"); + try { + _lock.release(); + } catch (IOException e) { + log.warning("Unable to release lock", "message", e.getMessage(), e); + } + try { + _lockChannel.close(); + } catch (IOException e) { + log.warning("Unable to close lock channel", "message", e.getMessage(), e); + } + _lockChannel = null; + _lock = null; + } + } + + /** + * Downloads the digest files and validates their signature. + * @throws IOException + */ + protected void downloadDigestFiles () + throws IOException + { + for (int version = 1; version <= Digest.VERSION; version++) { + downloadControlFile(Digest.digestFile(version), version); + } + } + + /** + * Downloads a new copy of the specified control file, optionally validating its signature. + * If the download is successful, moves it over the old file on the filesystem. + * + *

TODO: Switch to PKCS #7 or CMS. + * + * @param sigVersion if {@code 0} no validation will be performed, if {@code > 0} then this + * should indicate the version of the digest file being validated which indicates which + * algorithm to use to verify the signature. See {@link Digest#VERSION}. + */ + protected void downloadControlFile (String path, int sigVersion) + throws IOException + { + File target = downloadFile(path); + + if (sigVersion > 0) { + if (_envc.certs.isEmpty()) { + log.info("No signing certs, not verifying digest.txt", "path", path); + + } else { + File signatureFile = downloadFile(path + SIGNATURE_SUFFIX); + byte[] signature = null; + try (FileInputStream signatureStream = new FileInputStream(signatureFile)) { + signature = StreamUtil.toByteArray(signatureStream); + } finally { + FileUtil.deleteHarder(signatureFile); // delete the file regardless + } + + byte[] buffer = new byte[8192]; + int length, validated = 0; + for (Certificate cert : _envc.certs) { + try (FileInputStream dataInput = new FileInputStream(target)) { + Signature sig = Signature.getInstance(Digest.sigAlgorithm(sigVersion)); + sig.initVerify(cert); + while ((length = dataInput.read(buffer)) != -1) { + sig.update(buffer, 0, length); + } + + if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) { + log.info("Signature does not match", "cert", cert.getPublicKey()); + continue; + } else { + log.info("Signature matches", "cert", cert.getPublicKey()); + validated++; + } + + } catch (IOException ioe) { + log.warning("Failure validating signature of " + target + ": " + ioe); + + } catch (GeneralSecurityException gse) { + // no problem! + + } + } + + // if we couldn't find a key that validates our digest, we are the hosed! + if (validated == 0) { + // delete the temporary digest file as we know it is invalid + FileUtil.deleteHarder(target); + throw new IOException("m.corrupt_digest_signature_error"); + } + } + } + + // now move the temporary file over the original + File original = getLocalPath(path); + if (!FileUtil.renameTo(target, original)) { + throw new IOException("Failed to rename(" + target + ", " + original + ")"); + } + } + + /** + * Download a path to a temporary file, returning a {@link File} instance with the path + * contents. + */ + protected File downloadFile (String path) + throws IOException + { + File target = getLocalPath(path + "_new"); + + URL targetURL = null; + try { + targetURL = getRemoteURL(path); + } catch (Exception e) { + log.warning("Requested to download invalid control file", + "appbase", _vappbase, "path", path, "error", e); + throw (IOException) new IOException("Invalid path '" + path + "'.").initCause(e); + } + + log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'."); + + // stream the URL into our temporary file + URLConnection uconn = ConnectionUtil.open(proxy, targetURL, 0, 0); + // we have to tell Java not to use caches here, otherwise it will cache any request for + // same URL for the lifetime of this JVM (based on the URL string, not the URL object); + // if the getdown.txt file, for example, changes in the meanwhile, we would never hear + // about it; turning off caches is not a performance concern, because when Getdown asks + // to download a file, it expects it to come over the wire, not from a cache + uconn.setUseCaches(false); + uconn.setRequestProperty("Accept-Encoding", "gzip"); + try (InputStream fin = uconn.getInputStream()) { + String encoding = uconn.getContentEncoding(); + boolean gzip = "gzip".equalsIgnoreCase(encoding); + try (InputStream fin2 = (gzip ? new GZIPInputStream(fin) : fin)) { + try (FileOutputStream fout = new FileOutputStream(target)) { + StreamUtil.copy(fin2, fout); + } + } + } + + return target; + } + + /** Helper function for creating {@link Resource} instances. */ + protected Resource createResource (String path, EnumSet attrs) + throws MalformedURLException + { + return new Resource(path, getRemoteURL(path), getLocalPath(path), attrs); + } + + /** Helper function to add all values in {@code values} (if non-null) to {@code target}. */ + protected static void addAll (String[] values, List target) { + if (values != null) { + for (String value : values) { + target.add(value); + } + } + } + + /** + * Make an immutable List from the specified int array. + */ + public static List intsToList (int[] values) + { + List list = new ArrayList<>(values.length); + for (int val : values) { + list.add(val); + } + return Collections.unmodifiableList(list); + } + + /** + * Make an immutable List from the specified String array. + */ + public static List stringsToList (String[] values) + { + return values == null ? null : Collections.unmodifiableList(Arrays.asList(values)); + } + + /** Used to parse resources with the specified name. */ + protected void parseResources (Config config, String name, EnumSet attrs, + List list) + { + String[] rsrcs = config.getMultiValue(name); + if (rsrcs == null) { + return; + } + for (String rsrc : rsrcs) { + try { + list.add(createResource(rsrc, attrs)); + } catch (Exception e) { + log.warning("Invalid resource '" + rsrc + "'. " + e); + } + } + } + + /** Possibly generates and returns a google analytics tracking cookie. */ + protected String getGATrackingCode () + { + if (_trackingGAHash == null) { + return ""; + } + long time = System.currentTimeMillis() / 1000; + if (_trackingStart == 0) { + _trackingStart = time; + } + if (_trackingId == 0) { + int low = 100000000, high = 1000000000; + _trackingId = low + _rando.nextInt(high-low); + } + StringBuilder cookie = new StringBuilder("&utmcc=__utma%3D").append(_trackingGAHash); + cookie.append(".").append(_trackingId); + cookie.append(".").append(_trackingStart).append(".").append(_trackingStart); + cookie.append(".").append(time).append(".1%3B%2B"); + cookie.append("__utmz%3D").append(_trackingGAHash).append("."); + cookie.append(_trackingStart).append(".1.1."); + cookie.append("utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B"); + int low = 1000000000, high = 2000000000; + cookie.append("&utmn=").append(_rando.nextInt(high-low)); + return cookie.toString(); + } + + /** + * Encodes a path for use in a URL. + */ + protected static String encodePath (String path) + { + try { + // we want to keep slashes because we're encoding an entire path; also we need to turn + // + into %20 because web servers don't like + in paths or file names, blah + return URLEncoder.encode(path, "UTF-8").replace("%2F", "/").replace("+", "%20"); + } catch (UnsupportedEncodingException ue) { + log.warning("Failed to URL encode " + path + ": " + ue); + return path; + } + } + + protected File getLocalPath (File appdir, String path) + { + return new File(appdir, path); + } + + protected final EnvConfig _envc; + protected File _config; + protected Digest _digest; + + protected long _version = -1; + protected long _targetVersion = -1; + protected String _appbase; + protected URL _vappbase; + protected URL _latest; + protected String _class; + protected String _dockName; + protected String _dockIconPath; + protected boolean _strictComments; + protected boolean _windebug; + protected boolean _allowOffline; + protected int _maxConcDownloads; + + protected String _trackingURL; + protected Set _trackingPcts; + protected String _trackingCookieName; + protected String _trackingCookieProperty; + protected String _trackingURLSuffix; + protected String _trackingGAHash; + protected long _trackingStart; + protected int _trackingId; + + protected String _javaVersionProp = "java.version"; + protected String _javaVersionRegex = "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?"; + protected long _javaMinVersion, _javaMaxVersion; + protected boolean _javaExactVersionRequired; + protected String _javaLocation; + + protected List _codes = new ArrayList<>(); + protected List _resources = new ArrayList<>(); + + protected boolean _useCodeCache; + protected int _codeCacheRetentionDays; + + protected Map _auxgroups = new HashMap<>(); + protected Map _auxactive = new HashMap<>(); + + protected List _jvmargs = new ArrayList<>(); + protected List _appargs = new ArrayList<>(); + + protected String[] _optimumJvmArgs; + + protected List _txtJvmArgs = new ArrayList<>(); + + /** If a warning has been issued about not being able to set modtimes. */ + protected boolean _warnedAboutSetLastModified; + + /** Locks gettingdown.lock in the app dir. Held the entire time updating is going on.*/ + protected FileLock _lock; + + /** Channel to the file underlying _lock. Kept around solely so the lock doesn't close. */ + protected FileChannel _lockChannel; + + protected Random _rando = new Random(); + + protected static final String[] EMPTY_STRING_ARRAY = new String[0]; + + protected static final String ENV_VAR_PREFIX = "%ENV."; + protected static final Pattern ENV_VAR_PATTERN = Pattern.compile("%ENV\\.(.*?)%"); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java new file mode 100644 index 0000000..19a9fc5 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java @@ -0,0 +1,43 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2016 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.Arrays; +import java.util.List; + +import com.threerings.getdown.util.StringUtil; + +/** + * Contains static data provided during the build process. + */ +public class Build { + + /** The date and time at which the code was built: in {@code yyyy-MM-dd HH:mm} format. */ + public static String time () { + return "2019-04-05 14:07"; + } + + /** The Maven version of the Getdown project. */ + public static String version () { + return "1.8.3-SNAPSHOT"; + } + + /** + *

The hosts which Getdown is allowed to communicate with. An empty list indicates that + * no whitelist is configured and there are no limitations. By default, no host whitelist + * is added to the binary, so it can be used to download and run applications from any + * server. + * + *

To create a custom Getdown build that can only talk to whitelisted servers, set + * the {@code getdown.host.whitelist} property on the command line while building the JAR + * (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used + * (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas + * (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}). + */ + public static List hostWhitelist () { + return Arrays.asList(StringUtil.parseStringArray("")); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl new file mode 100644 index 0000000..60a8ff3 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Build.java.tmpl @@ -0,0 +1,43 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2016 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.Arrays; +import java.util.List; + +import com.threerings.getdown.util.StringUtil; + +/** + * Contains static data provided during the build process. + */ +public class Build { + + /** The date and time at which the code was built: in {@code yyyy-MM-dd HH:mm} format. */ + public static String time () { + return "@build_time@"; + } + + /** The Maven version of the Getdown project. */ + public static String version () { + return "@build_version@"; + } + + /** + *

The hosts which Getdown is allowed to communicate with. An empty list indicates that + * no whitelist is configured and there are no limitations. By default, no host whitelist + * is added to the binary, so it can be used to download and run applications from any + * server. + * + *

To create a custom Getdown build that can only talk to whitelisted servers, set + * the {@code getdown.host.whitelist} property on the command line while building the JAR + * (e.g. {@code mvn package -Dgetdown.host.whitelist=my.server.com}). Wildcards can be used + * (e.g. {@code *.mycompany.com}) and multiple values can be separated by commas + * (e.g. {@code app1.foo.com,app2.bar.com,app3.baz.com}). + */ + public static List hostWhitelist () { + return Arrays.asList(StringUtil.parseStringArray("@host_whitelist@")); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java new file mode 100644 index 0000000..9c2fce3 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java @@ -0,0 +1,76 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Represents the class path and it's elements of the application to be launched. The class path + * can either be represented as an {@link #asArgumentString() argument string} for the java command + * line or as an {@link #asUrls() array of URLs} to be used by a {@link URLClassLoader}. + */ +public class ClassPath +{ + public ClassPath (LinkedHashSet classPathEntries) + { + _classPathEntries = Collections.unmodifiableSet(classPathEntries); + } + + /** + * Returns the class path as an java command line argument string, e.g. + * + *

+     *   /path/to/a.jar:/path/to/b.jar
+     * 
+ */ + public String asArgumentString () + { + StringBuilder builder = new StringBuilder(); + String delimiter = ""; + for (File entry: _classPathEntries) { + builder.append(delimiter).append(entry.getAbsolutePath()); + delimiter = File.pathSeparator; + } + return builder.toString(); + } + + /** + * Returns the class path entries as an array of URLs to be used for example by an + * {@link URLClassLoader}. + */ + public URL[] asUrls () + { + URL[] urls = new URL[_classPathEntries.size()]; + int i = 0; + for (File entry : _classPathEntries) { + urls[i++] = getURL(entry); + } + return urls; + } + + public Set getClassPathEntries () + { + return _classPathEntries; + } + + + private static URL getURL (File file) + { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("URL of file is illegal: " + file.getAbsolutePath(), e); + } + } + + private final Set _classPathEntries; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java new file mode 100644 index 0000000..bc8d140 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java @@ -0,0 +1,228 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.concurrent.*; + +import com.threerings.getdown.util.Config; +import com.threerings.getdown.util.MessageUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Manages the digest.txt file and the computing and processing of digests for an + * application. + */ +public class Digest +{ + /** The current version of the digest protocol. */ + public static final int VERSION = 2; + + /** + * Returns the name of the digest file for the specified protocol version. + */ + public static String digestFile (int version) { + String infix = version > 1 ? String.valueOf(version) : ""; + return FILE_NAME + infix + FILE_SUFFIX; + } + + /** + * Returns the crypto algorithm used to sign digest files of the specified version. + */ + public static String sigAlgorithm (int version) { + switch (version) { + case 1: return "SHA1withRSA"; + case 2: return "SHA256withRSA"; + default: throw new IllegalArgumentException("Invalid digest version " + version); + } + } + + /** + * Creates a digest file at the specified location using the supplied list of resources. + * @param version the version of the digest protocol to use. + */ + public static void createDigest (int version, List resources, File output) + throws IOException + { + // first compute the digests for all the resources in parallel + ExecutorService exec = Executors.newFixedThreadPool(SysProps.threadPoolSize()); + final Map digests = new ConcurrentHashMap<>(); + final BlockingQueue completed = new LinkedBlockingQueue<>(); + final int fversion = version; + + long start = System.currentTimeMillis(); + + Set pending = new HashSet<>(resources); + for (final Resource rsrc : resources) { + exec.execute(new Runnable() { + public void run () { + try { + MessageDigest md = getMessageDigest(fversion); + digests.put(rsrc, rsrc.computeDigest(fversion, md, null)); + completed.add(rsrc); + } catch (Throwable t) { + completed.add(new IOException("Error computing digest for: " + rsrc). + initCause(t)); + } + } + }); + } + + // queue a shutdown of the thread pool when the tasks are done + exec.shutdown(); + + try { + while (pending.size() > 0) { + Object done = completed.poll(600, TimeUnit.SECONDS); + if (done instanceof IOException) { + throw (IOException)done; + } else if (done instanceof Resource) { + pending.remove((Resource)done); + } else { + throw new AssertionError("What is this? " + done); + } + } + } catch (InterruptedException ie) { + throw new IOException("Timeout computing digests. Wow."); + } + + StringBuilder data = new StringBuilder(); + try (FileOutputStream fos = new FileOutputStream(output); + OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8); + PrintWriter pout = new PrintWriter(osw)) { + // compute and append the digest of each resource in the list + for (Resource rsrc : resources) { + String path = rsrc.getPath(); + String digest = digests.get(rsrc); + note(data, path, digest); + pout.println(path + " = " + digest); + } + // finally compute and append the digest for the file contents + MessageDigest md = getMessageDigest(version); + byte[] contents = data.toString().getBytes(UTF_8); + String filename = digestFile(version); + pout.println(filename + " = " + StringUtil.hexlate(md.digest(contents))); + } + + long elapsed = System.currentTimeMillis() - start; + log.debug("Computed digests [rsrcs=" + resources.size() + ", time=" + elapsed + "ms]"); + } + + /** + * Obtains an appropriate message digest instance for use by the Getdown system. + */ + public static MessageDigest getMessageDigest (int version) + { + String algo = version > 1 ? "SHA-256" : "MD5"; + try { + return MessageDigest.getInstance(algo); + } catch (NoSuchAlgorithmException nsae) { + throw new RuntimeException("JVM does not support " + algo + ". Gurp!"); + } + } + + /** + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory, using the current digest version. + */ + public Digest (File appdir, boolean strictComments) throws IOException { + this(appdir, VERSION, strictComments); + } + + /** + * Creates a digest instance which will parse and validate the digest in the supplied + * application directory. + * @param version the version of the digest protocol to use. + */ + public Digest (File appdir, int version, boolean strictComments) throws IOException + { + // parse and validate our digest file contents + String filename = digestFile(version); + StringBuilder data = new StringBuilder(); + File dfile = new File(appdir, filename); + Config.ParseOpts opts = Config.createOpts(false); + opts.strictComments = strictComments; + // bias = toward key: the key is the filename and could conceivably contain = signs, value + // is the hex encoded hash which will not contain = + opts.biasToKey = true; + for (String[] pair : Config.parsePairs(dfile, opts)) { + if (pair[0].equals(filename)) { + _metaDigest = pair[1]; + break; + } + _digests.put(pair[0], pair[1]); + note(data, pair[0], pair[1]); + } + + // we've reached the end, validate our contents + MessageDigest md = getMessageDigest(version); + byte[] contents = data.toString().getBytes(UTF_8); + String hash = StringUtil.hexlate(md.digest(contents)); + if (!hash.equals(_metaDigest)) { + String err = MessageUtil.tcompose("m.invalid_digest_file", _metaDigest, hash); + throw new IOException(err); + } + } + + /** + * Returns the digest for the digest file. + */ + public String getMetaDigest () + { + return _metaDigest; + } + + /** + * Computes the hash of the specified resource and compares it with the value parsed from + * the digest file. Logs a message if the resource fails validation. + * + * @return true if the resource is valid, false if it failed the digest check or if an I/O + * error was encountered during the validation process. + */ + public boolean validateResource (Resource resource, ProgressObserver obs) + { + try { + String chash = resource.computeDigest(VERSION, getMessageDigest(VERSION), obs); + String ehash = _digests.get(resource.getPath()); + if (chash.equals(ehash)) { + return true; + } + log.info("Resource failed digest check", + "rsrc", resource, "computed", chash, "expected", ehash); + } catch (Throwable t) { + log.info("Resource failed digest check", "rsrc", resource, "error", t); + } + return false; + } + + /** + * Returns the digest of the given {@code resource}. + */ + public String getDigest (Resource resource) + { + return _digests.get(resource.getPath()); + } + + /** Used by {@link #createDigest} and {@link Digest}. */ + protected static void note (StringBuilder data, String path, String digest) + { + data.append(path).append(" = ").append(digest).append("\n"); + } + + protected HashMap _digests = new HashMap<>(); + protected String _metaDigest = ""; + + protected static final String FILE_NAME = "digest"; + protected static final String FILE_SUFFIX = ".txt"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java new file mode 100644 index 0000000..6de4e2e --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java @@ -0,0 +1,229 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.FileInputStream; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.*; + +import com.threerings.getdown.util.StringUtil; + +/** Configuration that comes from our "environment" (command line args, sys props, etc.). */ +public final class EnvConfig { + + /** Used to report problems or feedback by {@link #create}. */ + public static final class Note { + public static enum Level { INFO, WARN, ERROR }; + public static Note info (String msg) { return new Note(Level.INFO, msg); } + public static Note warn (String msg) { return new Note(Level.WARN, msg); } + public static Note error (String msg) { return new Note(Level.ERROR, msg); } + public final Level level; + public final String message; + public Note (Level level, String message) { + this.level = level; + this.message = message; + } + } + + /** + * Creates an environment config, obtaining information (in order) from the following sources: + * + *
    + *
  • A {@code bootstrap.properties} file bundled with the jar.
  • + *
  • System properties supplied to the JVM.
  • + *
  • The supplied command line arguments ({@code argv}).
  • + *
+ * + * If a later source supplies a configuration already provided by a prior source, a warning + * message will be logged to indicate the conflict, and the prior source will be used. + * + * @param notes a list into which notes are added, to be logged after the logging system has + * been initialized (which cannot happen until the appdir is known). If any {@code ERROR} notes + * are included, the app should terminate after reporting them. + * @return an env config instance, or {@code null} if no appdir could be located via any + * configuration source. + */ + public static EnvConfig create (String[] argv, List notes) { + String appDir = null, appDirProv = null; + String appId = null, appIdProv = null; + String appBase = null, appBaseProv = null; + + // start with bootstrap.properties config, if avaialble + try { + ResourceBundle bundle = ResourceBundle.getBundle("bootstrap"); + if (bundle.containsKey("appdir")) { + appDir = bundle.getString("appdir"); + appDir = appDir.replace(USER_HOME_KEY, System.getProperty("user.home")); + appDirProv = "bootstrap.properties"; + } + if (bundle.containsKey("appid")) { + appId = bundle.getString("appid"); + appIdProv = "bootstrap.properties"; + } + if (bundle.containsKey("appbase")) { + appBase = bundle.getString("appbase"); + appBaseProv = "bootstrap.properties"; + } + // if any system properties are specified (keys prefixed with sys.), set those up + for (String key : bundle.keySet()) { + if (key.startsWith("sys.")) { + String skey = key.substring(4); + String svalue = bundle.getString(key); + notes.add(Note.info("Setting system property from bundle: " + + skey + "='" + svalue + "'")); + System.setProperty(skey, svalue); + } + } + + } catch (MissingResourceException e) { + // bootstrap.properties is optional; no need for a warning + } + + // next seek config from system properties + String spropsAppDir = SysProps.appDir(); + if (!StringUtil.isBlank(spropsAppDir)) { + if (appDir == null) { + appDir = spropsAppDir; + appDirProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appdir' system property, have appdir via '" + + appDirProv + "'")); + } + } + String spropsAppId = SysProps.appId(); + if (!StringUtil.isBlank(spropsAppId)) { + if (appId == null) { + appId = spropsAppId; + appIdProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appid' system property, have appid via '" + + appIdProv + "'")); + } + } + String spropsAppBase = SysProps.appBase(); + if (!StringUtil.isBlank(spropsAppBase)) { + if (appBase == null) { + appBase = spropsAppBase; + appBaseProv = "system property"; + } else { + notes.add(Note.warn("Ignoring 'appbase' system property, have appbase via '" + + appBaseProv + "'")); + } + } + + // finally obtain config from command line arguments + String argvAppDir = argv.length > 0 ? argv[0] : null; + if (!StringUtil.isBlank(argvAppDir)) { + if (appDir == null) { + appDir = argvAppDir; + appDirProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appdir' command line arg, have appdir via '" + + appDirProv + "'")); + } + } + String argvAppId = argv.length > 1 ? argv[1] : null; + if (!StringUtil.isBlank(argvAppId)) { + if (appId == null) { + appId = argvAppId; + appIdProv = "command line"; + } else { + notes.add(Note.warn("Ignoring 'appid' command line arg, have appid via '" + + appIdProv + "'")); + } + } + + // ensure that we were able to fine an app dir + if (appDir == null) { + return null; // caller will report problem to user + } + + notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir)); + if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId)); + if (appBase != null) notes.add( + Note.info("Using appbase from " + appBaseProv + ": " + appBase)); + + // ensure that the appdir refers to a directory that exists + File appDirFile = new File(appDir); + if (!appDirFile.exists()) { + // if we have a bootstrap URL then we auto-create the app dir; this enables an + // installer to simply place a getdown.jar file somewhere and create an OS shortcut + // that runs getdown with an appdir and appbase specified, and have getdown create the + // appdir and download the app into it + if (!StringUtil.isBlank(appBase)) { + if (appDirFile.mkdirs()) { + notes.add(Note.info("Auto-created app directory '" + appDir + "'")); + } else { + notes.add(Note.warn("Unable to auto-create app dir: '" + appDir + "'")); + } + } else { + notes.add(Note.error("Invalid appdir '" + appDir + "': directory does not exist")); + return null; + } + } else if (!appDirFile.isDirectory()) { + notes.add(Note.error("Invalid appdir '" + appDir + "': refers to non-directory")); + return null; + } + + // pass along anything after the first two args as extra app args + List appArgs = argv.length > 2 ? + Arrays.asList(argv).subList(2, argv.length) : + Collections.emptyList(); + + // load X.509 certificate if it exists + File crtFile = new File(appDirFile, Digest.digestFile(Digest.VERSION) + ".crt"); + List certs = new ArrayList<>(); + if (crtFile.exists()) { + try (FileInputStream fis = new FileInputStream(crtFile)) { + X509Certificate certificate = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(fis); + certs.add(certificate); + } catch (Exception e) { + notes.add(Note.error("Certificate error: " + e.getMessage())); + } + } + + return new EnvConfig(appDirFile, appId, appBase, certs, appArgs); + } + + /** The directory in which the application and metadata is stored. */ + public final File appDir; + + /** Either {@code null} or an identifier for a secondary application that should be + * launched. That app will use {@code appid.class} and {@code appid.apparg} to configure + * itself but all other parameters will be the same as the primary app. */ + public final String appId; + + /** Either {@code null} or fallback {@code appbase} to use if one cannot be read from a + * {@code getdown.txt} file during startup. */ + public final String appBase; + + /** Zero or more signing certificates used to verify the digest file. */ + public final List certs; + + /** Additional arguments to pass on to launched application. These will be added after the + * args in the getdown.txt file. */ + public final List appArgs; + + public EnvConfig (File appDir) { + this(appDir, null, null, Collections.emptyList(), + Collections.emptyList()); + } + + private EnvConfig (File appDir, String appId, String appBase, List certs, + List appArgs) { + this.appDir = appDir; + this.appId = appId; + this.appBase = appBase; + this.certs = certs; + this.appArgs = appArgs; + } + + private static final String USER_HOME_KEY = "${user.home}"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java new file mode 100644 index 0000000..b0a1dc9 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java @@ -0,0 +1,135 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.jar.JarFile; + +import com.threerings.getdown.cache.GarbageCollector; +import com.threerings.getdown.cache.ResourceCache; +import com.threerings.getdown.util.FileUtil; +import static com.threerings.getdown.Log.log; + +public class PathBuilder +{ + /** Name of directory to store cached code files in. */ + public static final String CODE_CACHE_DIR = ".cache"; + + /** Name of directory to store cached native resources in. */ + public static final String NATIVE_CACHE_DIR = ".ncache"; + + /** + * Builds either a default or cached classpath based on {@code app}'s configuration. + */ + public static ClassPath buildClassPath (Application app) throws IOException + { + return app.useCodeCache() ? buildCachedClassPath(app) : buildDefaultClassPath(app); + } + + /** + * Builds a {@link ClassPath} instance for {@code app} using the code resources in place in + * the app directory. + */ + public static ClassPath buildDefaultClassPath (Application app) + { + LinkedHashSet classPathEntries = new LinkedHashSet(); + for (Resource resource: app.getActiveCodeResources()) { + classPathEntries.add(resource.getFinalTarget()); + } + return new ClassPath(classPathEntries); + } + + /** + * Builds a {@link ClassPath} instance for {@code app} by first copying the code resources into + * a cache directory and then referencing them from there. This avoids problems with + * overwriting in-use classpath elements when the application is later updated. This also + * "garbage collects" expired caches if necessary. + */ + public static ClassPath buildCachedClassPath (Application app) throws IOException + { + File codeCacheDir = new File(app.getAppDir(), CODE_CACHE_DIR); + + // a negative value of code_cache_retention_days allows to clean up the cache forcefully + long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays()); + if (retainMillis != 0L) { + GarbageCollector.collect(codeCacheDir, retainMillis); + } + + ResourceCache cache = new ResourceCache(codeCacheDir); + LinkedHashSet classPathEntries = new LinkedHashSet<>(); + for (Resource resource: app.getActiveCodeResources()) { + String digest = app.getDigest(resource); + File entry = cache.cacheFile(resource.getFinalTarget(), digest.substring(0, 2), digest); + classPathEntries.add(entry); + } + + return new ClassPath(classPathEntries); + } + + /** + * Builds a {@link ClassPath} instance by first caching all native jars (indicated by + * nresource=[native jar]), unpacking them, and referencing the locations of each of the + * unpacked files. Also performs garbage collection similar to {@link #buildCachedClassPath} + * + * @param app used to determine native jars and related information. + * @param addCurrentLibraryPath if true, it adds the locations referenced by + * {@code System.getProperty("java.library.path")} as well. + * @return a classpath instance if at least one native resource was found and unpacked, + * {@code null} if no native resources were used by the application. + */ + public static ClassPath buildLibsPath (Application app, + boolean addCurrentLibraryPath) throws IOException { + List resources = app.getNativeResources(); + if (resources.isEmpty()) { + return null; + } + + LinkedHashSet nativedirs = new LinkedHashSet<>(); + File nativeCacheDir = new File(app.getAppDir(), NATIVE_CACHE_DIR); + ResourceCache cache = new ResourceCache(nativeCacheDir); + + // negative value forces total garbage collection, 0 avoids garbage collection at all + long retainMillis = TimeUnit.DAYS.toMillis(app.getCodeCacheRetentionDays()); + if (retainMillis != 0L) { + GarbageCollector.collectNative(nativeCacheDir, retainMillis); + } + + for (Resource resource : resources) { + // Use untruncated cache subdirectory names to avoid overwriting issues when unpacking, + // in the off chance that two native jars share a directory AND contain files with the + // same names + String digest = app.getDigest(resource); + File cachedFile = cache.cacheFile(resource.getFinalTarget(), digest, digest); + File cachedParent = cachedFile.getParentFile(); + File unpackedIndicator = new File(cachedParent, cachedFile.getName() + ".unpacked"); + + if (!unpackedIndicator.exists()) { + try { + FileUtil.unpackJar(new JarFile(cachedFile), cachedParent, false); + unpackedIndicator.createNewFile(); + } catch (IOException ioe) { + log.warning("Failed to unpack native jar", + "file", cachedFile.getAbsolutePath(), ioe); + // Keep going and unpack the other jars... + } + } + + nativedirs.add(cachedFile.getParentFile()); + } + + if (addCurrentLibraryPath) { + for (String path : System.getProperty("java.library.path").split(File.pathSeparator)) { + nativedirs.add(new File(path)); + } + } + + return new ClassPath(nativedirs); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java new file mode 100644 index 0000000..e70bd4b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java @@ -0,0 +1,19 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +/** + * System property constants associated with Getdown. + */ +public class Properties +{ + /** This property will be set to "true" on the application when it is being run by getdown. */ + public static final String GETDOWN = "com.threerings.getdown"; + + /** If accepting connections from the launched application, this property + * will be set to the connection server port. */ + public static final String CONNECT_PORT = "com.threerings.getdown.connectPort"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java new file mode 100644 index 0000000..3e2f446 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java @@ -0,0 +1,394 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.*; +import java.net.URL; +import java.security.MessageDigest; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Models a single file resource used by an {@link Application}. + */ +public class Resource implements Comparable +{ + /** Defines special attributes for resources. */ + public static enum Attr { + /** Indicates that the resource should be unpacked. */ + UNPACK, + /** If present, when unpacking a resource, any directories created by the newly + * unpacked resource will first be cleared of files before unpacking. */ + CLEAN, + /** Indicates that the resource should be marked executable. */ + EXEC, + /** Indicates that the resource should be downloaded before a UI is displayed. */ + PRELOAD, + /** Indicates that the resource is a jar containing native libs. */ + NATIVE + }; + + public static final EnumSet NORMAL = EnumSet.noneOf(Attr.class); + public static final EnumSet UNPACK = EnumSet.of(Attr.UNPACK); + public static final EnumSet EXEC = EnumSet.of(Attr.EXEC); + public static final EnumSet PRELOAD = EnumSet.of(Attr.PRELOAD); + public static final EnumSet NATIVE = EnumSet.of(Attr.NATIVE); + + /** + * Computes the MD5 hash of the supplied file. + * @param version the version of the digest protocol to use. + */ + public static String computeDigest (int version, File target, MessageDigest md, + ProgressObserver obs) + throws IOException + { + md.reset(); + byte[] buffer = new byte[DIGEST_BUFFER_SIZE]; + int read; + + boolean isJar = isJar(target.getPath()); + boolean isPacked200Jar = isPacked200Jar(target.getPath()); + + // if this is a jar, we need to compute the digest in a "timestamp and file order" agnostic + // manner to properly correlate jardiff patched jars with their unpatched originals + if (isJar || isPacked200Jar){ + File tmpJarFile = null; + JarFile jar = null; + try { + // if this is a compressed jar file, uncompress it to compute the jar file digest + if (isPacked200Jar){ + tmpJarFile = new File(target.getPath() + ".tmp"); + FileUtil.unpackPacked200Jar(target, tmpJarFile); + jar = new JarFile(tmpJarFile); + } else{ + jar = new JarFile(target); + } + + List entries = Collections.list(jar.entries()); + Collections.sort(entries, ENTRY_COMP); + + int eidx = 0; + for (JarEntry entry : entries) { + // old versions of the digest code skipped metadata + if (version < 2) { + if (entry.getName().startsWith("META-INF")) { + updateProgress(obs, eidx, entries.size()); + continue; + } + } + + try (InputStream in = jar.getInputStream(entry)) { + while ((read = in.read(buffer)) != -1) { + md.update(buffer, 0, read); + } + } + + updateProgress(obs, eidx, entries.size()); + } + + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException ioe) { + log.warning("Error closing jar", "path", target, "jar", jar, "error", ioe); + } + } + if (tmpJarFile != null) { + FileUtil.deleteHarder(tmpJarFile); + } + } + + } else { + long totalSize = target.length(), position = 0L; + try (FileInputStream fin = new FileInputStream(target)) { + while ((read = fin.read(buffer)) != -1) { + md.update(buffer, 0, read); + position += read; + updateProgress(obs, position, totalSize); + } + } + } + return StringUtil.hexlate(md.digest()); + } + + /** + * Creates a resource with the supplied remote URL and local path. + */ + public Resource (String path, URL remote, File local, EnumSet attrs) + { + _path = path; + _remote = remote; + _local = local; + _localNew = new File(local.toString() + "_new"); + String lpath = _local.getPath(); + _marker = new File(lpath + "v"); + + _attrs = attrs; + _isJar = isJar(lpath); + _isPacked200Jar = isPacked200Jar(lpath); + boolean unpack = attrs.contains(Attr.UNPACK); + if (unpack && _isJar) { + _unpacked = _local.getParentFile(); + } else if(unpack && _isPacked200Jar) { + String dotJar = ".jar", lname = _local.getName(); + String uname = lname.substring(0, lname.lastIndexOf(dotJar) + dotJar.length()); + _unpacked = new File(_local.getParent(), uname); + } + } + + /** + * Returns the path associated with this resource. + */ + public String getPath () + { + return _path; + } + + /** + * Returns the local location of this resource. + */ + public File getLocal () + { + return _local; + } + + /** + * Returns the location of the to-be-installed new version of this resource. + */ + public File getLocalNew () + { + return _localNew; + } + + /** + * Returns the location of the unpacked resource. + */ + public File getUnpacked () + { + return _unpacked; + } + + /** + * Returns the final target of this resource, whether it has been unpacked or not. + */ + public File getFinalTarget () + { + return shouldUnpack() ? getUnpacked() : getLocal(); + } + + /** + * Returns the remote location of this resource. + */ + public URL getRemote () + { + return _remote; + } + + /** + * Returns true if this resource should be unpacked as a part of the validation process. + */ + public boolean shouldUnpack () + { + return _attrs.contains(Attr.UNPACK) && !SysProps.noUnpack(); + } + + /** + * Returns true if this resource should be pre-downloaded. + */ + public boolean shouldPredownload () + { + return _attrs.contains(Attr.PRELOAD); + } + + /** + * Returns true if this resource is a native lib jar. + */ + public boolean isNative () + { + return _attrs.contains(Attr.NATIVE); + } + + /** + * Computes the MD5 hash of this resource's underlying file. + * Note: This is both CPU and I/O intensive. + * @param version the version of the digest protocol to use. + */ + public String computeDigest (int version, MessageDigest md, ProgressObserver obs) + throws IOException + { + File file; + if (_local.toString().toLowerCase(Locale.ROOT).endsWith(Application.CONFIG_FILE)) { + file = _local; + } else { + file = _localNew.exists() ? _localNew : _local; + } + return computeDigest(version, file, md, obs); + } + + /** + * Returns true if this resource has an associated "validated" marker + * file. + */ + public boolean isMarkedValid () + { + if (!_local.exists()) { + clearMarker(); + return false; + } + return _marker.exists(); + } + + /** + * Creates a "validated" marker file for this resource to indicate + * that its MD5 hash has been computed and compared with the value in + * the digest file. + * + * @throws IOException if we fail to create the marker file. + */ + public void markAsValid () + throws IOException + { + _marker.createNewFile(); + } + + /** + * Removes any "validated" marker file associated with this resource. + */ + public void clearMarker () + { + if (_marker.exists() && !FileUtil.deleteHarder(_marker)) { + log.warning("Failed to erase marker file '" + _marker + "'."); + } + } + + /** + * Installs the {@code getLocalNew} version of this resource to {@code getLocal}. + * @param validate whether or not to mark the resource as valid after installing. + */ + public void install (boolean validate) throws IOException { + File source = getLocalNew(), dest = getLocal(); + log.info("- " + source); + if (!FileUtil.renameTo(source, dest)) { + throw new IOException("Failed to rename " + source + " to " + dest); + } + applyAttrs(); + if (validate) { + markAsValid(); + } + } + + /** + * Unpacks this resource file into the directory that contains it. + */ + public void unpack () throws IOException + { + // sanity check + if (!_isJar && !_isPacked200Jar) { + throw new IOException("Requested to unpack non-jar file '" + _local + "'."); + } + if (_isJar) { + try (JarFile jar = new JarFile(_local)) { + FileUtil.unpackJar(jar, _unpacked, _attrs.contains(Attr.CLEAN)); + } + } else { + FileUtil.unpackPacked200Jar(_local, _unpacked); + } + } + + /** + * Applies this resources special attributes: unpacks this resource if needed, marks it as + * executable if needed. + */ + public void applyAttrs () throws IOException { + if (shouldUnpack()) { + unpack(); + } + if (_attrs.contains(Attr.EXEC)) { + FileUtil.makeExecutable(_local); + } + } + + /** + * Wipes this resource file along with any "validated" marker file that may be associated with + * it. + */ + public void erase () + { + clearMarker(); + if (_local.exists() && !FileUtil.deleteHarder(_local)) { + log.warning("Failed to erase resource '" + _local + "'."); + } + } + + @Override public int compareTo (Resource other) { + return _path.compareTo(other._path); + } + + @Override public boolean equals (Object other) + { + if (other instanceof Resource) { + return _path.equals(((Resource)other)._path); + } else { + return false; + } + } + + @Override public int hashCode () + { + return _path.hashCode(); + } + + @Override public String toString () + { + return _path; + } + + /** Helper function to simplify the process of reporting progress. */ + protected static void updateProgress (ProgressObserver obs, long pos, long total) + { + if (obs != null) { + obs.progress((int)(100 * pos / total)); + } + } + + protected static boolean isJar (String path) + { + return path.endsWith(".jar") || path.endsWith(".jar_new"); + } + + protected static boolean isPacked200Jar (String path) + { + return path.endsWith(".jar.pack") || path.endsWith(".jar.pack_new") || + path.endsWith(".jar.pack.gz")|| path.endsWith(".jar.pack.gz_new"); + } + + protected String _path; + protected URL _remote; + protected File _local, _localNew, _marker, _unpacked; + protected EnumSet _attrs; + protected boolean _isJar, _isPacked200Jar; + + /** Used to sort the entries in a jar file. */ + protected static final Comparator ENTRY_COMP = new Comparator() { + @Override public int compare (JarEntry e1, JarEntry e2) { + return e1.getName().compareTo(e2.getName()); + } + }; + + protected static final int DIGEST_BUFFER_SIZE = 5 * 1025; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java new file mode 100644 index 0000000..0d96ecb --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java @@ -0,0 +1,185 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.threerings.getdown.util.VersionUtil; + +/** + * This class encapsulates all system properties that are read and processed by Getdown. Don't + * stick a call to {@code System.getProperty} randomly into the code, put it in here and give it an + * accessor so that it's easy to see all of the secret system property arguments that Getdown makes + * use of. + */ +public class SysProps +{ + /** Configures the appdir (in lieu of passing it in argv). Usage: {@code -Dappdir=foo}. */ + public static String appDir () { + return System.getProperty("appdir"); + } + + /** Configures the appid (in lieu of passing it in argv). Usage: {@code -Dappid=foo}. */ + public static String appId () { + return System.getProperty("appid"); + } + + /** Configures the bootstrap appbase (used in lieu of providing a skeleton getdown.txt, and as + * a last resort fallback). Usage: {@code -Dappbase=URL}. */ + public static String appBase () { + return System.getProperty("appbase"); + } + + /** If true, disables redirection of logging into {@code launcher.log}. + * Usage: {@code -Dno_log_redir}. */ + public static boolean noLogRedir () { + return System.getProperty("no_log_redir") != null; + } + + /** Overrides the domain on {@code appbase}. Usage: {@code -Dappbase_domain=foo}. */ + public static String appbaseDomain () { + return System.getProperty("appbase_domain"); + } + + /** Overrides enter {@code appbase}. Usage: {@code -Dappbase_override=URL}. */ + public static String appbaseOverride () { + return System.getProperty("appbase_override"); + } + + /** If true, Getdown installs the app without ever bringing up a UI (except in the event of an + * error). NOTE: it does not launch the app. See {@link #launchInSilent}. + * Usage: {@code -Dsilent}. */ + public static boolean silent () { + return System.getProperty("silent") != null; + } + + /** Instructs Getdown to install/update the app without ever bringing up a UI (except in the + * event of an error), and then launch it. + * Usage: {@code -Dsilent=launch}. */ + public static boolean launchInSilent () { + return "launch".equals(System.getProperty("silent")); + } + + /** + * Instructs Getdown to launch the app without updating it, or ever bringing up a UI (except + * in the event of an error). + * Usage: {@code -Dsilent=noupdate}. + */ + public static boolean noUpdate() { + return "noupdate".equals(System.getProperty("silent")); + } + + /** If true, Getdown does not automatically install updates after downloading them. It waits + * for the application to call `Getdown.install`. + * Usage: {@code -Dno_install}. */ + public static boolean noInstall () { + return System.getProperty("no_install") != null; + } + + /** Specifies the delay (in minutes) to wait before starting the update and install process. + * Minimum delay is 0 minutes, or no delay (negative values are rounded up to 0 minutes). + * Maximum delay is 1 day, or 1440 minutes (larger values are rounded down to 1 day). + * Usage: {@code -Ddelay=N}. */ + public static int startDelay () { + return Math.min(Math.max(Integer.getInteger("delay", 0), 0), 60 * 24); + } + + /** If true, Getdown will not unpack {@code uresource} jars. Usage: {@code -Dno_unpack}. */ + public static boolean noUnpack () { + return Boolean.getBoolean("no_unpack"); + } + + /** If true, Getdown will run the application in the same VM in which Getdown is running. If + * false (the default), Getdown will fork a new VM. Note that reusing the same VM prevents + * Getdown from configuring some launch-time-only VM parameters (like -mxN etc.). + * Usage: {@code -Ddirect}. */ + public static boolean direct () { + return Boolean.getBoolean("direct"); + } + + /** Specifies the connection timeout (in seconds) to use when downloading control files from + * the server. This is chiefly useful when you are running in versionless mode and want Getdown + * to more quickly timeout its startup update check if the server with which it is + * communicating is not available. Usage: {@code -Dconnect_timeout=N}. */ + public static int connectTimeout () { + return Integer.getInteger("connect_timeout", 0); + } + + /** Specifies the read timeout (in seconds) to use when downloading all files from the server. + * The default is 30 seconds, meaning that if a download stalls for more than 30 seconds, the + * update process wil fail. Setting the timeout to zero (or a negative value) will disable it. + * Usage: {@code -Dread_timeout=N}. */ + public static int readTimeout () { + return Integer.getInteger("read_timeout", 30); + } + + /** Returns the number of threads used to perform digesting and verifying operations in + * parallel. Usage: {@code -Dthread_pool_size=N} */ + public static int threadPoolSize () { + int defaultSize = Math.max(Runtime.getRuntime().availableProcessors()-1, 1); + return Integer.getInteger("thread_pool_size", defaultSize); + } + + /** Parses a Java version system property using the supplied regular expression. The numbers + * extracted from the regexp will be placed in each consecutive hundreds position in the + * returned value. + * + *

For example, {@code java.version} takes the form {@code 1.8.0_31}, and with the regexp + * {@code (\d+)\.(\d+)\.(\d+)(_\d+)?} we would parse {@code 1, 8, 0, 31} and combine them into + * the final value {@code 1080031}. + * + *

Note that non-numeric characters matched by the regular expression will simply be + * ignored, and optional groups which do not match are treated as zero in the final version + * calculation. + * + *

One can instead parse {@code java.runtime.version} which takes the form {@code + * 1.8.0_31-b13}. Using regexp {@code (\d+)\.(\d+)\.(\d+)_(\d+)-b(\d+)} we would parse + * {@code 1, 8, 0, 31, 13} and combine them into a final value {@code 108003113}. + * + *

Other (or future) JVMs may provide different version properties which can be parsed as + * desired using this general scheme as long as the numbers appear from left to right in order + * of significance. + * + * @throws IllegalArgumentException if no system named {@code propName} exists, or if + * {@code propRegex} does not match the returned version string. + */ + public static long parseJavaVersion (String propName, String propRegex) { + String verstr = System.getProperty(propName); + if (verstr == null) throw new IllegalArgumentException( + "No system property '" + propName + "'."); + + long vers = VersionUtil.parseJavaVersion(propRegex, verstr); + if (vers == 0L) throw new IllegalArgumentException( + "Regexp '" + propRegex + "' does not match '" + verstr + "' (from " + propName + ")"); + return vers; + } + + /** + * Applies {@code appbase_override} or {@code appbase_domain} if they are set. + */ + public static String overrideAppbase (String appbase) { + String appbaseOverride = appbaseOverride(); + if (appbaseOverride != null) { + return appbaseOverride; + } else { + return replaceDomain(appbase); + } + } + + /** + * If appbase_domain property is set, replace the domain on the provided string. + */ + public static String replaceDomain (String appbase) + { + String appbaseDomain = appbaseDomain(); + if (appbaseDomain != null) { + Matcher m = Pattern.compile("(https?://[^/]+)(.*)").matcher(appbase); + appbase = m.replaceAll(appbaseDomain + "$2"); + } + return appbase; + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java new file mode 100644 index 0000000..6033e2f --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java @@ -0,0 +1,229 @@ +// +// 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.IOException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import com.threerings.getdown.data.Resource; + +import static com.threerings.getdown.Log.log; + +/** + * Handles the download of a collection of files, first issuing HTTP head requests to obtain size + * information and then downloading the files individually, reporting progress back via protected + * callback methods. Note: these methods are all called arbitrary download threads, so + * implementors must take care to only execute thread-safe code or simply pass a message to the AWT + * thread, for example. + */ +public abstract class Downloader +{ + /** + * Start the downloading process. + * @param resources the resources to download. + * @param maxConcurrent the maximum number of concurrent downloads allowed. + * @return true if the download completed, false if it was aborted (via {@link #abort}). + */ + public boolean download (Collection resources, int maxConcurrent) + { + // first compute the total size of our download + resolvingDownloads(); + for (Resource rsrc : resources) { + try { + _sizes.put(rsrc, Math.max(checkSize(rsrc), 0L)); + } catch (IOException ioe) { + downloadFailed(rsrc, ioe); + } + } + + long totalSize = sum(_sizes.values()); + log.info("Downloading " + resources.size() + " resources", + "totalBytes", totalSize, "maxConcurrent", maxConcurrent); + + // make a note of the time at which we started the download + _start = System.currentTimeMillis(); + + // start the downloads + ExecutorService exec = Executors.newFixedThreadPool(maxConcurrent); + for (final Resource rsrc : resources) { + // make sure the resource's target directory exists + File parent = new File(rsrc.getLocal().getParent()); + if (!parent.exists() && !parent.mkdirs()) { + log.warning("Failed to create target directory for resource '" + rsrc + "'."); + } + + exec.execute(new Runnable() { + @Override public void run () { + try { + if (_state != State.ABORTED) { + download(rsrc); + } + } catch (IOException ioe) { + _state = State.FAILED; + downloadFailed(rsrc, ioe); + } + } + }); + } + exec.shutdown(); + + // wait for the downloads to complete + try { + exec.awaitTermination(10, TimeUnit.DAYS); + + // report download completion if we did not already do so via our final resource + if (_state == State.DOWNLOADING) { + downloadProgress(100, 0); + } + + } catch (InterruptedException ie) { + exec.shutdownNow(); + downloadFailed(null, ie); + } + + return _state != State.ABORTED; + } + + /** + * Aborts the in-progress download. + */ + public void abort () { + _state = State.ABORTED; + } + + /** + * Called before the downloader begins the series of HTTP head requests to determine the + * size of the files it needs to download. + */ + protected void resolvingDownloads () {} + + /** + * Reports ongoing progress toward completion of the overall downloading task. One call is + * guaranteed to be made reporting 100% completion if the download is not aborted and no + * resources fail. + * + * @param percent the percent completion of the complete download process (based on total bytes + * downloaded versus total byte size of all resources). + * @param remaining the estimated download time remaining in seconds, or {@code -1} if the time + * can not yet be determined. + */ + protected void downloadProgress (int percent, long remaining) {} + + /** + * Called if a failure occurs while downloading a resource. No progress will be reported after + * a download fails, but additional download failures may be reported. + * + * @param rsrc the resource that failed to download, or null if the download failed due to + * thread interruption. + * @param cause the exception detailing the failure. + */ + protected void downloadFailed (Resource rsrc, Exception cause) {} + + /** + * Performs the protocol-specific portion of checking download size. + */ + protected abstract long checkSize (Resource rsrc) throws IOException; + + /** + * Periodically called by the protocol-specific downloaders to update their progress. This + * should be called at least once for each resource to be downloaded, with the total downloaded + * size for that resource. It can also be called periodically along the way for each resource + * to communicate incremental progress. + * + * @param rsrc the resource currently being downloaded. + * @param currentSize the number of bytes currently downloaded for said resource. + * @param actualSize the size reported for this resource now that we're actually downloading + * it. Some web servers lie about Content-length when doing a HEAD request, so by reporting + * updated sizes here we can recover from receiving bogus information in the earlier + * {@link #checkSize} phase. + */ + protected synchronized void reportProgress (Resource rsrc, long currentSize, long actualSize) + { + // update the actual size for this resource (but don't let it shrink) + _sizes.put(rsrc, actualSize = Math.max(actualSize, _sizes.get(rsrc))); + + // update the current downloaded size for said resource; don't allow the downloaded bytes + // to exceed the original claimed size of the resource, otherwise our progress will get + // booched and we'll end up back on the Daily WTF: http://tinyurl.com/29wt4oq + _downloaded.put(rsrc, Math.min(actualSize, currentSize)); + + // notify the observer if it's been sufficiently long since our last notification + long now = System.currentTimeMillis(); + if ((now - _lastUpdate) >= UPDATE_DELAY) { + _lastUpdate = now; + + // total up our current and total bytes + long downloaded = sum(_downloaded.values()); + long totalSize = sum(_sizes.values()); + + // compute our bytes per second + long secs = (now - _start) / 1000L; + long bps = (secs == 0) ? 0 : (downloaded / secs); + + // compute our percentage completion + int pctdone = (totalSize == 0) ? 0 : (int)((downloaded * 100f) / totalSize); + + // estimate our time remaining + long remaining = (bps <= 0 || totalSize == 0) ? -1 : (totalSize - downloaded) / bps; + + // if we're complete or failed, when we don't want to report again + if (_state == State.DOWNLOADING) { + if (pctdone == 100) _state = State.COMPLETE; + downloadProgress(pctdone, remaining); + } + } + } + + /** + * Sums the supplied values. + */ + protected static long sum (Iterable values) + { + long acc = 0L; + for (Long value : values) { + acc += value; + } + return acc; + } + + protected enum State { DOWNLOADING, COMPLETE, FAILED, ABORTED } + + /** + * Accomplishes the copying of the resource from remote location to local location using + * protocol-specific code. This method should periodically check whether {@code _state} is set + * to aborted and abort any in-progress download if so. + */ + protected abstract void download (Resource rsrc) throws IOException; + + /** The reported sizes of our resources. */ + protected Map _sizes = new HashMap<>(); + + /** The bytes downloaded for each resource. */ + protected Map _downloaded = new HashMap<>(); + + /** The time at which the file transfer began. */ + protected long _start; + + /** The current transfer rate in bytes per second. */ + protected long _bytesPerSecond; + + /** The time at which the last progress update was posted to the progress observer. */ + protected long _lastUpdate; + + /** A wee state machine to ensure we call our callbacks sanely. */ + protected volatile State _state = State.DOWNLOADING; + + /** The delay in milliseconds between notifying progress observers of file download + * progress. */ + protected static final long UPDATE_DELAY = 500L; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java new file mode 100644 index 0000000..a7a3287 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java @@ -0,0 +1,115 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.net; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.ConnectionUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Implements downloading files over HTTP + */ +public class HTTPDownloader extends Downloader +{ + public HTTPDownloader (Proxy proxy) + { + _proxy = proxy; + } + + @Override protected long checkSize (Resource rsrc) throws IOException + { + URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0); + try { + // if we're accessing our data via HTTP, we only need a HEAD request + if (conn instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection)conn; + hcon.setRequestMethod("HEAD"); + hcon.connect(); + // make sure we got a satisfactory response code + if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Unable to check up-to-date for " + + rsrc.getRemote() + ": " + hcon.getResponseCode()); + } + } + return conn.getContentLength(); + + } finally { + // let it be known that we're done with this connection + conn.getInputStream().close(); + } + } + + @Override protected void download (Resource rsrc) throws IOException + { + // TODO: make FileChannel download impl (below) robust and allow apps to opt-into it via a + // system property + if (true) { + // download the resource from the specified URL + URLConnection conn = ConnectionUtil.open(_proxy, rsrc.getRemote(), 0, 0); + conn.connect(); + + // make sure we got a satisfactory response code + if (conn instanceof HttpURLConnection) { + HttpURLConnection hcon = (HttpURLConnection)conn; + if (hcon.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Unable to download resource " + rsrc.getRemote() + ": " + + hcon.getResponseCode()); + } + } + long actualSize = conn.getContentLength(); + log.info("Downloading resource", "url", rsrc.getRemote(), "size", actualSize); + long currentSize = 0L; + byte[] buffer = new byte[4*4096]; + try (InputStream in = conn.getInputStream(); + FileOutputStream out = new FileOutputStream(rsrc.getLocalNew())) { + + // TODO: look to see if we have a download info file + // containing info on potentially partially downloaded data; + // if so, use a "Range: bytes=HAVE-" header. + + // read in the file data + int read; + while ((read = in.read(buffer)) != -1) { + // abort the download if the downloader is aborted + if (_state == State.ABORTED) { + break; + } + // write it out to our local copy + out.write(buffer, 0, read); + // note that we've downloaded some data + currentSize += read; + reportProgress(rsrc, currentSize, actualSize); + } + } + + } else { + log.info("Downloading resource", "url", rsrc.getRemote(), "size", "unknown"); + File localNew = rsrc.getLocalNew(); + try (ReadableByteChannel rbc = Channels.newChannel(rsrc.getRemote().openStream()); + FileOutputStream fos = new FileOutputStream(localNew)) { + // TODO: more work is needed here, transferFrom can fail to transfer the entire + // file, in which case it's not clear what we're supposed to do.. call it again? + // will it repeatedly fail? + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + reportProgress(rsrc, localNew.length(), localNew.length()); + } + } + } + + protected final Proxy _proxy; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java new file mode 100644 index 0000000..22446ec --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java @@ -0,0 +1,32 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.spi; + +/** + * A service provider interface that handles the storage of proxy credentials. + */ +public interface ProxyAuth +{ + /** Credentials for a proxy server. */ + public static class Credentials { + public final String username; + public final String password; + public Credentials (String username, String password) { + this.username = username; + this.password = password; + } + } + + /** + * Loads the credentials for the app installed in {@code appDir}. + */ + public Credentials loadCredentials (String appDir); + + /** + * Encrypts and saves the credentials for the app installed in {@code appDir}. + */ + public void saveCredentials (String appDir, String username, String password); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java new file mode 100644 index 0000000..c2e740b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java @@ -0,0 +1,232 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +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.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import java.security.MessageDigest; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.StreamUtil; + +/** + * Generates patch files between two particular revisions of an + * application. The differences between all the files in the two + * revisions are bundled into a single patch file which is placed into the + * target version directory. + */ +public class Differ +{ + /** + * Creates a single patch file that contains the differences between + * the two specified application directories. The patch file will be + * created in the nvdir directory with name + * patchV.dat where V is the old application version. + */ + public void createDiff (File nvdir, File ovdir, boolean verbose) + throws IOException + { + // sanity check + String nvers = nvdir.getName(); + String overs = ovdir.getName(); + try { + if (Long.parseLong(nvers) <= Long.parseLong(overs)) { + String err = "New version (" + nvers + ") must be greater " + + "than old version (" + overs + ")."; + throw new IOException(err); + } + } catch (NumberFormatException nfe) { + throw new IOException("Non-numeric versions? [nvers=" + nvers + + ", overs=" + overs + "]."); + } + + Application oapp = new Application(new EnvConfig(ovdir)); + oapp.init(false); + ArrayList orsrcs = new ArrayList<>(); + orsrcs.addAll(oapp.getCodeResources()); + orsrcs.addAll(oapp.getResources()); + + Application napp = new Application(new EnvConfig(nvdir)); + napp.init(false); + ArrayList nrsrcs = new ArrayList<>(); + nrsrcs.addAll(napp.getCodeResources()); + nrsrcs.addAll(napp.getResources()); + + // first create a patch for the main application + File patch = new File(nvdir, "patch" + overs + ".dat"); + createPatch(patch, orsrcs, nrsrcs, verbose); + + // next create patches for any auxiliary resource groups + for (Application.AuxGroup ag : napp.getAuxGroups()) { + orsrcs = new ArrayList<>(); + Application.AuxGroup oag = oapp.getAuxGroup(ag.name); + if (oag != null) { + orsrcs.addAll(oag.codes); + orsrcs.addAll(oag.rsrcs); + } + nrsrcs = new ArrayList<>(); + nrsrcs.addAll(ag.codes); + nrsrcs.addAll(ag.rsrcs); + patch = new File(nvdir, "patch-" + ag.name + overs + ".dat"); + createPatch(patch, orsrcs, nrsrcs, verbose); + } + } + + protected void createPatch (File patch, ArrayList orsrcs, + ArrayList nrsrcs, boolean verbose) + throws IOException + { + int version = Digest.VERSION; + MessageDigest md = Digest.getMessageDigest(version); + try (FileOutputStream fos = new FileOutputStream(patch); + BufferedOutputStream buffered = new BufferedOutputStream(fos); + JarOutputStream jout = new JarOutputStream(buffered)) { + + // for each file in the new application, it either already exists + // in the old application, or it is new + for (Resource rsrc : nrsrcs) { + int oidx = orsrcs.indexOf(rsrc); + Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx); + if (orsrc != null) { + // first see if they are the same + String odig = orsrc.computeDigest(version, md, null); + String ndig = rsrc.computeDigest(version, md, null); + if (odig.equals(ndig)) { + if (verbose) { + System.out.println("Unchanged: " + rsrc.getPath()); + } + // by leaving it out, it will be left as is during the + // patching process + continue; + } + + // otherwise potentially create a jar diff + if (rsrc.getPath().endsWith(".jar")) { + if (verbose) { + System.out.println("JarDiff: " + rsrc.getPath()); + } + // here's a juicy one: JarDiff blindly pulls ZipEntry + // objects out of one jar file and stuffs them into + // another without clearing out things like the + // compressed size, so if, for whatever reason (like + // different JRE versions or phase of the moon) the + // compressed size in the old jar file is different + // than the compressed size generated when creating the + // jardiff jar file, ZipOutputStream will choke and + // we'll be hosed; so we recreate the jar files in + // their entirety before running jardiff on 'em + File otemp = rebuildJar(orsrc.getLocal()); + File temp = rebuildJar(rsrc.getLocal()); + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.PATCH)); + jarDiff(otemp, temp, jout); + FileUtil.deleteHarder(otemp); + FileUtil.deleteHarder(temp); + continue; + } + } + + if (verbose) { + System.out.println("Addition: " + rsrc.getPath()); + } + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.CREATE)); + pipe(rsrc.getLocal(), jout); + } + + // now any file remaining in orsrcs needs to be removed + for (Resource rsrc : orsrcs) { + // add an entry with the resource name and the deletion suffix + if (verbose) { + System.out.println("Removal: " + rsrc.getPath()); + } + jout.putNextEntry(new ZipEntry(rsrc.getPath() + Patcher.DELETE)); + } + + System.out.println("Created patch file: " + patch); + + } catch (IOException ioe) { + FileUtil.deleteHarder(patch); + throw ioe; + } + } + + protected File rebuildJar (File target) + throws IOException + { + File temp = File.createTempFile("differ", "jar"); + try (JarFile jar = new JarFile(target); + FileOutputStream tempFos = new FileOutputStream(temp); + BufferedOutputStream tempBos = new BufferedOutputStream(tempFos); + JarOutputStream jout = new JarOutputStream(tempBos)) { + byte[] buffer = new byte[4096]; + for (Enumeration< JarEntry > iter = jar.entries(); iter.hasMoreElements();) { + JarEntry entry = iter.nextElement(); + entry.setCompressedSize(-1); + jout.putNextEntry(entry); + try (InputStream in = jar.getInputStream(entry)) { + int size = in.read(buffer); + while (size != -1) { + jout.write(buffer, 0, size); + size = in.read(buffer); + } + } + } + } + return temp; + } + + protected void jarDiff (File ofile, File nfile, JarOutputStream jout) + throws IOException + { + JarDiff.createPatch(ofile.getPath(), nfile.getPath(), jout, false); + } + + public static void main (String[] args) + { + if (args.length < 2) { + System.err.println( + "Usage: Differ [-verbose] new_vers_dir old_vers_dir"); + System.exit(255); + } + Differ differ = new Differ(); + boolean verbose = false; + int aidx = 0; + if (args[0].equals("-verbose")) { + verbose = true; + aidx++; + } + try { + differ.createDiff(new File(args[aidx++]), + new File(args[aidx++]), verbose); + } catch (IOException ioe) { + System.err.println("Error: " + ioe.getMessage()); + System.exit(255); + } + } + + protected static void pipe (File file, JarOutputStream jout) + throws IOException + { + try (FileInputStream fin = new FileInputStream(file)) { + StreamUtil.copy(fin, jout); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java new file mode 100644 index 0000000..b04a653 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java @@ -0,0 +1,129 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Signature; + +import java.util.ArrayList; +import java.util.List; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.data.Digest; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.Resource; +import com.threerings.getdown.util.Base64; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handles the generation of the digest.txt file. + */ +public class Digester +{ + /** + * A command line entry point for the digester. + */ + public static void main (String[] args) + throws IOException, GeneralSecurityException + { + switch (args.length) { + case 1: + createDigests(new File(args[0]), null, null, null); + break; + case 4: + createDigests(new File(args[0]), new File(args[1]), args[2], args[3]); + break; + default: + System.err.println("Usage: Digester app_dir [keystore_path password alias]"); + System.exit(255); + } + } + + /** + * Creates digest file(s) and optionally signs them if {@code keystore} is not null. + */ + public static void createDigests (File appdir, File keystore, String password, String alias) + throws IOException, GeneralSecurityException + { + for (int version = 1; version <= Digest.VERSION; version++) { + createDigest(version, appdir); + if (keystore != null) { + signDigest(version, appdir, keystore, password, alias); + } + } + } + + /** + * Creates a digest file in the specified application directory. + */ + public static void createDigest (int version, File appdir) + throws IOException + { + File target = new File(appdir, Digest.digestFile(version)); + System.out.println("Generating digest file '" + target + "'..."); + + // create our application and instruct it to parse its business + Application app = new Application(new EnvConfig(appdir)); + app.init(false); + + List rsrcs = new ArrayList<>(); + rsrcs.add(app.getConfigResource()); + rsrcs.addAll(app.getCodeResources()); + rsrcs.addAll(app.getResources()); + for (Application.AuxGroup ag : app.getAuxGroups()) { + rsrcs.addAll(ag.codes); + rsrcs.addAll(ag.rsrcs); + } + + // now generate the digest file + Digest.createDigest(version, rsrcs, target); + } + + /** + * Creates a digest file in the specified application directory. + */ + public static void signDigest (int version, File appdir, + File storePath, String storePass, String storeAlias) + throws IOException, GeneralSecurityException + { + String filename = Digest.digestFile(version); + File inputFile = new File(appdir, filename); + File signatureFile = new File(appdir, filename + Application.SIGNATURE_SUFFIX); + + try (FileInputStream storeInput = new FileInputStream(storePath); + FileInputStream dataInput = new FileInputStream(inputFile); + FileOutputStream signatureOutput = new FileOutputStream(signatureFile)) { + + // initialize the keystore + KeyStore store = KeyStore.getInstance("JKS"); + store.load(storeInput, storePass.toCharArray()); + PrivateKey key = (PrivateKey)store.getKey(storeAlias, storePass.toCharArray()); + + // sign the digest file + String algo = Digest.sigAlgorithm(version); + Signature sig = Signature.getInstance(algo); + byte[] buffer = new byte[8192]; + int length; + + sig.initSign(key); + while ((length = dataInput.read(buffer)) != -1) { + sig.update(buffer, 0, length); + } + + // Write out the signature + String signed = Base64.encodeToString(sig.sign(), Base64.DEFAULT); + signatureOutput.write(signed.getBytes(UTF_8)); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java new file mode 100644 index 0000000..1cea0ea --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java @@ -0,0 +1,449 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +/* + * @(#)JarDiff.java 1.7 05/11/17 + * + * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * -Redistribution of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * -Redistribution in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any kind. ALL + * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING + * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") + * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE + * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST + * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, + * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY + * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, + * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or intended + * for use in the design, construction, operation or maintenance of any + * nuclear facility. + */ + +package com.threerings.getdown.tools; + +import java.io.*; +import java.util.*; +import java.util.jar.*; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * JarDiff is able to create a jar file containing the delta between two jar files (old and new). + * The delta jar file can then be applied to the old jar file to reconstruct the new jar file. + * + *

Refer to the JNLP spec for details on how this is done. + * + * @version 1.13, 06/26/03 + */ +public class JarDiff implements JarDiffCodes +{ + private static final int DEFAULT_READ_SIZE = 2048; + private static byte[] newBytes = new byte[DEFAULT_READ_SIZE]; + private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE]; + + // The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java + // and other stuff here. Instead, we use an explicit _debug flag. + private static boolean _debug; + + /** + * Creates a patch from the two passed in files, writing the result to os. + */ + public static void createPatch (String oldPath, String newPath, + OutputStream os, boolean minimal) throws IOException + { + try (JarFile2 oldJar = new JarFile2(oldPath); + JarFile2 newJar = new JarFile2(newPath)) { + + HashMap moved = new HashMap<>(); + HashSet implicit = new HashSet<>(); + HashSet moveSrc = new HashSet<>(); + HashSet newEntries = new HashSet<>(); + + // FIRST PASS + // Go through the entries in new jar and + // determine which files are candidates for implicit moves + // ( files that has the same filename and same content in old.jar + // and new.jar ) + // and for files that cannot be implicitly moved, we will either + // find out whether it is moved or new (modified) + for (JarEntry newEntry : newJar) { + String newname = newEntry.getName(); + + // Return best match of contents, will return a name match if possible + String oldname = oldJar.getBestMatch(newJar, newEntry); + if (oldname == null) { + // New or modified entry + if (_debug) { + System.out.println("NEW: "+ newname); + } + newEntries.add(newname); + } else { + // Content already exist - need to do a move + + // Should do implicit move? Yes, if names are the same, and + // no move command already exist from oldJar + if (oldname.equals(newname) && !moveSrc.contains(oldname)) { + if (_debug) { + System.out.println(newname + " added to implicit set!"); + } + implicit.add(newname); + } else { + // The 1.0.1/1.0 JarDiffPatcher cannot handle + // multiple MOVE command with same src. + // The work around here is if we are going to generate + // a MOVE command with duplicate src, we will + // instead add the target as a new file. This way + // the jardiff can be applied by 1.0.1/1.0 + // JarDiffPatcher also. + if (!minimal && (implicit.contains(oldname) || + moveSrc.contains(oldname) )) { + + // generate non-minimal jardiff + // for backward compatibility + + if (_debug) { + + System.out.println("NEW: "+ newname); + } + newEntries.add(newname); + } else { + // Use newname as key, since they are unique + if (_debug) { + System.err.println("moved.put " + newname + " " + oldname); + } + moved.put(newname, oldname); + moveSrc.add(oldname); + } + // Check if this disables an implicit 'move ' + if (implicit.contains(oldname) && minimal) { + + if (_debug) { + System.err.println("implicit.remove " + oldname); + + System.err.println("moved.put " + oldname + " " + oldname); + + } + implicit.remove(oldname); + moved.put(oldname, oldname); + moveSrc.add(oldname); + } + } + } + } + + // SECOND PASS: = - - + // - + ArrayList deleted = new ArrayList<>(); + for (JarEntry oldEntry : oldJar) { + String oldName = oldEntry.getName(); + if (!implicit.contains(oldName) && !moveSrc.contains(oldName) + && !newEntries.contains(oldName)) { + if (_debug) { + System.err.println("deleted.add " + oldName); + } + deleted.add(oldName); + } + } + + //DEBUG + if (_debug) { + //DEBUG: print out moved map + System.out.println("MOVED MAP!!!"); + for (Map.Entry entry : moved.entrySet()) { + System.out.println(entry); + } + + //DEBUG: print out IMOVE map + System.out.println("IMOVE MAP!!!"); + for (String newName : implicit) { + System.out.println("key is " + newName); + } + } + + JarOutputStream jos = new JarOutputStream(os); + + // Write out all the MOVEs and REMOVEs + createIndex(jos, deleted, moved); + + // Put in New and Modified entries + for (String newName : newEntries) { + if (_debug) { + System.out.println("New File: " + newName); + } + writeEntry(jos, newJar.getEntryByName(newName), newJar); + } + + jos.finish(); +// jos.close(); + } + } + + /** + * Writes the index file out to jos. + * oldEntries gives the names of the files that were removed, + * movedMap maps from the new name to the old name. + */ + private static void createIndex (JarOutputStream jos, List oldEntries, + Map movedMap) + throws IOException + { + StringWriter writer = new StringWriter(); + writer.write(VERSION_HEADER); + writer.write("\r\n"); + + // Write out entries that have been removed + for (String name : oldEntries) { + writer.write(REMOVE_COMMAND); + writer.write(" "); + writeEscapedString(writer, name); + writer.write("\r\n"); + } + + // And those that have moved + for (String newName : movedMap.keySet()) { + String oldName = movedMap.get(newName); + writer.write(MOVE_COMMAND); + writer.write(" "); + writeEscapedString(writer, oldName); + writer.write(" "); + writeEscapedString(writer, newName); + writer.write("\r\n"); + } + + jos.putNextEntry(new JarEntry(INDEX_NAME)); + byte[] bytes = writer.toString().getBytes(UTF_8); + jos.write(bytes, 0, bytes.length); + } + + protected static Writer writeEscapedString (Writer writer, String string) + throws IOException + { + int index = 0; + int last = 0; + char[] chars = null; + + while ((index = string.indexOf(' ', index)) != -1) { + if (last != index) { + if (chars == null) { + chars = string.toCharArray(); + } + writer.write(chars, last, index - last); + } + last = index; + index++; + writer.write('\\'); + } + if (last != 0 && chars != null) { + writer.write(chars, last, chars.length - last); + } + else { + // no spaces + writer.write(string); + } + + return writer; + } + + private static void writeEntry (JarOutputStream jos, JarEntry entry, JarFile2 file) + throws IOException + { + try (InputStream data = file.getJarFile().getInputStream(entry)) { + jos.putNextEntry(entry); + int size = data.read(newBytes); + while (size != -1) { + jos.write(newBytes, 0, size); + size = data.read(newBytes); + } + } + } + + /** + * JarFile2 wraps a JarFile providing some convenience methods. + */ + private static class JarFile2 implements Iterable, Closeable + { + private JarFile _jar; + private List _entries; + private HashMap _nameToEntryMap; + private HashMap> _crcToEntryMap; + + public JarFile2 (String path) throws IOException { + _jar = new JarFile(new File(path)); + index(); + } + + public JarFile getJarFile () { + return _jar; + } + + // from interface Iterable + @Override + public Iterator iterator () { + return _entries.iterator(); + } + + public JarEntry getEntryByName (String name) { + return _nameToEntryMap.get(name); + } + + /** + * Returns true if the two InputStreams differ. + */ + private static boolean differs (InputStream oldIS, InputStream newIS) throws IOException { + int newSize = 0; + int oldSize; + int total = 0; + boolean retVal = false; + + while (newSize != -1) { + newSize = newIS.read(newBytes); + oldSize = oldIS.read(oldBytes); + + if (newSize != oldSize) { + if (_debug) { + System.out.println("\tread sizes differ: " + newSize + + " " + oldSize + " total " + total); + } + retVal = true; + break; + } + if (newSize > 0) { + while (--newSize >= 0) { + total++; + if (newBytes[newSize] != oldBytes[newSize]) { + if (_debug) { + System.out.println("\tbytes differ at " + + total); + } + retVal = true; + break; + } + if ( retVal ) { + //Jump out + break; + } + newSize = 0; + } + } + } + + return retVal; + } + + public String getBestMatch (JarFile2 file, JarEntry entry) throws IOException { + // check for same name and same content, return name if found + if (contains(file, entry)) { + return (entry.getName()); + } + + // return name of same content file or null + return (hasSameContent(file,entry)); + } + + public boolean contains (JarFile2 f, JarEntry e) throws IOException { + + JarEntry thisEntry = getEntryByName(e.getName()); + + // Look up name in 'this' Jar2File - if not exist return false + if (thisEntry == null) + return false; + + // Check CRC - if no match - return false + if (thisEntry.getCrc() != e.getCrc()) + return false; + + // Check contents - if no match - return false + try (InputStream oldIS = getJarFile().getInputStream(thisEntry); + InputStream newIS = f.getJarFile().getInputStream(e)) { + return !differs(oldIS, newIS); + } + } + + public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException { + String thisName = null; + Long crcL = Long.valueOf(entry.getCrc()); + // check if this jar contains files with the passed in entry's crc + if (_crcToEntryMap.containsKey(crcL)) { + // get the Linked List with files with the crc + LinkedList ll = _crcToEntryMap.get(crcL); + // go through the list and check for content match + ListIterator li = ll.listIterator(0); + while (li.hasNext()) { + JarEntry thisEntry = li.next(); + // check for content match + try (InputStream oldIS = getJarFile().getInputStream(thisEntry); + InputStream newIS = file.getJarFile().getInputStream(entry)) { + if (!differs(oldIS, newIS)) { + thisName = thisEntry.getName(); + return thisName; + } + } + } + } + return thisName; + } + + private void index () throws IOException { + Enumeration entries = _jar.entries(); + + _nameToEntryMap = new HashMap<>(); + _crcToEntryMap = new HashMap<>(); + _entries = new ArrayList<>(); + if (_debug) { + System.out.println("indexing: " + _jar.getName()); + } + if (entries != null) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + long crc = entry.getCrc(); + Long crcL = Long.valueOf(crc); + if (_debug) { + System.out.println("\t" + entry.getName() + " CRC " + crc); + } + + _nameToEntryMap.put(entry.getName(), entry); + _entries.add(entry); + + // generate the CRC to entries map + if (_crcToEntryMap.containsKey(crcL)) { + // key exist, add the entry to the correcponding linked list + LinkedList ll = _crcToEntryMap.get(crcL); + ll.add(entry); + _crcToEntryMap.put(crcL, ll); + + } else { + // create a new entry in the hashmap for the new key + LinkedList ll = new LinkedList(); + ll.add(entry); + _crcToEntryMap.put(crcL, ll); + } + } + } + } + + @Override + public void close() throws IOException { + _jar.close(); + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java new file mode 100644 index 0000000..3b5db80 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java @@ -0,0 +1,24 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +/** + * Constants shared by {@link JarDiff} and {@link JarDiffPatcher}. + */ +public interface JarDiffCodes +{ + /** The name of the jardiff control file. */ + String INDEX_NAME = "META-INF/INDEX.JD"; + + /** The version header used in the control file. */ + String VERSION_HEADER = "version 1.0"; + + /** A jardiff command to remove an entry. */ + String REMOVE_COMMAND = "remove"; + + /** A jardiff command to move an entry. */ + String MOVE_COMMAND = "move"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java new file mode 100644 index 0000000..b5a0a17 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java @@ -0,0 +1,292 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +import com.threerings.getdown.util.ProgressObserver; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Applies a jardiff patch to a jar file. + */ +public class JarDiffPatcher implements JarDiffCodes +{ + /** + * Patches the specified jar file using the supplied patch file and writing + * the new jar file to the supplied target. + * + * @param jarPath the path to the original jar file. + * @param diffPath the path to the jardiff patch file. + * @param target the output stream to which we will write the patched jar. + * @param observer an optional observer to be notified of patching progress. + * + * @throws IOException if any problem occurs during patching. + */ + public void patchJar (String jarPath, String diffPath, File target, ProgressObserver observer) + throws IOException + { + File oldFile = new File(jarPath), diffFile = new File(diffPath); + + try (JarFile oldJar = new JarFile(oldFile); + JarFile jarDiff = new JarFile(diffFile); + JarOutputStream jos = new JarOutputStream(new FileOutputStream(target))) { + + Set ignoreSet = new HashSet<>(); + Map renameMap = new HashMap<>(); + determineNameMapping(jarDiff, ignoreSet, renameMap); + + // get all keys in renameMap + String[] keys = renameMap.keySet().toArray(new String[renameMap.size()]); + + // Files to implicit move + Set oldjarNames = new HashSet<>(); + Enumeration oldEntries = oldJar.entries(); + if (oldEntries != null) { + while (oldEntries.hasMoreElements()) { + oldjarNames.add(oldEntries.nextElement().getName()); + } + } + + // size depends on the three parameters below, which is basically the + // counter for each loop that do the actual writes to the output file + // since oldjarNames.size() changes in the first two loop below, we + // need to adjust the size accordingly also when oldjarNames.size() + // changes + double size = oldjarNames.size() + keys.length + jarDiff.size(); + double currentEntry = 0; + + // Handle all remove commands + oldjarNames.removeAll(ignoreSet); + size -= ignoreSet.size(); + + // Add content from JARDiff + Enumeration entries = jarDiff.entries(); + if (entries != null) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!INDEX_NAME.equals(entry.getName())) { + updateObserver(observer, currentEntry, size); + currentEntry++; + writeEntry(jos, entry, jarDiff); + + // Remove entry from oldjarNames since no implicit move is + // needed + boolean wasInOld = oldjarNames.remove(entry.getName()); + + // Update progress counters. If it was in old, we do not + // need an implicit move, so adjust total size. + if (wasInOld) { + size--; + } + + } else { + // no write is done, decrement size + size--; + } + } + } + + // go through the renameMap and apply move for each entry + for (String newName : keys) { + // Apply move command + String oldName = renameMap.get(newName); + + // Get source JarEntry + JarEntry oldEntry = oldJar.getJarEntry(oldName); + if (oldEntry == null) { + String moveCmd = MOVE_COMMAND + oldName + " " + newName; + throw new IOException("error.badmove: " + moveCmd); + } + + // Create dest JarEntry + JarEntry newEntry = new JarEntry(newName); + newEntry.setTime(oldEntry.getTime()); + newEntry.setSize(oldEntry.getSize()); + newEntry.setCompressedSize(oldEntry.getCompressedSize()); + newEntry.setCrc(oldEntry.getCrc()); + newEntry.setMethod(oldEntry.getMethod()); + newEntry.setExtra(oldEntry.getExtra()); + newEntry.setComment(oldEntry.getComment()); + + updateObserver(observer, currentEntry, size); + currentEntry++; + + try (InputStream data = oldJar.getInputStream(oldEntry)) { + writeEntry(jos, newEntry, data); + } + + // Remove entry from oldjarNames since no implicit move is needed + boolean wasInOld = oldjarNames.remove(oldName); + + // Update progress counters. If it was in old, we do not need an + // implicit move, so adjust total size. + if (wasInOld) { + size--; + } + } + + // implicit move + Iterator iEntries = oldjarNames.iterator(); + if (iEntries != null) { + while (iEntries.hasNext()) { + String name = iEntries.next(); + JarEntry entry = oldJar.getJarEntry(name); + if (entry == null) { + // names originally retrieved from the JAR, so this should never happen + throw new AssertionError("JAR entry not found: " + name); + } + updateObserver(observer, currentEntry, size); + currentEntry++; + writeEntry(jos, entry, oldJar); + } + } + updateObserver(observer, currentEntry, size); + } + } + + protected void updateObserver (ProgressObserver observer, double currentSize, double size) + { + if (observer != null) { + observer.progress((int)(100*currentSize/size)); + } + } + + protected void determineNameMapping ( + JarFile jarDiff, Set ignoreSet, Map renameMap) + throws IOException + { + InputStream is = jarDiff.getInputStream(jarDiff.getEntry(INDEX_NAME)); + if (is == null) { + throw new IOException("error.noindex"); + } + + LineNumberReader indexReader = + new LineNumberReader(new InputStreamReader(is, UTF_8)); + String line = indexReader.readLine(); + if (line == null || !line.equals(VERSION_HEADER)) { + throw new IOException("jardiff.error.badheader: " + line); + } + + while ((line = indexReader.readLine()) != null) { + if (line.startsWith(REMOVE_COMMAND)) { + List sub = getSubpaths( + line.substring(REMOVE_COMMAND.length())); + + if (sub.size() != 1) { + throw new IOException("error.badremove: " + line); + } + ignoreSet.add(sub.get(0)); + + } else if (line.startsWith(MOVE_COMMAND)) { + List sub = getSubpaths( + line.substring(MOVE_COMMAND.length())); + if (sub.size() != 2) { + throw new IOException("error.badmove: " + line); + } + + // target of move should be the key + if (renameMap.put(sub.get(1), sub.get(0)) != null) { + // invalid move - should not move to same target twice + throw new IOException("error.badmove: " + line); + } + + } else if (line.length() > 0) { + throw new IOException("error.badcommand: " + line); + } + } + } + + protected List getSubpaths (String path) + { + int index = 0; + int length = path.length(); + ArrayList sub = new ArrayList<>(); + + while (index < length) { + while (index < length && Character.isWhitespace + (path.charAt(index))) { + index++; + } + if (index < length) { + int start = index; + int last = start; + String subString = null; + + while (index < length) { + char aChar = path.charAt(index); + if (aChar == '\\' && (index + 1) < length && + path.charAt(index + 1) == ' ') { + + if (subString == null) { + subString = path.substring(last, index); + } else { + subString += path.substring(last, index); + } + last = ++index; + } else if (Character.isWhitespace(aChar)) { + break; + } + index++; + } + if (last != index) { + if (subString == null) { + subString = path.substring(last, index); + } else { + subString += path.substring(last, index); + } + } + sub.add(subString); + } + } + return sub; + } + + protected void writeEntry (JarOutputStream jos, JarEntry entry, JarFile file) + throws IOException + { + try (InputStream data = file.getInputStream(entry)) { + writeEntry(jos, entry, data); + } + } + + protected void writeEntry (JarOutputStream jos, JarEntry entry, InputStream data) + throws IOException + { + jos.putNextEntry(new JarEntry(entry.getName())); + + // Read the entry + int size = data.read(newBytes); + while (size != -1) { + jos.write(newBytes, 0, size); + size = data.read(newBytes); + } + } + + protected static final int DEFAULT_READ_SIZE = 2048; + + protected static byte[] newBytes = new byte[DEFAULT_READ_SIZE]; + protected static byte[] oldBytes = new byte[DEFAULT_READ_SIZE]; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java new file mode 100644 index 0000000..4ead59b --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java @@ -0,0 +1,205 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.tools; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import com.threerings.getdown.util.FileUtil; +import com.threerings.getdown.util.ProgressObserver; +import com.threerings.getdown.util.StreamUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Applies a unified patch file to an application directory, providing + * percentage completion feedback along the way. Note: the + * patcher is not thread safe. Create a separate patcher instance for each + * patching action that is desired. + */ +public class Patcher +{ + /** A suffix appended to file names to indicate that a file should be newly created. */ + public static final String CREATE = ".create"; + + /** A suffix appended to file names to indicate that a file should be patched. */ + public static final String PATCH = ".patch"; + + /** A suffix appended to file names to indicate that a file should be deleted. */ + public static final String DELETE = ".delete"; + + /** + * Applies the specified patch file to the application living in the + * specified application directory. The supplied observer, if + * non-null, will be notified of progress along the way. + * + *

Note: this method runs on the calling thread, thus the + * caller may want to make use of a separate thread in conjunction + * with the patcher so that the user interface is not blocked for the + * duration of the patch. + */ + public void patch (File appdir, File patch, ProgressObserver obs) + throws IOException + { + // save this information for later + _obs = obs; + _plength = patch.length(); + + try (JarFile file = new JarFile(patch)) { + Enumeration entries = file.entries(); // old skool! + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String path = entry.getName(); + long elength = entry.getCompressedSize(); + + // depending on the suffix, we do The Right Thing (tm) + if (path.endsWith(CREATE)) { + path = strip(path, CREATE); + log.info("Creating " + path + "..."); + createFile(file, entry, new File(appdir, path)); + + } else if (path.endsWith(PATCH)) { + path = strip(path, PATCH); + log.info("Patching " + path + "..."); + patchFile(file, entry, appdir, path); + + } else if (path.endsWith(DELETE)) { + path = strip(path, DELETE); + log.info("Removing " + path + "..."); + File target = new File(appdir, path); + if (!FileUtil.deleteHarder(target)) { + log.warning("Failure deleting '" + target + "'."); + } + + } else { + log.warning("Skipping bogus patch file entry: " + path); + } + + // note that we've completed this entry + _complete += elength; + } + } + } + + protected String strip (String path, String suffix) + { + return path.substring(0, path.length() - suffix.length()); + } + + protected void createFile (JarFile file, ZipEntry entry, File target) + { + // create our copy buffer if necessary + if (_buffer == null) { + _buffer = new byte[COPY_BUFFER_SIZE]; + } + + // make sure the file's parent directory exists + File pdir = target.getParentFile(); + if (!pdir.exists() && !pdir.mkdirs()) { + log.warning("Failed to create parent for '" + target + "'."); + } + + try (InputStream in = file.getInputStream(entry); + FileOutputStream fout = new FileOutputStream(target)) { + + int total = 0, read; + while ((read = in.read(_buffer)) != -1) { + total += read; + fout.write(_buffer, 0, read); + updateProgress(total); + } + + } catch (IOException ioe) { + log.warning("Error creating '" + target + "': " + ioe); + } + } + + protected void patchFile (JarFile file, ZipEntry entry, + File appdir, String path) + { + File target = new File(appdir, path); + File patch = new File(appdir, entry.getName()); + File otarget = new File(appdir, path + ".old"); + JarDiffPatcher patcher = null; + + // make sure no stale old target is lying around to mess us up + FileUtil.deleteHarder(otarget); + + // pipe the contents of the patch into a file + try (InputStream in = file.getInputStream(entry); + FileOutputStream fout = new FileOutputStream(patch)) { + + StreamUtil.copy(in, fout); + StreamUtil.close(fout); + + // move the current version of the jar to .old + if (!FileUtil.renameTo(target, otarget)) { + log.warning("Failed to .oldify '" + target + "'."); + return; + } + + // we'll need this to pass progress along to our observer + final long elength = entry.getCompressedSize(); + ProgressObserver obs = new ProgressObserver() { + public void progress (int percent) { + updateProgress((int)(percent * elength / 100)); + } + }; + + // now apply the patch to create the new target file + patcher = new JarDiffPatcher(); + patcher.patchJar(otarget.getPath(), patch.getPath(), target, obs); + + } catch (IOException ioe) { + if (patcher == null) { + log.warning("Failed to write patch file '" + patch + "': " + ioe); + } else { + log.warning("Error patching '" + target + "': " + ioe); + } + + } finally { + // clean up our temporary files + FileUtil.deleteHarder(patch); + FileUtil.deleteHarder(otarget); + } + } + + protected void updateProgress (int progress) + { + if (_obs != null) { + _obs.progress((int)(100 * (_complete + progress) / _plength)); + } + } + + public static void main (String[] args) + { + if (args.length != 2) { + System.err.println("Usage: Patcher appdir patch_file"); + System.exit(-1); + } + + Patcher patcher = new Patcher(); + try { + patcher.patch(new File(args[0]), new File(args[1]), null); + } catch (IOException ioe) { + System.err.println("Error: " + ioe.getMessage()); + System.exit(-1); + } + } + + protected ProgressObserver _obs; + protected long _complete, _plength; + protected byte[] _buffer; + + protected static final int COPY_BUFFER_SIZE = 4096; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java new file mode 100644 index 0000000..2a5db79 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.threerings.getdown.util; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to {@code Base64OutputStream} to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using ASCII + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(US_ASCII), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + 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, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -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, -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, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + 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, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -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, -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, + }; + + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder(int flags, byte[] output) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + return new String(encode(input, flags), US_ASCII); + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + return new String(encode(input, offset, len, flags), US_ASCII); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + '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', + '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', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + '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', + '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', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder(int flags, byte[] output) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { } // don't instantiate +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java new file mode 100644 index 0000000..047cead --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Color.java @@ -0,0 +1,27 @@ +// +// 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; + +/** + * Utilities for handling ARGB colors. + */ +public class Color +{ + public final static int CLEAR = 0x00000000; + public final static int WHITE = 0xFFFFFFFF; + public final static int BLACK = 0xFF000000; + + public static float brightness (int argb) { + // TODO: we're ignoring alpha here... + int red = (argb >> 16) & 0xFF; + int green = (argb >> 8) & 0xFF; + int blue = (argb >> 0) & 0xFF; + int max = Math.max(Math.max(red, green), blue); + return ((float) max) / 255.0f; + } + + private Color () {} +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java new file mode 100644 index 0000000..4fc5e16 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Config.java @@ -0,0 +1,378 @@ +// +// 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.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static com.threerings.getdown.Log.log; + +/** + * Handles parsing and runtime access for Getdown's config files (mainly {@code getdown.txt}). + * These files contain zero or more mappings for a particular string key. Config values can be + * fetched as single strings, lists of strings, or parsed into primitives or compound data types + * like colors and rectangles. + */ +public class Config +{ + /** Empty configuration. */ + public static final Config EMPTY = new Config(new HashMap()); + + /** Options that control the {@link #parsePairs} function. */ + public static class ParseOpts { + // these should be tweaked as desired by the caller + public boolean biasToKey = false; + public boolean strictComments = false; + + // these are filled in by parseConfig + public String osname = null; + public String osarch = null; + } + + /** + * Creates a parse configuration, filling in the platform filters (or not) depending on the + * value of {@code checkPlatform}. + */ + public static ParseOpts createOpts (boolean checkPlatform) { + ParseOpts opts = new ParseOpts(); + if (checkPlatform) { + opts.osname = StringUtil.deNull(System.getProperty("os.name")).toLowerCase(Locale.ROOT); + opts.osarch = StringUtil.deNull(System.getProperty("os.arch")).toLowerCase(Locale.ROOT); + } + return opts; + } + + /** + * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 + * encoding. + * + * @param opts options that influence the parsing. See {@link #createOpts}. + * + * @return a list of String[] instances containing the key/value pairs in the + * order they were parsed from the file. + */ + public static List parsePairs (File source, ParseOpts opts) + throws IOException + { + // annoyingly FileReader does not allow encoding to be specified (uses platform default) + try (FileInputStream fis = new FileInputStream(source); + InputStreamReader input = new InputStreamReader(fis, StandardCharsets.UTF_8)) { + return parsePairs(input, opts); + } + } + + /** + * See {@link #parsePairs(File,ParseOpts)}. + */ + public static List parsePairs (Reader source, ParseOpts opts) throws IOException + { + List pairs = new ArrayList<>(); + for (String line : FileUtil.readLines(source)) { + // nix comments + int cidx = line.indexOf("#"); + if (opts.strictComments ? cidx == 0 : cidx != -1) { + line = line.substring(0, cidx); + } + + // trim whitespace and skip blank lines + line = line.trim(); + if (StringUtil.isBlank(line)) { + continue; + } + + // parse our key/value pair + String[] pair = new String[2]; + // 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("="); + if (eidx != -1) { + pair[0] = line.substring(0, eidx).trim(); + pair[1] = line.substring(eidx+1).trim(); + } else { + pair[0] = line; + pair[1] = ""; + } + + // if the pair has an os qualifier, we need to process it + if (pair[1].startsWith("[")) { + int qidx = pair[1].indexOf("]"); + if (qidx == -1) { + log.warning("Bogus platform specifier", "key", pair[0], "value", pair[1]); + continue; // omit the pair entirely + } + // if we're checking qualifiers and the os doesn't match this qualifier, skip it + String quals = pair[1].substring(1, qidx); + if (opts.osname != null && !checkQualifiers(quals, opts.osname, opts.osarch)) { + log.debug("Skipping", "quals", quals, + "osname", opts.osname, "osarch", opts.osarch, + "key", pair[0], "value", pair[1]); + continue; + } + // otherwise filter out the qualifier text + pair[1] = pair[1].substring(qidx+1).trim(); + } + + pairs.add(pair); + } + + return pairs; + } + + /** + * Takes a comma-separated String of four integers and returns a rectangle using those ints as + * the its x, y, width, and height. + */ + public static Rectangle parseRect (String name, String value) + { + if (!StringUtil.isBlank(value)) { + int[] v = StringUtil.parseIntArray(value); + if (v != null && v.length == 4) { + return new Rectangle(v[0], v[1], v[2], v[3]); + } + log.warning("Ignoring invalid rect '" + name + "' config '" + value + "'."); + } + return null; + } + + /** + * Parses the given hex color value (e.g. FFCC99) and returns an {@code Integer} with that + * value. If the given value is null or not a valid hexadecimal number, this will return null. + */ + public static Integer parseColor (String hexValue) + { + if (!StringUtil.isBlank(hexValue)) { + try { + // if no alpha channel is specified, use 255 (full alpha) + int alpha = hexValue.length() > 6 ? 0 : 0xFF000000; + return Integer.parseInt(hexValue, 16) | alpha; + } catch (NumberFormatException e) { + log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e); + } + } + return null; + } + + /** + * Parses a configuration file containing key/value pairs. The file must be in the UTF-8 + * encoding. + * + * @return a map from keys to values, where a value will be an array of strings if more than + * one key/value pair in the config file was associated with the same key. + */ + public static Config parseConfig (File source, ParseOpts opts) + throws IOException + { + Map data = new HashMap<>(); + + // I thought that we could use HashMap and put new String[] {pair[1]} for + // the null case, but it mysteriously dies on launch, so leaving it as HashMap for now + for (String[] pair : parsePairs(source, opts)) { + Object value = data.get(pair[0]); + if (value == null) { + data.put(pair[0], pair[1]); + } else if (value instanceof String) { + data.put(pair[0], new String[] { (String)value, pair[1] }); + } else if (value instanceof String[]) { + String[] values = (String[])value; + String[] nvalues = new String[values.length+1]; + System.arraycopy(values, 0, nvalues, 0, values.length); + nvalues[values.length] = pair[1]; + data.put(pair[0], nvalues); + } + } + + // special magic for the getdown.txt config: if the parsed data contains 'strict_comments = + // true' then we reparse the file with strict comments (i.e. # is only assumed to start a + // comment in column 0) + if (!opts.strictComments && Boolean.parseBoolean((String)data.get("strict_comments"))) { + opts.strictComments = true; + return parseConfig(source, opts); + } + + return new Config(data); + } + + public Config (Map data) { + _data = data; + } + + /** + * Returns whether {@code name} has a value in this config. + */ + public boolean hasValue (String name) { + return _data.containsKey(name); + } + + /** + * Returns the raw-value for {@code name}. This may be a {@code String}, {@code String[]}, or + * {@code null}. + */ + public Object getRaw (String name) { + return _data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code null}. + */ + public String getString (String name) { + return (String)_data.get(name); + } + + /** + * Returns the specified config value as a string, or {@code def}. + */ + public String getString (String name, String def) { + String value = (String)_data.get(name); + return value == null ? def : value; + } + + /** + * Returns the specified config value as a boolean. + */ + public boolean getBoolean (String name) { + return Boolean.parseBoolean(getString(name)); + } + + /** + * Massages a single string into an array and leaves existing array values as is. Simplifies + * access to parameters that are expected to be arrays. + */ + public String[] getMultiValue (String name) + { + Object value = _data.get(name); + if (value instanceof String) { + return new String[] { (String)value }; + } else { + return (String[])value; + } + } + + /** Used to parse rectangle specifications from the config file. */ + public Rectangle getRect (String name, Rectangle def) + { + String value = getString(name); + Rectangle rect = parseRect(name, value); + return (rect == null) ? def : rect; + } + + /** + * Parses and returns the config value for {@code name} as an int. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public int getInt (String name, int def) { + String value = getString(name); + try { + return value == null ? def : Integer.parseInt(value); + } catch (Exception e) { + log.warning("Ignoring invalid int '" + name + "' config '" + value + "',"); + return def; + } + } + + /** + * Parses and returns the config value for {@code name} as a long. If no value is provided, + * {@code def} is returned. If the value is invalid, a warning is logged and {@code def} is + * returned. + */ + public long getLong (String name, long def) { + String value = getString(name); + try { + return value == null ? def : Long.parseLong(value); + } catch (Exception e) { + log.warning("Ignoring invalid long '" + name + "' config '" + value + "',"); + return def; + } + } + + /** Used to parse color specifications from the config file. */ + public int getColor (String name, int def) + { + String value = getString(name); + Integer color = parseColor(value); + return (color == null) ? def : color; + } + + /** Parses a list of strings from the config file. */ + public String[] getList (String name) + { + String value = getString(name); + return (value == null) ? new String[0] : StringUtil.parseStringArray(value); + } + + /** + * Parses a URL from the config file, checking first for a localized version. + */ + public String getUrl (String name, String def) + { + String value = getString(name + "." + Locale.getDefault().getLanguage()); + if (StringUtil.isBlank(value)) { + value = getString(name); + } + if (StringUtil.isBlank(value)) { + value = def; + } + if (!StringUtil.isBlank(value)) { + try { + HostWhitelist.verify(new URL(value)); + } catch (MalformedURLException e) { + log.warning("Invalid URL.", "url", value, e); + value = null; + } + } + return value; + } + + /** + * A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following + * form: + *

+     * id = os[-arch]
+     * ids = id | id,ids
+     * quals = !id | ids
+     * 
+ * Examples: [linux-amd64,linux-x86_64], [windows], [mac os x], [!windows]. Negative qualifiers + * must appear alone, they cannot be used with other qualifiers (positive or negative). + */ + protected static boolean checkQualifiers (String quals, String osname, String osarch) + { + if (quals.startsWith("!")) { + if (quals.indexOf(",") != -1) { // sanity check + log.warning("Multiple qualifiers cannot be used when one of the qualifiers " + + "is negative", "quals", quals); + return false; + } + return !checkQualifier(quals.substring(1), osname, osarch); + } + for (String qual : quals.split(",")) { + if (checkQualifier(qual, osname, osarch)) { + return true; // if we have a positive match, we can immediately return true + } + } + return false; // we had no positive matches, so return false + } + + /** A helper function for {@link #checkQualifiers}. */ + protected static boolean checkQualifier (String qual, String osname, String osarch) + { + String[] bits = qual.trim().toLowerCase(Locale.ROOT).split("-"); + String os = bits[0], arch = (bits.length > 1) ? bits[1] : ""; + return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1); + } + + private final Map _data; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java new file mode 100644 index 0000000..21b0569 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java @@ -0,0 +1,73 @@ +// +// 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); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java new file mode 100644 index 0000000..ec9d887 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java @@ -0,0 +1,239 @@ +// +// 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.*; +import java.util.*; +import java.util.jar.*; +import java.util.zip.GZIPInputStream; + +import com.threerings.getdown.Log; +import static com.threerings.getdown.Log.log; + +/** + * File related utilities. + */ +public class FileUtil +{ + /** + * Gets the specified source file to the specified destination file by hook or crook. Windows + * has all sorts of problems which we work around in this method. + * + * @return true if we managed to get the job done, false otherwise. + */ + public static boolean renameTo (File source, File dest) + { + // if we're on a civilized operating system we may be able to simple rename it + if (source.renameTo(dest)) { + return true; + } + + // fall back to trying to rename the old file out of the way, rename the new file into + // place and then delete the old file + if (dest.exists()) { + File temp = new File(dest.getPath() + "_old"); + if (temp.exists() && !deleteHarder(temp)) { + log.warning("Failed to delete old intermediate file " + temp + "."); + // the subsequent code will probably fail + } + if (dest.renameTo(temp) && source.renameTo(dest)) { + if (!deleteHarder(temp)) { + log.warning("Failed to delete intermediate file " + temp + "."); + } + return true; + } + } + + // as a last resort, try copying the old data over the new + try { + copy(source, dest); + } catch (IOException ioe) { + log.warning("Failed to copy " + source + " to " + dest + ": " + ioe); + return false; + } + + if (!deleteHarder(source)) { + log.warning("Failed to delete " + source + " after brute force copy to " + dest + "."); + } + return true; + } + + /** + * "Tries harder" to delete {@code file} than just calling {@code delete} on it. Presently this + * just means "try a second time if the first time fails, and if that fails then try to delete + * when the virtual machine terminates." On Windows Vista, sometimes deletes fail but then + * succeed if you just try again. Given that delete failure is a rare occurrence, we can + * implement this hacky workaround without any negative consequences for normal behavior. + */ + public static boolean deleteHarder (File file) { + // if at first you don't succeed... try, try again + boolean deleted = (file.delete() || file.delete()); + if (!deleted) { + file.deleteOnExit(); + } + return deleted; + } + + /** + * Force deletes {@code file} and all of its children recursively using {@link #deleteHarder}. + * Note that some children may still be deleted even if {@code false} is returned. Also, since + * {@link #deleteHarder} is used, the {@code file} could be deleted once the jvm exits even if + * {@code false} is returned. + * + * @param file file to delete. + * @return true iff {@code file} was successfully deleted. + */ + public static boolean deleteDirHarder (File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + deleteDirHarder(child); + } + } + return deleteHarder(file); + } + + /** + * Reads the contents of the supplied input stream into a list of lines. Closes the reader on + * successful or failed completion. + */ + public static List readLines (Reader in) + throws IOException + { + List lines = new ArrayList<>(); + try (BufferedReader bin = new BufferedReader(in)) { + for (String line = null; (line = bin.readLine()) != null; lines.add(line)) {} + } + return lines; + } + + /** + * Unpacks the specified jar file into the specified target directory. + * @param cleanExistingDirs if true, all files in all directories contained in {@code jar} will + * be deleted prior to unpacking the jar. + */ + public static void unpackJar (JarFile jar, File target, boolean cleanExistingDirs) + throws IOException + { + if (cleanExistingDirs) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + if (entry.isDirectory()) { + File efile = new File(target, entry.getName()); + if (efile.exists()) { + for (File f : efile.listFiles()) { + if (!f.isDirectory()) + f.delete(); + } + } + } + } + } + + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + File efile = new File(target, entry.getName()); + + // if we're unpacking a normal jar file, it will have special path + // entries that allow us to create our directories first + if (entry.isDirectory()) { + if (!efile.exists() && !efile.mkdir()) { + log.warning("Failed to create jar entry path", "jar", jar, "entry", entry); + } + continue; + } + + // but some do not, so we want to ensure that our directories exist + // prior to getting down and funky + File parent = new File(efile.getParent()); + if (!parent.exists() && !parent.mkdirs()) { + log.warning("Failed to create jar entry parent", "jar", jar, "parent", parent); + continue; + } + + try (BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(efile)); + InputStream jin = jar.getInputStream(entry)) { + StreamUtil.copy(jin, fout); + } catch (Exception e) { + throw new IOException( + Log.format("Failure unpacking", "jar", jar, "entry", efile), e); + } + } + } + + /** + * Unpacks a pack200 packed jar file from {@code packedJar} into {@code target}. If {@code + * packedJar} has a {@code .gz} extension, it will be gunzipped first. + */ + public static void unpackPacked200Jar (File packedJar, File target) throws IOException + { + try (InputStream packJarIn = new FileInputStream(packedJar); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(target))) { + boolean gz = (packedJar.getName().endsWith(".gz") || + packedJar.getName().endsWith(".gz_new")); + try (InputStream packJarIn2 = (gz ? new GZIPInputStream(packJarIn) : packJarIn)) { + Pack200.Unpacker unpacker = Pack200.newUnpacker(); + unpacker.unpack(packJarIn2, jarOut); + } + } + } + + /** + * Copies the given {@code source} file to the given {@code target}. + */ + public static void copy (File source, File target) throws IOException { + try (FileInputStream in = new FileInputStream(source); + FileOutputStream out = new FileOutputStream(target)) { + StreamUtil.copy(in, out); + } + } + + /** + * Marks {@code file} as executable, if it exists. Catches and logs any errors that occur. + */ + public static void makeExecutable (File file) { + try { + if (file.exists()) { + if (!file.setExecutable(true, false)) { + log.warning("Failed to mark as executable", "file", file); + } + } + } catch (Exception e) { + log.warning("Failed to mark as executable", "file", file, "error", e); + } + } + + /** + * Used by {@link #walkTree}. + */ + public interface Visitor + { + void visit (File file); + } + + /** + * Walks all files in {@code root}, calling {@code visitor} on each file in the tree. + */ + public static void walkTree (File root, Visitor visitor) + { + File[] children = root.listFiles(); + if (children == null) return; + Deque stack = new ArrayDeque<>(Arrays.asList(children)); + while (!stack.isEmpty()) { + File currentFile = stack.pop(); + if (currentFile.exists()) { + visitor.visit(currentFile); + File[] currentChildren = currentFile.listFiles(); + if (currentChildren != null) { + for (File file : currentChildren) { + stack.push(file); + } + } + } + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java new file mode 100644 index 0000000..c05992a --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java @@ -0,0 +1,54 @@ +// +// 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.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +import com.threerings.getdown.data.Build; + +/** + * Optional support for compiling a URL host whitelist into the Getdown JAR. + * Useful if you're on the paranoid end of the security spectrum. + * + * @see Build#hostWhitelist() + */ +public final class HostWhitelist +{ + /** + * Verifies that the specified URL should be accessible, per the built-in host whitelist. + * See {@link Build#hostWhitelist()} and {@link #verify(List,URL)}. + */ + public static URL verify (URL url) throws MalformedURLException + { + return verify(Build.hostWhitelist(), url); + } + + /** + * Verifies that the specified URL should be accessible, per the supplied host whitelist. + * If the URL should not be accessible, this method throws a {@link MalformedURLException}. + * If the URL should be accessible, this method simply returns the {@link URL} passed in. + */ + public static URL verify (List hosts, URL url) throws MalformedURLException + { + if (url == null || hosts.isEmpty()) { + // either there is no URL to validate or no whitelist was configured + return url; + } + + String urlHost = url.getHost(); + for (String host : hosts) { + String regex = host.replace(".", "\\.").replace("*", ".*"); + if (urlHost.matches(regex)) { + return url; + } + } + + throw new MalformedURLException( + "The host for the specified URL (" + url + ") is not in the host whitelist: " + hosts); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java new file mode 100644 index 0000000..829e38f --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java @@ -0,0 +1,251 @@ +// +// 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Locale; + +import static com.threerings.getdown.Log.log; + +/** + * Useful routines for launching Java applications from within other Java + * applications. + */ +public class LaunchUtil +{ + /** The directory into which a local VM installation should be unpacked. */ + public static final String LOCAL_JAVA_DIR = "java_vm"; + + /** + * Writes a 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 + * specified version and relaunch the application. + * + * @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 + * probably getdown-pro.jar or getdown-retro-pro.jar if you are using + * the results of the standard build. + * @param newVersion the new version to which Getdown will update when it is executed. + * + * @return true if the relaunch succeeded, false if we were unable to relaunch due to being on + * Windows 9x where we cannot launch subprocesses without waiting around for them to exit, + * reading their stdout and stderr all the while. If true is returned, the application may exit + * 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. + * + * @exception IOException thrown if we were unable to create the version.txt file + * 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 + * resort to telling the user that it is in a bad way. + */ + public static boolean updateVersionAndRelaunch ( + File appdir, String getdownJarName, String newVersion) + throws IOException + { + // create the file that instructs Getdown to upgrade + File vfile = new File(appdir, "version.txt"); + try (PrintStream ps = new PrintStream(new FileOutputStream(vfile))) { + ps.println(newVersion); + } + + // make sure that we can find our getdown.jar file and can safely launch children + File pro = new File(appdir, getdownJarName); + if (mustMonitorChildren() || !pro.exists()) { + return false; + } + + // do the deed + String[] args = new String[] { + getJVMPath(appdir), "-jar", pro.toString(), appdir.getPath() + }; + log.info("Running " + StringUtil.join(args, "\n ")); + try { + Runtime.getRuntime().exec(args, null); + return true; + } catch (IOException ioe) { + log.warning("Failed to run getdown", ioe); + return false; + } + } + + /** + * Reconstructs the path to the JVM used to launch this process. + */ + public static String getJVMPath (File appdir) + { + return getJVMPath(appdir, false); + } + + /** + * Reconstructs the path to the JVM used to launch this process. + * + * @param windebug if true we will use java.exe instead of javaw.exe on Windows. + */ + public static String getJVMPath (File appdir, boolean windebug) + { + // first look in our application directory for an installed VM + String vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR).getAbsolutePath(), windebug); + if (vmpath == null && isMacOS()) { + vmpath = checkJVMPath(new File(appdir, LOCAL_JAVA_DIR + "/Contents/Home").getAbsolutePath(), windebug); + } + + // then fall back to the VM in which we're already running + if (vmpath == null) { + vmpath = checkJVMPath(System.getProperty("java.home"), windebug); + } + + // then throw up our hands and hope for the best + if (vmpath == null) { + log.warning("Unable to find java [appdir=" + appdir + + ", java.home=" + System.getProperty("java.home") + "]!"); + vmpath = "java"; + } + + // Oddly, the Mac OS X specific java flag -Xdock:name will only work if java is launched + // from /usr/bin/java, and not if launched by directly referring to /bin/java, + // even though the former is a symlink to the latter! To work around this, see if the + // desired jvm is in fact pointed to by /usr/bin/java and, if so, use that instead. + if (isMacOS()) { + try { + File localVM = new File("/usr/bin/java").getCanonicalFile(); + if (localVM.equals(new File(vmpath).getCanonicalFile())) { + vmpath = "/usr/bin/java"; + } + } catch (IOException ioe) { + log.warning("Failed to check Mac OS canonical VM path.", ioe); + } + } + + return vmpath; + } + + /** + * Upgrades Getdown by moving an installation managed copy of the Getdown jar file over the + * non-managed copy (which would be used to run Getdown itself). + * + *

If the upgrade fails for a variety of reasons, warnings are logged but no other actions + * are taken. There's not much else one can do other than try again next time around. + */ + public static void upgradeGetdown (File oldgd, File curgd, File newgd) + { + // we assume getdown's jar file size changes with every upgrade, this is not guaranteed, + // but in reality it will, and it allows us to avoid pointlessly upgrading getdown every + // time the client is updated which is unnecessarily flirting with danger + if (!newgd.exists() || newgd.length() == curgd.length()) { + return; + } + + log.info("Updating Getdown with " + newgd + "..."); + + // clear out any old getdown + if (oldgd.exists()) { + FileUtil.deleteHarder(oldgd); + } + + // now try updating using renames + if (!curgd.exists() || curgd.renameTo(oldgd)) { + if (newgd.renameTo(curgd)) { + FileUtil.deleteHarder(oldgd); // yay! + try { + // copy the moved file back to getdown-dop-new.jar so that we don't end up + // downloading another copy next time + FileUtil.copy(curgd, newgd); + } catch (IOException e) { + log.warning("Error copying updated Getdown back: " + e); + } + return; + } + + log.warning("Unable to renameTo(" + oldgd + ")."); + // try to unfuck ourselves + if (!oldgd.renameTo(curgd)) { + log.warning("Oh God, why dost thee scorn me so."); + } + } + + // that didn't work, let's try copying it + log.info("Attempting to upgrade by copying over " + curgd + "..."); + try { + FileUtil.copy(newgd, curgd); + } catch (IOException ioe) { + log.warning("Mayday! Brute force copy method also failed.", ioe); + } + } + + /** + * Returns true if, on this operating system, we have to stick around and read the stderr from + * our children processes to prevent them from filling their output buffers and hanging. + */ + public static boolean mustMonitorChildren () + { + String osname = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + return (osname.indexOf("windows 98") != -1 || osname.indexOf("windows me") != -1); + } + + /** + * Returns true if we're running in a JVM that identifies its operating system as Windows. + */ + public static final boolean isWindows () { return _isWindows; } + + /** + * Returns true if we're running in a JVM that identifies its operating system as MacOS. + */ + public static final boolean isMacOS () { return _isMacOS; } + + /** + * Returns true if we're running in a JVM that identifies its operating system as Linux. + */ + public static final boolean isLinux () { return _isLinux; } + + /** + * Checks whether a Java Virtual Machine can be located in the supplied path. + */ + protected static String checkJVMPath (String vmhome, boolean windebug) + { + String vmbase = vmhome + File.separator + "bin" + File.separator; + String vmpath = vmbase + "java"; + if (new File(vmpath).exists()) { + return vmpath; + } + + if (!windebug) { + vmpath = vmbase + "javaw.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + } + + vmpath = vmbase + "java.exe"; + if (new File(vmpath).exists()) { + return vmpath; + } + + return null; + } + + /** Flag indicating that we're on Windows; initialized when this class is first loaded. */ + protected static boolean _isWindows; + /** Flag indicating that we're on MacOS; initialized when this class is first loaded. */ + protected static boolean _isMacOS; + /** Flag indicating that we're on Linux; initialized when this class is first loaded. */ + protected static boolean _isLinux; + + static { + try { + String osname = System.getProperty("os.name"); + osname = (osname == null) ? "" : osname; + _isWindows = (osname.indexOf("Windows") != -1); + _isMacOS = (osname.indexOf("Mac OS") != -1 || + osname.indexOf("MacOS") != -1); + _isLinux = (osname.indexOf("Linux") != -1); + } catch (Exception e) { + // can't grab system properties; we'll just pretend we're not on any of these OSes + } + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java new file mode 100644 index 0000000..28dbdcf --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java @@ -0,0 +1,144 @@ +// +// 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; + +public class MessageUtil { + + /** + * Returns whether or not the provided string is tainted. See {@link #taint}. Null strings + * are considered untainted. + */ + public static boolean isTainted (String text) + { + return text != null && text.startsWith(TAINT_CHAR); + } + + /** + * Call this to "taint" any string that has been entered by an entity outside the application + * so that the translation code knows not to attempt to translate this string when doing + * recursive translations. + */ + public static String taint (Object text) + { + return TAINT_CHAR + text; + } + + /** + * Removes the tainting character added to a string by {@link #taint}. If the provided string + * is not tainted, this silently returns the originally provided string. + */ + public static String untaint (String text) + { + return isTainted(text) ? text.substring(TAINT_CHAR.length()) : text; + } + + /** + * Composes a message key with an array of arguments. The message can subsequently be + * decomposed and translated without prior knowledge of how many arguments were provided. + */ + public static String compose (String key, Object... args) + { + StringBuilder buf = new StringBuilder(); + buf.append(key); + buf.append('|'); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + buf.append('|'); + } + // escape the string while adding to the buffer + String arg = (args[i] == null) ? "" : String.valueOf(args[i]); + int alength = arg.length(); + for (int p = 0; p < alength; p++) { + char ch = arg.charAt(p); + if (ch == '|') { + buf.append("\\!"); + } else if (ch == '\\') { + buf.append("\\\\"); + } else { + buf.append(ch); + } + } + } + return buf.toString(); + } + + /** + * Compose a message with String args. This is just a convenience so callers do not have to + * cast their String[] to an Object[]. + */ + public static String compose (String key, String... args) + { + return compose(key, (Object[]) args); + } + + /** + * A convenience method for calling {@link #compose(String,Object[])} with an array of + * arguments that will be automatically tainted (see {@link #taint}). + */ + public static String tcompose (String key, Object... args) + { + int acount = args.length; + String[] targs = new String[acount]; + for (int ii = 0; ii < acount; ii++) { + targs[ii] = taint(args[ii]); + } + return compose(key, (Object[]) targs); + } + + /** + * A convenience method for calling {@link #compose(String,String[])} with an array of argument + * that will be automatically tainted. + */ + public static String tcompose (String key, String... args) + { + for (int ii = 0, nn = args.length; ii < nn; ii++) { + args[ii] = taint(args[ii]); + } + return compose(key, args); + } + + /** + * Used to escape single quotes so that they are not interpreted by MessageFormat. + * As we assume all single quotes are to be escaped, we cannot use the characters + * { and } 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 + * MessageFormat instance. + */ + public static String escape (String message) + { + return message.replace("'", "''"); + } + + /** + * Unescapes characters that are escaped in a call to compose. + */ + public static String unescape (String value) + { + int bsidx = value.indexOf('\\'); + if (bsidx == -1) { + return value; + } + + StringBuilder buf = new StringBuilder(); + int vlength = value.length(); + for (int ii = 0; ii < vlength; ii++) { + char ch = value.charAt(ii); + if (ch != '\\' || ii == vlength-1) { + buf.append(ch); + } else { + // look at the next character + ch = value.charAt(++ii); + buf.append((ch == '!') ? '|' : ch); + } + } + + return buf.toString(); + } + + /** Text prefixed by this character will be considered tainted when doing recursive + * translations and won't be translated. */ + protected static final String TAINT_CHAR = "~"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java new file mode 100644 index 0000000..d74b011 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java @@ -0,0 +1,50 @@ +// +// 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; + +/** + * Accumulates the progress from a number of (potentially parallel) elements into a single smoothly + * progressing progress. + */ +public class ProgressAggregator +{ + public ProgressAggregator (ProgressObserver target, long[] sizes) { + _target = target; + _sizes = sizes; + _progress = new int[sizes.length]; + } + + public ProgressObserver startElement (final int index) { + return new ProgressObserver() { + public void progress (int percent) { + _progress[index] = percent; + updateAggProgress(); + } + }; + } + + protected void updateAggProgress () { + long totalSize = 0L, currentSize = 0L; + synchronized (this) { + for (int ii = 0, ll = _sizes.length; ii < ll; ii++) { + long size = _sizes[ii]; + totalSize += size; + currentSize += (int)((size * _progress[ii])/100.0); + } + } + _target.progress((int)(100.0*currentSize / totalSize)); + } + + protected static long sum (long[] sizes) { + long totalSize = 0L; + for (long size : sizes) totalSize += size; + return totalSize; + } + + protected ProgressObserver _target; + protected long[] _sizes; + protected int[] _progress; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java new file mode 100644 index 0000000..ad4c560 --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java @@ -0,0 +1,18 @@ +// +// 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; + +/** + * Used to communicate progress. + */ +public interface ProgressObserver +{ + /** + * Informs the observer that we have completed the specified + * percentage of the process. + */ + public void progress (int percent); +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java new file mode 100644 index 0000000..3671d7d --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java @@ -0,0 +1,40 @@ +// +// 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; + +/** + * An immutable rectangle. + */ +public class Rectangle +{ + public final int x; + public final int y; + public final int width; + public final int height; + + public Rectangle (int x, int y, int width, int height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public Rectangle union (Rectangle other) { + int x1 = Math.min(x, other.x); + int x2 = Math.max(x + width, other.x + other.width); + int y1 = Math.min(y, other.y); + int y2 = Math.max(y + height, other.y + other.height); + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + /** {@inheritDoc} */ + public String toString () + { + return getClass().getName() + "[x=" + x + ", y=" + y + + ", width=" + width + ", height=" + height + "]"; + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java new file mode 100644 index 0000000..373cfff --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java @@ -0,0 +1,96 @@ +// +// 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; + +import static com.threerings.getdown.Log.log; + +public class StreamUtil { + /** + * Convenient close for a stream. Use in a finally clause and love life. + */ + public static void close (InputStream in) + { + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + log.warning("Error closing input stream", "stream", in, "cause", ioe); + } + } + } + + /** + * Convenient close for a stream. Use in a finally clause and love life. + */ + public static void close (OutputStream out) + { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + log.warning("Error closing output stream", "stream", out, "cause", ioe); + } + } + } + + /** + * Convenient close for a Reader. Use in a finally clause and love life. + */ + public static void close (Reader in) + { + if (in != null) { + try { + in.close(); + } catch (IOException ioe) { + log.warning("Error closing reader", "reader", in, "cause", ioe); + } + } + } + + /** + * Convenient close for a Writer. Use in a finally clause and love life. + */ + public static void close (Writer out) + { + if (out != null) { + try { + out.close(); + } catch (IOException ioe) { + log.warning("Error closing writer", "writer", out, "cause", ioe); + } + } + } + + /** + * Copies the contents of the supplied input stream to the supplied output stream. + */ + public static T copy (InputStream in, T out) + throws IOException + { + byte[] buffer = new byte[4096]; + for (int read = 0; (read = in.read(buffer)) > 0; ) { + out.write(buffer, 0, read); + } + return out; + } + + /** + * Reads the contents of the supplied stream into a byte array. + */ + public static byte[] toByteArray (InputStream stream) + throws IOException + { + return copy(stream, new ByteArrayOutputStream()).toByteArray(); + } +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java new file mode 100644 index 0000000..03d3c9c --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java @@ -0,0 +1,206 @@ +// +// 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.util.StringTokenizer; + +public class StringUtil { + + /** + * @return true if the specified string could be a valid URL (contains no illegal characters) + */ + public static boolean couldBeValidUrl (String url) + { + return url.matches("[A-Za-z0-9\\-\\._~:/\\?#\\[\\]@!$&'\\(\\)\\*\\+,;=%]+"); + } + + /** + * @return true if the string is null or consists only of whitespace, false otherwise. + */ + public static boolean isBlank (String value) + { + for (int ii = 0, ll = (value == null) ? 0 : value.length(); ii < ll; ii++) { + if (!Character.isWhitespace(value.charAt(ii))) { + return false; + } + } + return true; + } + + /** + * Parses an array of integers from it's string representation. The array should be represented + * as a bare list of numbers separated by commas, for example: + * + *

25, 17, 21, 99
+ * + * Any inability to parse the int array will result in the function returning null. + */ + public static int[] parseIntArray (String source) + { + StringTokenizer tok = new StringTokenizer(source, ","); + int[] vals = new int[tok.countTokens()]; + for (int i = 0; tok.hasMoreTokens(); i++) { + try { + // trim the whitespace from the token + vals[i] = Integer.parseInt(tok.nextToken().trim()); + } catch (NumberFormatException nfe) { + return null; + } + } + return vals; + } + + /** + * Parses an array of strings from a single string. The array should be represented as a bare + * list of strings separated by commas, for example: + * + *
mary, had, a, little, lamb, and, an, escaped, comma,,
+ * + * If a comma is desired in one of the strings, it should be escaped by putting two commas in a + * row. Any inability to parse the string array will result in the function returning null. + */ + public static String[] parseStringArray (String source) + { + return parseStringArray(source, false); + } + + /** + * Like {@link #parseStringArray(String)} but can be instructed to invoke {@link String#intern} + * on the strings being parsed into the array. + */ + public static String[] parseStringArray (String source, boolean intern) + { + int tcount = 0, tpos = -1, tstart = 0; + + // empty strings result in zero length arrays + if (source.length() == 0) { + return new String[0]; + } + + // sort out escaped commas + source = source.replace(",,", "%COMMA%"); + + // count up the number of tokens + while ((tpos = source.indexOf(",", tpos+1)) != -1) { + tcount++; + } + + String[] tokens = new String[tcount+1]; + tpos = -1; tcount = 0; + + // do the split + while ((tpos = source.indexOf(",", tpos+1)) != -1) { + tokens[tcount] = source.substring(tstart, tpos); + tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ","); + if (intern) { + tokens[tcount] = tokens[tcount].intern(); + } + tstart = tpos+1; + tcount++; + } + + // grab the last token + tokens[tcount] = source.substring(tstart); + tokens[tcount] = tokens[tcount].trim().replace("%COMMA%", ","); + + return tokens; + } + + /** + * @return the supplied string if it is non-null, "" if it is null. + */ + public static String deNull (String value) + { + return (value == null) ? "" : value; + } + + /** + * Generates a string from the supplied bytes that is the HEX encoded representation of those + * bytes. Returns the empty string for a null or empty byte array. + * + * @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 + * length of the array). + */ + public static String hexlate (byte[] bytes, int count) + { + if (bytes == null) { + return ""; + } + + count = Math.min(count, bytes.length); + char[] chars = new char[count*2]; + + for (int i = 0; i < count; i++) { + int val = bytes[i]; + if (val < 0) { + val += 256; + } + chars[2*i] = XLATE.charAt(val/16); + chars[2*i+1] = XLATE.charAt(val%16); + } + + return new String(chars); + } + + /** + * Generates a string from the supplied bytes that is the HEX encoded representation of those + * bytes. + */ + public static String hexlate (byte[] bytes) + { + return (bytes == null) ? "" : hexlate(bytes, bytes.length); + } + + /** + * Joins an array of strings (or objects which will be converted to strings) into a single + * string separated by commas. + */ + public static String join (Object[] values) + { + return join(values, false); + } + + /** + * Joins an array of strings into a single string, separated by commas, and optionally escaping + * commas that occur in the individual string values such that a subsequent call to {@link + * #parseStringArray} would recreate the string array properly. Any elements in the values + * array that are null will be treated as an empty string. + */ + public static String join (Object[] values, boolean escape) + { + return join(values, ", ", escape); + } + + /** + * Joins the supplied array of strings into a single string separated by the supplied + * separator. + */ + public static String join (Object[] values, String separator) + { + return join(values, separator, false); + } + + /** + * Helper function for the various join methods. + */ + protected static String join (Object[] values, String separator, boolean escape) + { + StringBuilder buf = new StringBuilder(); + int vlength = values.length; + for (int i = 0; i < vlength; i++) { + if (i > 0) { + buf.append(separator); + } + String value = (values[i] == null) ? "" : values[i].toString(); + buf.append((escape) ? value.replace(",", ",,") : value); + } + return buf.toString(); + } + + /** Used by {@link #hexlate} and {@link #unhexlate}. */ + protected static final String XLATE = "0123456789abcdef"; +} diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java new file mode 100644 index 0000000..49e4e6e --- /dev/null +++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java @@ -0,0 +1,114 @@ +// +// 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.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.threerings.getdown.data.SysProps; +import static com.threerings.getdown.Log.log; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Version related utilities. + */ +public class VersionUtil +{ + /** + * Reads a version number from a file. + */ + public static long readVersion (File vfile) + { + long fileVersion = -1; + try (BufferedReader bin = + new BufferedReader(new InputStreamReader(new FileInputStream(vfile), UTF_8))) { + String vstr = bin.readLine(); + if (!StringUtil.isBlank(vstr)) { + fileVersion = Long.parseLong(vstr); + } + } catch (Exception e) { + log.info("Unable to read version file: " + e.getMessage()); + } + + return fileVersion; + } + + /** + * Writes a version number to a file. + */ + public static void writeVersion (File vfile, long version) throws IOException + { + try (PrintStream out = new PrintStream(new FileOutputStream(vfile))) { + out.println(version); + } catch (Exception e) { + log.warning("Unable to write version file: " + e.getMessage()); + } + } + + /** + * Parses {@code versStr} using {@code versRegex} into a (long) integer version number. + * @see SysProps#parseJavaVersion + */ + public static long parseJavaVersion (String versRegex, String versStr) + { + Matcher m = Pattern.compile(versRegex).matcher(versStr); + if (!m.matches()) return 0L; + + long vers = 0L; + for (int ii = 1; ii <= m.groupCount(); ii++) { + String valstr = m.group(ii); + int value = (valstr == null) ? 0 : parseInt(valstr); + vers *= 100; + vers += value; + } + return vers; + } + + /** + * Reads and parses the version from the {@code release} file bundled with a JVM. + */ + public static long readReleaseVersion (File relfile, String versRegex) + { + try (BufferedReader in = + new BufferedReader(new InputStreamReader(new FileInputStream(relfile), UTF_8))) { + String line = null, relvers = null; + while ((line = in.readLine()) != null) { + if (line.startsWith("JAVA_VERSION=")) { + relvers = line.substring("JAVA_VERSION=".length()).replace('"', ' ').trim(); + } + } + + if (relvers == null) { + log.warning("No JAVA_VERSION line in 'release' file", "file", relfile); + return 0L; + } + return parseJavaVersion(versRegex, relvers); + + } catch (Exception e) { + log.warning("Failed to read version from 'release' file", "file", relfile, e); + return 0L; + } + } + + private static int parseInt (String str) { + int value = 0; + for (int ii = 0, ll = str.length(); ii < ll; ii++) { + char c = str.charAt(ii); + if (c >= '0' && c <= '9') { + value *= 10; + value += (c - '0'); + } + } + return value; + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java new file mode 100644 index 0000000..d5a3937 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java @@ -0,0 +1,71 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * Validates that cache garbage is collected and deleted correctly. + */ +public class GarbageCollectorTest +{ + @Before public void setupFiles () throws IOException + { + _cachedFile = _folder.newFile("abc123.jar"); + _lastAccessedFile = _folder.newFile("abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX); + } + + @Test public void shouldDeleteCacheEntryIfRetentionPeriodIsReached () + { + gcNow(); + assertFalse(_cachedFile.exists()); + assertFalse(_lastAccessedFile.exists()); + } + + @Test public void shouldDeleteCacheFolderIfFolderIsEmpty () + { + gcNow(); + assertFalse(_folder.getRoot().exists()); + } + + private void gcNow() { + GarbageCollector.collect(_folder.getRoot(), -1); + } + + @Test public void shouldKeepFilesInCacheIfRententionPeriodIsNotReached () + { + GarbageCollector.collect(_folder.getRoot(), TimeUnit.DAYS.toMillis(1)); + assertTrue(_cachedFile.exists()); + assertTrue(_lastAccessedFile.exists()); + } + + @Test public void shouldDeleteCachedFileIfLastAccessedFileIsMissing () + { + assumeTrue(_lastAccessedFile.delete()); + gcNow(); + assertFalse(_cachedFile.exists()); + } + + @Test public void shouldDeleteLastAccessedFileIfCachedFileIsMissing () + { + assumeTrue(_cachedFile.delete()); + gcNow(); + assertFalse(_lastAccessedFile.exists()); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _cachedFile; + private File _lastAccessedFile; +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java new file mode 100644 index 0000000..860c72a --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java @@ -0,0 +1,72 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.cache; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; + +/** + * Asserts the correct functionality of the {@link ResourceCache}. + */ +public class ResourceCacheTest +{ + @Before public void setupCache () throws IOException { + _fileToCache = _folder.newFile("filetocache.jar"); + _cache = new ResourceCache(_folder.newFolder(".cache")); + } + + @Test public void shouldCacheFile () throws IOException + { + assertEquals("abc123.jar", cacheFile().getName()); + } + + private File cacheFile() throws IOException + { + return _cache.cacheFile(_fileToCache, "abc123", "abc123"); + } + + @Test public void shouldTrackFileUsage () throws IOException + { + String name = "abc123.jar" + ResourceCache.LAST_ACCESSED_FILE_SUFFIX; + File lastAccessedFile = new File(cacheFile().getParentFile(), name); + assertTrue(lastAccessedFile.exists()); + } + + @Test public void shouldNotCacheTheSameFile () throws Exception + { + File cachedFile = cacheFile(); + cachedFile.setLastModified(YESTERDAY); + long expectedLastModified = cachedFile.lastModified(); + // caching it another time + File sameCachedFile = cacheFile(); + assertEquals(expectedLastModified, sameCachedFile.lastModified()); + } + + @Test public void shouldRememberWhenFileWasRequested () throws Exception + { + File cachedFile = cacheFile(); + String name = cachedFile.getName() + ResourceCache.LAST_ACCESSED_FILE_SUFFIX; + File lastAccessedFile = new File(cachedFile.getParentFile(), name); + lastAccessedFile.setLastModified(YESTERDAY); + long lastAccessed = lastAccessedFile.lastModified(); + // caching it another time + cacheFile(); + assertTrue(lastAccessedFile.lastModified() > lastAccessed); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _fileToCache; + private ResourceCache _cache; + + private static final long YESTERDAY = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java new file mode 100644 index 0000000..5344f3b --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java @@ -0,0 +1,54 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.LinkedHashSet; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link ClassPath}. + */ +public class ClassPathTest +{ + @Before public void createJarsAndSetupClassPath () throws IOException + { + _firstJar = _folder.newFile("a.jar"); + _secondJar = _folder.newFile("b.jar"); + + LinkedHashSet classPathEntries = new LinkedHashSet(); + classPathEntries.add(_firstJar); + classPathEntries.add(_secondJar); + _classPath = new ClassPath(classPathEntries); + } + + @Test public void shouldCreateValidArgumentString () + { + assertEquals( + _firstJar.getAbsolutePath() + File.pathSeparator + _secondJar.getAbsolutePath(), + _classPath.asArgumentString()); + } + + @Test public void shouldProvideJarUrls () throws MalformedURLException, URISyntaxException + { + URL[] actualUrls = _classPath.asUrls(); + assertEquals(_firstJar, new File(actualUrls[0].toURI())); + assertEquals(_secondJar, new File(actualUrls[1].toURI())); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); + + private File _firstJar, _secondJar; + private ClassPath _classPath; +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java new file mode 100644 index 0000000..6178651 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java @@ -0,0 +1,142 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.io.File; + +import org.junit.*; +import static org.junit.Assert.*; + +public class EnvConfigTest { + + static String CWD = System.getProperty("user.dir"); + static String TESTID = "testid"; + static String TESTBASE = "https://test.com/test"; + + private void debugNotes(List notes) { + for (EnvConfig.Note note : notes) { + System.out.println(note.message); + } + } + + private void checkNoNotes (List notes) { + StringBuilder msg = new StringBuilder(); + for (EnvConfig.Note note : notes) { + if (note.level != EnvConfig.Note.Level.INFO) { + msg.append("\n").append(note.message); + } + } + if (msg.length() > 0) { + fail("Unexpected notes:" + msg.toString()); + } + } + private void checkDir (EnvConfig cfg) { + assertTrue(cfg != null); + assertEquals(new File(CWD), cfg.appDir); + } + private void checkNoAppId (EnvConfig cfg) { + assertNull(cfg.appId); + } + private void checkAppId (EnvConfig cfg, String appId) { + assertEquals(appId, cfg.appId); + } + private void checkAppBase (EnvConfig cfg, String appBase) { + assertEquals(appBase, cfg.appBase); + } + private void checkNoAppBase (EnvConfig cfg) { + assertNull(cfg.appBase); + } + private void checkNoAppArgs (EnvConfig cfg) { + assertTrue(cfg.appArgs.isEmpty()); + } + private void checkAppArgs (EnvConfig cfg, String... args) { + assertEquals(Arrays.asList(args), cfg.appArgs); + } + + @Test public void testArgvDir () { + List notes = new ArrayList<>(); + String[] args = { CWD }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testArgvDirId () { + List notes = new ArrayList<>(); + String[] args = { CWD, TESTID }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testArgvDirArgs () { + List notes = new ArrayList<>(); + String[] args = { CWD, "", "one", "two" }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkAppArgs(cfg, "one", "two"); + } + + @Test public void testArgvDirIdArgs () { + List notes = new ArrayList<>(); + String[] args = { CWD, TESTID, "one", "two" }; + EnvConfig cfg = EnvConfig.create(args, notes); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkNoAppBase(cfg); + checkAppArgs(cfg, "one", "two"); + } + + private EnvConfig sysPropsConfig (List notes, String... keyVals) { + for (int ii = 0; ii < keyVals.length; ii += 2) { + System.setProperty(keyVals[ii], keyVals[ii+1]); + } + EnvConfig cfg = EnvConfig.create(new String[0], notes); + for (int ii = 0; ii < keyVals.length; ii += 2) { + System.clearProperty(keyVals[ii]); + } + return cfg; + } + + @Test public void testSysPropsDir () { + List notes = new ArrayList<>(); + EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkNoAppId(cfg); + checkNoAppBase(cfg); + checkNoAppArgs(cfg); + } + + @Test public void testSysPropsDirIdBase () { + List notes = new ArrayList<>(); + EnvConfig cfg = sysPropsConfig(notes, "appdir", CWD, "appid", TESTID, "appbase", TESTBASE); + // debugNotes(notes); + checkNoNotes(notes); + checkDir(cfg); + checkAppId(cfg, TESTID); + checkAppBase(cfg, TESTBASE); + checkNoAppArgs(cfg); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java new file mode 100644 index 0000000..7f35094 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java @@ -0,0 +1,70 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; + +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class PathBuilderTest +{ + @Before public void setupFilesAndResources () throws IOException + { + _firstJarFile = _appdir.newFile("a.jar"); + _secondJarFile = _appdir.newFile("b.jar"); + + when(_firstJar.getFinalTarget()).thenReturn(_firstJarFile); + when(_secondJar.getFinalTarget()).thenReturn(_secondJarFile); + when(_application.getActiveCodeResources()).thenReturn(Arrays.asList(_firstJar, _secondJar)); + when(_application.getAppDir()).thenReturn(_appdir.getRoot()); + } + + @Test public void shouldBuildDefaultClassPath () throws IOException + { + ClassPath classPath = PathBuilder.buildDefaultClassPath(_application); + String expectedClassPath = _firstJarFile.getAbsolutePath() + File.pathSeparator + + _secondJarFile.getAbsolutePath(); + assertEquals(expectedClassPath, classPath.asArgumentString()); + } + + @Test public void shouldBuildCachedClassPath () throws IOException + { + when(_application.getDigest(_firstJar)).thenReturn("first"); + when(_application.getDigest(_secondJar)).thenReturn("second"); + when(_application.getCodeCacheRetentionDays()).thenReturn(1); + + Path firstCachedJarFile = _appdir.getRoot().toPath(). + resolve(PathBuilder.CODE_CACHE_DIR).resolve("fi").resolve("first.jar"); + + Path secondCachedJarFile = _appdir.getRoot().toPath(). + resolve(PathBuilder.CODE_CACHE_DIR).resolve("se").resolve("second.jar"); + + String expectedClassPath = firstCachedJarFile.toAbsolutePath() + File.pathSeparator + + secondCachedJarFile.toAbsolutePath(); + + ClassPath classPath = PathBuilder.buildCachedClassPath(_application); + assertEquals(expectedClassPath, classPath.asArgumentString()); + } + + @Mock protected Application _application; + @Mock protected Resource _firstJar; + @Mock protected Resource _secondJar; + + protected File _firstJarFile, _secondJarFile; + + @Rule public TemporaryFolder _appdir = new TemporaryFolder(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java new file mode 100644 index 0000000..042a13f --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java @@ -0,0 +1,63 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + +package com.threerings.getdown.data; + +import org.junit.*; +import static org.junit.Assert.*; + +public class SysPropsTest { + + @After public void clearProps () { + System.clearProperty("delay"); + System.clearProperty("appbase_domain"); + System.clearProperty("appbase_override"); + } + + private static final String[] APPBASES = { + "http://foobar.com/myapp", + "https://foobar.com/myapp", + "http://foobar.com:8080/myapp", + "https://foobar.com:8080/myapp" + }; + + @Test public void testStartDelay () { + + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "x"); + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "-7"); + assertEquals(0, SysProps.startDelay()); + + System.setProperty("delay", "7"); + assertEquals(7, SysProps.startDelay()); + + System.setProperty("delay", "1440"); + assertEquals(1440, SysProps.startDelay()); + + System.setProperty("delay", "1441"); + assertEquals(1440, SysProps.startDelay()); + } + + @Test public void testAppbaseDomain () { + System.setProperty("appbase_domain", "https://barbaz.com"); + for (String appbase : APPBASES) { + assertEquals("https://barbaz.com/myapp", SysProps.overrideAppbase(appbase)); + } + System.setProperty("appbase_domain", "http://barbaz.com"); + for (String appbase : APPBASES) { + assertEquals("http://barbaz.com/myapp", SysProps.overrideAppbase(appbase)); + } + } + + @Test public void testAppbaseOverride () { + System.setProperty("appbase_override", "https://barbaz.com/newapp"); + for (String appbase : APPBASES) { + assertEquals("https://barbaz.com/newapp", SysProps.overrideAppbase(appbase)); + } + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java new file mode 100644 index 0000000..7aa48ee --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java @@ -0,0 +1,23 @@ +// +// 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 org.junit.Test; +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link Color}. + */ +public class ColorTest +{ + @Test + public void testBrightness() { + assertEquals(0, Color.brightness(0xFF000000), 0.0000001); + assertEquals(1, Color.brightness(0xFFFFFFFF), 0.0000001); + assertEquals(0.0117647, Color.brightness(0xFF010203), 0.0000001); + assertEquals(1, Color.brightness(0xFF00FFC8), 0.0000001); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java new file mode 100644 index 0000000..cdc5a91 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java @@ -0,0 +1,171 @@ +// +// 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.io.StringReader; +import java.util.List; +import java.util.Random; + +import org.junit.*; +import static org.junit.Assert.*; + +/** + * Tests {@link Config}. + */ +public class ConfigTest +{ + public static class Pair { + public final String key; + public final String value; + public Pair (String key, String value) { + this.key = key; + this.value = value; + } + } + + public static final Pair[] SIMPLE_PAIRS = { + new Pair("one", "two"), + new Pair("three", "four"), + new Pair("five", "six"), + new Pair("seven", "eight"), + new Pair("nine", "ten"), + }; + + @Test public void testSimplePairs () throws IOException + { + List pairs = Config.parsePairs( + toReader(SIMPLE_PAIRS), Config.createOpts(true)); + for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) { + assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]); + assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]); + } + } + + @Test public void testQualifiedPairs () throws IOException + { + Pair linux = new Pair("one", "[linux] two"); + Pair mac = new Pair("three", "[mac os x] four"); + Pair linuxAndMac = new Pair("five", "[linux, mac os x] six"); + Pair linux64 = new Pair("seven", "[linux-x86_64] eight"); + Pair linux64s = new Pair("nine", "[linux-x86_64, linux-amd64] ten"); + Pair mac64 = new Pair("eleven", "[mac os x-x86_64] twelve"); + Pair win64 = new Pair("thirteen", "[windows-x86_64] fourteen"); + Pair notWin = new Pair("fifteen", "[!windows] sixteen"); + Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin }; + + Config.ParseOpts opts = Config.createOpts(false); + opts.osname = "linux"; + opts.osarch = "i386"; + List parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osarch = "amd64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osname = "mac os x"; + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(exists(parsed, mac.key)); + assertTrue(exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(exists(parsed, notWin.key)); + + opts.osname = "windows"; + opts.osarch = "i386"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + + opts.osarch = "x86_64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + + opts.osarch = "amd64"; + parsed = Config.parsePairs(toReader(pairs), opts); + assertTrue(!exists(parsed, linux.key)); + assertTrue(!exists(parsed, mac.key)); + assertTrue(!exists(parsed, linuxAndMac.key)); + assertTrue(!exists(parsed, linux64.key)); + assertTrue(!exists(parsed, linux64s.key)); + assertTrue(!exists(parsed, mac64.key)); + assertTrue(!exists(parsed, win64.key)); + assertTrue(!exists(parsed, notWin.key)); + } + + protected static boolean exists (List pairs, String key) + { + for (String[] pair : pairs) { + if (pair[0].equals(key)) { + return true; + } + } + return false; + } + + protected static StringReader toReader (Pair[] pairs) + { + StringBuilder builder = new StringBuilder(); + for (Pair pair : pairs) { + // throw some whitespace in to ensure it's trimmed + builder.append(whitespace()).append(pair.key). + append(whitespace()).append("="). + append(whitespace()).append(pair.value). + append(whitespace()).append("\n"); + } + return new StringReader(builder.toString()); + } + + protected static String whitespace () + { + return _rando.nextBoolean() ? " " : ""; + } + + protected static Random _rando = new Random(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java new file mode 100644 index 0000000..cfd53a2 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java @@ -0,0 +1,60 @@ +// +// 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.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +import org.junit.*; +import org.junit.rules.TemporaryFolder; + +import static org.junit.Assert.*; + +/** + * Tests {@link FileUtil}. + */ +public class FileUtilTest +{ + @Test public void testReadLines () throws IOException + { + String data = "This is a test\nof a file with\na few lines\n"; + List lines = FileUtil.readLines(new StringReader(data)); + String[] linesBySplit = data.split("\n"); + assertEquals(linesBySplit.length, lines.size()); + for (int ii = 0; ii < lines.size(); ii++) { + assertEquals(linesBySplit[ii], lines.get(ii)); + } + } + + @Test public void shouldCopyFile () throws IOException + { + File source = _folder.newFile("source.txt"); + File target = new File(_folder.getRoot(), "target.txt"); + assertFalse(target.exists()); + FileUtil.copy(source, target); + assertTrue(target.exists()); + } + + @Test public void shouldRecursivelyWalkOverFilesAndFolders () throws IOException + { + _folder.newFile("a.txt"); + new File(_folder.newFolder("b"), "b.txt").createNewFile(); + + class CountingVisitor implements FileUtil.Visitor { + int fileCount = 0; + @Override public void visit(File file) { + fileCount++; + } + } + CountingVisitor visitor = new CountingVisitor(); + FileUtil.walkTree(_folder.getRoot(), visitor); + assertEquals(3, visitor.fileCount); + } + + @Rule public TemporaryFolder _folder = new TemporaryFolder(); +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java new file mode 100644 index 0000000..703afef --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java @@ -0,0 +1,159 @@ +// +// 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 static org.junit.Assert.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +/** + * Tests {@link HostWhitelist}. + */ +public class HostWhitelistTest +{ + @Test + public void testVerify () throws MalformedURLException + { + checkCanVerify("foo.com", "http://foo.com", true); + checkCanVerify("foo.com", "http://foo.com/", true); + checkCanVerify("foo.com", "http://foo.com/x/y/z", true); + checkCanVerify("foo.com", "http://www.foo.com", false); + checkCanVerify("foo.com", "http://www.foo.com/", false); + checkCanVerify("foo.com", "http://www.foo.com/x/y/z", false); + checkCanVerify("foo.com", "http://a.b.foo.com", false); + checkCanVerify("foo.com", "http://a.b.foo.com/", false); + checkCanVerify("foo.com", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("foo.com", "http://oo.com", false); + checkCanVerify("foo.com", "http://f.oo.com", false); + checkCanVerify("foo.com", "http://a.f.oo.com", false); + + checkCanVerify("*.foo.com", "http://foo.com", false); + checkCanVerify("*.foo.com", "http://foo.com/", false); + checkCanVerify("*.foo.com", "http://foo.com/x/y/z", false); + checkCanVerify("*.foo.com", "http://www.foo.com", true); + checkCanVerify("*.foo.com", "http://www.foo.com/", true); + checkCanVerify("*.foo.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com/", true); + checkCanVerify("*.foo.com", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.foo.com", "http://oo.com", false); + checkCanVerify("*.foo.com", "http://f.oo.com", false); + checkCanVerify("*.foo.com", "http://a.f.oo.com", false); + + checkCanVerify("*.com", "http://foo.com", true); + checkCanVerify("*.com", "http://foo.com/", true); + checkCanVerify("*.com", "http://foo.com/x/y/z", true); + checkCanVerify("*.com", "http://www.foo.com", true); + checkCanVerify("*.com", "http://www.foo.com/", true); + checkCanVerify("*.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.com", "http://a.b.foo.com", true); + checkCanVerify("*.com", "http://a.b.foo.com/", true); + checkCanVerify("*.com", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.com", "http://oo.com", true); + checkCanVerify("*.com", "http://f.oo.com", true); + checkCanVerify("*.com", "http://a.f.oo.com", true); + + checkCanVerify("*.net", "http://foo.com", false); + checkCanVerify("*.net", "http://foo.com/", false); + checkCanVerify("*.net", "http://foo.com/x/y/z", false); + checkCanVerify("*.net", "http://www.foo.com", false); + checkCanVerify("*.net", "http://www.foo.com/", false); + checkCanVerify("*.net", "http://www.foo.com/x/y/z", false); + checkCanVerify("*.net", "http://a.b.foo.com", false); + checkCanVerify("*.net", "http://a.b.foo.com/", false); + checkCanVerify("*.net", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("*.net", "http://oo.com", false); + checkCanVerify("*.net", "http://f.oo.com", false); + checkCanVerify("*.net", "http://a.f.oo.com", false); + + checkCanVerify("www.*.com", "http://foo.com", false); + checkCanVerify("www.*.com", "http://foo.com/", false); + checkCanVerify("www.*.com", "http://foo.com/x/y/z", false); + checkCanVerify("www.*.com", "http://www.foo.com", true); + checkCanVerify("www.*.com", "http://www.foo.com/", true); + checkCanVerify("www.*.com", "http://www.foo.com/x/y/z", true); + checkCanVerify("www.*.com", "http://a.b.foo.com", false); + checkCanVerify("www.*.com", "http://a.b.foo.com/", false); + checkCanVerify("www.*.com", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("www.*.com", "http://oo.com", false); + checkCanVerify("www.*.com", "http://f.oo.com", false); + checkCanVerify("www.*.com", "http://a.f.oo.com", false); + checkCanVerify("www.*.com", "http://www.a.f.oo.com", true); + + checkCanVerify("foo.*", "http://foo.com", true); + checkCanVerify("foo.*", "http://foo.com/", true); + checkCanVerify("foo.*", "http://foo.com/x/y/z", true); + checkCanVerify("foo.*", "http://www.foo.com", false); + checkCanVerify("foo.*", "http://www.foo.com/", false); + checkCanVerify("foo.*", "http://www.foo.com/x/y/z", false); + checkCanVerify("foo.*", "http://a.b.foo.com", false); + checkCanVerify("foo.*", "http://a.b.foo.com/", false); + checkCanVerify("foo.*", "http://a.b.foo.com/x/y/z", false); + checkCanVerify("foo.*", "http://oo.com", false); + checkCanVerify("foo.*", "http://f.oo.com", false); + checkCanVerify("foo.*", "http://a.f.oo.com", false); + + checkCanVerify("*.foo.*", "http://foo.com", false); + checkCanVerify("*.foo.*", "http://foo.com/", false); + checkCanVerify("*.foo.*", "http://foo.com/x/y/z", false); + checkCanVerify("*.foo.*", "http://www.foo.com", true); + checkCanVerify("*.foo.*", "http://www.foo.com/", true); + checkCanVerify("*.foo.*", "http://www.foo.com/x/y/z", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com/", true); + checkCanVerify("*.foo.*", "http://a.b.foo.com/x/y/z", true); + checkCanVerify("*.foo.*", "http://oo.com", false); + checkCanVerify("*.foo.*", "http://f.oo.com", false); + checkCanVerify("*.foo.*", "http://a.f.oo.com", false); + + checkCanVerify("127.0.0.1", "http://127.0.0.1", true); + checkCanVerify("127.0.0.1", "http://127.0.0.1/", true); + checkCanVerify("127.0.0.1", "http://127.0.0.1/x/y/z", true); + checkCanVerify("*.0.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.*.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.*.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.0.*", "http://127.0.0.1/abc", true); + checkCanVerify("127.*.1", "http://127.0.0.1/abc", true); + checkCanVerify("*.0.1", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.*", "http://127.0.0.1/abc", true); + checkCanVerify("*", "http://127.0.0.1/abc", true); + checkCanVerify("127.0.0.2", "http://127.0.0.1", false); + checkCanVerify("127.0.2.1", "http://127.0.0.1", false); + checkCanVerify("127.2.0.1", "http://127.0.0.1", false); + checkCanVerify("222.0.0.1", "http://127.0.0.1", false); + + checkCanVerify("", "http://foo.com", true); + checkCanVerify("", "http://aaa.bbb.net/xyz", true); + checkCanVerify("", "https://127.0.0.1/abc", true); + + checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://aaa.bbb.com/m", true); + checkCanVerify("aaa.bbb.com, xxx.yyy.com,*.jjj.net", "http://xxx.yyy.com/n", true); + checkCanVerify("aaa.bbb.com,xxx.yyy.com, *.jjj.net", "http://www.jjj.net/o", true); + } + + private static void checkCanVerify (String whitelist, String url, boolean expectedToPass) + throws MalformedURLException + { + List w = Arrays.asList(StringUtil.parseStringArray(whitelist)); + URL u = new URL(url); + boolean passed; + + try { + HostWhitelist.verify(w, u); + passed = true; + } catch (MalformedURLException e) { + passed = false; + } + + assertEquals("with whitelist '" + whitelist + "' and URL '" + url + "'", + expectedToPass, passed); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java new file mode 100644 index 0000000..f70bab9 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java @@ -0,0 +1,28 @@ +// +// 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 org.junit.Test; + +import static com.threerings.getdown.util.StringUtil.couldBeValidUrl; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests {@link StringUtil}. + */ +public class StringUtilTest +{ + @Test public void testCouldBeValidUrl () + { + assertTrue(couldBeValidUrl("http://www.foo.com/")); + assertTrue(couldBeValidUrl("http://www.foo.com/A-B-C/1_2_3/~bar/q.jsp?x=u+i&y=2;3;4#baz%20baz")); + assertTrue(couldBeValidUrl("https://user:secret@www.foo.com/")); + + assertFalse(couldBeValidUrl("http://www.foo.com & echo hello")); + assertFalse(couldBeValidUrl("http://www.foo.com\"")); + } +} diff --git a/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java new file mode 100644 index 0000000..165fbe3 --- /dev/null +++ b/getdown/src/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java @@ -0,0 +1,53 @@ +// +// 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 org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class VersionUtilTest { + + @Test + public void shouldParseJavaVersion () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8.0_152"); + assertEquals(1_080_152, version); + } + + @Test + public void shouldParseJavaVersion8 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "1.8"); + assertEquals(1_080_000, version); + } + + @Test + public void shouldParseJavaVersion9 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "9"); + assertEquals(9_000_000, version); + } + + @Test + public void shouldParseJavaVersion10 () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)(?:\\.(\\d+)(?:\\.(\\d+)(_\\d+)?)?)?", "10"); + assertEquals(10_000_000, version); + } + + @Test + public void shouldParseJavaRuntimeVersion () + { + long version = VersionUtil.parseJavaVersion( + "(\\d+)\\.(\\d+)\\.(\\d+)(_\\d+)?(-b\\d+)?", "1.8.0_131-b11"); + assertEquals(108_013_111, version); + } +} diff --git a/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/getdown/src/getdown/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/getdown/src/getdown/core/target/antrun/build-main.xml b/getdown/src/getdown/core/target/antrun/build-main.xml new file mode 100644 index 0000000..6fe3710 --- /dev/null +++ b/getdown/src/getdown/core/target/antrun/build-main.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/classes/LICENSE b/getdown/src/getdown/core/target/classes/LICENSE new file mode 100644 index 0000000..0d9b255 --- /dev/null +++ b/getdown/src/getdown/core/target/classes/LICENSE @@ -0,0 +1,24 @@ +Getdown - application installer, patcher and launcher + +Copyright (C) 2004-2016 Getdown authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. diff --git a/getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000..545f20b Binary files /dev/null and b/getdown/src/getdown/core/target/getdown-core-1.8.3-SNAPSHOT.jar differ diff --git a/getdown/src/getdown/core/target/maven-archiver/pom.properties b/getdown/src/getdown/core/target/maven-archiver/pom.properties new file mode 100644 index 0000000..6990c1c --- /dev/null +++ b/getdown/src/getdown/core/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Fri Apr 05 14:07:47 BST 2019 +version=1.8.3-SNAPSHOT +groupId=com.threerings.getdown +artifactId=getdown-core diff --git a/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..4110671 --- /dev/null +++ b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,63 @@ +com/threerings/getdown/util/StringUtil.class +com/threerings/getdown/util/LaunchUtil.class +com/threerings/getdown/tools/JarDiff.class +com/threerings/getdown/data/Application$2$1.class +com/threerings/getdown/tools/Patcher$1.class +com/threerings/getdown/data/Application$StatusDisplay.class +com/threerings/getdown/util/Color.class +com/threerings/getdown/Log$OneLineFormatter.class +com/threerings/getdown/tools/Patcher.class +com/threerings/getdown/spi/ProxyAuth.class +com/threerings/getdown/data/EnvConfig.class +com/threerings/getdown/tools/Differ.class +com/threerings/getdown/net/Downloader$State.class +com/threerings/getdown/data/Application$3.class +com/threerings/getdown/util/StreamUtil.class +com/threerings/getdown/util/Config$ParseOpts.class +com/threerings/getdown/net/Downloader$1.class +com/threerings/getdown/tools/JarDiffCodes.class +com/threerings/getdown/data/EnvConfig$Note$Level.class +com/threerings/getdown/util/HostWhitelist.class +com/threerings/getdown/util/Base64$Coder.class +com/threerings/getdown/Log.class +com/threerings/getdown/data/Digest$1.class +com/threerings/getdown/data/Application.class +com/threerings/getdown/data/Properties.class +com/threerings/getdown/data/Application$AuxGroup.class +com/threerings/getdown/util/VersionUtil.class +com/threerings/getdown/util/Base64$Decoder.class +com/threerings/getdown/data/Resource$Attr.class +com/threerings/getdown/data/Resource$1.class +com/threerings/getdown/data/Build.class +com/threerings/getdown/util/Rectangle.class +com/threerings/getdown/util/ConnectionUtil.class +com/threerings/getdown/cache/GarbageCollector.class +com/threerings/getdown/util/Base64$Encoder.class +com/threerings/getdown/tools/JarDiff$JarFile2.class +com/threerings/getdown/util/Base64.class +com/threerings/getdown/data/ClassPath.class +com/threerings/getdown/util/FileUtil$Visitor.class +com/threerings/getdown/data/Application$2.class +com/threerings/getdown/Log$Shim.class +com/threerings/getdown/data/Resource.class +com/threerings/getdown/data/SysProps.class +com/threerings/getdown/data/Digest.class +com/threerings/getdown/tools/JarDiffPatcher.class +com/threerings/getdown/cache/GarbageCollector$1.class +com/threerings/getdown/data/Application$3$1.class +com/threerings/getdown/util/ProgressAggregator$1.class +com/threerings/getdown/data/Application$UpdateInterface$Step.class +com/threerings/getdown/data/PathBuilder.class +com/threerings/getdown/util/MessageUtil.class +com/threerings/getdown/util/Config.class +com/threerings/getdown/spi/ProxyAuth$Credentials.class +com/threerings/getdown/net/Downloader.class +com/threerings/getdown/data/EnvConfig$Note.class +com/threerings/getdown/util/ProgressObserver.class +com/threerings/getdown/tools/Digester.class +com/threerings/getdown/cache/ResourceCache.class +com/threerings/getdown/util/FileUtil.class +com/threerings/getdown/net/HTTPDownloader.class +com/threerings/getdown/data/Application$UpdateInterface.class +com/threerings/getdown/util/ProgressAggregator.class +com/threerings/getdown/data/Application$1.class diff --git a/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..b8d8e16 --- /dev/null +++ b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,35 @@ +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Properties.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Base64.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/MessageUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Application.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/StringUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/VersionUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/HostWhitelist.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/cache/ResourceCache.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ProgressAggregator.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Digest.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/ClassPath.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Differ.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffCodes.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ProgressObserver.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/SysProps.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/Log.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/FileUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/net/HTTPDownloader.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/ConnectionUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/StreamUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/spi/ProxyAuth.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Resource.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Color.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Rectangle.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/Digester.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/cache/GarbageCollector.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/Build.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/data/PathBuilder.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/util/Config.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiff.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/net/Downloader.java +/Users/bsoares/git/getdown2/getdown/core/src/main/java/com/threerings/getdown/tools/JarDiffPatcher.java diff --git a/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..6e36ab2 --- /dev/null +++ b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst @@ -0,0 +1,15 @@ +com/threerings/getdown/util/FileUtilTest.class +com/threerings/getdown/util/ColorTest.class +com/threerings/getdown/util/HostWhitelistTest.class +com/threerings/getdown/data/ClassPathTest.class +com/threerings/getdown/data/EnvConfigTest.class +com/threerings/getdown/util/ConfigTest.class +com/threerings/getdown/data/PathBuilderTest.class +com/threerings/getdown/util/ConfigTest$Pair.class +com/threerings/getdown/util/StringUtilTest.class +com/threerings/getdown/cache/ResourceCacheTest.class +com/threerings/getdown/cache/GarbageCollectorTest.class +com/threerings/getdown/util/VersionUtilTest.class +com/threerings/getdown/tests/DigesterIT.class +com/threerings/getdown/util/FileUtilTest$1CountingVisitor.class +com/threerings/getdown/data/SysPropsTest.class diff --git a/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..c98d02a --- /dev/null +++ b/getdown/src/getdown/core/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,13 @@ +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/cache/GarbageCollectorTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/SysPropsTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/ColorTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/StringUtilTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/EnvConfigTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/PathBuilderTest.java +/Users/bsoares/git/getdown2/getdown/core/src/it/java/com/threerings/getdown/tests/DigesterIT.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/cache/ResourceCacheTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/VersionUtilTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/ConfigTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/FileUtilTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/data/ClassPathTest.java +/Users/bsoares/git/getdown2/getdown/core/src/test/java/com/threerings/getdown/util/HostWhitelistTest.java diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.GarbageCollectorTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.GarbageCollectorTest.xml new file mode 100644 index 0000000..b1f9264 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.GarbageCollectorTest.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml new file mode 100644 index 0000000..67105c2 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.cache.ResourceCacheTest.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml new file mode 100644 index 0000000..fc47a93 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.ClassPathTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml new file mode 100644 index 0000000..c643f44 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.EnvConfigTest.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml new file mode 100644 index 0000000..1bf9d19 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.PathBuilderTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml new file mode 100644 index 0000000..cd06cc3 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.data.SysPropsTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml new file mode 100644 index 0000000..1fa7137 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ColorTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml new file mode 100644 index 0000000..6f7d3e9 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.ConfigTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml new file mode 100644 index 0000000..f9cc27e --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.FileUtilTest.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml new file mode 100644 index 0000000..5836b6b --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.HostWhitelistTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml new file mode 100644 index 0000000..e7e4341 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.StringUtilTest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml new file mode 100644 index 0000000..f17e12d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/TEST-com.threerings.getdown.util.VersionUtilTest.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt new file mode 100644 index 0000000..92b4d7d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.GarbageCollectorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.cache.GarbageCollectorTest +------------------------------------------------------------------------------- +Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.074 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt new file mode 100644 index 0000000..754966c --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.cache.ResourceCacheTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.cache.ResourceCacheTest +------------------------------------------------------------------------------- +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt new file mode 100644 index 0000000..c7133d5 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.ClassPathTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.ClassPathTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt new file mode 100644 index 0000000..33de8bc --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.EnvConfigTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.EnvConfigTest +------------------------------------------------------------------------------- +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt new file mode 100644 index 0000000..f7d5f9b --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.PathBuilderTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.PathBuilderTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.804 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt new file mode 100644 index 0000000..fc7d058 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.data.SysPropsTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.data.SysPropsTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.003 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt new file mode 100644 index 0000000..cc44a40 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ColorTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.ColorTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt new file mode 100644 index 0000000..b8da385 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.ConfigTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.ConfigTest +------------------------------------------------------------------------------- +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt new file mode 100644 index 0000000..bca7cfe --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.FileUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.FileUtilTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.004 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt new file mode 100644 index 0000000..bd0d97d --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.HostWhitelistTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.HostWhitelistTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt new file mode 100644 index 0000000..14f28f8 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.StringUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.StringUtilTest +------------------------------------------------------------------------------- +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec diff --git a/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt new file mode 100644 index 0000000..32439d1 --- /dev/null +++ b/getdown/src/getdown/core/target/surefire-reports/com.threerings.getdown.util.VersionUtilTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.threerings.getdown.util.VersionUtilTest +------------------------------------------------------------------------------- +Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 sec diff --git a/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker b/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/getdown/src/getdown/core/target/test-classes/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/getdown/src/getdown/launcher/.project b/getdown/src/getdown/launcher/.project new file mode 100644 index 0000000..d77a6e8 --- /dev/null +++ b/getdown/src/getdown/launcher/.project @@ -0,0 +1,23 @@ + + + getdown-launcher + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..abdea9a --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding/=UTF-8 diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..54e5672 --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs b/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/getdown/src/getdown/launcher/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/getdown/src/getdown/launcher/pom.xml b/getdown/src/getdown/launcher/pom.xml new file mode 100644 index 0000000..cf94571 --- /dev/null +++ b/getdown/src/getdown/launcher/pom.xml @@ -0,0 +1,176 @@ + + + 4.0.0 + + com.threerings.getdown + getdown + 1.8.3-SNAPSHOT + + + getdown-launcher + jar + Getdown Launcher + The Getdown app updater/launcher + + + + lib-repo + file://${basedir}/../lib + + + + + + com.threerings.getdown + getdown-core + ${project.version} + + + com.samskivert + samskivert + 1.2 + + + jregistrykey + jregistrykey + 1.0 + true + + + + + + + com.github.wvengen + proguard-maven-plugin + 2.0.14 + + + package + proguard + + + + + net.sf.proguard + proguard-base + 6.0.3 + runtime + + + + 6.0.3 + ${project.build.directory} + ${project.build.finalName}.jar + ${project.build.finalName}.jar + + + + com.threerings.getdown + getdown-core + + + com.samskivert + samskivert + + !**/*.java, + !**/swing/RuntimeAdjust*, + !**/swing/util/ButtonUtil*, + !**/util/CalendarUtil*, + !**/util/Calendars*, + !**/util/Log4JLogger*, + !**/util/PrefsConfig*, + !**/util/SignalUtil*, + com/samskivert/Log.class, + **/samskivert/io/**, + **/samskivert/swing/**, + **/samskivert/text/**, + **/samskivert/util/** + + + + jregistrykey + jregistrykey + + + + true + + + + + + + ${rt.jar.path} + + false + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + com.threerings.getdown.launcher.GetdownApp + + + all-permissions + Getdown + * + * + * + true + + + + + + + + + + + non-mac-jre + + ${java.home}/../lib/rt.jar + + + ${java.home}/../lib/rt.jar + + + + non-mac-jdk + + ${java.home}/lib/rt.jar + + + ${java.home}/lib/rt.jar + + + + java-9-jdk + + ${java.home}/jmods/java.base.jmod + + + + + com.github.wvengen + proguard-maven-plugin + + + ${java.home}/jmods/java.base.jmod + ${java.home}/jmods/java.desktop.jmod + ${java.home}/jmods/java.logging.jmod + ${java.home}/jmods/jdk.jsobject.jmod + + + + + + + + diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java new file mode 100644 index 0000000..dc1e54e --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java @@ -0,0 +1,100 @@ +// +// 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.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.samskivert.swing.GroupLayout; +import com.samskivert.swing.Spacer; +import com.samskivert.swing.VGroupLayout; + +import com.threerings.getdown.util.MessageUtil; +import static com.threerings.getdown.Log.log; + +/** + * Displays a confirmation that the user wants to abort installation. + */ +public final class AbortPanel extends JFrame + implements ActionListener +{ + public AbortPanel (Getdown getdown, ResourceBundle msgs) + { + _getdown = getdown; + _msgs = msgs; + + setLayout(new VGroupLayout()); + setResizable(false); + setTitle(get("m.abort_title")); + + JLabel message = new JLabel(get("m.abort_confirm")); + message.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + add(message); + add(new Spacer(5, 5)); + + JPanel row = GroupLayout.makeButtonBox(GroupLayout.CENTER); + JButton button; + row.add(button = new JButton(get("m.abort_ok"))); + button.setActionCommand("ok"); + button.addActionListener(this); + row.add(button = new JButton(get("m.abort_cancel"))); + button.setActionCommand("cancel"); + button.addActionListener(this); + getRootPane().setDefaultButton(button); + add(row); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + // this is annoyingly hardcoded, but we can't just force the width + // or the JLabel will claim a bogus height thinking it can lay its + // text out all on one line which will booch the whole UI's + // preferred size + return new Dimension(300, 200); + } + + // documentation inherited from interface + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("ok")) { + System.exit(0); + } else { + setVisible(false); + } + } + + /** Used to look up localized messages. */ + protected String get (String key) + { + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected Getdown _getdown; + protected ResourceBundle _msgs; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java new file mode 100644 index 0000000..99def4f --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java @@ -0,0 +1,1071 @@ +// +// 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.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; + +import javax.imageio.ImageIO; +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLayeredPane; + +import com.samskivert.swing.util.SwingUtil; +import com.threerings.getdown.data.*; +import com.threerings.getdown.data.Application.UpdateInterface.Step; +import com.threerings.getdown.net.Downloader; +import com.threerings.getdown.net.HTTPDownloader; +import com.threerings.getdown.tools.Patcher; +import com.threerings.getdown.util.*; + +import static com.threerings.getdown.Log.log; + +/** + * Manages the main control for the Getdown application updater and deployment system. + */ +public abstract class Getdown extends Thread + implements Application.StatusDisplay, RotatingBackgrounds.ImageLoader +{ + public Getdown (EnvConfig envc) + { + super("Getdown"); + try { + // 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. + _silent = SysProps.silent(); + if (_silent) { + _launchInSilent = SysProps.launchInSilent(); + _noUpdate = SysProps.noUpdate(); + } + // If we're running in a headless environment and have not otherwise customized + // silence, operate without a UI and do launch the app. + if (!_silent && GraphicsEnvironment.isHeadless()) { + log.info("Running in headless JVM, will attempt to operate without UI."); + _silent = true; + _launchInSilent = true; + } + _delay = SysProps.startDelay(); + } catch (SecurityException se) { + // don't freak out, just assume non-silent and no delay; we're probably already + // recovering from a security failure + } + try { + _msgs = ResourceBundle.getBundle("com.threerings.getdown.messages"); + } catch (Exception e) { + // welcome to hell, where java can't cope with a classpath that contains jars that live + // in a directory that contains a !, at least the same bug happens on all platforms + String dir = envc.appDir.toString(); + if (dir.equals(".")) { + dir = System.getProperty("user.dir"); + } + String errmsg = "The directory in which this application is installed:\n" + dir + + "\nis invalid (" + e.getMessage() + "). If the full path to the app directory " + + "contains the '!' character, this will trigger this error."; + fail(errmsg); + } + _app = new Application(envc); + _startup = System.currentTimeMillis(); + } + + /** + * Returns true if there are pending new resources, waiting to be installed. + */ + public boolean isUpdateAvailable () + { + return _readyToInstall && !_toInstallResources.isEmpty(); + } + + /** + * Installs the currently pending new resources. + */ + public void install () throws IOException + { + if (SysProps.noInstall()) { + log.info("Skipping install due to 'no_install' sysprop."); + } else if (_readyToInstall) { + log.info("Installing " + _toInstallResources.size() + " downloaded resources:"); + for (Resource resource : _toInstallResources) { + resource.install(true); + } + _toInstallResources.clear(); + _readyToInstall = false; + log.info("Install completed."); + } else { + log.info("Nothing to install."); + } + } + + @Override + public void run () + { + // if we have no messages, just bail because we're hosed; the error message will be + // displayed to the user already + if (_msgs == null) { + return; + } + + log.info("Getdown starting", "version", Build.version(), "built", Build.time()); + + // determine whether or not we can write to our install directory + File instdir = _app.getLocalPath(""); + if (!instdir.canWrite()) { + String path = instdir.getPath(); + if (path.equals(".")) { + path = System.getProperty("user.dir"); + } + fail(MessageUtil.tcompose("m.readonly_error", path)); + return; + } + + try { + _dead = false; + // 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 + if (detectProxy() || _app.allowOffline()) { + 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 () { + if (ProxyUtil.autoDetectProxy(_app)) { + return true; + } + + // otherwise see if we actually need a proxy; first we have to initialize our application + // to get some sort of interface configuration and the appbase URL + log.info("Checking whether we need to use a proxy..."); + try { + readConfig(true); + } catch (IOException ioe) { + // no worries + } + updateStatus("m.detecting_proxy"); + if (!ProxyUtil.canLoadWithoutProxy(_app.getConfigResource().getRemote())) { + return 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."); + ProxyUtil.saveProxy(_app, null, null); + return true; + } + + protected void readConfig (boolean preloads) throws IOException { + Config config = _app.init(true); + if (preloads) doPredownloads(_app.getResources()); + _ifc = new Application.UpdateInterface(config); + } + + /** + * Downloads and installs (without verifying) any resources that are marked with a + * {@code PRELOAD} attribute. + * @param resources the full set of resources from the application (the predownloads will be + * extracted from it). + */ + protected void doPredownloads (Collection resources) { + List predownloads = new ArrayList<>(); + for (Resource rsrc : resources) { + if (rsrc.shouldPredownload() && !rsrc.getLocal().exists()) { + predownloads.add(rsrc); + } + } + + try { + download(predownloads); + for (Resource rsrc : predownloads) { + rsrc.install(false); // install but don't validate yet + } + } catch (IOException ioe) { + log.warning("Failed to predownload resources. Continuing...", ioe); + } + } + + /** + * Does the actual application validation, update and launching business. + */ + protected void getdown () + { + try { + // first parses our application deployment file + try { + readConfig(true); + } catch (IOException ioe) { + log.warning("Failed to initialize: " + ioe); + _app.attemptRecovery(this); + // and re-initalize + readConfig(true); + // and force our UI to be recreated with the updated info + createInterfaceAsync(true); + } + if (!_noUpdate && !_app.lockForUpdates()) { + throw new MultipleGetdownRunning(); + } + + // Update the config modtime so a sleeping getdown will notice the change. + File config = _app.getLocalPath(Application.CONFIG_FILE); + if (!config.setLastModified(System.currentTimeMillis())) { + log.warning("Unable to set modtime on config file, will be unable to check for " + + "another instance of getdown running while this one waits."); + } + if (_delay > 0) { + // don't hold the lock while waiting, let another getdown proceed if it starts. + _app.releaseLock(); + // Store the config modtime before waiting the delay amount of time + long lastConfigModtime = config.lastModified(); + log.info("Waiting " + _delay + " minutes before beginning actual work."); + Thread.sleep(_delay * 60 * 1000); + if (lastConfigModtime < config.lastModified()) { + log.warning("getdown.txt was modified while getdown was waiting."); + throw new MultipleGetdownRunning(); + } + } + + // if no_update was specified, directly start the app without updating + if (_noUpdate) { + log.info("Launching without update!"); + launch(); + return; + } + + // we create this tracking counter here so that we properly note the first time through + // the update process whether we previously had validated resources (which means this + // is not a first time install); we may, in the course of updating, wipe out our + // validation markers and revalidate which would make us think we were doing a fresh + // install if we didn't specifically remember that we had validated resources the first + // time through + int[] alreadyValid = new int[1]; + + // we'll keep track of all the resources we unpack + Set unpacked = new HashSet<>(); + + _toInstallResources = new HashSet<>(); + _readyToInstall = false; + + // setStep(Step.START); + for (int ii = 0; ii < MAX_LOOPS; ii++) { + // make sure we have the desired version and that the metadata files are valid... + setStep(Step.VERIFY_METADATA); + setStatusAsync("m.validating", -1, -1L, false); + if (_app.verifyMetadata(this)) { + log.info("Application requires update."); + update(); + // loop back again and reverify the metadata + continue; + } + + // now verify (and download) our resources... + setStep(Step.VERIFY_RESOURCES); + setStatusAsync("m.validating", -1, -1L, false); + Set toDownload = new HashSet<>(); + _app.verifyResources(_progobs, alreadyValid, unpacked, + _toInstallResources, toDownload); + + if (toDownload.size() > 0) { + // we have resources to download, also note them as to-be-installed + for (Resource r : toDownload) { + if (!_toInstallResources.contains(r)) { + _toInstallResources.add(r); + } + } + + try { + // if any of our resources have already been marked valid this is not a + // first time install and we don't want to enable tracking + _enableTracking = (alreadyValid[0] == 0); + reportTrackingEvent("app_start", -1); + + // redownload any that are corrupt or invalid... + log.info(toDownload.size() + " of " + _app.getAllActiveResources().size() + + " rsrcs require update (" + alreadyValid[0] + " assumed valid)."); + setStep(Step.REDOWNLOAD_RESOURCES); + download(toDownload); + + reportTrackingEvent("app_complete", -1); + + } finally { + _enableTracking = false; + } + + // now we'll loop back and try it all again + continue; + } + + // if we aren't running in a JVM that meets our version requirements, either + // complain or attempt to download and install the appropriate version + if (!_app.haveValidJavaVersion()) { + // download and install the necessary version of java, then loop back again and + // reverify everything; if we can't download java; we'll throw an exception + log.info("Attempting to update Java VM..."); + setStep(Step.UPDATE_JAVA); + _enableTracking = true; // always track JVM downloads + try { + updateJava(); + } finally { + _enableTracking = false; + } + continue; + } + + // if we were downloaded in full from another service (say, Steam), we may + // not have unpacked all of our resources yet + if (Boolean.getBoolean("check_unpacked")) { + File ufile = _app.getLocalPath("unpacked.dat"); + long version = -1; + long aversion = _app.getVersion(); + if (!ufile.exists()) { + ufile.createNewFile(); + } else { + version = VersionUtil.readVersion(ufile); + } + + if (version < aversion) { + log.info("Performing unpack", "version", version, "aversion", aversion); + setStep(Step.UNPACK); + updateStatus("m.validating"); + _app.unpackResources(_progobs, unpacked); + try { + VersionUtil.writeVersion(ufile, aversion); + } catch (IOException ioe) { + log.warning("Failed to update unpacked version", ioe); + } + } + } + + // assuming we're not doing anything funny, install the update + _readyToInstall = true; + install(); + + // Only launch if we aren't in silent mode. Some mystery program starting out + // of the blue would be disconcerting. + if (!_silent || _launchInSilent) { + // And another final check for the lock. It'll already be held unless + // we're in silent mode. + _app.lockForUpdates(); + launch(); + } + return; + } + + log.warning("Pants! We couldn't get the job done."); + throw new IOException("m.unable_to_repair"); + + } catch (Exception e) { + log.warning("getdown() 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); + } + } + // Since we're dead, clear off the 'time remaining' label along with displaying the + // error message + fail(msg); + _app.releaseLock(); + } + } + + // documentation inherited from interface + @Override + public void updateStatus (String message) + { + setStatusAsync(message, -1, -1L, true); + } + + /** + * Load the image at the path. Before trying the exact path/file specified we will look to see + * if we can find a localized version by sticking a {@code _} in front of the "." in + * the filename. + */ + @Override + public BufferedImage loadImage (String path) + { + if (StringUtil.isBlank(path)) { + return null; + } + + File imgpath = null; + try { + // First try for a localized image. + String localeStr = Locale.getDefault().getLanguage(); + imgpath = _app.getLocalPath(path.replace(".", "_" + localeStr + ".")); + return ImageIO.read(imgpath); + } catch (IOException ioe) { + // No biggie, we'll try the generic one. + } + + // If that didn't work, try a generic one. + try { + imgpath = _app.getLocalPath(path); + return ImageIO.read(imgpath); + } catch (IOException ioe2) { + log.warning("Failed to load image", "path", imgpath, "error", ioe2); + return null; + } + } + + /** + * Downloads and installs an Java VM bundled with the application. This is called if we are not + * running with the necessary Java version. + */ + protected void updateJava () + throws IOException + { + Resource vmjar = _app.getJavaVMResource(); + if (vmjar == null) { + throw new IOException("m.java_download_failed"); + } + + reportTrackingEvent("jvm_start", -1); + + updateStatus("m.downloading_java"); + List list = new ArrayList<>(); + list.add(vmjar); + download(list); + + reportTrackingEvent("jvm_unpack", -1); + + updateStatus("m.unpacking_java"); + vmjar.install(true); + + // these only run on non-Windows platforms, so we use Unix file separators + String localJavaDir = LaunchUtil.LOCAL_JAVA_DIR + "/"; + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "bin/java")); + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/jspawnhelper")); + FileUtil.makeExecutable(_app.getLocalPath(localJavaDir + "lib/amd64/jspawnhelper")); + + // lastly regenerate the .jsa dump file that helps Java to start up faster + String vmpath = LaunchUtil.getJVMPath(_app.getLocalPath("")); + try { + log.info("Regenerating classes.jsa for " + vmpath + "..."); + Runtime.getRuntime().exec(vmpath + " -Xshare:dump"); + } catch (Exception e) { + log.warning("Failed to regenerate .jsa dump file", "error", e); + } + + reportTrackingEvent("jvm_complete", -1); + } + + /** + * Called if the application is determined to be of an old version. + */ + protected void update () + throws IOException + { + // first clear all validation markers + _app.clearValidationMarkers(); + + // attempt to download the patch files + Resource patch = _app.getPatchResource(null); + if (patch != null) { + List list = new ArrayList<>(); + list.add(patch); + + // add the auxiliary group patch files for activated groups + for (Application.AuxGroup aux : _app.getAuxGroups()) { + if (_app.isAuxGroupActive(aux.name)) { + patch = _app.getPatchResource(aux.name); + if (patch != null) { + list.add(patch); + } + } + } + + // show the patch notes button, if applicable + if (!StringUtil.isBlank(_ifc.patchNotesUrl)) { + createInterfaceAsync(false); + EventQueue.invokeLater(new Runnable() { + public void run () { + _patchNotes.setVisible(true); + } + }); + } + + // download the patch files... + setStep(Step.DOWNLOAD); + download(list); + + // and apply them... + setStep(Step.PATCH); + updateStatus("m.patching"); + + long[] sizes = new long[list.size()]; + Arrays.fill(sizes, 1L); + ProgressAggregator pragg = new ProgressAggregator(_progobs, sizes); + int ii = 0; for (Resource prsrc : list) { + ProgressObserver pobs = pragg.startElement(ii++); + try { + // install the patch file (renaming them from _new) + prsrc.install(false); + // now apply the patch + Patcher patcher = new Patcher(); + patcher.patch(prsrc.getLocal().getParentFile(), prsrc.getLocal(), pobs); + } catch (Exception e) { + log.warning("Failed to apply patch", "prsrc", prsrc, e); + } + + // clean up the patch file + if (!FileUtil.deleteHarder(prsrc.getLocal())) { + log.warning("Failed to delete '" + prsrc + "'."); + } + } + } + + // if the patch resource is null, that means something was booched in the application, so + // we skip the patching process but update the metadata which will result in a "brute + // force" upgrade + + // finally update our metadata files... + _app.updateMetadata(); + // ...and reinitialize the application + readConfig(false); + } + + /** + * Called if the application is determined to require resource downloads. + */ + protected void download (Collection resources) + throws IOException + { + // create our user interface + createInterfaceAsync(false); + + Downloader dl = new HTTPDownloader(_app.proxy) { + @Override protected void resolvingDownloads () { + updateStatus("m.resolving"); + } + + @Override protected void downloadProgress (int percent, long remaining) { + // check for another getdown running at 0 and every 10% after that + if (_lastCheck == -1 || percent >= _lastCheck + 10) { + if (_delay > 0) { + // stop the presses if something else is holding the lock + boolean locked = _app.lockForUpdates(); + _app.releaseLock(); + if (locked) abort(); + } + _lastCheck = percent; + } + setStatusAsync("m.downloading", stepToGlobalPercent(percent), remaining, true); + if (percent > 0) { + reportTrackingEvent("progress", percent); + } + } + + @Override protected void downloadFailed (Resource rsrc, Exception e) { + updateStatus(MessageUtil.tcompose("m.failure", e.getMessage())); + log.warning("Download failed", "rsrc", rsrc, e); + } + + /** The last percentage at which we checked for another getdown running, or -1 for not + * having checked at all. */ + protected int _lastCheck = -1; + }; + if (!dl.download(resources, _app.maxConcurrentDownloads())) { + // if we aborted due to detecting another getdown running, we want to report here + throw new MultipleGetdownRunning(); + } + } + + /** + * Called to launch the application if everything is determined to be ready to go. + */ + protected void launch () + { + setStep(Step.LAUNCH); + setStatusAsync("m.launching", stepToGlobalPercent(100), -1L, false); + + try { + if (invokeDirect()) { + // we want to close the Getdown window, as the app is launching + disposeContainer(); + _app.releaseLock(); + _app.invokeDirect(); + + } else { + Process proc; + if (_app.hasOptimumJvmArgs()) { + // if we have "optimum" arguments, we want to try launching with them first + proc = _app.createProcess(true); + + long fallback = System.currentTimeMillis() + FALLBACK_CHECK_TIME; + boolean error = false; + while (fallback > System.currentTimeMillis()) { + try { + error = proc.exitValue() != 0; + break; + } catch (IllegalThreadStateException e) { + Thread.yield(); + } + } + + if (error) { + log.info("Failed to launch with optimum arguments; falling back."); + proc = _app.createProcess(false); + } + } else { + proc = _app.createProcess(false); + } + + // close standard in to avoid choking standard out of the launched process + proc.getInputStream().close(); + // close standard out, since we're not going to write to anything to it anyway + proc.getOutputStream().close(); + + // on Windows 98 and ME we need to stick around and read the output of stderr lest + // the process fill its output buffer and choke, yay! + final InputStream stderr = proc.getErrorStream(); + if (LaunchUtil.mustMonitorChildren()) { + // close our window if it's around + disposeContainer(); + _container = null; + copyStream(stderr, System.err); + log.info("Process exited: " + proc.waitFor()); + + } else { + // spawn a daemon thread that will catch the early bits of stderr in case the + // launch fails + Thread t = new Thread() { + @Override public void run () { + copyStream(stderr, System.err); + } + }; + t.setDaemon(true); + t.start(); + } + } + + // if we have a UI open and we haven't been around for at least 5 seconds (the default + // for min_show_seconds), don't stick a fork in ourselves straight away but give our + // lovely user a chance to see what we're doing + long uptime = System.currentTimeMillis() - _startup; + long minshow = _ifc.minShowSeconds * 1000L; + if (_container != null && uptime < minshow) { + try { + Thread.sleep(minshow - uptime); + } catch (Exception e) { + } + } + + // pump the percent up to 100% + setStatusAsync(null, 100, -1L, false); + exit(0); + + } catch (Exception e) { + log.warning("launch() failed.", e); + } + } + + /** + * Creates our user interface, which we avoid doing unless we actually have to update + * something. NOTE: this happens on the next UI tick, not immediately. + * + * @param reinit - if the interface should be reinitialized if it already exists. + */ + protected void createInterfaceAsync (final boolean reinit) + { + if (_silent || (_container != null && !reinit)) { + return; + } + + EventQueue.invokeLater(new Runnable() { + public void run () { + if (_container == null || reinit) { + if (_container == null) { + _container = createContainer(); + } else { + _container.removeAll(); + } + configureContainer(); + _layers = new JLayeredPane(); + _container.add(_layers, BorderLayout.CENTER); + _patchNotes = new JButton(new AbstractAction(_msgs.getString("m.patch_notes")) { + @Override public void actionPerformed (ActionEvent event) { + showDocument(_ifc.patchNotesUrl); + } + }); + _patchNotes.setFont(StatusPanel.FONT); + _layers.add(_patchNotes); + _status = new StatusPanel(_msgs); + _layers.add(_status); + initInterface(); + } + showContainer(); + } + }); + } + + /** + * Initializes the interface with the current UpdateInterface and backgrounds. + */ + protected void initInterface () + { + RotatingBackgrounds newBackgrounds = getBackground(); + if (_background == null || newBackgrounds.getNumImages() > 0) { + // Leave the old _background in place if there is an old one to leave in place + // and the new getdown.txt didn't yield any images. + _background = newBackgrounds; + } + _status.init(_ifc, _background, getProgressImage()); + Dimension size = _status.getPreferredSize(); + _status.setSize(size); + _layers.setPreferredSize(size); + + _patchNotes.setBounds(_ifc.patchNotes.x, _ifc.patchNotes.y, + _ifc.patchNotes.width, _ifc.patchNotes.height); + _patchNotes.setVisible(false); + + // we were displaying progress while the UI wasn't up. Now that it is, whatever progress + // is left is scaled into a 0-100 DISPLAYED progress. + _uiDisplayPercent = _lastGlobalPercent; + _stepMinPercent = _lastGlobalPercent = 0; + } + + protected RotatingBackgrounds getBackground () + { + if (_ifc.rotatingBackgrounds != null) { + if (_ifc.backgroundImage != null) { + log.warning("ui.background_image and ui.rotating_background were both specified. " + + "The rotating images are being used."); + } + return new RotatingBackgrounds(_ifc.rotatingBackgrounds, _ifc.errorBackground, + Getdown.this); + } else if (_ifc.backgroundImage != null) { + return new RotatingBackgrounds(loadImage(_ifc.backgroundImage)); + } else { + return new RotatingBackgrounds(); + } + } + + protected Image getProgressImage () + { + return loadImage(_ifc.progressImage); + } + + protected void handleWindowClose () + { + if (_dead) { + exit(0); + } else { + if (_abort == null) { + _abort = new AbortPanel(Getdown.this, _msgs); + } + _abort.pack(); + SwingUtil.centerWindow(_abort); + _abort.setVisible(true); + _abort.setState(JFrame.NORMAL); + _abort.requestFocus(); + } + } + + /** + * Update the status to indicate getdown has failed for the reason in message. + */ + protected void fail (String message) + { + _dead = true; + setStatusAsync(message, stepToGlobalPercent(0), -1L, true); + } + + /** + * Set the current step, which will be used to globalize per-step percentages. + */ + protected void setStep (Step step) + { + int finalPercent = -1; + for (Integer perc : _ifc.stepPercentages.get(step)) { + if (perc > _stepMaxPercent) { + finalPercent = perc; + break; + } + } + if (finalPercent == -1) { + // we've gone backwards and this step will be ignored + return; + } + + _stepMaxPercent = finalPercent; + _stepMinPercent = _lastGlobalPercent; + } + + /** + * Convert a step percentage to the global percentage. + */ + protected int stepToGlobalPercent (int percent) + { + int adjustedMaxPercent = + ((_stepMaxPercent - _uiDisplayPercent) * 100) / (100 - _uiDisplayPercent); + _lastGlobalPercent = Math.max(_lastGlobalPercent, + _stepMinPercent + (percent * (adjustedMaxPercent - _stepMinPercent)) / 100); + return _lastGlobalPercent; + } + + /** + * Updates the status. NOTE: this happens on the next UI tick, not immediately. + */ + protected void setStatusAsync (final String message, final int percent, final long remaining, + boolean createUI) + { + if (_status == null && createUI) { + createInterfaceAsync(false); + } + + EventQueue.invokeLater(new Runnable() { + public void run () { + if (_status == null) { + if (message != null) { + log.info("Dropping status '" + message + "'."); + } + return; + } + if (message != null) { + _status.setStatus(message, _dead); + } + if (_dead) { + _status.setProgress(0, -1L); + } else if (percent >= 0) { + _status.setProgress(percent, remaining); + } + } + }); + } + + protected void reportTrackingEvent (String event, int progress) + { + if (!_enableTracking) { + return; + + } else if (progress > 0) { + // we need to make sure we do the right thing if we skip over progress levels + do { + URL url = _app.getTrackingProgressURL(++_reportedProgress); + if (url != null) { + new ProgressReporter(url).start(); + } + } while (_reportedProgress <= progress); + + } else { + URL url = _app.getTrackingURL(event); + if (url != null) { + new ProgressReporter(url).start(); + } + } + } + + /** + * Creates the container in which our user interface will be displayed. + */ + protected abstract Container createContainer (); + + /** + * Configures the interface container based on the latest UI config. + */ + protected abstract void configureContainer (); + + /** + * Shows the container in which our user interface will be displayed. + */ + protected abstract void showContainer (); + + /** + * Disposes the container in which we have our user interface. + */ + protected abstract void disposeContainer (); + + /** + * If this method returns true we will run the application in the same JVM, otherwise we will + * fork off a new JVM. Some options are not supported if we do not fork off a new JVM. + */ + protected boolean invokeDirect () + { + return SysProps.direct(); + } + + /** + * Requests to show the document at the specified URL in a new window. + */ + protected abstract void showDocument (String url); + + /** + * Requests that Getdown exit. + */ + protected abstract void exit (int exitCode); + + /** + * Copies the supplied stream from the specified input to the specified output. Used to copy + * our child processes stderr and stdout to our own stderr and stdout. + */ + protected static void copyStream (InputStream in, PrintStream out) + { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = reader.readLine()) != null) { + out.print(line); + out.flush(); + } + } catch (IOException ioe) { + log.warning("Failure copying", "in", in, "out", out, "error", ioe); + } + } + + /** Used to fetch a progress report URL. */ + protected class ProgressReporter extends Thread + { + public ProgressReporter (URL url) { + 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 { + 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); + } + } + + protected URL _url; + } + + /** Used to pass progress on to our user interface. */ + protected ProgressObserver _progobs = new ProgressObserver() { + public void progress (int percent) { + setStatusAsync(null, stepToGlobalPercent(percent), -1L, false); + } + }; + + protected Application _app; + protected Application.UpdateInterface _ifc = new Application.UpdateInterface(Config.EMPTY); + + protected ResourceBundle _msgs; + protected Container _container; + protected JLayeredPane _layers; + protected StatusPanel _status; + protected JButton _patchNotes; + protected AbortPanel _abort; + protected RotatingBackgrounds _background; + + protected boolean _dead; + protected boolean _silent; + protected boolean _launchInSilent; + protected boolean _noUpdate; + protected long _startup; + + protected Set _toInstallResources; + protected boolean _readyToInstall; + + protected boolean _enableTracking = true; + protected int _reportedProgress = 0; + + /** Number of minutes to wait after startup before beginning any real heavy lifting. */ + protected int _delay; + + protected int _stepMaxPercent; + protected int _stepMinPercent; + protected int _lastGlobalPercent; + protected int _uiDisplayPercent; + + protected static final int MAX_LOOPS = 5; + protected static final long FALLBACK_CHECK_TIME = 1000L; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java new file mode 100644 index 0000000..fde79f3 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java @@ -0,0 +1,253 @@ +// +// 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.awt.Color; +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.KeyStroke; +import javax.swing.WindowConstants; + +import com.samskivert.swing.util.SwingUtil; +import com.threerings.getdown.data.EnvConfig; +import com.threerings.getdown.data.SysProps; +import com.threerings.getdown.util.LaunchUtil; +import com.threerings.getdown.util.StringUtil; +import static com.threerings.getdown.Log.log; + +/** + * The main application entry point for Getdown. + */ +public class GetdownApp +{ + /** + * The main entry point of the Getdown launcher application. + */ + public static void main (String[] argv) { + try { + start(argv); + } catch (Exception e) { + log.warning("main() failed.", e); + } + } + + /** + * Runs Getdown as an application, using the arguments supplie as {@code argv}. + * @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been + * called on it. + * @throws Exception if anything goes wrong starting Getdown. + */ + public static Getdown start (String[] argv) throws Exception { + List notes = new ArrayList<>(); + EnvConfig envc = EnvConfig.create(argv, notes); + if (envc == null) { + if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message); + else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]"); + System.exit(-1); + } + + // pipe our output into a file in the application directory + if (!SysProps.noLogRedir()) { + File logFile = new File(envc.appDir, "launcher.log"); + try { + PrintStream logOut = new PrintStream( + new BufferedOutputStream(new FileOutputStream(logFile)), true); + System.setOut(logOut); + System.setErr(logOut); + } catch (IOException ioe) { + log.warning("Unable to redirect output to '" + logFile + "': " + ioe); + } + } + + // report any notes from reading our env config, and abort if necessary + boolean abort = false; + for (EnvConfig.Note note : notes) { + switch (note.level) { + case INFO: log.info(note.message); break; + case WARN: log.warning(note.message); break; + case ERROR: log.error(note.message); abort = true; break; + } + } + if (abort) System.exit(-1); + + // record a few things for posterity + log.info("------------------ VM Info ------------------"); + log.info("-- OS Name: " + System.getProperty("os.name")); + log.info("-- OS Arch: " + System.getProperty("os.arch")); + log.info("-- OS Vers: " + System.getProperty("os.version")); + log.info("-- Java Vers: " + System.getProperty("java.version")); + log.info("-- Java Home: " + System.getProperty("java.home")); + log.info("-- User Name: " + System.getProperty("user.name")); + log.info("-- User Home: " + System.getProperty("user.home")); + log.info("-- Cur dir: " + System.getProperty("user.dir")); + log.info("---------------------------------------------"); + + Getdown app = new Getdown(envc) { + @Override + protected Container createContainer () { + // create our user interface, and display it + if (_frame == null) { + _frame = new JFrame(""); + _frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing (WindowEvent evt) { + handleWindowClose(); + } + }); + // handle close on ESC + String cancelId = "Cancel"; // $NON-NLS-1$ + _frame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelId); + _frame.getRootPane().getActionMap().put(cancelId, new AbstractAction() { + public void actionPerformed (ActionEvent e) { + handleWindowClose(); + } + }); + // this cannot be called in configureContainer as it is only allowed before the + // frame has been displayed for the first time + _frame.setUndecorated(_ifc.hideDecorations); + _frame.setResizable(false); + } else { + _frame.getContentPane().removeAll(); + } + _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + return _frame.getContentPane(); + } + + @Override + protected void configureContainer () { + if (_frame == null) return; + + _frame.setTitle(_ifc.name); + + try { + _frame.setBackground(new Color(_ifc.background, true)); + } catch (Exception e) { + log.warning("Failed to set background", "bg", _ifc.background, e); + } + + if (_ifc.iconImages != null) { + ArrayList icons = new ArrayList<>(); + for (String path : _ifc.iconImages) { + Image img = loadImage(path); + if (img == null) { + log.warning("Error loading icon image", "path", path); + } else { + icons.add(img); + } + } + if (icons.isEmpty()) { + log.warning("Failed to load any icons", "iconImages", _ifc.iconImages); + } else { + _frame.setIconImages(icons); + } + } + } + + @Override + protected void showContainer () { + if (_frame != null) { + _frame.pack(); + SwingUtil.centerWindow(_frame); + _frame.setVisible(true); + } + } + + @Override + protected void disposeContainer () { + if (_frame != null) { + _frame.dispose(); + _frame = null; + } + } + + @Override + protected void showDocument (String url) { + if (!StringUtil.couldBeValidUrl(url)) { + // command injection would be possible if we allowed e.g. spaces and double quotes + log.warning("Invalid document URL.", "url", url); + return; + } + String[] cmdarray; + if (LaunchUtil.isWindows()) { + String osName = System.getProperty("os.name", ""); + if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) { + cmdarray = new String[] { + "command.com", "/c", "start", "\"" + url + "\"" }; + } else { + cmdarray = new String[] { + "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" }; + } + } else if (LaunchUtil.isMacOS()) { + cmdarray = new String[] { "open", url }; + } else { // Linux, Solaris, etc. + cmdarray = new String[] { "firefox", url }; + } + try { + Runtime.getRuntime().exec(cmdarray); + } catch (Exception e) { + log.warning("Failed to open browser.", "cmdarray", cmdarray, e); + } + } + + @Override + protected void exit (int exitCode) { + // if we're running the app in the same JVM, don't call System.exit, but do + // make double sure that the download window is closed. + if (invokeDirect()) { + disposeContainer(); + } else { + System.exit(exitCode); + } + } + + @Override + protected void fail (String message) { + super.fail(message); + // 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 + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + // if the frame was set to be undecorated, make window decoration available + // to allow the user to close the window + if (_frame != null && _frame.isUndecorated()) { + _frame.dispose(); + Color bg = _frame.getBackground(); + if (bg != null && bg.getAlpha() < 255) { + // decorated windows do not allow alpha backgrounds + _frame.setBackground( + new Color(bg.getRed(), bg.getGreen(), bg.getBlue())); + } + _frame.setUndecorated(false); + showContainer(); + } + } + }); + } + + protected JFrame _frame; + }; + app.start(); + return app; + } +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java new file mode 100644 index 0000000..5ac7449 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java @@ -0,0 +1,20 @@ +// +// 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.IOException; + +/** + * Thrown when it's detected that multiple instances of the same getdown installer are running. + */ +public class MultipleGetdownRunning extends IOException +{ + public MultipleGetdownRunning () + { + super("m.another_getdown_running"); + } + +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java new file mode 100644 index 0000000..2178273 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java @@ -0,0 +1,195 @@ +// +// 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.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import com.samskivert.swing.GroupLayout; +import com.samskivert.swing.Spacer; +import com.samskivert.swing.VGroupLayout; +import com.threerings.getdown.util.MessageUtil; +import static com.threerings.getdown.Log.log; + +/** + * Displays an interface with which the user can configure their proxy + * settings. + */ +public final class ProxyPanel extends JPanel implements ActionListener +{ + public ProxyPanel (Getdown getdown, ResourceBundle msgs) + { + _getdown = getdown; + _msgs = msgs; + + setLayout(new VGroupLayout()); + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + add(new SaneLabelField(get("m.configure_proxy"))); + add(new Spacer(5, 5)); + + JPanel row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_host")), BorderLayout.WEST); + row.add(_host = new SaneTextField()); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_port")), BorderLayout.WEST); + row.add(_port = new SaneTextField()); + add(row); + + add(new Spacer(5, 5)); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_auth_required")), BorderLayout.WEST); + _useAuth = new JCheckBox(); + row.add(_useAuth); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_username")), BorderLayout.WEST); + _username = new SaneTextField(); + _username.setEnabled(false); + row.add(_username); + add(row); + + row = new JPanel(new GridLayout()); + row.add(new SaneLabelField(get("m.proxy_password")), BorderLayout.WEST); + _password = new SanePasswordField(); + _password.setEnabled(false); + row.add(_password); + add(row); + + _useAuth.addItemListener(new ItemListener() { + @Override public void itemStateChanged (ItemEvent event) { + boolean selected = (event.getStateChange() == ItemEvent.SELECTED); + _username.setEnabled(selected); + _password.setEnabled(selected); + } + }); + + add(new Spacer(5, 5)); + + row = GroupLayout.makeButtonBox(GroupLayout.CENTER); + JButton button; + row.add(button = new JButton(get("m.proxy_ok"))); + button.setActionCommand("ok"); + button.addActionListener(this); + row.add(button = new JButton(get("m.proxy_cancel"))); + button.setActionCommand("cancel"); + button.addActionListener(this); + add(row); + } + + public void setProxy (String host, String port) { + if (host != null) { + _host.setText(host); + } + if (port != null) { + _port.setText(port); + } + } + + // documentation inherited + @Override + public void addNotify () + { + super.addNotify(); + _host.requestFocusInWindow(); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + // this is annoyingly hardcoded, but we can't just force the width + // or the JLabel will claim a bogus height thinking it can lay its + // text out all on one line which will booch the whole UI's + // preferred size + return new Dimension(500, 320); + } + + // documentation inherited from interface + @Override + public void actionPerformed (ActionEvent e) + { + String cmd = e.getActionCommand(); + if (cmd.equals("ok")) { + String user = null, pass = null; + if (_useAuth.isSelected()) { + user = _username.getText(); + // we have to keep the proxy password around for every HTTP request, so having it + // in a char[] that gets zeroed out after use is not viable for this use case + pass = new String(_password.getPassword()); + } + _getdown.configProxy(_host.getText(), _port.getText(), user, pass); + } else { + // they canceled, we're outta here + System.exit(0); + } + } + + /** Used to look up localized messages. */ + protected String get (String key) + { + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected static class SaneLabelField extends JLabel { + public SaneLabelField(String message) { super(message); } + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 200); + } + } + protected static class SaneTextField extends JTextField { + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 150); + } + } + protected static class SanePasswordField extends JPasswordField { + @Override public Dimension getPreferredSize () { + return clampWidth(super.getPreferredSize(), 150); + } + } + + protected static Dimension clampWidth (Dimension dim, int minWidth) { + dim.width = Math.max(dim.width, minWidth); + return dim; + } + + protected Getdown _getdown; + protected ResourceBundle _msgs; + + protected JTextField _host; + protected JTextField _port; + protected JCheckBox _useAuth; + protected JTextField _username; + protected JPasswordField _password; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java new file mode 100644 index 0000000..a36b5fa --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java @@ -0,0 +1,210 @@ +// +// 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.Authenticator; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.Iterator; +import java.util.ServiceLoader; + +import ca.beq.util.win32.registry.RegistryKey; +import ca.beq.util.win32.registry.RegistryValue; +import ca.beq.util.win32.registry.RootKey; + +import com.threerings.getdown.data.Application; +import com.threerings.getdown.spi.ProxyAuth; +import com.threerings.getdown.util.Config; +import com.threerings.getdown.util.ConnectionUtil; +import com.threerings.getdown.util.LaunchUtil; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +public class ProxyUtil { + + public static boolean autoDetectProxy (Application app) + { + String host = null, port = null; + + // check for a proxy configured via system properties + if (System.getProperty("https.proxyHost") != null) { + host = System.getProperty("https.proxyHost"); + port = System.getProperty("https.proxyPort"); + } + if (StringUtil.isBlank(host) && System.getProperty("http.proxyHost") != null) { + host = System.getProperty("http.proxyHost"); + port = System.getProperty("http.proxyPort"); + } + + // check the Windows registry + if (StringUtil.isBlank(host) && LaunchUtil.isWindows()) { + try { + String rhost = null, rport = null; + boolean enabled = false; + RegistryKey.initialize(); + RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY); + for (Iterator iter = r.values(); iter.hasNext(); ) { + RegistryValue value = (RegistryValue)iter.next(); + if (value.getName().equals("ProxyEnable")) { + enabled = value.getStringValue().equals("1"); + } + if (value.getName().equals("ProxyServer")) { + String strval = value.getStringValue(); + int cidx = strval.indexOf(":"); + if (cidx != -1) { + rport = strval.substring(cidx+1); + strval = strval.substring(0, cidx); + } + rhost = strval; + } + } + if (enabled) { + host = rhost; + port = rport; + } else { + log.info("Detected no proxy settings in the registry."); + } + } catch (Throwable t) { + log.info("Failed to find proxy settings in Windows registry", "error", t); + } + } + + // look for a proxy.txt file + if (StringUtil.isBlank(host)) { + String[] hostPort = loadProxy(app); + host = hostPort[0]; + port = hostPort[1]; + } + + if (StringUtil.isBlank(host)) { + return false; + } + + // yay, we found a proxy configuration, configure it in the app + initProxy(app, host, port, null, null); + return true; + } + + public static boolean canLoadWithoutProxy (URL rurl) + { + log.info("Testing whether proxy is needed, via: " + rurl); + try { + // try to make a HEAD request for this URL (use short connect and read timeouts) + URLConnection conn = ConnectionUtil.open(Proxy.NO_PROXY, rurl, 5, 5); + 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; + } + } catch (IOException ioe) { + log.info("Failed to HEAD " + rurl + ": " + ioe); + log.info("We probably need a proxy, but auto-detection failed."); + } + return false; + } + + public static void configProxy (Application app, String host, String port, + String username, String password) { + // save our proxy host and port in a local file + saveProxy(app, host, port); + + // save our credentials via the SPI + if (!StringUtil.isBlank(username) && !StringUtil.isBlank(password)) { + ServiceLoader loader = ServiceLoader.load(ProxyAuth.class); + Iterator iterator = loader.iterator(); + String appDir = app.getAppDir().getAbsolutePath(); + while (iterator.hasNext()) { + iterator.next().saveCredentials(appDir, username, password); + } + } + + // also configure them in the app + initProxy(app, host, port, username, password); + } + + public static String[] loadProxy (Application app) { + File pfile = app.getLocalPath("proxy.txt"); + if (pfile.exists()) { + try { + Config pconf = Config.parseConfig(pfile, Config.createOpts(false)); + return new String[] { pconf.getString("host"), pconf.getString("port") }; + } catch (IOException ioe) { + log.warning("Failed to read '" + pfile + "': " + ioe); + } + } + return new String[] { null, null}; + } + + public static void saveProxy (Application app, String host, String port) { + File pfile = app.getLocalPath("proxy.txt"); + try (PrintStream pout = new PrintStream(new FileOutputStream(pfile))) { + if (!StringUtil.isBlank(host)) { + pout.println("host = " + host); + } + if (!StringUtil.isBlank(port)) { + pout.println("port = " + port); + } + } catch (IOException ioe) { + log.warning("Error creating proxy file '" + pfile + "': " + ioe); + } + } + + public static void initProxy (Application app, String host, String port, + String username, String password) + { + // check whether we have saved proxy credentials + String appDir = app.getAppDir().getAbsolutePath(); + ServiceLoader loader = ServiceLoader.load(ProxyAuth.class); + Iterator iter = loader.iterator(); + ProxyAuth.Credentials creds = iter.hasNext() ? iter.next().loadCredentials(appDir) : null; + if (creds != null) { + username = creds.username; + password = creds.password; + } + boolean haveCreds = !StringUtil.isBlank(username) && !StringUtil.isBlank(password); + + int pport = StringUtil.isBlank(port) ? 80 : Integer.valueOf(port); + log.info("Using proxy", "host", host, "port", pport, "haveCreds", haveCreds); + app.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, pport)); + + if (haveCreds) { + final String fuser = username; + final char[] fpass = password.toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override protected PasswordAuthentication getPasswordAuthentication () { + return new PasswordAuthentication(fuser, fpass); + } + }); + } + } + + protected static final String PROXY_REGISTRY = + "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java new file mode 100644 index 0000000..d3aa2bd --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java @@ -0,0 +1,132 @@ +// +// 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.awt.Image; +import java.util.List; + +import static com.threerings.getdown.Log.log; + +public final class RotatingBackgrounds +{ + public interface ImageLoader { + /** Loads and returns the image with the supplied path. */ + public Image loadImage (String path); + } + + /** + * Creates a placeholder if there are no images. Just returns null from getImage every time. + */ + public RotatingBackgrounds () + { + makeEmpty(); + } + + /** Creates a single image background. */ + public RotatingBackgrounds (Image background) + { + percentages = new int[] { 0 }; + minDisplayTime = new int[] { 0 }; + images = new Image[] { background }; + errorImage = images[0]; + } + + /** + * Create a sequence of images to be rotated through from backgrounds. + * + * 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 + * percentage of the download process, unless one hasn't been active for its minimum display + * time when the next should be shown. In that case, it's left up until its been there for its + * minimum display time and then the next one gets to come up. + */ + public RotatingBackgrounds (List backgrounds, String errorBackground, ImageLoader loader) + { + percentages = new int[backgrounds.size()]; + minDisplayTime = new int[backgrounds.size()]; + images = new Image[backgrounds.size()]; + for (int ii = 0; ii < backgrounds.size(); ii++) { + String background = backgrounds.get(ii); + String[] pieces = background.split(";"); + if (pieces.length != 2) { + log.warning("Unable to parse background image '" + background + "'"); + makeEmpty(); + return; + } + images[ii] = loader.loadImage(pieces[0]); + try { + minDisplayTime[ii] = Integer.parseInt(pieces[1]); + } catch (NumberFormatException e) { + log.warning("Unable to parse background image display time '" + background + "'"); + makeEmpty(); + return; + } + percentages[ii] = (int)((ii/(float)backgrounds.size()) * 100); + } + if (errorBackground == null) { + errorImage = images[0]; + } else { + errorImage = loader.loadImage(errorBackground); + } + } + + /** + * @return the image to display at the given progress or null if there aren't any. + */ + public Image getImage (int progress) + { + if (images.length == 0) { + return null; + } + long now = System.currentTimeMillis(); + if (current != images.length - 1 + && (current == -1 || (progress >= percentages[current + 1] && + (now - currentDisplayStart) / 1000 > minDisplayTime[current]))) { + current++; + currentDisplayStart = now; + } + return images[current]; + } + + /** + * Returns the image to display if an error has caused getdown to fail. + */ + public Image getErrorImage () + { + return errorImage; + } + + /** + * @return the number of images in this RotatingBackgrounds + */ + public int getNumImages() { + return images.length; + } + + protected void makeEmpty () + { + percentages = new int[] {}; + minDisplayTime = new int[] {}; + images = new Image[] {}; + } + + /** Time at which the currently displayed image was first displayed in millis. */ + protected long currentDisplayStart; + + /** The index of the currently displayed image or -1 if we haven't displayed any. */ + protected int current = -1; + + protected Image[] images; + + /** The image to display if getdown has failed due to an error. */ + protected Image errorImage; + + /** Percentage at which each image should be displayed. */ + protected int[] percentages; + + /** Time to show each image in seconds. */ + protected int[] minDisplayTime; +} diff --git a/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java new file mode 100644 index 0000000..99f44ca --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java @@ -0,0 +1,396 @@ +// +// 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.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.ImageObserver; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import javax.swing.JComponent; +import javax.swing.Timer; + +import com.samskivert.swing.Label; +import com.samskivert.swing.LabelStyleConstants; +import com.samskivert.swing.util.SwingUtil; +import com.samskivert.util.Throttle; + +import com.threerings.getdown.data.Application.UpdateInterface; +import com.threerings.getdown.util.MessageUtil; +import com.threerings.getdown.util.Rectangle; +import com.threerings.getdown.util.StringUtil; + +import static com.threerings.getdown.Log.log; + +/** + * Displays download and patching status. + */ +public final class StatusPanel extends JComponent + implements ImageObserver +{ + public StatusPanel (ResourceBundle msgs) + { + _msgs = msgs; + + // Add a bit of "throbbing" to the display by updating the number of dots displayed after + // our status. This lets users know things are still working. + _timer = new Timer(1000, + new ActionListener() { + public void actionPerformed (ActionEvent event) { + if (_status != null && !_displayError) { + _statusDots = (_statusDots % 3) + 1; // 1, 2, 3, 1, 2, 3, etc. + updateStatusLabel(); + } + } + }); + } + + public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg) + { + _ifc = ifc; + _bg = bg; + Image img = _bg.getImage(_progress); + int width = img == null ? -1 : img.getWidth(this); + int height = img == null ? -1 : img.getHeight(this); + if (width == -1 || height == -1) { + Rectangle bounds = ifc.progress.union(ifc.status); + // assume the x inset defines the frame padding; add it on the left, right, and bottom + _psize = new Dimension(bounds.x + bounds.width + bounds.x, + bounds.y + bounds.height + bounds.x); + } else { + _psize = new Dimension(width, height); + } + _barimg = barimg; + invalidate(); + } + + @Override + public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height) + { + boolean updated = false; + if ((infoflags & WIDTH) != 0) { + _psize.width = width; + updated = true; + } + if ((infoflags & HEIGHT) != 0) { + _psize.height = height; + updated = true; + } + if (updated) { + invalidate(); + setSize(_psize); + getParent().setSize(_psize); + } + return (infoflags & ALLBITS) == 0; + } + + /** + * Adjusts the progress display to the specified percentage. + */ + public void setProgress (int percent, long remaining) + { + boolean needsRepaint = false; + + // maybe update the progress label + if (_progress != percent) { + _progress = percent; + if (!_ifc.hideProgressText) { + String msg = MessageFormat.format(get("m.complete"), percent); + _newplab = createLabel(msg, new Color(_ifc.progressText, true)); + } + needsRepaint = true; + } + + // maybe update the remaining label + if (remaining > 1) { + // skip this estimate if it's been less than a second since our last one came in + if (!_rthrottle.throttleOp()) { + _remain[_ridx++%_remain.length] = remaining; + } + + // smooth the remaining time by taking the trailing average of the last four values + remaining = 0; + int values = Math.min(_ridx, _remain.length); + for (int ii = 0; ii < values; ii++) { + remaining += _remain[ii]; + } + remaining /= values; + + if (!_ifc.hideProgressText) { + // now compute our display value + int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60); + String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds; + String msg = MessageFormat.format(get("m.remain"), remstr); + _newrlab = createLabel(msg, new Color(_ifc.statusText, true)); + } + needsRepaint = true; + + } else if (_rlabel != null || _newrlab != null) { + _rthrottle = new Throttle(1, 1000); + _ridx = 0; + _newrlab = _rlabel = null; + needsRepaint = true; + } + + if (needsRepaint) { + repaint(); + } + } + + /** + * Displays the specified status string. + */ + public void setStatus (String status, boolean displayError) + { + _status = xlate(status); + _displayError = displayError; + updateStatusLabel(); + } + + /** + * Stop the throbbing. + */ + public void stopThrob () + { + _timer.stop(); + _statusDots = 3; + updateStatusLabel(); + } + + @Override + public void addNotify () + { + super.addNotify(); + _timer.start(); + } + + @Override + public void removeNotify () + { + _timer.stop(); + super.removeNotify(); + } + + // documentation inherited + @Override + public void paintComponent (Graphics g) + { + super.paintComponent(g); + Graphics2D gfx = (Graphics2D)g; + + // attempt to draw a background image... + Image img; + if (_displayError) { + img = _bg.getErrorImage(); + } else { + img = _bg.getImage(_progress); + } + if (img != null) { + gfx.drawImage(img, 0, 0, this); + } + + Object oalias = SwingUtil.activateAntiAliasing(gfx); + + // if we have new labels; lay them out + if (_newlab != null) { + _newlab.layout(gfx); + _label = _newlab; + _newlab = null; + } + if (_newplab != null) { + _newplab.layout(gfx); + _plabel = _newplab; + _newplab = null; + } + if (_newrlab != null) { + _newrlab.layout(gfx); + _rlabel = _newrlab; + _newrlab = null; + } + + if (_barimg != null) { + gfx.setClip(_ifc.progress.x, _ifc.progress.y, + _progress * _ifc.progress.width / 100, + _ifc.progress.height); + gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null); + gfx.setClip(null); + } else { + gfx.setColor(new Color(_ifc.progressBar, true)); + gfx.fillRect(_ifc.progress.x, _ifc.progress.y, + _progress * _ifc.progress.width / 100, + _ifc.progress.height); + } + + if (_plabel != null) { + int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2; + int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2; + _plabel.render(gfx, _ifc.progress.x + xmarg, _ifc.progress.y + ymarg); + } + + if (_label != null) { + _label.render(gfx, _ifc.status.x, getStatusY(_label)); + } + + if (_rlabel != null) { + // put the remaining label at the end of the status area. This could be dangerous + // but I think the only time we would display it is with small statuses. + int x = _ifc.status.x + _ifc.status.width - _rlabel.getSize().width; + _rlabel.render(gfx, x, getStatusY(_rlabel)); + } + + SwingUtil.restoreAntiAliasing(gfx, oalias); + } + + // documentation inherited + @Override + public Dimension getPreferredSize () + { + return _psize; + } + + /** + * Update the status label. + */ + protected void updateStatusLabel () + { + String status = _status; + if (!_displayError) { + for (int ii = 0; ii < _statusDots; ii++) { + status += " ."; + } + } + _newlab = createLabel(status, new Color(_ifc.statusText, true)); + // set the width of the label to the width specified + int width = _ifc.status.width; + if (width == 0) { + // unless we had trouble reading that width, in which case use the entire window + width = getWidth(); + } + // but the window itself might not be initialized and have a width of 0 + if (width > 0) { + _newlab.setTargetWidth(width); + } + repaint(); + } + + /** + * Get the y coordinate of a label in the status area. + */ + protected int getStatusY (Label label) + { + // if the status region is higher than the progress region, we + // want to align the label with the bottom of its region + // rather than the top + if (_ifc.status.y > _ifc.progress.y) { + return _ifc.status.y; + } + return _ifc.status.y + (_ifc.status.height - label.getSize().height); + } + + /** + * Create a label, taking care of adding the shadow if needed. + */ + protected Label createLabel (String text, Color color) + { + Label label = new Label(text, color, FONT); + if (_ifc.textShadow != 0) { + label.setAlternateColor(new Color(_ifc.textShadow, true)); + label.setStyle(LabelStyleConstants.SHADOW); + } + return label; + } + + /** Used by {@link #setStatus}. */ + protected String xlate (String compoundKey) + { + // to be more efficient about creating unnecessary objects, we + // do some checking before splitting + int tidx = compoundKey.indexOf('|'); + if (tidx == -1) { + return get(compoundKey); + + } else { + String key = compoundKey.substring(0, tidx); + String argstr = compoundKey.substring(tidx+1); + String[] args = argstr.split("\\|"); + // unescape and translate the arguments + for (int i = 0; i < args.length; i++) { + // if the argument is tainted, do no further translation + // (it might contain |s or other fun stuff) + if (MessageUtil.isTainted(args[i])) { + args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i])); + } else { + args[i] = xlate(MessageUtil.unescape(args[i])); + } + } + return get(key, args); + } + } + + /** Used by {@link #setStatus}. */ + protected String get (String key, String[] args) + { + String msg = get(key); + if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args); + return key + String.valueOf(Arrays.asList(args)); + } + + /** Used by {@link #setStatus}, and {@link #setProgress}. */ + protected String get (String key) + { + // if we have no _msgs that means we're probably recovering from a + // failure to load the translation messages in the first place, so + // just give them their key back because it's probably an english + // string; whee! + if (_msgs == null) { + return key; + } + + // if this string is tainted, we don't translate it, instead we + // simply remove the taint character and return it to the caller + if (MessageUtil.isTainted(key)) { + return MessageUtil.untaint(key); + } + try { + return _msgs.getString(key); + } catch (MissingResourceException mre) { + log.warning("Missing translation message '" + key + "'."); + return key; + } + } + + protected Image _barimg; + protected RotatingBackgrounds _bg; + protected Dimension _psize; + + protected ResourceBundle _msgs; + + protected int _progress = -1; + protected String _status; + protected int _statusDots = 1; + protected boolean _displayError; + protected Label _label, _newlab; + protected Label _plabel, _newplab; + protected Label _rlabel, _newrlab; + + protected UpdateInterface _ifc; + protected Timer _timer; + + protected long[] _remain = new long[4]; + protected int _ridx; + protected Throttle _rthrottle = new Throttle(1, 1000L); + + protected static final Font FONT = new Font("SansSerif", Font.BOLD, 12); +} diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties new file mode 100644 index 0000000..19b2999 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages.properties @@ -0,0 +1,110 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Abort installation? +m.abort_confirm = Are you sure you want to stop installation? \ + You can resume at a later time by running the application again. +m.abort_ok = Quit +m.abort_cancel = Continue installation + +m.detecting_proxy = Trying to auto-detect proxy settings + +m.configure_proxy = We were unable to connect to the application server to download data. \ +

Please make sure that no virus scanner or firewall is blocking network communicaton with \ + the server. \ +

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. + +m.proxy_extra = If you are sure that you don't use a proxy then \ + perhaps there is a temporary Internet outage that is preventing us from \ + communicating with the servers. In this case, you can cancel and try \ + installing again later. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = Cancel + +m.downloading_java = Downloading Java Virtual Machine +m.unpacking_java = Unpacking Java Virtual Machine + +m.resolving = Resolving downloads +m.downloading = Downloading data +m.failure = Download failed: {0} + +m.checking = Checking for update +m.validating = Validating +m.patching = Patching +m.launching = Launching + +m.patch_notes = Patch Notes +m.play_again = Play Again + +m.complete = {0}% complete +m.remain = {0} remaining + +m.updating_metadata = Downloading control files + +m.init_failed = Our configuration file is missing or corrupt. Attempting \ + to download a new copy... + +m.java_download_failed = We were unable to automatically download the \ + necessary version of Java for your computer.\n\n\ + Please go to www.java.com and download the latest version of \ + Java, then try running the application again. + +m.java_unpack_failed = We were unable to unpack an updated version of \ + Java. Please make sure you have at least 100 MB of free space on your \ + harddrive and try running the application again.\n\n\ + If that does not solve the problem, go to www.java.com and download and \ + install the latest version of Java and try again. + +m.unable_to_repair = We were unable to download the necessary files after \ + five attempts. You can try running the application again, but if it \ + fails you may need to uninstall and reinstall. + +m.unknown_error = The application has failed to launch due to some strange \ + error from which we could not recover. Please visit\n{0} for information on \ + how to recover. +m.init_error = The application has failed to launch due to the following \ + error:\n{0}\n\nPlease visit\n{1} for \ + information on how to handle such problems. + +m.readonly_error = The directory in which this application is installed: \ + \n{0}\nis read-only. Please install the application into a directory where \ + you have write access. + +m.missing_resource = The application has failed to launch due to a missing \ + resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \ + problems. + +m.insufficient_permissions_error = You did not accept this application's \ + digital signature. If you want to run the application, you will need to accept \ + its digital signature.\n\nTo do so, you will need to quit your web browser, \ + restart it, and return to this web page to relaunch the application. When the \ + security dialog is shown, click the button to accept the digital signature \ + and grant this application the privileges it needs to run. + +m.corrupt_digest_signature_error = We couldn't verify the application's digital \ + signature.\nPlease check that you are launching the application from\nthe \ + correct website. + +m.default_install_error = the support section of the website + +m.another_getdown_running = Multiple instances of this application's \ + installer are running. This one will stop and let another complete. + +m.applet_stopped = Getdown's applet was told to stop working. + +# application/digest errors +m.missing_appbase = The configuration file is missing the 'appbase'. +m.invalid_version = The configuration file specifies an invalid version. +m.invalid_appbase = The configuration file specifies an invalid 'appbase'. +m.missing_class = The configuration file is missing the application class. +m.missing_code = The configuration file specifies no code resources. +m.invalid_digest_file = The digest file is invalid. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties new file mode 100644 index 0000000..8e36835 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_de.properties @@ -0,0 +1,116 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Installation abbrechen? +m.abort_confirm = Bist du sicher, dass du die Installation abbrechen \ +m\u00f6chtest? \ + Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \ +ausf\u00fchrst. +m.abort_ok = Beenden +m.abort_cancel = Installation fortsetzen + +m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln + +m.configure_proxy = Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \ +

Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \ + im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \ + mit dem Server blockiert.
\ + Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \ + stehenden Feldern und klicken sie auf OK. + +m.proxy_extra = Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \ + an Ihren Administrator. + +m.proxy_host = Proxy-Adresse +m.proxy_port = Proxy-Port +m.proxy_username = Benutzername +m.proxy_password = Passwort +m.proxy_auth_required = Authentisierung erforderlich +m.proxy_ok = OK +m.proxy_cancel = Abbrechen + +m.downloading_java = Lade Java Virtual Machine herunter +m.unpacking_java = Entpacke Java Virtual Machine + +m.resolving = Bereite Download vor +m.downloading = Lade Daten herunter +m.failure = Download fehlgeschlagen: {0} + +m.checking = Suche nach Updates +m.validating = Validiere Download +m.patching = Patche +m.launching = Starte + +m.patch_notes = Patchnotes + +m.complete = {0}% abgeschlossen +m.remain = {0} \u00fcbrig + +m.updating_metadata = Lade Steuerungsdateien herunter + +m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \ +Versuche, eine neue Kopie herunterzuladen... + +m.java_download_failed = Wir konnten die notwendige Javaversion f\u00fcr deinen \ +Computer nicht automatisch herunterladen. \n\n \ +Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die \ +Anwendung erneut starten. + +m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \ +entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \ +Festplatte frei sind und versuche dann die Anwendung erneut zu \ +starten.\n\n\ \ +Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \ +Javaversion herunterladen und installieren und dann erneut versuchen. + +m.unable_to_repair = Wir konnten die notwendigen Dateien nach 5 Versuchen \ +nicht herunterladen. Du kannst versuchen, die Anwendung erneut zu starten, \ +aber wenn dies erneut fehlschl\u00e4gt, musst du die Anwendung deinstallieren \ +und erneut installieren. + +m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \ +nicht gestartet werden. Bitte auf \n{0} weiterlesen. + +m.init_error = Die Anwendung konnte wegen folgendem Fehler nicht gestartet \ +werden:\n{0}\n\n Bitte auf \n{1} weiterlesen, um zu erfahren, wie bei \ +solchen Problemen vorzugehen ist. + +m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \ + \n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \ +Schreibzugriff installieren. + +m.missing_resource = Die Anwendung konnte nicht gestartet werden, da die \ +folgende Quelle nicht gefunden wurde:\n{0}\n\n\ Bitte auf \n{1} \ +weiterlesen, um zu erfahren, wie bei solchen Problemen vorzugehen ist. + +m.insufficient_permissions_error = Du hast die digitale Signatur dieser \ +Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, \ +musst du ihre digitale Signatur akzeptieren. \n\Um das zu tun, musst du \ +deinen Browser beenden, neu starten und erneut die Anwendung von dieser \ +Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die \ +digitale Signatur akzeptieren, um der Anwendung die n\u00f6tigen Rechte zu \ +geben, die sie braucht, um zu laufen. + +m.corrupt_digest_signature_error = Wir konnten die digitale Signatur \ +dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung \ +von der richtigen Webseite aus startest. + +m.default_install_error = der Support-Webseite + +m.another_getdown_running = Diese Installationsanwendung l\u00e4uft in mehreren \ +Instanzen. Diese Instanz wird sich beenden und eine andere Instanz den \ +Vorgang erledigen lassen. + +m.applet_stopped = Die Anwendung wurde beendet. + + +# application/digest errors +m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'. +m.invalid_version = In der Konfigurationsdatei steht die falsche Version. +m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'. +m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse. +m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen. +m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig. + diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties new file mode 100644 index 0000000..609b025 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_es.properties @@ -0,0 +1,115 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u00bfCancelar la instalaci\u00f3n? +m.abort_confirm = \u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \ + Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n. +m.abort_ok = Cancelar +m.abort_cancel = Continuar la instalaci\u00f3n + +m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy + +m.configure_proxy = No ha sido posible conectar con nuestros servidores para \ + descargar los datos del juego. \ +

  • Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \ + de bloquear javaw.exe no podemos descargar el juego. Necesitar\u00e1s \ + permitir que javaw.exe tenga acceso al Internet. Puedes intentar \ + correr el juego de nuevo, pero es posible que debas dar permisos a javaw.exe en la \ + configuraci\u00f3n de tu cortafuegos ( Inicio -> Panel de control -> Firewall de Windows ).
\ +

Es posible que tu computadora tenga acceso al Internet por medio de un proxy por lo que \ + no ha sido posible detectar autom\u00e1ticamente tu configuraci\u00f3n. Si conoces tu \ + configuraci\u00f3n proxy, puedes anotarla abajo. + +m.proxy_extra = Si est\u00e1s seguro de que no tienes un proxy entonces \ + tal vez exista un falla temporal en el Internet que est\u00e1 evitando que podamos \ + comunicarnos con los servidores. En este caso, puedes cancelar e intentar \ + instalarla de nuevo m\u00e1s tarde. + +m.proxy_host = IP proxy +m.proxy_port = Puerto proxy +m.proxy_username = Nombre de usuario +m.proxy_password = Contrase\u00f1a +m.proxy_auth_required = Autenticacion requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Descargando Java Virtual Machine +m.unpacking_java = Desempacando Java Virtual Machine + +m.resolving = Resolviendo descarga +m.downloading = Descargando datos +m.failure = Descarga fallida: {0} + +m.checking = Buscando actualizaciones +m.validating = Validando +m.patching = Parchando +m.launching = Lanzando + +m.patch_notes = Notas del parche + +m.complete = {0}% completado +m.remain = {0} restante + +m.updating_metadata = Descargando los archivos de control + +m.init_failed = Un archivo de configuraci\u00f3n est\u00e1 faltante o est\u00e1 corrupto. Intentando \ + descargar una nueva copia... + +m.java_download_failed = No ha sido posible descargar autom\u00e1ticamente la \ + versi\u00f3n de Java necesaria para tu computadora.\n\n\ + Por favor ve a www.java.com y descarga la \u00faltima versi\u00f3n de \ + Java, despu\u00e9s intenta correr de nuevo la aplicaci\u00f3n. + +m.java_unpack_failed = No ha sido posible desempacar una versi\u00f3n actualizada de \ + Java. Por favor aseg\u00farate de tener al menos 100 MB de espacio libre en tu \ + disco duro e intenta correr de nuevo la aplicaci\u00f3n.\n\n\ + Si eso no soluciona el problema, ve a www.java.com y descarga e \ + instala la \u00faltima versi\u00f3n de Java e intenta de nuevo. + +m.unable_to_repair = No ha sido posible descargar los archivos necesarios despu\u00e9s de \ + cinco intentos. Puedes intentar correr de nuevo la aplicaci\u00f3n, pero si falla \ + de nuevo podr\u00edas necesitar desinstalar y reinstalar. + +m.unknown_error = La aplicaci\u00f3n no ha podido iniciar debido a un extra\u00f1o \ + error del que no se pudo recobrar. Por favor visita\n{0} para ver informaci\u00f3n acerca \ + de como recuperarla. +m.init_error = La aplicaci\u00f3n no ha podido iniciar debido al siguiente \ + error:\n{0}\n\nPor favor visita\n{1} para \ + ver informaci\u00f3n acerca de como manejar ese tipo de problemas. + +m.readonly_error = El directorio en el que esta aplicaci\u00f3n est\u00e1 instalada: \ + \n{0}\nes solo lectura. Por favor instala la aplicaci\u00f3n en un directorio en el cual \ + tengas acceso de escritura. + +m.missing_resource = La aplicaci\u00f3n no ha podido iniciar debido a un recurso \ + faltante:\n{0}\n\nPor favor visita\n{1} para informaci\u00f3n acerca de como solucionar \ + estos problemas. + +m.insufficient_permissions_error = No aceptaste la firma digital de \ + esta aplicaci\u00f3n. Si quieres correr la aplicaci\u00f3n, necesitas aceptar \ + su firma digital.\n\nPara hacerlo, necesitas cerrar tu navegador, \ + reiniciarlo, y regresar a esta p\u00e1gina web para reiniciar la aplicaci\u00f3n. Cuando se muestre \ + el di\u00e1logo de seguridad, haz clic en el bot\u00f3n para aceptar la firmar digital \ + y otorgar a esta aplicaci\u00f3n los privilegios que necesita para correr. + +m.corrupt_digest_signature_error = No pudimos verificar la firma digital \ + de la aplicaci\u00f3n.\nPor favor revisa que est\u00e9s lanzando la aplicaci\u00f3n desde\nel \ + sitio web correcto. + +m.default_install_error = la secci\u00f3n de asistencia de este sitio web + +m.another_getdown_running = Est\u00e1n corriendo m\u00faltiples instancias de \ + este instalador. Este se detendr\u00e1 para permitir que otra contin\u00fae. + +m.applet_stopped = Se le dijo al applet de Getdown que dejara de trabajar. + +# application/digest errors +m.missing_appbase = Al archivo de configuraci\u00f3n le falta el 'appbase'. +m.invalid_version = El archivo de configuraci\u00f3n especifica una versi\u00f3n no v\u00e1lida. +m.invalid_appbase = El archivo de configuraci\u00f3n especifica un 'appbase' no v\u00e1lido. +m.missing_class = Al archivo de configuraci\u00f3n le falta la clase de aplicaci\u00f3n. +m.missing_code = El archivo de configuraci\u00f3n especifica que no hay recursos de c\u00f3digo. +m.invalid_digest_file = El archivo digest no es v\u00e1lido. + diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties new file mode 100644 index 0000000..3666204 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_fr.properties @@ -0,0 +1,111 @@ +# +# $Id: messages.properties 485 2012-03-08 22:05:30Z ray.j.greenwell $ +# +# Getdown translation messages + +m.abort_title = Annuler l'installation? +m.abort_confirm =\u00cates-vous s\u00fbr de vouloir annuler l'installation? \ + Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau. +m.abort_ok = Quitter +m.abort_cancel = Continuer l'installation + +m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy + +m.configure_proxy =Connexion au serveur impossible. \ +

  • Veuillez v\u00e9rifier que javaw.exe n'est bloqu\u00e9 \ + par aucun pare-feu ou antivirus. \ + Vous pouvez vous rendre sur la configuration du pare-feu windows via \ + (D\u00e9marrer -> Panneau de Configuration -> Pare-feu Windows ).
\ +

Il est \u00e9galement possible que vous soyez derri\u00e8re un proxy que l'application \ + est incapable de d\u00e9tecter automatiquement. \ + Si tel est le cas, veuillez saisir les r\u00e9glages proxy ci-dessous. + +m.proxy_extra =Si vous \u00eates certain de ne pas utiliser de proxy, il est \ + possible qu'une interruption temporaire de la connexion internet emp\u00fbche la \ + communication avec les serveurs. Dans ce cas, vous pouvez relancer \ + l'installation ult\u00e9rieurement. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Nom d'utilisateur +m.proxy_password = Mot de passe +m.proxy_auth_required = Identification requise +m.proxy_ok = OK +m.proxy_cancel = Annuler + +m.downloading_java = T\u00e9l\u00e9chargement en cours de la Machine Virtuelle Java +m.unpacking_java = D\u00e9compression en cours de la Machine Virtuelle Java + +m.resolving = R\u00e9solution des t\u00e9l\u00e9chargements en cours +m.downloading = T\u00e9l\u00e9chargement des donn\u00e9es en cours +m.failure = \u00c9chec du t\u00e9l\u00e9chargement: {0} + +m.checking = V\u00e9rification de la mise-\u00e0-jour en cours +m.validating = Validation en cours +m.patching = Modification en cours +m.launching = Lancement en cours + +m.patch_notes = Notes de mise-\u00e0-jour + +m.complete = Complet \u00e0 {0}% +m.remain = {0} restant + +m.updating_metadata = T\u00e9l\u00e9chargement des fichiers de contr\u00f4les en cours + +m.init_failed = Notre fichier de configuration est perdu ou corrompu. T\u00e9l\u00e9chargement \ + d'une nouvelle copie en cours ... + +m.java_download_failed = Impossible de t\u00e9l\u00e9charger automatiquement la \ + version de Java n\u00e9cessaire.\n\n\ + Veuillez vous rendre sur www.java.com et t\u00e9l\u00e9charger et installer la version \ + la plus r\u00e9cente de Java, avant d'ex\u00e9cuter l'application \u00e0 nouveau. + +m.java_unpack_failed = Impossible de d\u00e9compresser la version de \ + Java n\u00e9cessaire. Veuillez v\u00e9rifier que vous avez au moins 100 MB d'espace libre \ + sur votre disque dur puis tenter d'ex\u00e9cuter l'application \u00e0 nouveau.\n\n\ + Si le probl\u00e8me persiste, rendez vous www.java.com et t\u00e9l\u00e9chargez et \ + installez la version plus r\u00e9cente de Java puis essayez de nouveau. + +m.unable_to_repair = Impossible de t\u00e9l\u00e9charger les fichiers n\u00e9cessaires apr\u00e8s \ + cinq tentatives. Vous pouvez tenter d'ex\u00e9cuter l'application \u00e0 nouveau, mais il est \ + possible qu'une d\u00e9sinstallation / r\u00e9installation soit n\u00e9cessaire. + +m.unknown_error = Une erreur inconnue a fait \u00e9chouer le lancement de l'application. \ + Veuillez visiter\n{0} pour plus d'informations. +m.init_error = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause de l'erreur \ + suivante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.readonly_error = Le r\u00e9pertoire d'installation de cette application: \ + \n{0}\nest en lecture seule. Veuillez installer l'application dans un r\u00e9pertoire avec \ + un acc\u00e8s en \u00e9criture. + +m.missing_resource = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause d'une \ + ressource manquante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.insufficient_permissions_error = Vous n'avez pas accepter la signature \ + num\u00e9rique de cette application. Si vous souhaitez ex\u00e9cuter cette application, vous \ + devez accepter sa signature num\u00e9rique.\n\nAfin de le faire, vous devez quitter votre \ + navigateur, le red\u00e9marrer, retourner \u00e0 cette page puis relancer l'application. \ + Une fois la bo\u00eete de dialogue de s\u00e9curit\u00e9 affich\u00e9e, cliquez sur le bouton \ + pour accepter la signature num\u00e9rique et accorder les permissions n\u00e9cessaires au bon \ + fonctionnement de l'application. + +m.corrupt_digest_signature_error = Nous ne pouvons pas v\u00e9rifier la signature num\u00e9rique \ + de l'application.\nVeuillez v\u00e9rifier que vous lancez l'application \ndepuis \ + la bonne adresse internet. + +m.default_install_error = la section de support du site + +m.another_getdown_running = Plusieurs instances d'installation de cette \ + application sont d\u00e9j\u00e0 en cours d'ex\u00e9cution. Cette instance va s'arr\u00eater \ + afin de permettre aux autres d'aboutir. + +m.applet_stopped = L'appelet Getdown a \u00e9t\u00e9 stopp\u00e9e. + +# application/digest errors +m.missing_appbase = Le fichier de configuration ne contient pas 'appbase'. +m.invalid_version = Le fichier de configuration sp\u00e9cifie une version invalide. +m.invalid_appbase = Le fichier de configuration sp\u00e9cifie un 'appbase' invalide. +m.missing_class = Le fichier de configuration ne contient pas la classe de l'application. +m.missing_code = Le fichier de configuration ne sp\u00e9cifie aucune ressource de code. +m.invalid_digest_file = Le fichier digest est invalide. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties new file mode 100644 index 0000000..33b3260 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_it.properties @@ -0,0 +1,114 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Annullare l'installazione? +m.abort_confirm = Sei sicuro di voler annullare l'installazione? \ + Potrai riprenderla in seguito, riavviando nuovamente l'applicazione. +m.abort_ok = Chiudi +m.abort_cancel = Continua l'installazione + +m.detecting_proxy = Provo a recuperare le configurazioni del proxy + +m.configure_proxy = Impossibile collegarsi al server per \ + recuperare i dati. \ +

  • Se il Firewall di Windows o Norton Internet Security bloccano \ + javaw.exe non si possono scaricare i dati. Devi \ + permettere a javaw.exe di accedere a internet. Puoi provare \ + di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \ + del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).
\ +

Il tuo computer potrebbe accedere a internet attraverso un proxy e \ + questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \ + tue impostazioni del proxy, puoi inserirle di seguito. + +m.proxy_extra = Se sei sicuro di non usare proxy \ + potrebbe essere un problema di internet o di collegamento con il server. \ + In questo caso puoi annullare e ripetere l'installazione più tardi. + +m.proxy_host = IP Proxy +m.proxy_port = Porta Proxy +m.proxy_username = Nome utente +m.proxy_password = Parola d'ordine +m.proxy_auth_required = Autenticazione richiesta +m.proxy_ok = OK +m.proxy_cancel = Annulla + +m.downloading_java = Scaricando la Java Virtual Machine +m.unpacking_java = Scompattando la Java Virtual Machine + +m.resolving = Recuperando i file da scaricare +m.downloading = Download dei dati +m.failure = Download fallito: {0} + +m.checking = Sto controllando gli aggiornamenti +m.validating = Validazione +m.patching = Applico le patch +m.launching = Avvio + +m.patch_notes = Note delle Patch +m.play_again = Avvia Nuovamente + +m.complete = {0}% completato +m.remain = {0} rimasto + +m.updating_metadata = Scarico i file di controllo + +m.init_failed = La configurazione è corrotta o mancante. Provo a \ + scaricarne una nuova copia... + +m.java_download_failed = Impossibile scaricare la versione corretta \ + di Java per il tuo computer.\n\n\ + Visita www.java.com e scarica l'ultima versione di \ + Java, poi lancia di nuovo l'applicazione. + +m.java_unpack_failed = Impossibile scompattare l'aggiornamento di \ + Java. Verifica di avere almeno 100 MB di spazio libero nel tuo \ + hard disk e prova a rilanciare l'applicazione.\n\n\ + Se l'errore persiste, vistia www.java.com, scarica e \ + installa l'ultima versione di Java e riprova. + +m.unable_to_repair = Impossibile scaricare i file necessari dopo 5 \ + tentativi. Puoi provare a rilanciare l'applicazione, ma se fallisce \ + di nuovo potresti dover reinstallarla. + +m.unknown_error = L'applicazione non è stata avviata a causa di uno strano \ + errore che non conosco. Visita\n{0} per avere informazioni \ + in merito. +m.init_error = L'applicazione non è stata avviata a causa del seguente \ + errore:\n{0}\n\nVistita\n{1} per avere \ + informazioni su come risolvere il problema. + +m.readonly_error = La directory dove l'applicazione è installata: \ + \n{0}\nè in sola lettura. Installa l'applicazione dove hai i diritti \ + di scrittura. + +m.missing_resource = L'applicazione non è stata avviata a causa di mancanza \ + di risorse:\n{0}\n\nVisita\n{1} per avere informazioni su come risolvere \ + questi problemi. + +m.insufficient_permissions_error = Non hai accettato la \ + firma digitale. Se vuoi eseguire l'applicazione devi accettare la \ + firma digitale.\n\nPer farlo, riavvia il tuo browser \ + e ritorna in questa pagina per rilanciare l'applicazione. Quando l'avviso \ + di sicurezza viene mostrato, clicca per accettare la firma digitale \ + ed eseguire l'applicazione con i privilegi necessari. + +m.corrupt_digest_signature_error = Impossibile verificare la firma digitale dell'applicazione \ + .\nControlla di aver lanciato l'applicazione dal\n\ + sito web corretto. + +m.default_install_error = la sezione di supporto del sito + +m.another_getdown_running = E' già in esecuzione un'istanza del programma. \ + Questa verrà chiusa. + +m.applet_stopped = L'applet di Getdown è stata interrotta. + +# application/digest errors +m.missing_appbase = Il tag "appbase" è mancante. +m.invalid_version = Il file di configurazione non contiene una versione valida (tag "version"). +m.invalid_appbase = Il tag "appbase" non è valido. +m.missing_class = Il file di configurazione non contiene la classe da eseguire (tag "class"). +m.missing_code = Il file di configurazione non contiene alcuna risorsa (tag "code"). +m.invalid_digest_file = Il file di digest non è valido. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties new file mode 100644 index 0000000..c344c16 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ja.properties @@ -0,0 +1,107 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f +m.abort_confirm = \u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \ + \u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002 +m.abort_ok = \u4e2d\u6b62 +m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c + +m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d + +m.configure_proxy = \u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \ + \u5931\u6557\u3057\u307e\u3057\u305f\u3002 \ +

  • \u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \ + javaw.exe\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 \u8a2d\u5b9a\u3092 \ + javaw.exe\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \ + \u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \ + \u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002
\ +

\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \ + \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002 \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \ + \u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_extra = \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \ + \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \ + \u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \ + \u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP +m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb + +m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d + +m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d +m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a {0} + +m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d +m.validating = \u8a8d\u8a3c\u4e2d +m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d +m.launching = \u5b9f\u884c\u4e2d + +m.complete = {0}\uff05\u5b8c\u4e86 +m.remain = \u3000\u6b8b\u308a{0} + +m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d + +m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026 + +m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \ + \u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \ + www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \ + \u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 + +m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \ + \u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \ + \u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f \ + \n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \ + \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \ + \u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \ + \u3055\u308c\u307e\u3057\u305f\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \ + \u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \ + \u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044 \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \ + \u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \ + \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a + +# application/digest errors +m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002 diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties new file mode 100644 index 0000000..3f8a47f --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_ko.properties @@ -0,0 +1,102 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? +m.abort_confirm = \uC815\uB9D0\uB85C \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \ + \uB098\uC911\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uC5EC \uC124\uCE58\uB97C \uC7AC\uAC1C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. +m.abort_ok = \uC911\uC9C0 +m.abort_cancel = \uACC4\uC18D\uD558\uC5EC \uC124\uCE58 + +m.detecting_proxy = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.configure_proxy = \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\ +

  • \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 javaw.exe\uC774 \uC124\uC815\uC5D0\uC11C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC744 \uACBD\uC6B0, \ + \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + javaw.exe\uAC00 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD560 \uC218 \uC788\uB3C4\uB85D \uC124\uC815\uC744 \uBCC0\uACBD\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \uAC8C\uC784\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uD6C4, \uBC29\uD654\uBCBD \uC124\uC815\uC5D0\uC11C javaw.exe\uB97C \uC0AD\uC81C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + ( \uC2DC\uC791 -> \uC81C\uC5B4\uD310 -> \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD )
\ +

\uCEF4\uD4E8\uD130\uAC00 \uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uD1B5\uD574 \uC778\uD130\uB137\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB2E4\uBA74, \uD504\uB85D\uC2DC \uC124\uC815\uC758 \uC790\uB3D9 \uAD6C\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \ + \uC0AC\uC6A9\uD558\uB294 \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uC54C\uACE0 \uC788\uC744 \uACBD\uC6B0 \uC544\uB798\uC5D0 \uC785\uB825\uD558\uC5EC \uC8FC\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.proxy_host = \uD504\uB85D\uC2DC IP +m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \uCDE8\uC18C + +m.downloading_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.unpacking_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uC555\uCD95\uC744 \uD574\uC81C\uD558\uB294 \uC911 + +m.resolving = \uB2E4\uC6B4\uB85C\uB4DC \uBD84\uC11D \uC911 +m.downloading = \uB370\uC774\uD130 \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.failure = \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: {0} + +m.checking = \uC5C5\uB370\uC774\uD2B8 \uCCB4\uD06C +m.validating = \uC720\uD6A8\uC131 \uAC80\uC0AC \uC911 +m.patching = \uD328\uCE58 \uC911 +m.launching = \uC2E4\uD589 \uC911 + +m.patch_notes = \uD328\uCE58 \uB178\uD2B8 +m.play_again = \uB2E4\uC2DC \uC2E4\uD589 + +m.complete = {0}% \uC644\uB8CC +m.remain = {0} \uB0A8\uC74C + +m.updating_metadata = \uCEE8\uD2B8\uB864 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911 + +m.init_failed = \uC124\uC815 \uD30C\uC77C\uC774 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uBCC0\uD615\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \uC0C8\uB85C\uC6B4 \uBCF5\uC0AC\uBCF8\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911\uC785\uB2C8\uB2E4... + +m.java_download_failed = \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uD544\uC694\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804\uC758 \uC790\uBC14\uB97C \uC790\uB3D9\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\n\ + \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.java_unpack_failed = \uC5C5\uB370\uC774\uD2B8\uB41C \uBC84\uC804\uC758 \uC790\uBC14\uC758 \uC555\uCD95\uC744 \uD480 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + \uD558\uB4DC\uB4DC\uB77C\uC774\uBE0C\uC5D0 \uCD5C\uC18C\uD55C 100MB\uC758 \uC6A9\uB7C9\uC744 \uD655\uBCF4\uD55C \uC774\uD6C4, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.\n\n\ + \uB9CC\uC57D \uBB38\uC81C\uAC00 \uD574\uACB0\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.unable_to_repair = \uB2E4\uC12F\uBC88\uC758 \uC2DC\uB3C4\uC5D0\uB3C4 \uD544\uC694\uD55C \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD574\uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD55C\uB2E4\uBA74, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC81C\uAC70\uD55C \uD6C4, \uB2E4\uC2DC \uC2E4\uD589\uD574\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.unknown_error = \uBCF5\uAD6C\uB420 \uC218 \uC5C6\uB294 \uC624\uB958\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \n{0}\uC5D0 \uB300\uD55C \uBCF5\uAD6C \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.init_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC544\uB798\uC640 \uAC19\uC740 \uC5D0\uB7EC\uB85C \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC5D0\uB7EC:\ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.readonly_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC124\uCE58\uB41C \uB514\uB809\uD1A0\uB9AC: \ + \n{0}\n\uAC00 \uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC77D\uAE30 \uAD8C\uD55C\uC774 \uC2B9\uC778\uB41C \uB809\uD1A0\uB9AC\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC124\uCE58\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.missing_resource = \uB9AC\uC18C\uC2A4\uC758 \uC190\uC2E4\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. : \ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.insufficient_permissions_error = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uAE30 \uC704\uD574\uC11C \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \n\n\uADF8\uB9AC\uACE0 \uB098\uC11C \uC6F9 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uACE0 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC5EC \uC6F9\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC640 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC7AC\uC2DC\uC791\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. \ + \uBCF4\uC548\uC5D0 \uB300\uD55C \uB300\uD654\uC0C1\uC790\uAC00 \uBCF4\uC774\uBA74, \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC5D0 \uB300\uD55C \uD655\uC778 \uBC84\uD2BC\uC744 \uD074\uB9AD\uD558\uACE0, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uAE30 \uC704\uD55C \ + \uAD8C\uD55C\uC744 \uBD80\uC5EC\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.corrupt_digest_signature_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \ + \uC62C\uBC14\uB978 \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uACE0 \uC788\uB294 \uC9C0 \uD655\uC778\uBC14\uB78D\uB2C8\uB2E4. + +m.default_install_error = \uC6F9\uC0AC\uC774\uD2B8\uC758 \uC9C0\uC6D0 \uBA54\uB274(support section)\uB97C \uD655\uC778\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.another_getdown_running = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uC778\uC2A4\uD1A8\uB7EC\uC758 \uB2E4\uC911 \uC778\uC2A4\uD134\uC2A4\uAC00 \uC2E4\uD589\uC911\uC785\uB2C8\uB2E4. \ + \uD558\uB098\uAC00 \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uC911\uB2E8\uB429\uB2C8\uB2E4. + +m.applet_stopped = Getdown \uC560\uD50C\uB9BF \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. + +# application/digest errors +m.missing_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 'appbase' \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.invalid_version = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C \uBC84\uC804\uC774 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.invalid_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C 'appbase'\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.missing_class = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uD074\uB798\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.missing_code = \uC124\uC815 \uD30C\uC77C\uC5D0 \uB9AC\uC18C\uC2A4\uC5D0 \uB300\uD55C \uCF54\uB4DC\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +m.invalid_digest_file = \uB2E4\uC774\uC81C\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties new file mode 100644 index 0000000..47db91c --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_pt.properties @@ -0,0 +1,118 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Cancelar a instala\u00E7\u00E3o? +m.abort_confirm = Tem certeza que deseja cancelar a instala\u00E7\u00E3o? \ + Voc\u00EA pode continuar a instala\u00E7\u00E3o mais tarde, \ + basta executar a aplica\u00E7\u00E3o novamente. +m.abort_ok = Sair +m.abort_cancel = Continuar a instala\u00E7\u00E3o + +m.detecting_proxy = Tentando detectar automaticamente as configura\u00E7\u00F5es de proxy + +m.configure_proxy = N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \ + fazer o download dos dados. \ +

  • Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \ + para bloquear o programa javaw.exe n\u00E3o ser\u00E1 poss\u00EDvel realizar \ + o download. Voc\u00EA ter\u00E1 que permitir que o programa javaw.exe acesse \ + a internet. Voc\u00EA pode tentar executar o programa novamente, mas voc\u00EA precisa \ + remover o programa javaw.exe das configura\u00E7\u00F5es do firewall (Iniciar -> Painel \ + de controle -> Firewall do Windows).
\ +

Seu computador pode estar acessando a internet atrav\u00E9s de um proxy e n\u00E3o foi \ + capaz de detectar automaticamente as configura\u00E7\u00F5es de proxy. \ + Voc\u00EA pode informar esses dados abaixo. + +m.proxy_extra = Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u00E3o pode ser \ + que exista um problema tempor\u00E1rio que est\u00E1 impedindo a comunica\u00E7\u00E3o \ + com os nossos servidores. Neste caso voc\u00EA pode cancelar e tentar instalar novamente \ + mais tarde. + +m.proxy_host = IP do Proxy +m.proxy_port = Porta do Proxy +m.proxy_username = Nome de usu\u00e1rio +m.proxy_password = Senha +m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Fazendo o download da m\u00E1quina virtual Java +m.unpacking_java = Descompactando a m\u00E1quina virtual Java + +m.resolving = Resolvendo downloads +m.downloading = Transferindo dados +m.failure = Download falhou: {0} + +m.checking = Verificando atualiza\u00E7\u00F5es +m.validating = Validando +m.patching = Atualizando +m.launching = Executando + +m.patch_notes = Corrigir notas +m.play_again = Jogar de novo + +m.complete = {0}% completo +m.remain = {0} Permanecer + +m.updating_metadata = Transferindo arquivos de controle + +m.init_failed = Nosso arquivo de configura\u00E7\u00E3o est\u00E1 ausente ou corrompido. Tente \ + baixar uma nova c\u00F3pia... + +m.java_download_failed = N\u00E3o conseguimos baixar automaticamente a\ + vers\u00E3o necess\u00E1ria do Java para o seu computador.\n\n\ + Por favor, acesse www.java.com, baixe e instale a \u00FAltima vers\u00E3o do \ + Java, em seguida, tente executar o aplicativo novamente. + +m.java_unpack_failed = N\u00E3o conseguimos descompactar uma vers\u00E3o atualizada do \ + Java. Por favor, certifique-se de ter pelo menos 100 MB de espa\u00E7o livre em seu \ + disco r\u00EDgido e tente executar o aplicativo novamente. \n\n\ + Se isso n\u00E3o resolver o problema, acesse www.java.com,baixe e \ + instale a \u00FAltima vers\u00E3o do Java e tente novamente. + +m.unable_to_repair = N\u00E3o conseguimos baixar os arquivos necess\u00E1rios depois de \ + cinco tentativas. Voc\u00EA pode tentar executar o aplicativo novamente, mas se ele \ + falhar pode ser necess\u00E1rio desinstalar e reinstalar. + +m.unknown_error = A aplica\u00E7\u00E3o falhou ao iniciar devido a algum erro estranho \ + do qual n\u00E3o conseguimos recuperar. Por favor, visite \n{0} para obter \ + informa\u00E7\u00F5es sobre como recuperar. +m.init_error = A aplica\u00E7\u00E3o falhou ao iniciar devido ao seguinte \ + erro:\n{0}\n\nPor favor visite \n{1} para \ + informa\u00E7\u00F5es sobre como lidar com esse problema. + +m.readonly_error =O diret\u00F3rio no qual este aplicativo est\u00E1 instalado: \ + \n{0}\n \u00E9 somente leitura. Por favor, instale o aplicativo em um diret\u00F3rio onde \ + voc\u00EA tem acesso de grava\u00E7\u00E3o. + +m.missing_resource = A aplica\u00E7\u00E3o falhou ao iniciar devido a uma falta \ + de recurso:\n{0}\n\n Por favor, visite\n{1} para obter informa\u00E7\u00F5es sobre \ + como lidar com tal problema. + +m.insufficient_permissions_error = Voc\u00EA n\u00E3o aceitou a assinatura digital \ + do aplicativo. Se voc\u00EA quiser executar o aplicativo, voc\u00EA ter\u00E1 que aceitar \ + a assinatura digital. \n\nPara fazer isso, voc\u00EA ter\u00E1 que sair do seu navegador, \ + reinici\u00E1-lo, e retornar a esta p\u00E1gina web para executar a aplica\u00E7\u00E3o. \ + Quando o di\u00E1logo de seguran\u00E7a aparecer, clique no bot\u00E3o para aceitar a \ + assinatura digital e conceder a este aplicativo os privil\u00E9gios necess\u00E1rios \ + para executar. + +m.corrupt_digest_signature_error = N\u00E3o conseguimos verificar a assinatura digital \ + do aplicativo.\nPor favor, verifique se voc\u00EA est\u00E1 utilizando o aplicativo \nde um \ + site correto. + +m.default_install_error = a se\u00E7\u00E3o de suporte do site + +m.another_getdown_running = V\u00E1rias inst\u00E2ncias desta aplica\u00E7\u00E3o \ + est\u00E3o em execu\u00E7\u00E3o. Esta ir\u00E1 parar e deixar outra completar suas atividades. + +m.applet_stopped = Foi solicitado ao miniaplicativo GetDow que parasse de trabalhar. + +# application/digest errors +m.missing_appbase = O arquivo de configura\u00E7\u00E3o n\u00E3o possui o 'AppBase'. +m.invalid_version = O arquivo de configura\u00E7\u00E3o especifica uma vers\u00E3o inv\u00E1lida. +m.invalid_appbase = O arquivo de configura\u00E7\u00E3o especifica um 'AppBase' inv\u00E1lido. +m.missing_class = O arquivo de configura\u00E7\u00E3o n\u00E3o possui a classe de aplicativo. +m.missing_code = O arquivo de configura\u00E7\u00E3o n\u00E3o especifica um recurso de c\u00F3digo. +m.invalid_digest_file = O arquivo digest \u00E9 inv\u00E1lido. diff --git a/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties new file mode 100644 index 0000000..2c27543 --- /dev/null +++ b/getdown/src/getdown/launcher/src/main/resources/com/threerings/getdown/messages_zh.properties @@ -0,0 +1,61 @@ +# +# $Id$ +# +# Getdown translation messages + +m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668 + +m.configure_proxy = \u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \ + \u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \ + \u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002 + +m.proxy_extra = \u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \ + \u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002

\ + \u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \ + \u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002 + +m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740 +m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = \u786e\u5b9a +m.proxy_cancel = \u53d6\u6d88 + +m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9 +m.downloading = \u4e0b\u8f7d\u6570\u636e +m.failure = \u4e0b\u8f7d\u5931\u8d25: {0} + +m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9 +m.validating = \u786e\u8ba4 +m.patching = \u5347\u7ea7 +m.launching = \u542f\u52a8 + +m.complete = {0}% \u5b8c\u6210 +m.remain = {0} \u5269\u4f59\u65f6\u95f4 + +m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6 + +m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d... + +m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\ +\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002 + + +m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002 + +m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002 + + +m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002 + +# application/digest errors +m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002 +m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002 +m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002 +m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002 +m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002 +m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002 diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties new file mode 100644 index 0000000..19b2999 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages.properties @@ -0,0 +1,110 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Abort installation? +m.abort_confirm = Are you sure you want to stop installation? \ + You can resume at a later time by running the application again. +m.abort_ok = Quit +m.abort_cancel = Continue installation + +m.detecting_proxy = Trying to auto-detect proxy settings + +m.configure_proxy = We were unable to connect to the application server to download data. \ +

Please make sure that no virus scanner or firewall is blocking network communicaton with \ + the server. \ +

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. + +m.proxy_extra = If you are sure that you don't use a proxy then \ + perhaps there is a temporary Internet outage that is preventing us from \ + communicating with the servers. In this case, you can cancel and try \ + installing again later. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = Cancel + +m.downloading_java = Downloading Java Virtual Machine +m.unpacking_java = Unpacking Java Virtual Machine + +m.resolving = Resolving downloads +m.downloading = Downloading data +m.failure = Download failed: {0} + +m.checking = Checking for update +m.validating = Validating +m.patching = Patching +m.launching = Launching + +m.patch_notes = Patch Notes +m.play_again = Play Again + +m.complete = {0}% complete +m.remain = {0} remaining + +m.updating_metadata = Downloading control files + +m.init_failed = Our configuration file is missing or corrupt. Attempting \ + to download a new copy... + +m.java_download_failed = We were unable to automatically download the \ + necessary version of Java for your computer.\n\n\ + Please go to www.java.com and download the latest version of \ + Java, then try running the application again. + +m.java_unpack_failed = We were unable to unpack an updated version of \ + Java. Please make sure you have at least 100 MB of free space on your \ + harddrive and try running the application again.\n\n\ + If that does not solve the problem, go to www.java.com and download and \ + install the latest version of Java and try again. + +m.unable_to_repair = We were unable to download the necessary files after \ + five attempts. You can try running the application again, but if it \ + fails you may need to uninstall and reinstall. + +m.unknown_error = The application has failed to launch due to some strange \ + error from which we could not recover. Please visit\n{0} for information on \ + how to recover. +m.init_error = The application has failed to launch due to the following \ + error:\n{0}\n\nPlease visit\n{1} for \ + information on how to handle such problems. + +m.readonly_error = The directory in which this application is installed: \ + \n{0}\nis read-only. Please install the application into a directory where \ + you have write access. + +m.missing_resource = The application has failed to launch due to a missing \ + resource:\n{0}\n\nPlease visit\n{1} for information on how to handle such \ + problems. + +m.insufficient_permissions_error = You did not accept this application's \ + digital signature. If you want to run the application, you will need to accept \ + its digital signature.\n\nTo do so, you will need to quit your web browser, \ + restart it, and return to this web page to relaunch the application. When the \ + security dialog is shown, click the button to accept the digital signature \ + and grant this application the privileges it needs to run. + +m.corrupt_digest_signature_error = We couldn't verify the application's digital \ + signature.\nPlease check that you are launching the application from\nthe \ + correct website. + +m.default_install_error = the support section of the website + +m.another_getdown_running = Multiple instances of this application's \ + installer are running. This one will stop and let another complete. + +m.applet_stopped = Getdown's applet was told to stop working. + +# application/digest errors +m.missing_appbase = The configuration file is missing the 'appbase'. +m.invalid_version = The configuration file specifies an invalid version. +m.invalid_appbase = The configuration file specifies an invalid 'appbase'. +m.missing_class = The configuration file is missing the application class. +m.missing_code = The configuration file specifies no code resources. +m.invalid_digest_file = The digest file is invalid. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties new file mode 100644 index 0000000..8e36835 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_de.properties @@ -0,0 +1,116 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Installation abbrechen? +m.abort_confirm = Bist du sicher, dass du die Installation abbrechen \ +m\u00f6chtest? \ + Du kannst sp\u00e4ter fortfahren, indem du die Anwendung erneut \ +ausf\u00fchrst. +m.abort_ok = Beenden +m.abort_cancel = Installation fortsetzen + +m.detecting_proxy = Versuche Proxy-Einstellungen automatisch zu ermitteln + +m.configure_proxy = Es konnte keine Verbindung zum Applikations-Server aufgebaut werden. \ +

Bitte kontrollieren Sie die Proxyeinstellungen und stellen Sie sicher, dass keine lokal oder \ + im Netzwerk betriebene Sicherheitsanwendung (Virenscanner, Firewall, etc.) die Kommunikation \ + mit dem Server blockiert.
\ + Wenn kein Proxy verwendet werden soll, l\u00f6schen Sie bitte alle Eintr\u00e4ge in den unten \ + stehenden Feldern und klicken sie auf OK. + +m.proxy_extra = Sollten Sie keine Proxyeinstellungen gesetzt haben wenden Sie sich bitte \ + an Ihren Administrator. + +m.proxy_host = Proxy-Adresse +m.proxy_port = Proxy-Port +m.proxy_username = Benutzername +m.proxy_password = Passwort +m.proxy_auth_required = Authentisierung erforderlich +m.proxy_ok = OK +m.proxy_cancel = Abbrechen + +m.downloading_java = Lade Java Virtual Machine herunter +m.unpacking_java = Entpacke Java Virtual Machine + +m.resolving = Bereite Download vor +m.downloading = Lade Daten herunter +m.failure = Download fehlgeschlagen: {0} + +m.checking = Suche nach Updates +m.validating = Validiere Download +m.patching = Patche +m.launching = Starte + +m.patch_notes = Patchnotes + +m.complete = {0}% abgeschlossen +m.remain = {0} \u00fcbrig + +m.updating_metadata = Lade Steuerungsdateien herunter + +m.init_failed = Unsere Konfigurationsdatei fehlt oder ist besch\u00e4digt. \ +Versuche, eine neue Kopie herunterzuladen... + +m.java_download_failed = Wir konnten die notwendige Javaversion f\u00fcr deinen \ +Computer nicht automatisch herunterladen. \n\n \ +Bitte auf www.java.com die aktuelle Javaversion herunterladen und dann die \ +Anwendung erneut starten. + +m.java_unpack_failed = Wir konnten die aktualisierte Javaversion nicht \ +entpacken. Bitte stelle sicher, dass wenigstens 100MB Platz auf der \ +Festplatte frei sind und versuche dann die Anwendung erneut zu \ +starten.\n\n\ \ +Falls das das Problem nicht beseitigt, bitte auf www.java.com die aktuelle \ +Javaversion herunterladen und installieren und dann erneut versuchen. + +m.unable_to_repair = Wir konnten die notwendigen Dateien nach 5 Versuchen \ +nicht herunterladen. Du kannst versuchen, die Anwendung erneut zu starten, \ +aber wenn dies erneut fehlschl\u00e4gt, musst du die Anwendung deinstallieren \ +und erneut installieren. + +m.unknown_error = Die Anwendung konnte wegen eines unbekannten Fehlers \ +nicht gestartet werden. Bitte auf \n{0} weiterlesen. + +m.init_error = Die Anwendung konnte wegen folgendem Fehler nicht gestartet \ +werden:\n{0}\n\n Bitte auf \n{1} weiterlesen, um zu erfahren, wie bei \ +solchen Problemen vorzugehen ist. + +m.readonly_error = Das Verzeichnis, in dem die Anwendung installiert ist: \ + \n{0}\nist nicht schreibberechtigt. Bitte in ein Verzeichnis mit \ +Schreibzugriff installieren. + +m.missing_resource = Die Anwendung konnte nicht gestartet werden, da die \ +folgende Quelle nicht gefunden wurde:\n{0}\n\n\ Bitte auf \n{1} \ +weiterlesen, um zu erfahren, wie bei solchen Problemen vorzugehen ist. + +m.insufficient_permissions_error = Du hast die digitale Signatur dieser \ +Anwendung nicht akzeptiert. Falls du diese Anwendung benutzen willst, \ +musst du ihre digitale Signatur akzeptieren. \n\Um das zu tun, musst du \ +deinen Browser beenden, neu starten und erneut die Anwendung von dieser \ +Webseite aus starten. Wenn die Sicherheitsabfrage erscheint, bitte die \ +digitale Signatur akzeptieren, um der Anwendung die n\u00f6tigen Rechte zu \ +geben, die sie braucht, um zu laufen. + +m.corrupt_digest_signature_error = Wir konnten die digitale Signatur \ +dieser Anwendung nicht \u00fcberpr\u00fcfen.\nBitte \u00fcberpr\u00fcfe, ob du die Anwendung \ +von der richtigen Webseite aus startest. + +m.default_install_error = der Support-Webseite + +m.another_getdown_running = Diese Installationsanwendung l\u00e4uft in mehreren \ +Instanzen. Diese Instanz wird sich beenden und eine andere Instanz den \ +Vorgang erledigen lassen. + +m.applet_stopped = Die Anwendung wurde beendet. + + +# application/digest errors +m.missing_appbase = In der Konfigurationsdatei fehlt die 'appbase'. +m.invalid_version = In der Konfigurationsdatei steht die falsche Version. +m.invalid_appbase = In der Konfigurationsdatei steht die falsche 'appbase'. +m.missing_class = In der Konfigurationsdatei fehlt die Anwendungsklasse. +m.missing_code = Die Konfigurationsdatei enth\u00e4lt keine Codequellen. +m.invalid_digest_file = Die Hashwertedatei ist ung\u00fcltig. + diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties new file mode 100644 index 0000000..609b025 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_es.properties @@ -0,0 +1,115 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u00bfCancelar la instalaci\u00f3n? +m.abort_confirm = \u00bfEst\u00e1s seguro de querer cancelar la instalaci\u00f3n? \ + Puedes continuarla despu\u00e9s si corres de nuevo la aplicaci\u00f3n. +m.abort_ok = Cancelar +m.abort_cancel = Continuar la instalaci\u00f3n + +m.detecting_proxy = Detectando autom\u00e1ticamente la configuraci\u00f3n proxy + +m.configure_proxy = No ha sido posible conectar con nuestros servidores para \ + descargar los datos del juego. \ +

  • Si el cortafuegos de Windows o Norton Internet Security tiene instrucciones \ + de bloquear javaw.exe no podemos descargar el juego. Necesitar\u00e1s \ + permitir que javaw.exe tenga acceso al Internet. Puedes intentar \ + correr el juego de nuevo, pero es posible que debas dar permisos a javaw.exe en la \ + configuraci\u00f3n de tu cortafuegos ( Inicio -> Panel de control -> Firewall de Windows ).
\ +

Es posible que tu computadora tenga acceso al Internet por medio de un proxy por lo que \ + no ha sido posible detectar autom\u00e1ticamente tu configuraci\u00f3n. Si conoces tu \ + configuraci\u00f3n proxy, puedes anotarla abajo. + +m.proxy_extra = Si est\u00e1s seguro de que no tienes un proxy entonces \ + tal vez exista un falla temporal en el Internet que est\u00e1 evitando que podamos \ + comunicarnos con los servidores. En este caso, puedes cancelar e intentar \ + instalarla de nuevo m\u00e1s tarde. + +m.proxy_host = IP proxy +m.proxy_port = Puerto proxy +m.proxy_username = Nombre de usuario +m.proxy_password = Contrase\u00f1a +m.proxy_auth_required = Autenticacion requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Descargando Java Virtual Machine +m.unpacking_java = Desempacando Java Virtual Machine + +m.resolving = Resolviendo descarga +m.downloading = Descargando datos +m.failure = Descarga fallida: {0} + +m.checking = Buscando actualizaciones +m.validating = Validando +m.patching = Parchando +m.launching = Lanzando + +m.patch_notes = Notas del parche + +m.complete = {0}% completado +m.remain = {0} restante + +m.updating_metadata = Descargando los archivos de control + +m.init_failed = Un archivo de configuraci\u00f3n est\u00e1 faltante o est\u00e1 corrupto. Intentando \ + descargar una nueva copia... + +m.java_download_failed = No ha sido posible descargar autom\u00e1ticamente la \ + versi\u00f3n de Java necesaria para tu computadora.\n\n\ + Por favor ve a www.java.com y descarga la \u00faltima versi\u00f3n de \ + Java, despu\u00e9s intenta correr de nuevo la aplicaci\u00f3n. + +m.java_unpack_failed = No ha sido posible desempacar una versi\u00f3n actualizada de \ + Java. Por favor aseg\u00farate de tener al menos 100 MB de espacio libre en tu \ + disco duro e intenta correr de nuevo la aplicaci\u00f3n.\n\n\ + Si eso no soluciona el problema, ve a www.java.com y descarga e \ + instala la \u00faltima versi\u00f3n de Java e intenta de nuevo. + +m.unable_to_repair = No ha sido posible descargar los archivos necesarios despu\u00e9s de \ + cinco intentos. Puedes intentar correr de nuevo la aplicaci\u00f3n, pero si falla \ + de nuevo podr\u00edas necesitar desinstalar y reinstalar. + +m.unknown_error = La aplicaci\u00f3n no ha podido iniciar debido a un extra\u00f1o \ + error del que no se pudo recobrar. Por favor visita\n{0} para ver informaci\u00f3n acerca \ + de como recuperarla. +m.init_error = La aplicaci\u00f3n no ha podido iniciar debido al siguiente \ + error:\n{0}\n\nPor favor visita\n{1} para \ + ver informaci\u00f3n acerca de como manejar ese tipo de problemas. + +m.readonly_error = El directorio en el que esta aplicaci\u00f3n est\u00e1 instalada: \ + \n{0}\nes solo lectura. Por favor instala la aplicaci\u00f3n en un directorio en el cual \ + tengas acceso de escritura. + +m.missing_resource = La aplicaci\u00f3n no ha podido iniciar debido a un recurso \ + faltante:\n{0}\n\nPor favor visita\n{1} para informaci\u00f3n acerca de como solucionar \ + estos problemas. + +m.insufficient_permissions_error = No aceptaste la firma digital de \ + esta aplicaci\u00f3n. Si quieres correr la aplicaci\u00f3n, necesitas aceptar \ + su firma digital.\n\nPara hacerlo, necesitas cerrar tu navegador, \ + reiniciarlo, y regresar a esta p\u00e1gina web para reiniciar la aplicaci\u00f3n. Cuando se muestre \ + el di\u00e1logo de seguridad, haz clic en el bot\u00f3n para aceptar la firmar digital \ + y otorgar a esta aplicaci\u00f3n los privilegios que necesita para correr. + +m.corrupt_digest_signature_error = No pudimos verificar la firma digital \ + de la aplicaci\u00f3n.\nPor favor revisa que est\u00e9s lanzando la aplicaci\u00f3n desde\nel \ + sitio web correcto. + +m.default_install_error = la secci\u00f3n de asistencia de este sitio web + +m.another_getdown_running = Est\u00e1n corriendo m\u00faltiples instancias de \ + este instalador. Este se detendr\u00e1 para permitir que otra contin\u00fae. + +m.applet_stopped = Se le dijo al applet de Getdown que dejara de trabajar. + +# application/digest errors +m.missing_appbase = Al archivo de configuraci\u00f3n le falta el 'appbase'. +m.invalid_version = El archivo de configuraci\u00f3n especifica una versi\u00f3n no v\u00e1lida. +m.invalid_appbase = El archivo de configuraci\u00f3n especifica un 'appbase' no v\u00e1lido. +m.missing_class = Al archivo de configuraci\u00f3n le falta la clase de aplicaci\u00f3n. +m.missing_code = El archivo de configuraci\u00f3n especifica que no hay recursos de c\u00f3digo. +m.invalid_digest_file = El archivo digest no es v\u00e1lido. + diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties new file mode 100644 index 0000000..3666204 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_fr.properties @@ -0,0 +1,111 @@ +# +# $Id: messages.properties 485 2012-03-08 22:05:30Z ray.j.greenwell $ +# +# Getdown translation messages + +m.abort_title = Annuler l'installation? +m.abort_confirm =\u00cates-vous s\u00fbr de vouloir annuler l'installation? \ + Vous pourrez reprendre l'installation en ex\u00e9cutant l'application de nouveau. +m.abort_ok = Quitter +m.abort_cancel = Continuer l'installation + +m.detecting_proxy = D\u00e9tection automatique des r\u00e9glages proxy + +m.configure_proxy =Connexion au serveur impossible. \ +

  • Veuillez v\u00e9rifier que javaw.exe n'est bloqu\u00e9 \ + par aucun pare-feu ou antivirus. \ + Vous pouvez vous rendre sur la configuration du pare-feu windows via \ + (D\u00e9marrer -> Panneau de Configuration -> Pare-feu Windows ).
\ +

Il est \u00e9galement possible que vous soyez derri\u00e8re un proxy que l'application \ + est incapable de d\u00e9tecter automatiquement. \ + Si tel est le cas, veuillez saisir les r\u00e9glages proxy ci-dessous. + +m.proxy_extra =Si vous \u00eates certain de ne pas utiliser de proxy, il est \ + possible qu'une interruption temporaire de la connexion internet emp\u00fbche la \ + communication avec les serveurs. Dans ce cas, vous pouvez relancer \ + l'installation ult\u00e9rieurement. + +m.proxy_host = Proxy IP +m.proxy_port = Proxy port +m.proxy_username = Nom d'utilisateur +m.proxy_password = Mot de passe +m.proxy_auth_required = Identification requise +m.proxy_ok = OK +m.proxy_cancel = Annuler + +m.downloading_java = T\u00e9l\u00e9chargement en cours de la Machine Virtuelle Java +m.unpacking_java = D\u00e9compression en cours de la Machine Virtuelle Java + +m.resolving = R\u00e9solution des t\u00e9l\u00e9chargements en cours +m.downloading = T\u00e9l\u00e9chargement des donn\u00e9es en cours +m.failure = \u00c9chec du t\u00e9l\u00e9chargement: {0} + +m.checking = V\u00e9rification de la mise-\u00e0-jour en cours +m.validating = Validation en cours +m.patching = Modification en cours +m.launching = Lancement en cours + +m.patch_notes = Notes de mise-\u00e0-jour + +m.complete = Complet \u00e0 {0}% +m.remain = {0} restant + +m.updating_metadata = T\u00e9l\u00e9chargement des fichiers de contr\u00f4les en cours + +m.init_failed = Notre fichier de configuration est perdu ou corrompu. T\u00e9l\u00e9chargement \ + d'une nouvelle copie en cours ... + +m.java_download_failed = Impossible de t\u00e9l\u00e9charger automatiquement la \ + version de Java n\u00e9cessaire.\n\n\ + Veuillez vous rendre sur www.java.com et t\u00e9l\u00e9charger et installer la version \ + la plus r\u00e9cente de Java, avant d'ex\u00e9cuter l'application \u00e0 nouveau. + +m.java_unpack_failed = Impossible de d\u00e9compresser la version de \ + Java n\u00e9cessaire. Veuillez v\u00e9rifier que vous avez au moins 100 MB d'espace libre \ + sur votre disque dur puis tenter d'ex\u00e9cuter l'application \u00e0 nouveau.\n\n\ + Si le probl\u00e8me persiste, rendez vous www.java.com et t\u00e9l\u00e9chargez et \ + installez la version plus r\u00e9cente de Java puis essayez de nouveau. + +m.unable_to_repair = Impossible de t\u00e9l\u00e9charger les fichiers n\u00e9cessaires apr\u00e8s \ + cinq tentatives. Vous pouvez tenter d'ex\u00e9cuter l'application \u00e0 nouveau, mais il est \ + possible qu'une d\u00e9sinstallation / r\u00e9installation soit n\u00e9cessaire. + +m.unknown_error = Une erreur inconnue a fait \u00e9chouer le lancement de l'application. \ + Veuillez visiter\n{0} pour plus d'informations. +m.init_error = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause de l'erreur \ + suivante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.readonly_error = Le r\u00e9pertoire d'installation de cette application: \ + \n{0}\nest en lecture seule. Veuillez installer l'application dans un r\u00e9pertoire avec \ + un acc\u00e8s en \u00e9criture. + +m.missing_resource = Le lancement de l'application a \u00e9chou\u00e9 \u00e0 cause d'une \ + ressource manquante:\n{0}\n\nVeuillez visiter\n{1} pour plus d'informations. + +m.insufficient_permissions_error = Vous n'avez pas accepter la signature \ + num\u00e9rique de cette application. Si vous souhaitez ex\u00e9cuter cette application, vous \ + devez accepter sa signature num\u00e9rique.\n\nAfin de le faire, vous devez quitter votre \ + navigateur, le red\u00e9marrer, retourner \u00e0 cette page puis relancer l'application. \ + Une fois la bo\u00eete de dialogue de s\u00e9curit\u00e9 affich\u00e9e, cliquez sur le bouton \ + pour accepter la signature num\u00e9rique et accorder les permissions n\u00e9cessaires au bon \ + fonctionnement de l'application. + +m.corrupt_digest_signature_error = Nous ne pouvons pas v\u00e9rifier la signature num\u00e9rique \ + de l'application.\nVeuillez v\u00e9rifier que vous lancez l'application \ndepuis \ + la bonne adresse internet. + +m.default_install_error = la section de support du site + +m.another_getdown_running = Plusieurs instances d'installation de cette \ + application sont d\u00e9j\u00e0 en cours d'ex\u00e9cution. Cette instance va s'arr\u00eater \ + afin de permettre aux autres d'aboutir. + +m.applet_stopped = L'appelet Getdown a \u00e9t\u00e9 stopp\u00e9e. + +# application/digest errors +m.missing_appbase = Le fichier de configuration ne contient pas 'appbase'. +m.invalid_version = Le fichier de configuration sp\u00e9cifie une version invalide. +m.invalid_appbase = Le fichier de configuration sp\u00e9cifie un 'appbase' invalide. +m.missing_class = Le fichier de configuration ne contient pas la classe de l'application. +m.missing_code = Le fichier de configuration ne sp\u00e9cifie aucune ressource de code. +m.invalid_digest_file = Le fichier digest est invalide. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties new file mode 100644 index 0000000..33b3260 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_it.properties @@ -0,0 +1,114 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Annullare l'installazione? +m.abort_confirm = Sei sicuro di voler annullare l'installazione? \ + Potrai riprenderla in seguito, riavviando nuovamente l'applicazione. +m.abort_ok = Chiudi +m.abort_cancel = Continua l'installazione + +m.detecting_proxy = Provo a recuperare le configurazioni del proxy + +m.configure_proxy = Impossibile collegarsi al server per \ + recuperare i dati. \ +

  • Se il Firewall di Windows o Norton Internet Security bloccano \ + javaw.exe non si possono scaricare i dati. Devi \ + permettere a javaw.exe di accedere a internet. Puoi provare \ + di nuovo, ma dovresti abilitare javaw.exe nella tua configurazione \ + del firewall ( Start -> Pannello di Controllo -> Windows Firewall ).
\ +

Il tuo computer potrebbe accedere a internet attraverso un proxy e \ + questo potrebbe non essere stato riconosciuto automaticamente. Se conosci le \ + tue impostazioni del proxy, puoi inserirle di seguito. + +m.proxy_extra = Se sei sicuro di non usare proxy \ + potrebbe essere un problema di internet o di collegamento con il server. \ + In questo caso puoi annullare e ripetere l'installazione più tardi. + +m.proxy_host = IP Proxy +m.proxy_port = Porta Proxy +m.proxy_username = Nome utente +m.proxy_password = Parola d'ordine +m.proxy_auth_required = Autenticazione richiesta +m.proxy_ok = OK +m.proxy_cancel = Annulla + +m.downloading_java = Scaricando la Java Virtual Machine +m.unpacking_java = Scompattando la Java Virtual Machine + +m.resolving = Recuperando i file da scaricare +m.downloading = Download dei dati +m.failure = Download fallito: {0} + +m.checking = Sto controllando gli aggiornamenti +m.validating = Validazione +m.patching = Applico le patch +m.launching = Avvio + +m.patch_notes = Note delle Patch +m.play_again = Avvia Nuovamente + +m.complete = {0}% completato +m.remain = {0} rimasto + +m.updating_metadata = Scarico i file di controllo + +m.init_failed = La configurazione è corrotta o mancante. Provo a \ + scaricarne una nuova copia... + +m.java_download_failed = Impossibile scaricare la versione corretta \ + di Java per il tuo computer.\n\n\ + Visita www.java.com e scarica l'ultima versione di \ + Java, poi lancia di nuovo l'applicazione. + +m.java_unpack_failed = Impossibile scompattare l'aggiornamento di \ + Java. Verifica di avere almeno 100 MB di spazio libero nel tuo \ + hard disk e prova a rilanciare l'applicazione.\n\n\ + Se l'errore persiste, vistia www.java.com, scarica e \ + installa l'ultima versione di Java e riprova. + +m.unable_to_repair = Impossibile scaricare i file necessari dopo 5 \ + tentativi. Puoi provare a rilanciare l'applicazione, ma se fallisce \ + di nuovo potresti dover reinstallarla. + +m.unknown_error = L'applicazione non è stata avviata a causa di uno strano \ + errore che non conosco. Visita\n{0} per avere informazioni \ + in merito. +m.init_error = L'applicazione non è stata avviata a causa del seguente \ + errore:\n{0}\n\nVistita\n{1} per avere \ + informazioni su come risolvere il problema. + +m.readonly_error = La directory dove l'applicazione è installata: \ + \n{0}\nè in sola lettura. Installa l'applicazione dove hai i diritti \ + di scrittura. + +m.missing_resource = L'applicazione non è stata avviata a causa di mancanza \ + di risorse:\n{0}\n\nVisita\n{1} per avere informazioni su come risolvere \ + questi problemi. + +m.insufficient_permissions_error = Non hai accettato la \ + firma digitale. Se vuoi eseguire l'applicazione devi accettare la \ + firma digitale.\n\nPer farlo, riavvia il tuo browser \ + e ritorna in questa pagina per rilanciare l'applicazione. Quando l'avviso \ + di sicurezza viene mostrato, clicca per accettare la firma digitale \ + ed eseguire l'applicazione con i privilegi necessari. + +m.corrupt_digest_signature_error = Impossibile verificare la firma digitale dell'applicazione \ + .\nControlla di aver lanciato l'applicazione dal\n\ + sito web corretto. + +m.default_install_error = la sezione di supporto del sito + +m.another_getdown_running = E' già in esecuzione un'istanza del programma. \ + Questa verrà chiusa. + +m.applet_stopped = L'applet di Getdown è stata interrotta. + +# application/digest errors +m.missing_appbase = Il tag "appbase" è mancante. +m.invalid_version = Il file di configurazione non contiene una versione valida (tag "version"). +m.invalid_appbase = Il tag "appbase" non è valido. +m.missing_class = Il file di configurazione non contiene la classe da eseguire (tag "class"). +m.missing_code = Il file di configurazione non contiene alcuna risorsa (tag "code"). +m.invalid_digest_file = Il file di digest non è valido. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties new file mode 100644 index 0000000..c344c16 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ja.properties @@ -0,0 +1,107 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f +m.abort_confirm = \u672c\u5f53\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u4e2d\u6b62\u3057\u307e\u3059\u304b\uff1f \ + \u5f8c\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u305f\u969b\u306b\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u518d\u958b\u3067\u304d\u307e\u3059\u3002 +m.abort_ok = \u4e2d\u6b62 +m.abort_cancel = \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u306e\u7d9a\u884c + +m.detecting_proxy = \u81ea\u52d5\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u5b9f\u884c\u4e2d + +m.configure_proxy = \u30b5\u30fc\u30d0\u306b\u63a5\u7d9a\u3067\u304d\u306a\u3044\u305f\u3081\u3001\u30b2\u30fc\u30e0\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306b \ + \u5931\u6557\u3057\u307e\u3057\u305f\u3002 \ +

  • \u30a6\u30a3\u30f3\u30c9\u30a6\u30ba\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u307e\u305f\u306f\u30ce\u30fc\u30c8\u30f3\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u304c \ + javaw.exe\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b\u3088\u3046\u8a2d\u5b9a\u3057\u3066\u3042\u308b\u5834\u5408\u306f\u3001\u30b2\u30fc\u30e0\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093\u3002 \u8a2d\u5b9a\u3092 \ + javaw.exe\u7d4c\u7531\u3067\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u308b\u3088\u3046\u306b\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u30b2\u30fc\u30e0\u3092\u518d\u8d77\u52d5 \ + \u3057\u305f\u5f8c\u3001\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\u306e\u8a2d\u5b9a\u304b\u3089javaw.exe \u3092\u524a\u9664 \ + \u3057\u3066\u304f\u3060\u3055\u3044\uff08\u30b9\u30bf\u30fc\u30c8\u2192\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d1\u30cd\u30eb\u2192\u30d5\u30a1\u30a4\u30a2\u30a6\u30a9\u30fc\u30eb\uff09\u3002
\ +

\u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u81ea\u52d5\u691c\u51fa\u304c\u3067\u304d\u307e\u305b\u3093\u3002\u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306f \ + \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u3078\u30a2\u30af\u30bb\u30b9\u3057\u3066\u3044\u307e\u3059\u3002 \u30d7\u30ed\u30ad\u30b7\u8a2d\u5b9a\u306e\u8a73\u7d30\u304c \ + \u308f\u304b\u3063\u3066\u3044\u308b\u5834\u5408\u306f\u3001\u4e0b\u306b\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_extra = \u30d7\u30ed\u30ad\u30b7\u3092\u4f7f\u7528\u3057\u3066\u3044\u306a\u3044\u5834\u5408\u306f\u3001\u4e00\u6642\u7684\u306a \ + \u30a4\u30f3\u30bf\u30fc\u30cd\u30c3\u30c8\u306e\u4e0d\u5177\u5408\u306b\u3088\u308a\u3001\u30b5\u30fc\u30d0\u3068\u4ea4\u4fe1\u3067\u304d\u306a\u3044\u72b6\u614b\u306b\u3042\u308b \ + \u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002 \u305d\u306e\u5834\u5408\u306f\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u3066\u3001 \ + \u5f8c\u307b\u3069\u6539\u3081\u3066\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.proxy_host = \u30d7\u30ed\u30ad\u30b7IP +m.proxy_port = \u30d7\u30ed\u30ad\u30b7\u30dd\u30fc\u30c8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \u30ad\u30e3\u30f3\u30bb\u30eb + +m.downloading_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.unpacking_java = Java\u30d0\u30fc\u30c1\u30e3\u30eb\u30de\u30b7\u30f3\u306e\u89e3\u51cd\u4e2d + +m.resolving = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u306e\u8a2d\u5b9a\u4e2d +m.downloading = \u30c7\u30fc\u30bf\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d +m.failure = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u5931\u6557\uff1a {0} + +m.checking = \u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u306e\u78ba\u8a8d\u4e2d +m.validating = \u8a8d\u8a3c\u4e2d +m.patching = \u4fee\u6b63\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u5b9f\u884c\u4e2d +m.launching = \u5b9f\u884c\u4e2d + +m.complete = {0}\uff05\u5b8c\u4e86 +m.remain = \u3000\u6b8b\u308a{0} + +m.updating_metadata = \u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30d5\u30a1\u30a4\u30eb\u306e\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d + +m.init_failed = \u74b0\u5883\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u306a\u3044\u304b\u3001\u307e\u305f\u306f\u58ca\u308c\u3066\u3044\u307e\u3059\u3002 \u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d\u2026 + +m.java_download_failed = \u304a\u4f7f\u3044\u306e\u30b3\u30f3\u30d4\u30e5\u30fc\u30bf\u30fc\u306b\u3001Java\u30d7\u30ed\u30b0\u30e9\u30e0\u306e\u6700\u65b0 \ + \u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u81ea\u52d5\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\n \ + www.java.com \u304b\u3089\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u624b\u52d5\u3067\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u3001 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.java_unpack_failed = Java\u306e\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30d0\u30fc\u30b8\u30e7\u30f3\u304c\u89e3\u51cd \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30cf\u30fc\u30c9\u30c9\u30e9\u30a4\u30d6\u306e\u30e1\u30e2\u30ea\u304c100MB\u4ee5\u4e0a\u3042\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304b\u3089 \ + \u518d\u5ea6\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n \ + \u554f\u984c\u304c\u89e3\u6c7a\u3057\u306a\u3044\u5834\u5408\u306f\u3001www.java.com \u304b\u3089Java\u306e\u6700\u65b0\u30d0\u30fc\u30b8\u30e7\u30f3\u3092 \ + \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3057\u3066\u304b\u3089\u3001\u518d\u5ea6\u304a\u8a66\u3057\u304f\u3060\u3055\u3044\u3002 + +m.unable_to_repair = 5\u56de\u8a66\u884c\u3057\u307e\u3057\u305f\u304c\u3001\u5fc5\u8981\u306a\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9 \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u5f8c\u307b\u3069\u6539\u3081\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \ + \u518d\u5ea6\u5931\u6557\u3057\u305f\u5834\u5408\u306f\u3001\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u5f8c\u306b\u518d\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.unknown_error = \u539f\u56e0\u4e0d\u660e\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c \ + \u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u89e3\u6c7a\u65b9\u6cd5\u3092\n{0}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 +m.init_error = \u6b21\u306e\u30a8\u30e9\u30fc\u306b\u3088\u308a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067 \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.readonly_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u305f\u30d5\u30a9\u30eb\u30c0\u306f \ + \n{0}\n\u8aad\u307f\u53d6\u308a\u5c02\u7528\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u66f8\u304d\u8fbc\u307f\u304c\u3067\u304d\u308b\u30d5\u30a9\u30eb\u30c0\u306b \ + \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.missing_resource = \u30ea\u30bd\u30fc\u30b9\u4e0d\u660e\u306e\u305f\u3081\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093 \ + \u3067\u3057\u305f\u3002\n{0}\n\n\u5bfe\u51e6\u65b9\u6cd5\u3092\n{1}\u3067\u78ba\u8a8d \ + \u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.insufficient_permissions_error = \u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u62d2\u5426 \ + \u3055\u308c\u307e\u3057\u305f\u3002 \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u5834\u5408\u306f\u3001\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u306e\u627f\u8a8d\u304c \ + \u5fc5\u8981\u3067\u3059\u3002\n\n\u627f\u8a8d\u306b\u306f\u3001\u30d6\u30e9\u30a6\u30b6\u3092\u9589\u3058\u3066\u304b\u3089\u518d\u5ea6\u958b\u304d\u3001 \ + \u672c\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3092\u518d\u8868\u793a\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044 \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e \ + \u8b66\u544a\u304c\u8868\u793a\u3055\u308c\u305f\u6642\u306f\u3001\u5b9f\u884c\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u3092\u627f\u8a8d\u3057\u3001 \ + \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.corrupt_digest_signature_error = \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30c7\u30b8\u30bf\u30eb\u7f72\u540d\u304c\u8a8d\u8a3c \ + \u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\n\u6307\u5b9a\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3057\u3066\u3044\u308b\u304b\n \ + \u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002 + +m.default_install_error = \u30db\u30fc\u30e0\u30da\u30fc\u30b8\u3067\u306e\u30b5\u30dd\u30fc\u30c8\u8868\u793a + +# application/digest errors +m.missing_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eappbase\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.invalid_version = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306f\u7121\u52b9\u306a\u30d0\u30fc\u30b8\u30e7\u30f3\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.invalid_appbase = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u306aappbase\u3092\u6307\u5b9a\u3057\u3066\u3044\u307e\u3059\u3002 +m.missing_class = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304c\u4e0d\u660e\u3067\u3059\u3002 +m.missing_code = \u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u3067\u30b3\u30fc\u30c9\u30ea\u30bd\u30fc\u30b9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002 +m.invalid_digest_file = \u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u304c\u7121\u52b9\u3067\u3059\u3002 diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties new file mode 100644 index 0000000..3f8a47f --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_ko.properties @@ -0,0 +1,102 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? +m.abort_confirm = \uC815\uB9D0\uB85C \uC124\uCE58\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \ + \uB098\uC911\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uC5EC \uC124\uCE58\uB97C \uC7AC\uAC1C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. +m.abort_ok = \uC911\uC9C0 +m.abort_cancel = \uACC4\uC18D\uD558\uC5EC \uC124\uCE58 + +m.detecting_proxy = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.configure_proxy = \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uBC1B\uAE30 \uC704\uD55C \uC11C\uBC84 \uC811\uC18D\uC5D0 \uC2E4\uD328\uD558\uC600\uC2B5\uB2C8\uB2E4.\ +

  • \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD \uB610\uB294 \uB178\uD134 \uC778\uD130\uB137 \uC2DC\uD050\uB9AC\uD2F0\uAC00 javaw.exe\uC774 \uC124\uC815\uC5D0\uC11C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC744 \uACBD\uC6B0, \ + \uAC8C\uC784 \uB370\uC774\uD130\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + javaw.exe\uAC00 \uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD560 \uC218 \uC788\uB3C4\uB85D \uC124\uC815\uC744 \uBCC0\uACBD\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \uAC8C\uC784\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD55C \uD6C4, \uBC29\uD654\uBCBD \uC124\uC815\uC5D0\uC11C javaw.exe\uB97C \uC0AD\uC81C\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + ( \uC2DC\uC791 -> \uC81C\uC5B4\uD310 -> \uC708\uB3C4\uC6B0 \uBC29\uD654\uBCBD )
\ +

\uCEF4\uD4E8\uD130\uAC00 \uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uD1B5\uD574 \uC778\uD130\uB137\uC5D0 \uC5F0\uACB0\uB418\uC5B4 \uC788\uB2E4\uBA74, \uD504\uB85D\uC2DC \uC124\uC815\uC758 \uC790\uB3D9 \uAD6C\uC131\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C, \ + \uC0AC\uC6A9\uD558\uB294 \uD504\uB85D\uC2DC \uC124\uC815\uC744 \uC54C\uACE0 \uC788\uC744 \uACBD\uC6B0 \uC544\uB798\uC5D0 \uC785\uB825\uD558\uC5EC \uC8FC\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.proxy_extra = \uC790\uB3D9 \uD504\uB85D\uC2DC\uB97C \uC124\uC815\uC744 \uC2DC\uB3C4 + +m.proxy_host = \uD504\uB85D\uC2DC IP +m.proxy_port = \uD504\uB85D\uC2DC \uD3EC\uD2B8 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = OK +m.proxy_cancel = \uCDE8\uC18C + +m.downloading_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.unpacking_java = \uC790\uBC14 \uAC00\uC0C1 \uBA38\uC2E0(JVM) \uC555\uCD95\uC744 \uD574\uC81C\uD558\uB294 \uC911 + +m.resolving = \uB2E4\uC6B4\uB85C\uB4DC \uBD84\uC11D \uC911 +m.downloading = \uB370\uC774\uD130 \uB2E4\uC6B4\uB85C\uB4DC \uC911 +m.failure = \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: {0} + +m.checking = \uC5C5\uB370\uC774\uD2B8 \uCCB4\uD06C +m.validating = \uC720\uD6A8\uC131 \uAC80\uC0AC \uC911 +m.patching = \uD328\uCE58 \uC911 +m.launching = \uC2E4\uD589 \uC911 + +m.patch_notes = \uD328\uCE58 \uB178\uD2B8 +m.play_again = \uB2E4\uC2DC \uC2E4\uD589 + +m.complete = {0}% \uC644\uB8CC +m.remain = {0} \uB0A8\uC74C + +m.updating_metadata = \uCEE8\uD2B8\uB864 \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911 + +m.init_failed = \uC124\uC815 \uD30C\uC77C\uC774 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uBCC0\uD615\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \uC0C8\uB85C\uC6B4 \uBCF5\uC0AC\uBCF8\uC744 \uB2E4\uC6B4\uB85C\uB4DC \uC911\uC785\uB2C8\uB2E4... + +m.java_download_failed = \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uD544\uC694\uD55C \uC0C8\uB85C\uC6B4 \uBC84\uC804\uC758 \uC790\uBC14\uB97C \uC790\uB3D9\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n\n\ + \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.java_unpack_failed = \uC5C5\uB370\uC774\uD2B8\uB41C \uBC84\uC804\uC758 \uC790\uBC14\uC758 \uC555\uCD95\uC744 \uD480 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \ + \uD558\uB4DC\uB4DC\uB77C\uC774\uBE0C\uC5D0 \uCD5C\uC18C\uD55C 100MB\uC758 \uC6A9\uB7C9\uC744 \uD655\uBCF4\uD55C \uC774\uD6C4, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624.\n\n\ + \uB9CC\uC57D \uBB38\uC81C\uAC00 \uD574\uACB0\uB418\uC9C0 \uC54A\uB294\uB2E4\uBA74, \uC790\uBC14 \uC6F9\uC0AC\uC774\uD2B8(www.java.com)\uB85C \uAC00\uC11C \uCD5C\uC2E0\uC758 \uC790\uBC14\uB97C \uB2E4\uC6B4\uB85C\uB4DC \uBC1B\uC73C\uC2E0 \uD6C4, \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC2ED\uC2DC\uC624. + +m.unable_to_repair = \uB2E4\uC12F\uBC88\uC758 \uC2DC\uB3C4\uC5D0\uB3C4 \uD544\uC694\uD55C \uD30C\uC77C\uC744 \uB2E4\uC6B4\uB85C\uB4DC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uB2E4\uC2DC \uC2DC\uC791\uD574\uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uB2E4\uC6B4\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD55C\uB2E4\uBA74, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC81C\uAC70\uD55C \uD6C4, \uB2E4\uC2DC \uC2E4\uD589\uD574\uBCF4\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.unknown_error = \uBCF5\uAD6C\uB420 \uC218 \uC5C6\uB294 \uC624\uB958\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \ + \n{0}\uC5D0 \uB300\uD55C \uBCF5\uAD6C \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.init_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC544\uB798\uC640 \uAC19\uC740 \uC5D0\uB7EC\uB85C \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC5D0\uB7EC:\ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.readonly_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC124\uCE58\uB41C \uB514\uB809\uD1A0\uB9AC: \ + \n{0}\n\uAC00 \uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC77D\uAE30 \uAD8C\uD55C\uC774 \uC2B9\uC778\uB41C \uB809\uD1A0\uB9AC\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC124\uCE58\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.missing_resource = \uB9AC\uC18C\uC2A4\uC758 \uC190\uC2E4\uB85C \uC778\uD558\uC5EC \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. : \ + \n{0}\n\n{1}\uC5D0 \uB300\uD55C \uBB38\uC81C \uD574\uACB0 \uBC29\uBC95\uC744 \uCC3E\uAE30 \uC704\uD574\uC11C \uBC29\uBB38\uD558\uC2DC\uAE38 \uBC14\uB78D\uB2C8\uB2E4. + +m.insufficient_permissions_error = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \ + \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC2E4\uD589\uD558\uAE30 \uC704\uD574\uC11C \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD558\uC5EC \uC8FC\uC2ED\uC2DC\uC624. \ + \n\n\uADF8\uB9AC\uACE0 \uB098\uC11C \uC6F9 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uACE0 \uB2E4\uC2DC \uC2DC\uC791\uD558\uC5EC \uC6F9\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uC640 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC744 \uC7AC\uC2DC\uC791\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. \ + \uBCF4\uC548\uC5D0 \uB300\uD55C \uB300\uD654\uC0C1\uC790\uAC00 \uBCF4\uC774\uBA74, \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC5D0 \uB300\uD55C \uD655\uC778 \uBC84\uD2BC\uC744 \uD074\uB9AD\uD558\uACE0, \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uAE30 \uC704\uD55C \ + \uAD8C\uD55C\uC744 \uBD80\uC5EC\uD574\uC8FC\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.corrupt_digest_signature_error = \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC758 \uB514\uC9C0\uD0C8 \uC11C\uBA85\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\n \ + \uC62C\uBC14\uB978 \uC6F9\uC0AC\uC774\uD2B8\uC5D0\uC11C \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158\uC774 \uC2E4\uD589\uB418\uACE0 \uC788\uB294 \uC9C0 \uD655\uC778\uBC14\uB78D\uB2C8\uB2E4. + +m.default_install_error = \uC6F9\uC0AC\uC774\uD2B8\uC758 \uC9C0\uC6D0 \uBA54\uB274(support section)\uB97C \uD655\uC778\uD558\uC2DC\uAE30 \uBC14\uB78D\uB2C8\uB2E4. + +m.another_getdown_running = \uC774 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uC778\uC2A4\uD1A8\uB7EC\uC758 \uB2E4\uC911 \uC778\uC2A4\uD134\uC2A4\uAC00 \uC2E4\uD589\uC911\uC785\uB2C8\uB2E4. \ + \uD558\uB098\uAC00 \uC644\uB8CC\uB420 \uB54C\uAE4C\uC9C0 \uC911\uB2E8\uB429\uB2C8\uB2E4. + +m.applet_stopped = Getdown \uC560\uD50C\uB9BF \uC2E4\uD589\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. + +# application/digest errors +m.missing_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 'appbase' \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.invalid_version = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C \uBC84\uC804\uC774 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.invalid_appbase = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC798\uBABB\uB41C 'appbase'\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. +m.missing_class = \uC124\uC815 \uD30C\uC77C\uC5D0 \uC5B4\uD50C\uB9AC\uCF00\uC774\uC158 \uD074\uB798\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. +m.missing_code = \uC124\uC815 \uD30C\uC77C\uC5D0 \uB9AC\uC18C\uC2A4\uC5D0 \uB300\uD55C \uCF54\uB4DC\uAC00 \uBA85\uC2DC\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. +m.invalid_digest_file = \uB2E4\uC774\uC81C\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties new file mode 100644 index 0000000..47db91c --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_pt.properties @@ -0,0 +1,118 @@ +# +# $Id$ +# +# Getdown translation messages + +m.abort_title = Cancelar a instala\u00E7\u00E3o? +m.abort_confirm = Tem certeza que deseja cancelar a instala\u00E7\u00E3o? \ + Voc\u00EA pode continuar a instala\u00E7\u00E3o mais tarde, \ + basta executar a aplica\u00E7\u00E3o novamente. +m.abort_ok = Sair +m.abort_cancel = Continuar a instala\u00E7\u00E3o + +m.detecting_proxy = Tentando detectar automaticamente as configura\u00E7\u00F5es de proxy + +m.configure_proxy = N\u00E3o foi poss\u00EDvel conectar aos nossos servidores para \ + fazer o download dos dados. \ +

  • Se o Firewall do Windows ou o Norton Internet Security est\u00E1 configurado \ + para bloquear o programa javaw.exe n\u00E3o ser\u00E1 poss\u00EDvel realizar \ + o download. Voc\u00EA ter\u00E1 que permitir que o programa javaw.exe acesse \ + a internet. Voc\u00EA pode tentar executar o programa novamente, mas voc\u00EA precisa \ + remover o programa javaw.exe das configura\u00E7\u00F5es do firewall (Iniciar -> Painel \ + de controle -> Firewall do Windows).
\ +

Seu computador pode estar acessando a internet atrav\u00E9s de um proxy e n\u00E3o foi \ + capaz de detectar automaticamente as configura\u00E7\u00F5es de proxy. \ + Voc\u00EA pode informar esses dados abaixo. + +m.proxy_extra = Se voc\u00EA tem certeza que n\u00E3o usa um proxy, ent\u00E3o pode ser \ + que exista um problema tempor\u00E1rio que est\u00E1 impedindo a comunica\u00E7\u00E3o \ + com os nossos servidores. Neste caso voc\u00EA pode cancelar e tentar instalar novamente \ + mais tarde. + +m.proxy_host = IP do Proxy +m.proxy_port = Porta do Proxy +m.proxy_username = Nome de usu\u00e1rio +m.proxy_password = Senha +m.proxy_auth_required = Autentifica\u00e7\u00e3o requerida +m.proxy_ok = OK +m.proxy_cancel = Cancelar + +m.downloading_java = Fazendo o download da m\u00E1quina virtual Java +m.unpacking_java = Descompactando a m\u00E1quina virtual Java + +m.resolving = Resolvendo downloads +m.downloading = Transferindo dados +m.failure = Download falhou: {0} + +m.checking = Verificando atualiza\u00E7\u00F5es +m.validating = Validando +m.patching = Atualizando +m.launching = Executando + +m.patch_notes = Corrigir notas +m.play_again = Jogar de novo + +m.complete = {0}% completo +m.remain = {0} Permanecer + +m.updating_metadata = Transferindo arquivos de controle + +m.init_failed = Nosso arquivo de configura\u00E7\u00E3o est\u00E1 ausente ou corrompido. Tente \ + baixar uma nova c\u00F3pia... + +m.java_download_failed = N\u00E3o conseguimos baixar automaticamente a\ + vers\u00E3o necess\u00E1ria do Java para o seu computador.\n\n\ + Por favor, acesse www.java.com, baixe e instale a \u00FAltima vers\u00E3o do \ + Java, em seguida, tente executar o aplicativo novamente. + +m.java_unpack_failed = N\u00E3o conseguimos descompactar uma vers\u00E3o atualizada do \ + Java. Por favor, certifique-se de ter pelo menos 100 MB de espa\u00E7o livre em seu \ + disco r\u00EDgido e tente executar o aplicativo novamente. \n\n\ + Se isso n\u00E3o resolver o problema, acesse www.java.com,baixe e \ + instale a \u00FAltima vers\u00E3o do Java e tente novamente. + +m.unable_to_repair = N\u00E3o conseguimos baixar os arquivos necess\u00E1rios depois de \ + cinco tentativas. Voc\u00EA pode tentar executar o aplicativo novamente, mas se ele \ + falhar pode ser necess\u00E1rio desinstalar e reinstalar. + +m.unknown_error = A aplica\u00E7\u00E3o falhou ao iniciar devido a algum erro estranho \ + do qual n\u00E3o conseguimos recuperar. Por favor, visite \n{0} para obter \ + informa\u00E7\u00F5es sobre como recuperar. +m.init_error = A aplica\u00E7\u00E3o falhou ao iniciar devido ao seguinte \ + erro:\n{0}\n\nPor favor visite \n{1} para \ + informa\u00E7\u00F5es sobre como lidar com esse problema. + +m.readonly_error =O diret\u00F3rio no qual este aplicativo est\u00E1 instalado: \ + \n{0}\n \u00E9 somente leitura. Por favor, instale o aplicativo em um diret\u00F3rio onde \ + voc\u00EA tem acesso de grava\u00E7\u00E3o. + +m.missing_resource = A aplica\u00E7\u00E3o falhou ao iniciar devido a uma falta \ + de recurso:\n{0}\n\n Por favor, visite\n{1} para obter informa\u00E7\u00F5es sobre \ + como lidar com tal problema. + +m.insufficient_permissions_error = Voc\u00EA n\u00E3o aceitou a assinatura digital \ + do aplicativo. Se voc\u00EA quiser executar o aplicativo, voc\u00EA ter\u00E1 que aceitar \ + a assinatura digital. \n\nPara fazer isso, voc\u00EA ter\u00E1 que sair do seu navegador, \ + reinici\u00E1-lo, e retornar a esta p\u00E1gina web para executar a aplica\u00E7\u00E3o. \ + Quando o di\u00E1logo de seguran\u00E7a aparecer, clique no bot\u00E3o para aceitar a \ + assinatura digital e conceder a este aplicativo os privil\u00E9gios necess\u00E1rios \ + para executar. + +m.corrupt_digest_signature_error = N\u00E3o conseguimos verificar a assinatura digital \ + do aplicativo.\nPor favor, verifique se voc\u00EA est\u00E1 utilizando o aplicativo \nde um \ + site correto. + +m.default_install_error = a se\u00E7\u00E3o de suporte do site + +m.another_getdown_running = V\u00E1rias inst\u00E2ncias desta aplica\u00E7\u00E3o \ + est\u00E3o em execu\u00E7\u00E3o. Esta ir\u00E1 parar e deixar outra completar suas atividades. + +m.applet_stopped = Foi solicitado ao miniaplicativo GetDow que parasse de trabalhar. + +# application/digest errors +m.missing_appbase = O arquivo de configura\u00E7\u00E3o n\u00E3o possui o 'AppBase'. +m.invalid_version = O arquivo de configura\u00E7\u00E3o especifica uma vers\u00E3o inv\u00E1lida. +m.invalid_appbase = O arquivo de configura\u00E7\u00E3o especifica um 'AppBase' inv\u00E1lido. +m.missing_class = O arquivo de configura\u00E7\u00E3o n\u00E3o possui a classe de aplicativo. +m.missing_code = O arquivo de configura\u00E7\u00E3o n\u00E3o especifica um recurso de c\u00F3digo. +m.invalid_digest_file = O arquivo digest \u00E9 inv\u00E1lido. diff --git a/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties new file mode 100644 index 0000000..2c27543 --- /dev/null +++ b/getdown/src/getdown/launcher/target/classes/com/threerings/getdown/messages_zh.properties @@ -0,0 +1,61 @@ +# +# $Id$ +# +# Getdown translation messages + +m.detecting_proxy = \u641c\u5bfb\u4ee3\u7406\u670d\u52a1\u5668 + +m.configure_proxy = \u6211\u4eec\u65e0\u6cd5\u8fde\u63a5\u5230\u670d\u52a1\u5668\u4e0b\u8f7d\u6e38\u620f\u6570\u636e\u3002\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e \ + \u60a8\u7684\u8ba1\u7b97\u673a\u662f\u901a\u8fc7\u4ee3\u7406\u670d\u52a1\u5668\u8fde\u63a5\u4e92\u8054\u7f51\u7684\uff0c\u5e76\u4e14\u6211\u4eec\u65e0\u6cd5\u81ea\u52a8\u83b7\u5f97\u4ee3\u7406\u670d\u52a1\u5668\u7684 \ + \u8bbe\u7f6e\u3002\u5982\u679c\u60a8\u77e5\u9053\u60a8\u4ee3\u7406\u670d\u52a1\u5668\u7684\u8bbe\u7f6e\uff0c\u60a8\u53ef\u4ee5\u5728\u4e0b\u9762\u8f93\u5165\u3002 + +m.proxy_extra = \u5982\u679c\u60a8\u786e\u5b9a\u60a8\u6ca1\u6709\u4f7f\u7528\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u6682\u65f6\u65e0\u6cd5 \ + \u8fde\u63a5\u5230\u4e92\u8054\u7f51\uff0c\u5bfc\u81f4\u65e0\u6cd5\u548c\u670d\u52a1\u5668\u901a\u8baf\u3002\u8fd9\u79cd\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u53d6\u6d88\uff0c\u7a0d\u5019\u518d\u91cd\u65b0\u5b89\u88c5\u3002

\ + \u5982\u679c\u60a8\u65e0\u6cd5\u786e\u5b9a\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u4ee3\u7406\u670d\u52a1\u5668\uff0c\u8bf7\u8bbf\u95ee\u6211\u4eec\u7f51\u7ad9\u4e2d\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c \ + \u4e86\u89e3\u5982\u4f55\u68c0\u6d4b\u60a8\u7684\u4ee3\u7406\u670d\u52a1\u5668\u8bbe\u7f6e\u3002 + +m.proxy_host = \u4ee3\u7406\u670d\u52a1\u5668\u7684IP\u5730\u5740 +m.proxy_port = \u4ee3\u7406\u670d\u52a1\u5668\u7684\u7aef\u53e3\u53f7 +m.proxy_username = Username +m.proxy_password = Password +m.proxy_auth_required = Authentication required +m.proxy_ok = \u786e\u5b9a +m.proxy_cancel = \u53d6\u6d88 + +m.resolving = \u5206\u6790\u9700\u4e0b\u8f7d\u5185\u5bb9 +m.downloading = \u4e0b\u8f7d\u6570\u636e +m.failure = \u4e0b\u8f7d\u5931\u8d25: {0} + +m.checking = \u68c0\u67e5\u66f4\u65b0\u5185\u5bb9 +m.validating = \u786e\u8ba4 +m.patching = \u5347\u7ea7 +m.launching = \u542f\u52a8 + +m.complete = {0}% \u5b8c\u6210 +m.remain = {0} \u5269\u4f59\u65f6\u95f4 + +m.updating_metadata = \u4e0b\u8f7d\u63a7\u5236\u6587\u4ef6 + +m.init_failed = \u65e0\u6cd5\u627e\u5230\u914d\u7f6e\u6587\u4ef6\u6216\u5df2\u635f\u574f\u3002\u5c1d\u8bd5\u91cd\u65b0\u4e0b\u8f7d... + +m.unable_to_repair = \u7ecf\u8fc75\u6b21\u5c1d\u8bd5\uff0c\u4f9d\u7136\u65e0\u6cd5\u4e0b\u8f7d\u6240\u9700\u7684\u6587\u4ef6\u3002\ +\u60a8\u53ef\u4ee5\u91cd\u65b0\u8fd0\u884c\u7a0b\u5e8f\uff0c\u4f46\u662f\u5982\u679c\u4f9d\u7136\u5931\u8d25\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u53cd\u5b89\u88c5\u5e76\u91cd\u65b0\u5b89\u88c5\u3002 + + +m.unknown_error = \u7531\u4e8e\u4e00\u4e9b\u65e0\u6cd5\u56de\u590d\u7684\u4e25\u91cd\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\u3002\ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u89e3\u51b3\u95ee\u9898\u3002 + +m.init_error = \u7531\u4e8e\u4e0b\u5217\u9519\u8bef\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u9519\u8bef\u3002 + + +m.missing_resource = \u7531\u4e8e\u65e0\u6cd5\u627e\u5230\u4e0b\u5217\u8d44\u6e90\uff0c\u7a0b\u5e8f\u542f\u52a8\u5931\u8d25\uff1a\n{0}\n\n \ +\u8bf7\u8bbf\u95ee\u6211\u4eec\u7684\u7f51\u7ad9\u7684\u6280\u672f\u652f\u6301\u90e8\u4efd\uff0c\u4e86\u89e3\u5982\u4f55\u5904\u7406\u8fd9\u4e9b\u95ee\u9898\u3002 + +# application/digest errors +m.missing_appbase = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230 'appbase'\u3002 +m.invalid_version = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684\u7248\u672c\u3002 +m.invalid_appbase = \u914d\u7f6e\u6587\u4ef6\u6307\u5b9a\u4e86\u65e0\u6548\u7684 'appbase'\u3002 +m.missing_class = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u7a0b\u5e8f\u6587\u4ef6\u3002 +m.missing_code = \u914d\u7f6e\u6587\u4ef6\u4e2d\u65e0\u6cd5\u627e\u5230\u6307\u5b9a\u7684\u8d44\u6e90\u3002 +m.invalid_digest_file = \u65e0\u6548\u7684\u914d\u7f6e\u6587\u4ef6\u3002 diff --git a/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar new file mode 100644 index 0000000..f71b6ee Binary files /dev/null and b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT.jar differ diff --git a/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar new file mode 100644 index 0000000..88428a4 Binary files /dev/null and b/getdown/src/getdown/launcher/target/getdown-launcher-1.8.3-SNAPSHOT_proguard_base.jar differ diff --git a/getdown/src/getdown/launcher/target/maven-archiver/pom.properties b/getdown/src/getdown/launcher/target/maven-archiver/pom.properties new file mode 100644 index 0000000..ab0c975 --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-archiver/pom.properties @@ -0,0 +1,4 @@ +#Created by Apache Maven 3.5.2 +version=1.8.3-SNAPSHOT +groupId=com.threerings.getdown +artifactId=getdown-launcher diff --git a/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..ecc6201 --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,28 @@ +com/threerings/getdown/launcher/GetdownApp$1$1.class +com/threerings/getdown/launcher/Getdown$4.class +com/threerings/getdown/launcher/RotatingBackgrounds$ImageLoader.class +com/threerings/getdown/launcher/ProxyPanel$SaneTextField.class +com/threerings/getdown/launcher/GetdownApp$2.class +com/threerings/getdown/launcher/Getdown$2.class +com/threerings/getdown/launcher/ProxyPanel.class +com/threerings/getdown/launcher/GetdownApp.class +com/threerings/getdown/launcher/Getdown$ProgressReporter.class +com/threerings/getdown/launcher/ProxyPanel$SaneLabelField.class +com/threerings/getdown/launcher/ProxyUtil$1.class +com/threerings/getdown/launcher/ProxyPanel$SanePasswordField.class +com/threerings/getdown/launcher/StatusPanel$1.class +com/threerings/getdown/launcher/GetdownApp$1$3.class +com/threerings/getdown/launcher/AbortPanel.class +com/threerings/getdown/launcher/GetdownApp$1$2.class +com/threerings/getdown/launcher/ProxyUtil.class +com/threerings/getdown/launcher/ProxyPanel$1.class +com/threerings/getdown/launcher/Getdown$5.class +com/threerings/getdown/launcher/StatusPanel.class +com/threerings/getdown/launcher/GetdownApp$1.class +com/threerings/getdown/launcher/RotatingBackgrounds.class +com/threerings/getdown/launcher/MultipleGetdownRunning.class +com/threerings/getdown/launcher/Getdown$1.class +com/threerings/getdown/launcher/Getdown.class +com/threerings/getdown/launcher/Getdown$3.class +com/threerings/getdown/launcher/Getdown$6.class +com/threerings/getdown/launcher/Getdown$4$1.class diff --git a/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..5ad07dd --- /dev/null +++ b/getdown/src/getdown/launcher/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,8 @@ +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/AbortPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/MultipleGetdownRunning.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/RotatingBackgrounds.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/StatusPanel.java +/Users/bsoares/git/getdown2/getdown/launcher/src/main/java/com/threerings/getdown/launcher/ProxyUtil.java diff --git a/getdown/src/getdown/launcher/target/proguard_map.txt b/getdown/src/getdown/launcher/target/proguard_map.txt new file mode 100644 index 0000000..4ecff17 --- /dev/null +++ b/getdown/src/getdown/launcher/target/proguard_map.txt @@ -0,0 +1,1349 @@ +ca.beq.util.win32.registry.KeyIterator -> ca.beq.util.win32.registry.KeyIterator: + ca.beq.util.win32.registry.RegistryKey m_key -> m_key + int m_index -> m_index + int m_hkey -> m_hkey + int m_maxsize -> m_maxsize + int m_count -> m_count + void (ca.beq.util.win32.registry.RegistryKey) -> + void initializeFields() -> initializeFields + boolean hasNext() -> hasNext + java.lang.Object next() -> next + java.lang.String getNext() -> getNext + void remove() -> remove +ca.beq.util.win32.registry.RegistryException -> ca.beq.util.win32.registry.RegistryException: + void () -> + void (java.lang.String) -> +ca.beq.util.win32.registry.RegistryKey -> ca.beq.util.win32.registry.RegistryKey: + boolean c_initSucceeded -> c_initSucceeded + ca.beq.util.win32.registry.RootKey m_root -> m_root + java.lang.String m_path -> m_path + void testInitialized() -> testInitialized + void initialize() -> initialize + void initialize(java.lang.String) -> initialize + boolean isInitialized() -> isInitialized + void checkInitialized() -> checkInitialized + void () -> + void (ca.beq.util.win32.registry.RootKey) -> + void (java.lang.String) -> + void (ca.beq.util.win32.registry.RootKey,java.lang.String) -> + ca.beq.util.win32.registry.RootKey getRootKey() -> getRootKey + java.lang.String getPath() -> getPath + java.lang.String makePath(java.lang.String) -> makePath + java.lang.String getName() -> getName + boolean exists() -> exists + void create() -> create + ca.beq.util.win32.registry.RegistryKey createSubkey(java.lang.String) -> createSubkey + void delete() -> delete + boolean hasSubkeys() -> hasSubkeys + boolean hasSubkey(java.lang.String) -> hasSubkey + java.util.Iterator subkeys() -> subkeys + java.util.Iterator values() -> values + boolean hasValue(java.lang.String) -> hasValue + boolean hasValues() -> hasValues + ca.beq.util.win32.registry.RegistryValue getValue(java.lang.String) -> getValue + void setValue(ca.beq.util.win32.registry.RegistryValue) -> setValue + void deleteValue(java.lang.String) -> deleteValue + java.lang.String toString() -> toString + void () -> +ca.beq.util.win32.registry.RegistryValue -> ca.beq.util.win32.registry.RegistryValue: + java.lang.String m_name -> m_name + ca.beq.util.win32.registry.ValueType m_type -> m_type + java.lang.Object m_data -> m_data + void () -> + void (java.lang.Object) -> + void (java.lang.String,java.lang.Object) -> + void (java.lang.String,ca.beq.util.win32.registry.ValueType,java.lang.Object) -> + void (java.lang.String,boolean) -> + void (java.lang.String,byte) -> + void (java.lang.String,int) -> + void (java.lang.String,long) -> + void (java.lang.String,float) -> + void (java.lang.String,double) -> + java.lang.String getName() -> getName + void setName(java.lang.String) -> setName + ca.beq.util.win32.registry.ValueType getType() -> getType + void setType(ca.beq.util.win32.registry.ValueType) -> setType + java.lang.Object getData() -> getData + void setData(java.lang.Object) -> setData + void setData(byte) -> setData + void setData(boolean) -> setData + void setData(int) -> setData + void setData(long) -> setData + void setData(float) -> setData + void setData(double) -> setData + java.lang.String getStringValue() -> getStringValue + java.lang.String toString() -> toString +ca.beq.util.win32.registry.RootKey -> ca.beq.util.win32.registry.RootKey: + java.lang.String m_name -> m_name + int m_value -> m_value + ca.beq.util.win32.registry.RootKey HKEY_CLASSES_ROOT -> HKEY_CLASSES_ROOT + ca.beq.util.win32.registry.RootKey HKEY_CURRENT_USER -> HKEY_CURRENT_USER + ca.beq.util.win32.registry.RootKey HKEY_LOCAL_MACHINE -> HKEY_LOCAL_MACHINE + ca.beq.util.win32.registry.RootKey HKEY_USERS -> HKEY_USERS + ca.beq.util.win32.registry.RootKey HKEY_CURRENT_CONFIG -> HKEY_CURRENT_CONFIG + ca.beq.util.win32.registry.RootKey HKEY_PERFORMANCE_DATA -> HKEY_PERFORMANCE_DATA + ca.beq.util.win32.registry.RootKey HKEY_DYN_DATA -> HKEY_DYN_DATA + void (java.lang.String,int) -> + int getValue() -> getValue + java.lang.String toString() -> toString + void () -> +ca.beq.util.win32.registry.ValueIterator -> ca.beq.util.win32.registry.ValueIterator: + ca.beq.util.win32.registry.RegistryKey m_key -> m_key + int m_index -> m_index + int m_hkey -> m_hkey + int m_maxsize -> m_maxsize + int m_count -> m_count + void (ca.beq.util.win32.registry.RegistryKey) -> + void initializeFields() -> initializeFields + boolean hasNext() -> hasNext + java.lang.Object next() -> next + java.lang.String getNext() -> getNext + void remove() -> remove +ca.beq.util.win32.registry.ValueType -> ca.beq.util.win32.registry.ValueType: + java.lang.String m_name -> m_name + int m_value -> m_value + ca.beq.util.win32.registry.ValueType REG_NONE -> REG_NONE + ca.beq.util.win32.registry.ValueType REG_SZ -> REG_SZ + ca.beq.util.win32.registry.ValueType REG_EXPAND_SZ -> REG_EXPAND_SZ + ca.beq.util.win32.registry.ValueType REG_BINARY -> REG_BINARY + ca.beq.util.win32.registry.ValueType REG_DWORD -> REG_DWORD + ca.beq.util.win32.registry.ValueType REG_DWORD_LITTLE_ENDIAN -> REG_DWORD_LITTLE_ENDIAN + ca.beq.util.win32.registry.ValueType REG_DWORD_BIG_ENDIAN -> REG_DWORD_BIG_ENDIAN + ca.beq.util.win32.registry.ValueType REG_MULTI_SZ -> REG_MULTI_SZ + ca.beq.util.win32.registry.ValueType REG_RESOURCE_LIST -> REG_RESOURCE_LIST + ca.beq.util.win32.registry.ValueType REG_LINK -> REG_LINK + ca.beq.util.win32.registry.ValueType REG_FULL_RESOURCE_DESCRIPTOR -> REG_FULL_RESOURCE_DESCRIPTOR + ca.beq.util.win32.registry.ValueType REG_RESOURCE_REQUIREMENTS_LIST -> REG_RESOURCE_REQUIREMENTS_LIST + void (java.lang.String,int) -> + int getValue() -> getValue + java.lang.String toString() -> toString + void () -> +com.samskivert.Log -> com.a.a: + com.samskivert.util.Logger log -> a + void () -> +com.samskivert.swing.DimenInfo -> com.a.a.a: + int count -> a + int totwid -> b + int tothei -> c + int maxwid -> d + int maxhei -> e + int numfix -> f + int fixwid -> g + int fixhei -> h + int maxfreewid -> i + int maxfreehei -> j + int totweight -> k + java.awt.Dimension[] dimens -> l + void () -> + java.lang.String toString() -> toString + boolean equals(java.lang.Object,java.lang.Object) -> a +com.samskivert.swing.GroupLayout -> com.a.a.b: + com.samskivert.swing.GroupLayout$Constraints FIXED -> a + com.samskivert.swing.GroupLayout$Policy NONE -> b + com.samskivert.swing.GroupLayout$Policy STRETCH -> c + com.samskivert.swing.GroupLayout$Policy EQUALIZE -> d + com.samskivert.swing.GroupLayout$Policy CONSTRAIN -> e + com.samskivert.swing.GroupLayout$Justification CENTER -> f + com.samskivert.swing.GroupLayout$Justification LEFT -> g + com.samskivert.swing.GroupLayout$Justification RIGHT -> h + com.samskivert.swing.GroupLayout$Justification TOP -> i + com.samskivert.swing.GroupLayout$Justification BOTTOM -> j + com.samskivert.swing.GroupLayout$Policy _policy -> k + com.samskivert.swing.GroupLayout$Policy _offpolicy -> l + int _gap -> m + com.samskivert.swing.GroupLayout$Justification _justification -> n + com.samskivert.swing.GroupLayout$Justification _offjust -> o + java.util.HashMap _constraints -> p + com.samskivert.swing.GroupLayout$Constraints DEFAULT_CONSTRAINTS -> q + void () -> + void addLayoutComponent(java.lang.String,java.awt.Component) -> addLayoutComponent + void removeLayoutComponent(java.awt.Component) -> removeLayoutComponent + void addLayoutComponent(java.awt.Component,java.lang.Object) -> addLayoutComponent + float getLayoutAlignmentX(java.awt.Container) -> getLayoutAlignmentX + float getLayoutAlignmentY(java.awt.Container) -> getLayoutAlignmentY + java.awt.Dimension minimumLayoutSize(java.awt.Container) -> minimumLayoutSize + java.awt.Dimension preferredLayoutSize(java.awt.Container) -> preferredLayoutSize + java.awt.Dimension maximumLayoutSize(java.awt.Container) -> maximumLayoutSize + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void invalidateLayout(java.awt.Container) -> invalidateLayout + com.samskivert.swing.GroupLayout$Constraints getConstraints(java.awt.Component) -> a + com.samskivert.swing.DimenInfo computeDimens(java.awt.Container,int) -> b + javax.swing.JPanel makeButtonBox(com.samskivert.swing.GroupLayout$Justification,java.awt.Component[]) -> a + void () -> +com.samskivert.swing.GroupLayout$Constraints -> com.a.a.b$a: + int _weight -> a + void (int) -> + boolean isFixed() -> a + int getWeight() -> b +com.samskivert.swing.GroupLayout$Justification -> com.a.a.b$b: + void () -> +com.samskivert.swing.GroupLayout$Policy -> com.a.a.b$c: + void () -> +com.samskivert.swing.HGroupLayout -> com.a.a.c: + void (com.samskivert.swing.GroupLayout$Policy,com.samskivert.swing.GroupLayout$Justification) -> + void () -> + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void layoutContainer(java.awt.Container) -> layoutContainer +com.samskivert.swing.Label -> com.a.a.d: + java.util.regex.Pattern COLOR_PATTERN -> a + java.lang.String _text -> b + java.lang.String _rawText -> c + int _style -> d + int _align -> e + java.awt.Dimension _constraints -> f + java.awt.Dimension _size -> g + float[] _leaders -> h + java.awt.Font _font -> i + java.awt.font.TextLayout[] _layouts -> j + java.awt.geom.Rectangle2D[] _lbounds -> k + java.awt.Color _alternateColor -> l + java.awt.Color _textColor -> m + boolean _mainDraw -> n + java.util.regex.Pattern ESCAPED_PATTERN -> o + java.lang.String unescapeColors(java.lang.String,boolean) -> a + void () -> + void (java.lang.String) -> + void (java.lang.String,java.awt.Color,java.awt.Font) -> + void (java.lang.String,int,java.awt.Color,java.awt.Color,java.awt.Font) -> + void setAlternateColor(java.awt.Color) -> a + void setStyle(int) -> a + void setTargetWidth(int) -> b + java.awt.Dimension getSize() -> a + void layout(java.awt.Graphics2D) -> a + java.util.List computeLines(java.awt.font.LineBreakMeasurer,int,java.awt.Dimension,boolean) -> a + void render(java.awt.Graphics2D,float,float) -> a + java.text.AttributedCharacterIterator textIterator(java.awt.Graphics2D) -> b + void addAttributes(java.text.AttributedString) -> a + double getWidth(java.awt.geom.Rectangle2D) -> a + java.awt.geom.Rectangle2D getBounds(java.awt.font.TextLayout) -> a + float getHeight(java.awt.font.TextLayout) -> b + void () -> +com.samskivert.swing.Spacer -> com.a.a.e: + void (int,int) -> + void (java.awt.Dimension) -> +com.samskivert.swing.VGroupLayout -> com.a.a.f: + void () -> + java.awt.Dimension getLayoutSize(java.awt.Container,int) -> a + void layoutContainer(java.awt.Container) -> layoutContainer +com.samskivert.swing.util.SwingUtil -> com.a.a.a.a: + boolean _defaultTextAntialiasing -> a + void centerWindow(java.awt.Window) -> a + java.lang.Object activateAntiAliasing(java.awt.Graphics2D) -> a + void restoreAntiAliasing(java.awt.Graphics2D,java.lang.Object) -> a + boolean getDefaultTextAntialiasing() -> a + void () -> +com.samskivert.util.AbstractIntSet -> com.a.b.a: + void () -> + boolean contains(int) -> a + int size() -> size + boolean isEmpty() -> isEmpty + boolean remove(int) -> b + java.util.Iterator iterator() -> iterator + boolean contains(java.lang.Object) -> contains + boolean remove(java.lang.Object) -> remove + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + boolean containsAll(java.util.Collection) -> containsAll + boolean addAll(java.util.Collection) -> addAll + boolean removeAll(java.util.Collection) -> removeAll + boolean retainAll(java.util.Collection) -> retainAll + boolean add(java.lang.Object) -> add +com.samskivert.util.AbstractInterator -> com.a.b.b: + void () -> + void remove() -> remove + java.lang.Object next() -> next +com.samskivert.util.ArrayUtil -> com.a.b.c: + java.lang.String safeToString(java.lang.Object) -> a + void () -> +com.samskivert.util.FormatterUtil -> com.a.b.d: + java.lang.String LINE_SEPARATOR -> a + void configureDefaultHandler(java.util.logging.Formatter) -> a + void () -> +com.samskivert.util.HashIntMap -> com.a.b.e: + com.samskivert.util.HashIntMap$Record[] _buckets -> a + int _size -> b + float _loadFactor -> c + com.samskivert.util.IntSet _keySet -> d + void (int,float) -> + void () -> + int size() -> size + boolean containsKey(java.lang.Object) -> containsKey + boolean containsKey(int) -> a + boolean containsValue(java.lang.Object) -> containsValue + java.lang.Object get(java.lang.Object) -> get + java.lang.Object put(int,java.lang.Object) -> a + java.lang.Object remove(java.lang.Object) -> remove + com.samskivert.util.HashIntMap$Record getImpl(int) -> b + com.samskivert.util.HashIntMap$Record removeImpl(int,boolean) -> a + void clear() -> clear + void ensureCapacity(int) -> c + int keyToIndex(int) -> d + void checkShrink() -> a + void resizeBuckets(int) -> e + java.util.Set entrySet() -> entrySet + java.util.Set keySet() -> keySet + com.samskivert.util.HashIntMap clone() -> b + com.samskivert.util.HashIntMap$Record[] createBuckets(int) -> f + java.lang.Object clone() -> clone + java.lang.Object put(java.lang.Object,java.lang.Object) -> put +com.samskivert.util.HashIntMap$1 -> com.a.b.f: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + int size() -> size + java.util.Iterator iterator() -> iterator +com.samskivert.util.HashIntMap$2 -> com.a.b.g: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + int size() -> size + java.util.Iterator iterator() -> iterator +com.samskivert.util.HashIntMap$3 -> com.a.b.h: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + com.samskivert.util.Interator interator() -> a + int size() -> size + boolean contains(int) -> a + boolean remove(int) -> b +com.samskivert.util.HashIntMap$3$1 -> com.a.b.i: + java.util.Iterator i -> a + com.samskivert.util.HashIntMap$3 this$1 -> b + void (com.samskivert.util.HashIntMap$3) -> + boolean hasNext() -> hasNext + int nextInt() -> a + void remove() -> remove +com.samskivert.util.HashIntMap$IntEntryIterator -> com.a.b.e$a: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + java.lang.Object next() -> next +com.samskivert.util.HashIntMap$MapEntryIterator -> com.a.b.e$b: + com.samskivert.util.HashIntMap this$0 -> a + void (com.samskivert.util.HashIntMap) -> + java.lang.Object next() -> next +com.samskivert.util.HashIntMap$Record -> com.a.b.e$c: + com.samskivert.util.HashIntMap$Record next -> a + int key -> b + java.lang.Object value -> c + void (int,java.lang.Object) -> + int getIntKey() -> a + java.lang.Object getValue() -> getValue + java.lang.Object setValue(java.lang.Object) -> setValue + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + com.samskivert.util.HashIntMap$Record clone() -> b + java.lang.Object clone() -> clone + java.lang.Object getKey() -> getKey +com.samskivert.util.HashIntMap$RecordIterator -> com.a.b.e$d: + int _index -> a + com.samskivert.util.HashIntMap$Record _record -> b + com.samskivert.util.HashIntMap$Record _last -> c + com.samskivert.util.HashIntMap this$0 -> d + void (com.samskivert.util.HashIntMap) -> + boolean hasNext() -> hasNext + com.samskivert.util.HashIntMap$Record nextRecord() -> a + void remove() -> remove +com.samskivert.util.IntMap -> com.a.b.j: + java.lang.Object put(int,java.lang.Object) -> a +com.samskivert.util.IntMap$IntEntry -> com.a.b.j$a: + int getIntKey() -> a +com.samskivert.util.IntSet -> com.a.b.k: + boolean contains(int) -> a + com.samskivert.util.Interator interator() -> a +com.samskivert.util.Interable -> com.a.b.l: + com.samskivert.util.Interator interator() -> a +com.samskivert.util.Interator -> com.a.b.m: + int nextInt() -> a +com.samskivert.util.JDK14Logger -> com.a.b.n: + void () -> + void init() -> a + com.samskivert.util.Logger getLogger(java.lang.String) -> a +com.samskivert.util.JDK14Logger$Impl -> com.a.b.n$a: + java.util.logging.Logger _impl -> a + java.util.logging.Level[] LEVELS -> b + void (java.util.logging.Logger) -> + boolean shouldLog(int) -> a + void doLog(int,java.lang.String,java.lang.Throwable) -> a + void () -> +com.samskivert.util.Logger -> com.a.b.o: + com.samskivert.util.Logger$Factory _factory -> a + void () -> + com.samskivert.util.Logger getLogger(java.lang.String) -> a + void warning(java.lang.Object,java.lang.Object[]) -> a + boolean shouldLog(int) -> a + void doLog(int,java.lang.String,java.lang.Throwable) -> a + com.samskivert.util.Logger$Factory createConfiguredFactory() -> a + void () -> +com.samskivert.util.Logger$Factory -> com.a.b.o$a: + void init() -> a + com.samskivert.util.Logger getLogger(java.lang.String) -> a +com.samskivert.util.OneLineLogFormatter -> com.a.b.p: + boolean _showWhere -> a + java.util.Date _date -> b + java.text.SimpleDateFormat _format -> c + java.text.FieldPosition _fpos -> d + void () -> + void (boolean) -> + java.lang.String format(java.util.logging.LogRecord) -> format + void configureDefaultHandler() -> a +com.samskivert.util.RunAnywhere -> com.a.b.q: + boolean _isMacOS -> a + boolean isMacOS() -> a + void () -> +com.samskivert.util.StringUtil -> com.a.b.r: + java.text.NumberFormat _ffmt -> a + com.samskivert.util.IntMap _letterToBits -> b + boolean isBlank(java.lang.String) -> a + java.lang.String toString(java.lang.Object) -> a + void toString(java.lang.StringBuilder,java.lang.Object,java.lang.String,java.lang.String) -> a + void toString(java.lang.StringBuilder,java.lang.Object,java.lang.String,java.lang.String,java.lang.String) -> a + void coordsToString(java.lang.StringBuilder,int,int) -> a + void () -> +com.samskivert.util.Throttle -> com.a.b.s: + long[] _ops -> a + int _lastOp -> b + long _period -> c + void (int,long) -> + boolean throttleOp() -> a + java.lang.String toString() -> toString +com.samskivert.util.Tuple -> com.a.b.t: + java.lang.Object left -> a + java.lang.Object right -> b + void (java.lang.Object,java.lang.Object) -> + int hashCode() -> hashCode + boolean equals(java.lang.Object) -> equals + java.lang.String toString() -> toString +com.threerings.getdown.Log -> com.threerings.getdown.Log: + com.threerings.getdown.Log$Shim log -> log + java.lang.String DATE_FORMAT -> DATE_FORMAT + java.util.logging.Level[] LEVELS -> LEVELS + void () -> + java.lang.String format(java.lang.Object,java.lang.Object[]) -> format + void () -> +com.threerings.getdown.Log$OneLineFormatter -> com.threerings.getdown.Log$OneLineFormatter: + java.util.Date _date -> _date + java.text.SimpleDateFormat _format -> _format + java.text.FieldPosition _fpos -> _fpos + void () -> + java.lang.String format(java.util.logging.LogRecord) -> format +com.threerings.getdown.Log$Shim -> com.threerings.getdown.Log$Shim: + java.util.logging.Logger _impl -> _impl + void () -> + void debug(java.lang.Object,java.lang.Object[]) -> debug + void info(java.lang.Object,java.lang.Object[]) -> info + void warning(java.lang.Object,java.lang.Object[]) -> warning + void error(java.lang.Object,java.lang.Object[]) -> error + void doLog(int,java.lang.Object,java.lang.Object[]) -> doLog +com.threerings.getdown.cache.GarbageCollector -> com.threerings.getdown.cache.GarbageCollector: + void () -> + void collect(java.io.File,long) -> collect + void collectNative(java.io.File,long) -> collectNative + boolean shouldDelete(java.io.File,long) -> shouldDelete + java.io.File getLastAccessedFile(java.io.File) -> getLastAccessedFile + boolean isLastAccessedFile(java.io.File) -> isLastAccessedFile + java.io.File getCachedFile(java.io.File) -> getCachedFile + java.io.File access$000(java.io.File) -> access$000 + java.io.File access$100(java.io.File) -> access$100 + boolean access$200(java.io.File,long) -> access$200 +com.threerings.getdown.cache.GarbageCollector$1 -> com.threerings.getdown.cache.a: + long val$retentionPeriodMillis -> a + void (long) -> + void visit(java.io.File) -> visit +com.threerings.getdown.cache.ResourceCache -> com.threerings.getdown.cache.ResourceCache: + java.io.File _cacheDir -> _cacheDir + java.lang.String LAST_ACCESSED_FILE_SUFFIX -> LAST_ACCESSED_FILE_SUFFIX + void (java.io.File) -> + void createDirectoryIfNecessary(java.io.File) -> createDirectoryIfNecessary + java.io.File cacheFile(java.io.File,java.lang.String,java.lang.String) -> cacheFile + void createNewFile(java.io.File) -> createNewFile + java.lang.String getFileSuffix(java.io.File) -> getFileSuffix +com.threerings.getdown.data.Application -> com.threerings.getdown.data.Application: + java.lang.String CONFIG_FILE -> CONFIG_FILE + java.lang.String VERSION_FILE -> VERSION_FILE + java.lang.String PROP_PASSTHROUGH_PREFIX -> PROP_PASSTHROUGH_PREFIX + java.lang.String SIGNATURE_SUFFIX -> SIGNATURE_SUFFIX + java.lang.String MANIFEST_CLASS -> MANIFEST_CLASS + java.net.Proxy proxy -> proxy + com.threerings.getdown.data.EnvConfig _envc -> _envc + java.io.File _config -> _config + com.threerings.getdown.data.Digest _digest -> _digest + long _version -> _version + long _targetVersion -> _targetVersion + java.lang.String _appbase -> _appbase + java.net.URL _vappbase -> _vappbase + java.net.URL _latest -> _latest + java.lang.String _class -> _class + java.lang.String _dockName -> _dockName + java.lang.String _dockIconPath -> _dockIconPath + boolean _strictComments -> _strictComments + boolean _windebug -> _windebug + boolean _allowOffline -> _allowOffline + int _maxConcDownloads -> _maxConcDownloads + java.lang.String _trackingURL -> _trackingURL + java.util.Set _trackingPcts -> _trackingPcts + java.lang.String _trackingCookieName -> _trackingCookieName + java.lang.String _trackingCookieProperty -> _trackingCookieProperty + java.lang.String _trackingURLSuffix -> _trackingURLSuffix + java.lang.String _trackingGAHash -> _trackingGAHash + long _trackingStart -> _trackingStart + int _trackingId -> _trackingId + java.lang.String _javaVersionProp -> _javaVersionProp + java.lang.String _javaVersionRegex -> _javaVersionRegex + long _javaMinVersion -> _javaMinVersion + long _javaMaxVersion -> _javaMaxVersion + boolean _javaExactVersionRequired -> _javaExactVersionRequired + java.lang.String _javaLocation -> _javaLocation + java.util.List _codes -> _codes + java.util.List _resources -> _resources + boolean _useCodeCache -> _useCodeCache + int _codeCacheRetentionDays -> _codeCacheRetentionDays + java.util.Map _auxgroups -> _auxgroups + java.util.Map _auxactive -> _auxactive + java.util.List _jvmargs -> _jvmargs + java.util.List _appargs -> _appargs + java.lang.String[] _optimumJvmArgs -> _optimumJvmArgs + java.util.List _txtJvmArgs -> _txtJvmArgs + boolean _warnedAboutSetLastModified -> _warnedAboutSetLastModified + java.nio.channels.FileLock _lock -> _lock + java.nio.channels.FileChannel _lockChannel -> _lockChannel + java.util.Random _rando -> _rando + java.lang.String[] EMPTY_STRING_ARRAY -> EMPTY_STRING_ARRAY + java.lang.String ENV_VAR_PREFIX -> ENV_VAR_PREFIX + java.util.regex.Pattern ENV_VAR_PATTERN -> ENV_VAR_PATTERN + void (com.threerings.getdown.data.EnvConfig) -> + java.io.File getAppDir() -> getAppDir + boolean useCodeCache() -> useCodeCache + int getCodeCacheRetentionDays() -> getCodeCacheRetentionDays + int maxConcurrentDownloads() -> maxConcurrentDownloads + com.threerings.getdown.data.Resource getConfigResource() -> getConfigResource + java.util.List getCodeResources() -> getCodeResources + java.util.List getResources() -> getResources + java.lang.String getDigest(com.threerings.getdown.data.Resource) -> getDigest + java.util.List getAllActiveResources() -> getAllActiveResources + com.threerings.getdown.data.Application$AuxGroup getAuxGroup(java.lang.String) -> getAuxGroup + java.lang.Iterable getAuxGroups() -> getAuxGroups + boolean isAuxGroupActive(java.lang.String) -> isAuxGroupActive + java.util.List getActiveCodeResources() -> getActiveCodeResources + java.util.List getNativeResources() -> getNativeResources + java.util.List getActiveResources() -> getActiveResources + com.threerings.getdown.data.Resource getPatchResource(java.lang.String) -> getPatchResource + com.threerings.getdown.data.Resource getJavaVMResource() -> getJavaVMResource + com.threerings.getdown.data.Resource getFullResource() -> getFullResource + java.net.URL getTrackingURL(java.lang.String) -> getTrackingURL + java.net.URL getTrackingProgressURL(int) -> getTrackingProgressURL + java.lang.String getTrackingCookieName() -> getTrackingCookieName + java.lang.String getTrackingCookieProperty() -> getTrackingCookieProperty + com.threerings.getdown.util.Config init(boolean) -> init + void fillAssignmentListFromPairs(java.lang.String,java.util.List) -> fillAssignmentListFromPairs + java.net.URL getRemoteURL(java.lang.String) -> getRemoteURL + java.io.File getLocalPath(java.lang.String) -> getLocalPath + boolean haveValidJavaVersion() -> haveValidJavaVersion + boolean hasOptimumJvmArgs() -> hasOptimumJvmArgs + boolean allowOffline() -> allowOffline + void attemptRecovery(com.threerings.getdown.data.Application$StatusDisplay) -> attemptRecovery + void updateMetadata() -> updateMetadata + java.lang.Process createProcess(boolean) -> createProcess + java.lang.String[] createEnvironment() -> createEnvironment + void invokeDirect() -> invokeDirect + java.lang.String processArg(java.lang.String) -> processArg + boolean verifyMetadata(com.threerings.getdown.data.Application$StatusDisplay) -> verifyMetadata + void verifyResources(com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> verifyResources + void verifyResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> verifyResource + void unpackResources(com.threerings.getdown.util.ProgressObserver,java.util.Set) -> unpackResources + void clearValidationMarkers() -> clearValidationMarkers + long getVersion() -> getVersion + java.net.URL createVAppBase(long) -> createVAppBase + void clearValidationMarkers(java.util.Iterator) -> clearValidationMarkers + void downloadConfigFile() -> downloadConfigFile + boolean lockForUpdates() -> lockForUpdates + void releaseLock() -> releaseLock + void downloadDigestFiles() -> downloadDigestFiles + void downloadControlFile(java.lang.String,int) -> downloadControlFile + java.io.File downloadFile(java.lang.String) -> downloadFile + com.threerings.getdown.data.Resource createResource(java.lang.String,java.util.EnumSet) -> createResource + void addAll(java.lang.String[],java.util.List) -> addAll + java.util.List intsToList(int[]) -> intsToList + java.util.List stringsToList(java.lang.String[]) -> stringsToList + void parseResources(com.threerings.getdown.util.Config,java.lang.String,java.util.EnumSet,java.util.List) -> parseResources + java.lang.String getGATrackingCode() -> getGATrackingCode + java.lang.String encodePath(java.lang.String) -> encodePath + java.io.File getLocalPath(java.io.File,java.lang.String) -> getLocalPath + void access$000(com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) -> access$000 + void () -> +com.threerings.getdown.data.Application$1 -> com.threerings.getdown.data.a: + com.threerings.getdown.data.Application this$0 -> a + void (com.threerings.getdown.data.Application,java.net.URL[],java.lang.ClassLoader) -> + java.security.PermissionCollection getPermissions(java.security.CodeSource) -> getPermissions +com.threerings.getdown.data.Application$2 -> com.threerings.getdown.data.b: + java.util.concurrent.BlockingQueue val$actions -> b + com.threerings.getdown.util.ProgressObserver val$fobs -> a + com.threerings.getdown.data.Application this$0 -> c + void (com.threerings.getdown.data.Application,java.util.concurrent.BlockingQueue,com.threerings.getdown.util.ProgressObserver) -> + void progress(int) -> progress +com.threerings.getdown.data.Application$2$1 -> com.threerings.getdown.data.c: + int val$percent -> a + com.threerings.getdown.data.Application$2 this$1 -> b + void (com.threerings.getdown.data.Application$2,int) -> + void run() -> run +com.threerings.getdown.data.Application$3 -> com.threerings.getdown.data.d: + com.threerings.getdown.data.Resource val$rsrc -> b + com.threerings.getdown.util.ProgressAggregator val$pagg -> c + int val$index -> d + int[] val$fAlreadyValid -> e + java.util.Set val$unpackedAsync -> f + java.util.Set val$toInstallAsync -> g + java.util.Set val$toDownloadAsync -> h + java.util.concurrent.BlockingQueue val$actions -> i + int[] val$completed -> a + com.threerings.getdown.data.Application this$0 -> j + void (com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressAggregator,int,int[],java.util.Set,java.util.Set,java.util.Set,java.util.concurrent.BlockingQueue,int[]) -> + void run() -> run +com.threerings.getdown.data.Application$3$1 -> com.threerings.getdown.data.e: + com.threerings.getdown.data.Application$3 this$1 -> a + void (com.threerings.getdown.data.Application$3) -> + void run() -> run +com.threerings.getdown.data.Application$AuxGroup -> com.threerings.getdown.data.Application$AuxGroup: + java.lang.String name -> name + java.util.List codes -> codes + java.util.List rsrcs -> rsrcs + void (java.lang.String,java.util.List,java.util.List) -> +com.threerings.getdown.data.Application$StatusDisplay -> com.threerings.getdown.data.Application$StatusDisplay: + void updateStatus(java.lang.String) -> updateStatus +com.threerings.getdown.data.Application$UpdateInterface -> com.threerings.getdown.data.Application$UpdateInterface: + java.lang.String name -> name + int background -> background + java.util.List rotatingBackgrounds -> rotatingBackgrounds + java.lang.String errorBackground -> errorBackground + java.util.List iconImages -> iconImages + java.lang.String backgroundImage -> backgroundImage + java.lang.String progressImage -> progressImage + com.threerings.getdown.util.Rectangle progress -> progress + int progressText -> progressText + int progressBar -> progressBar + com.threerings.getdown.util.Rectangle status -> status + int statusText -> statusText + int textShadow -> textShadow + java.lang.String installError -> installError + com.threerings.getdown.util.Rectangle patchNotes -> patchNotes + java.lang.String patchNotesUrl -> patchNotesUrl + boolean hideDecorations -> hideDecorations + boolean hideProgressText -> hideProgressText + int minShowSeconds -> minShowSeconds + java.util.Map stepPercentages -> stepPercentages + java.lang.String toString() -> toString + void (com.threerings.getdown.util.Config) -> +com.threerings.getdown.data.Application$UpdateInterface$Step -> com.threerings.getdown.data.Application$UpdateInterface$Step: + com.threerings.getdown.data.Application$UpdateInterface$Step UPDATE_JAVA -> UPDATE_JAVA + com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_METADATA -> VERIFY_METADATA + com.threerings.getdown.data.Application$UpdateInterface$Step DOWNLOAD -> DOWNLOAD + com.threerings.getdown.data.Application$UpdateInterface$Step PATCH -> PATCH + com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_RESOURCES -> VERIFY_RESOURCES + com.threerings.getdown.data.Application$UpdateInterface$Step REDOWNLOAD_RESOURCES -> REDOWNLOAD_RESOURCES + com.threerings.getdown.data.Application$UpdateInterface$Step UNPACK -> UNPACK + com.threerings.getdown.data.Application$UpdateInterface$Step LAUNCH -> LAUNCH + java.util.List defaultPercents -> defaultPercents + com.threerings.getdown.data.Application$UpdateInterface$Step[] $VALUES -> $VALUES + com.threerings.getdown.data.Application$UpdateInterface$Step[] values() -> values + com.threerings.getdown.data.Application$UpdateInterface$Step valueOf(java.lang.String) -> valueOf + void (java.lang.String,int,int[]) -> + void () -> +com.threerings.getdown.data.Build -> com.threerings.getdown.data.Build: + void () -> + java.lang.String time() -> time + java.lang.String version() -> version + java.util.List hostWhitelist() -> hostWhitelist +com.threerings.getdown.data.ClassPath -> com.threerings.getdown.data.ClassPath: + java.util.Set _classPathEntries -> _classPathEntries + void (java.util.LinkedHashSet) -> + java.lang.String asArgumentString() -> asArgumentString + java.net.URL[] asUrls() -> asUrls + java.util.Set getClassPathEntries() -> getClassPathEntries + java.net.URL getURL(java.io.File) -> getURL +com.threerings.getdown.data.Digest -> com.threerings.getdown.data.Digest: + int VERSION -> VERSION + java.util.HashMap _digests -> _digests + java.lang.String _metaDigest -> _metaDigest + java.lang.String FILE_NAME -> FILE_NAME + java.lang.String FILE_SUFFIX -> FILE_SUFFIX + java.lang.String digestFile(int) -> digestFile + java.lang.String sigAlgorithm(int) -> sigAlgorithm + void createDigest(int,java.util.List,java.io.File) -> createDigest + java.security.MessageDigest getMessageDigest(int) -> getMessageDigest + void (java.io.File,boolean) -> + void (java.io.File,int,boolean) -> + java.lang.String getMetaDigest() -> getMetaDigest + boolean validateResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver) -> validateResource + java.lang.String getDigest(com.threerings.getdown.data.Resource) -> getDigest + void note(java.lang.StringBuilder,java.lang.String,java.lang.String) -> note +com.threerings.getdown.data.Digest$1 -> com.threerings.getdown.data.f: + int val$fversion -> a + java.util.Map val$digests -> b + com.threerings.getdown.data.Resource val$rsrc -> c + java.util.concurrent.BlockingQueue val$completed -> d + void (int,java.util.Map,com.threerings.getdown.data.Resource,java.util.concurrent.BlockingQueue) -> + void run() -> run +com.threerings.getdown.data.EnvConfig -> com.threerings.getdown.data.EnvConfig: + java.io.File appDir -> appDir + java.lang.String appId -> appId + java.lang.String appBase -> appBase + java.util.List certs -> certs + java.util.List appArgs -> appArgs + java.lang.String USER_HOME_KEY -> USER_HOME_KEY + com.threerings.getdown.data.EnvConfig create(java.lang.String[],java.util.List) -> create + void (java.io.File) -> + void (java.io.File,java.lang.String,java.lang.String,java.util.List,java.util.List) -> +com.threerings.getdown.data.EnvConfig$Note -> com.threerings.getdown.data.EnvConfig$Note: + com.threerings.getdown.data.EnvConfig$Note$Level level -> level + java.lang.String message -> message + com.threerings.getdown.data.EnvConfig$Note info(java.lang.String) -> info + com.threerings.getdown.data.EnvConfig$Note warn(java.lang.String) -> warn + com.threerings.getdown.data.EnvConfig$Note error(java.lang.String) -> error + void (com.threerings.getdown.data.EnvConfig$Note$Level,java.lang.String) -> +com.threerings.getdown.data.EnvConfig$Note$Level -> com.threerings.getdown.data.EnvConfig$Note$Level: + com.threerings.getdown.data.EnvConfig$Note$Level INFO -> INFO + com.threerings.getdown.data.EnvConfig$Note$Level WARN -> WARN + com.threerings.getdown.data.EnvConfig$Note$Level ERROR -> ERROR + com.threerings.getdown.data.EnvConfig$Note$Level[] $VALUES -> $VALUES + com.threerings.getdown.data.EnvConfig$Note$Level[] values() -> values + com.threerings.getdown.data.EnvConfig$Note$Level valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.data.PathBuilder -> com.threerings.getdown.data.PathBuilder: + java.lang.String CODE_CACHE_DIR -> CODE_CACHE_DIR + java.lang.String NATIVE_CACHE_DIR -> NATIVE_CACHE_DIR + void () -> + com.threerings.getdown.data.ClassPath buildClassPath(com.threerings.getdown.data.Application) -> buildClassPath + com.threerings.getdown.data.ClassPath buildDefaultClassPath(com.threerings.getdown.data.Application) -> buildDefaultClassPath + com.threerings.getdown.data.ClassPath buildCachedClassPath(com.threerings.getdown.data.Application) -> buildCachedClassPath + com.threerings.getdown.data.ClassPath buildLibsPath(com.threerings.getdown.data.Application,boolean) -> buildLibsPath +com.threerings.getdown.data.Properties -> com.threerings.getdown.data.Properties: + java.lang.String GETDOWN -> GETDOWN + java.lang.String CONNECT_PORT -> CONNECT_PORT + void () -> +com.threerings.getdown.data.Resource -> com.threerings.getdown.data.Resource: + java.util.EnumSet NORMAL -> NORMAL + java.util.EnumSet UNPACK -> UNPACK + java.util.EnumSet EXEC -> EXEC + java.util.EnumSet PRELOAD -> PRELOAD + java.util.EnumSet NATIVE -> NATIVE + java.lang.String _path -> _path + java.net.URL _remote -> _remote + java.io.File _local -> _local + java.io.File _localNew -> _localNew + java.io.File _marker -> _marker + java.io.File _unpacked -> _unpacked + java.util.EnumSet _attrs -> _attrs + boolean _isJar -> _isJar + boolean _isPacked200Jar -> _isPacked200Jar + java.util.Comparator ENTRY_COMP -> ENTRY_COMP + int DIGEST_BUFFER_SIZE -> DIGEST_BUFFER_SIZE + java.lang.String computeDigest(int,java.io.File,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) -> computeDigest + void (java.lang.String,java.net.URL,java.io.File,java.util.EnumSet) -> + java.lang.String getPath() -> getPath + java.io.File getLocal() -> getLocal + java.io.File getLocalNew() -> getLocalNew + java.io.File getUnpacked() -> getUnpacked + java.io.File getFinalTarget() -> getFinalTarget + java.net.URL getRemote() -> getRemote + boolean shouldUnpack() -> shouldUnpack + boolean shouldPredownload() -> shouldPredownload + boolean isNative() -> isNative + java.lang.String computeDigest(int,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) -> computeDigest + boolean isMarkedValid() -> isMarkedValid + void markAsValid() -> markAsValid + void clearMarker() -> clearMarker + void install(boolean) -> install + void unpack() -> unpack + void applyAttrs() -> applyAttrs + void erase() -> erase + int compareTo(com.threerings.getdown.data.Resource) -> compareTo + boolean equals(java.lang.Object) -> equals + int hashCode() -> hashCode + java.lang.String toString() -> toString + void updateProgress(com.threerings.getdown.util.ProgressObserver,long,long) -> updateProgress + boolean isJar(java.lang.String) -> isJar + boolean isPacked200Jar(java.lang.String) -> isPacked200Jar + int compareTo(java.lang.Object) -> compareTo + void () -> +com.threerings.getdown.data.Resource$1 -> com.threerings.getdown.data.g: + void () -> + int compare(java.lang.Object,java.lang.Object) -> compare +com.threerings.getdown.data.Resource$Attr -> com.threerings.getdown.data.Resource$Attr: + com.threerings.getdown.data.Resource$Attr UNPACK -> UNPACK + com.threerings.getdown.data.Resource$Attr CLEAN -> CLEAN + com.threerings.getdown.data.Resource$Attr EXEC -> EXEC + com.threerings.getdown.data.Resource$Attr PRELOAD -> PRELOAD + com.threerings.getdown.data.Resource$Attr NATIVE -> NATIVE + com.threerings.getdown.data.Resource$Attr[] $VALUES -> $VALUES + com.threerings.getdown.data.Resource$Attr[] values() -> values + com.threerings.getdown.data.Resource$Attr valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.data.SysProps -> com.threerings.getdown.data.SysProps: + void () -> + java.lang.String appDir() -> appDir + java.lang.String appId() -> appId + java.lang.String appBase() -> appBase + boolean noLogRedir() -> noLogRedir + java.lang.String appbaseDomain() -> appbaseDomain + java.lang.String appbaseOverride() -> appbaseOverride + boolean silent() -> silent + boolean launchInSilent() -> launchInSilent + boolean noUpdate() -> noUpdate + boolean noInstall() -> noInstall + int startDelay() -> startDelay + boolean noUnpack() -> noUnpack + boolean direct() -> direct + int connectTimeout() -> connectTimeout + int readTimeout() -> readTimeout + int threadPoolSize() -> threadPoolSize + long parseJavaVersion(java.lang.String,java.lang.String) -> parseJavaVersion + java.lang.String overrideAppbase(java.lang.String) -> overrideAppbase + java.lang.String replaceDomain(java.lang.String) -> replaceDomain +com.threerings.getdown.launcher.AbortPanel -> com.threerings.getdown.launcher.AbortPanel: + com.threerings.getdown.launcher.Getdown _getdown -> _getdown + java.util.ResourceBundle _msgs -> _msgs + void (com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> + java.awt.Dimension getPreferredSize() -> getPreferredSize + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed + java.lang.String get(java.lang.String) -> get +com.threerings.getdown.launcher.Getdown -> com.threerings.getdown.launcher.Getdown: + com.threerings.getdown.util.ProgressObserver _progobs -> _progobs + com.threerings.getdown.data.Application _app -> _app + com.threerings.getdown.data.Application$UpdateInterface _ifc -> _ifc + java.util.ResourceBundle _msgs -> _msgs + java.awt.Container _container -> _container + javax.swing.JLayeredPane _layers -> _layers + com.threerings.getdown.launcher.StatusPanel _status -> _status + javax.swing.JButton _patchNotes -> _patchNotes + com.threerings.getdown.launcher.AbortPanel _abort -> _abort + com.threerings.getdown.launcher.RotatingBackgrounds _background -> _background + boolean _dead -> _dead + boolean _silent -> _silent + boolean _launchInSilent -> _launchInSilent + boolean _noUpdate -> _noUpdate + long _startup -> _startup + java.util.Set _toInstallResources -> _toInstallResources + boolean _readyToInstall -> _readyToInstall + boolean _enableTracking -> _enableTracking + int _reportedProgress -> _reportedProgress + int _delay -> _delay + int _stepMaxPercent -> _stepMaxPercent + int _stepMinPercent -> _stepMinPercent + int _lastGlobalPercent -> _lastGlobalPercent + int _uiDisplayPercent -> _uiDisplayPercent + int MAX_LOOPS -> MAX_LOOPS + long FALLBACK_CHECK_TIME -> FALLBACK_CHECK_TIME + void (com.threerings.getdown.data.EnvConfig) -> + boolean isUpdateAvailable() -> isUpdateAvailable + void install() -> install + void run() -> run + void configProxy(java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> configProxy + boolean detectProxy() -> detectProxy + void readConfig(boolean) -> readConfig + void doPredownloads(java.util.Collection) -> doPredownloads + void getdown() -> getdown + void updateStatus(java.lang.String) -> updateStatus + java.awt.image.BufferedImage loadImage(java.lang.String) -> loadImage + void updateJava() -> updateJava + void update() -> update + void download(java.util.Collection) -> download + void launch() -> launch + void createInterfaceAsync(boolean) -> createInterfaceAsync + void initInterface() -> initInterface + com.threerings.getdown.launcher.RotatingBackgrounds getBackground() -> getBackground + java.awt.Image getProgressImage() -> getProgressImage + void handleWindowClose() -> handleWindowClose + void fail(java.lang.String) -> fail + void setStep(com.threerings.getdown.data.Application$UpdateInterface$Step) -> setStep + int stepToGlobalPercent(int) -> stepToGlobalPercent + void setStatusAsync(java.lang.String,int,long,boolean) -> setStatusAsync + void reportTrackingEvent(java.lang.String,int) -> reportTrackingEvent + java.awt.Container createContainer() -> createContainer + void configureContainer() -> configureContainer + void showContainer() -> showContainer + void disposeContainer() -> disposeContainer + boolean invokeDirect() -> invokeDirect + void showDocument(java.lang.String) -> showDocument + void exit(int) -> exit + void copyStream(java.io.InputStream,java.io.PrintStream) -> copyStream + java.awt.Image loadImage(java.lang.String) -> loadImage +com.threerings.getdown.launcher.Getdown$1 -> com.threerings.getdown.launcher.a: + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$2 -> com.threerings.getdown.launcher.b: + int _lastCheck -> a + com.threerings.getdown.launcher.Getdown this$0 -> b + void (com.threerings.getdown.launcher.Getdown,java.net.Proxy) -> + void resolvingDownloads() -> resolvingDownloads + void downloadProgress(int,long) -> downloadProgress + void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) -> downloadFailed +com.threerings.getdown.launcher.Getdown$3 -> com.threerings.getdown.launcher.c: + java.io.InputStream val$stderr -> a + com.threerings.getdown.launcher.Getdown this$0 -> b + void (com.threerings.getdown.launcher.Getdown,java.io.InputStream) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$4 -> com.threerings.getdown.launcher.d: + boolean val$reinit -> b + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown,boolean) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$4$1 -> com.threerings.getdown.launcher.e: + com.threerings.getdown.launcher.Getdown$4 this$1 -> a + void (com.threerings.getdown.launcher.Getdown$4,java.lang.String) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.launcher.Getdown$5 -> com.threerings.getdown.launcher.f: + java.lang.String val$message -> a + int val$percent -> b + long val$remaining -> c + com.threerings.getdown.launcher.Getdown this$0 -> d + void (com.threerings.getdown.launcher.Getdown,java.lang.String,int,long) -> + void run() -> run +com.threerings.getdown.launcher.Getdown$6 -> com.threerings.getdown.launcher.g: + com.threerings.getdown.launcher.Getdown this$0 -> a + void (com.threerings.getdown.launcher.Getdown) -> + void progress(int) -> progress +com.threerings.getdown.launcher.Getdown$ProgressReporter -> com.threerings.getdown.launcher.Getdown$ProgressReporter: + java.net.URL _url -> _url + com.threerings.getdown.launcher.Getdown this$0 -> this$0 + void (com.threerings.getdown.launcher.Getdown,java.net.URL) -> + void run() -> run +com.threerings.getdown.launcher.GetdownApp -> com.threerings.getdown.launcher.GetdownApp: + void () -> + void main(java.lang.String[]) -> main + com.threerings.getdown.launcher.Getdown start(java.lang.String[]) -> start +com.threerings.getdown.launcher.GetdownApp$1 -> com.threerings.getdown.launcher.h: + javax.swing.JFrame _frame -> a + void (com.threerings.getdown.data.EnvConfig) -> + java.awt.Container createContainer() -> createContainer + void configureContainer() -> configureContainer + void showContainer() -> showContainer + void disposeContainer() -> disposeContainer + void showDocument(java.lang.String) -> showDocument + void exit(int) -> exit + void fail(java.lang.String) -> fail +com.threerings.getdown.launcher.GetdownApp$1$1 -> com.threerings.getdown.launcher.i: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void windowClosing(java.awt.event.WindowEvent) -> windowClosing +com.threerings.getdown.launcher.GetdownApp$1$2 -> com.threerings.getdown.launcher.j: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.launcher.GetdownApp$1$3 -> com.threerings.getdown.launcher.k: + com.threerings.getdown.launcher.GetdownApp$1 this$0 -> a + void (com.threerings.getdown.launcher.GetdownApp$1) -> + void run() -> run +com.threerings.getdown.launcher.GetdownApp$2 -> com.threerings.getdown.launcher.l: + int[] $SwitchMap$com$threerings$getdown$data$EnvConfig$Note$Level -> a + void () -> +com.threerings.getdown.launcher.MultipleGetdownRunning -> com.threerings.getdown.launcher.MultipleGetdownRunning: + void () -> +com.threerings.getdown.launcher.ProxyPanel -> com.threerings.getdown.launcher.ProxyPanel: + com.threerings.getdown.launcher.Getdown _getdown -> _getdown + java.util.ResourceBundle _msgs -> _msgs + javax.swing.JTextField _host -> _host + javax.swing.JTextField _port -> _port + javax.swing.JCheckBox _useAuth -> _useAuth + javax.swing.JTextField _username -> _username + javax.swing.JPasswordField _password -> _password + void (com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) -> + void setProxy(java.lang.String,java.lang.String) -> setProxy + void addNotify() -> addNotify + java.awt.Dimension getPreferredSize() -> getPreferredSize + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed + java.lang.String get(java.lang.String) -> get + java.awt.Dimension clampWidth(java.awt.Dimension,int) -> clampWidth +com.threerings.getdown.launcher.ProxyPanel$1 -> com.threerings.getdown.launcher.m: + com.threerings.getdown.launcher.ProxyPanel this$0 -> a + void (com.threerings.getdown.launcher.ProxyPanel) -> + void itemStateChanged(java.awt.event.ItemEvent) -> itemStateChanged +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField -> com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: + void (java.lang.String) -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField -> com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: + void () -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyPanel$SaneTextField -> com.threerings.getdown.launcher.ProxyPanel$SaneTextField: + void () -> + java.awt.Dimension getPreferredSize() -> getPreferredSize +com.threerings.getdown.launcher.ProxyUtil -> com.threerings.getdown.launcher.ProxyUtil: + java.lang.String PROXY_REGISTRY -> PROXY_REGISTRY + void () -> + boolean autoDetectProxy(com.threerings.getdown.data.Application) -> autoDetectProxy + boolean canLoadWithoutProxy(java.net.URL) -> canLoadWithoutProxy + void configProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> configProxy + java.lang.String[] loadProxy(com.threerings.getdown.data.Application) -> loadProxy + void saveProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String) -> saveProxy + void initProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) -> initProxy +com.threerings.getdown.launcher.ProxyUtil$1 -> com.threerings.getdown.launcher.n: + java.lang.String val$fuser -> a + char[] val$fpass -> b + void (java.lang.String,char[]) -> + java.net.PasswordAuthentication getPasswordAuthentication() -> getPasswordAuthentication +com.threerings.getdown.launcher.RotatingBackgrounds -> com.threerings.getdown.launcher.RotatingBackgrounds: + long currentDisplayStart -> currentDisplayStart + int current -> current + java.awt.Image[] images -> images + java.awt.Image errorImage -> errorImage + int[] percentages -> percentages + int[] minDisplayTime -> minDisplayTime + void () -> + void (java.awt.Image) -> + void (java.util.List,java.lang.String,com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader) -> + java.awt.Image getImage(int) -> getImage + java.awt.Image getErrorImage() -> getErrorImage + int getNumImages() -> getNumImages + void makeEmpty() -> makeEmpty +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader -> com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader: + java.awt.Image loadImage(java.lang.String) -> loadImage +com.threerings.getdown.launcher.StatusPanel -> com.threerings.getdown.launcher.StatusPanel: + java.awt.Image _barimg -> _barimg + com.threerings.getdown.launcher.RotatingBackgrounds _bg -> _bg + java.awt.Dimension _psize -> _psize + java.util.ResourceBundle _msgs -> _msgs + int _progress -> _progress + java.lang.String _status -> _status + int _statusDots -> _statusDots + boolean _displayError -> _displayError + com.samskivert.swing.Label _label -> _label + com.samskivert.swing.Label _newlab -> _newlab + com.samskivert.swing.Label _plabel -> _plabel + com.samskivert.swing.Label _newplab -> _newplab + com.samskivert.swing.Label _rlabel -> _rlabel + com.samskivert.swing.Label _newrlab -> _newrlab + com.threerings.getdown.data.Application$UpdateInterface _ifc -> _ifc + javax.swing.Timer _timer -> _timer + long[] _remain -> _remain + int _ridx -> _ridx + com.samskivert.util.Throttle _rthrottle -> _rthrottle + java.awt.Font FONT -> FONT + void (java.util.ResourceBundle) -> + void init(com.threerings.getdown.data.Application$UpdateInterface,com.threerings.getdown.launcher.RotatingBackgrounds,java.awt.Image) -> init + boolean imageUpdate(java.awt.Image,int,int,int,int,int) -> imageUpdate + void setProgress(int,long) -> setProgress + void setStatus(java.lang.String,boolean) -> setStatus + void stopThrob() -> stopThrob + void addNotify() -> addNotify + void removeNotify() -> removeNotify + void paintComponent(java.awt.Graphics) -> paintComponent + java.awt.Dimension getPreferredSize() -> getPreferredSize + void updateStatusLabel() -> updateStatusLabel + int getStatusY(com.samskivert.swing.Label) -> getStatusY + com.samskivert.swing.Label createLabel(java.lang.String,java.awt.Color) -> createLabel + java.lang.String xlate(java.lang.String) -> xlate + java.lang.String get(java.lang.String,java.lang.String[]) -> get + java.lang.String get(java.lang.String) -> get + void () -> +com.threerings.getdown.launcher.StatusPanel$1 -> com.threerings.getdown.launcher.o: + com.threerings.getdown.launcher.StatusPanel this$0 -> a + void (com.threerings.getdown.launcher.StatusPanel) -> + void actionPerformed(java.awt.event.ActionEvent) -> actionPerformed +com.threerings.getdown.net.Downloader -> com.threerings.getdown.net.Downloader: + java.util.Map _sizes -> _sizes + java.util.Map _downloaded -> _downloaded + long _start -> _start + long _bytesPerSecond -> _bytesPerSecond + long _lastUpdate -> _lastUpdate + com.threerings.getdown.net.Downloader$State _state -> _state + long UPDATE_DELAY -> UPDATE_DELAY + void () -> + boolean download(java.util.Collection,int) -> download + void abort() -> abort + void resolvingDownloads() -> resolvingDownloads + void downloadProgress(int,long) -> downloadProgress + void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) -> downloadFailed + long checkSize(com.threerings.getdown.data.Resource) -> checkSize + void reportProgress(com.threerings.getdown.data.Resource,long,long) -> reportProgress + long sum(java.lang.Iterable) -> sum + void download(com.threerings.getdown.data.Resource) -> download +com.threerings.getdown.net.Downloader$1 -> com.threerings.getdown.net.a: + com.threerings.getdown.data.Resource val$rsrc -> a + com.threerings.getdown.net.Downloader this$0 -> b + void (com.threerings.getdown.net.Downloader,com.threerings.getdown.data.Resource) -> + void run() -> run +com.threerings.getdown.net.Downloader$State -> com.threerings.getdown.net.Downloader$State: + com.threerings.getdown.net.Downloader$State DOWNLOADING -> DOWNLOADING + com.threerings.getdown.net.Downloader$State COMPLETE -> COMPLETE + com.threerings.getdown.net.Downloader$State FAILED -> FAILED + com.threerings.getdown.net.Downloader$State ABORTED -> ABORTED + com.threerings.getdown.net.Downloader$State[] $VALUES -> $VALUES + com.threerings.getdown.net.Downloader$State[] values() -> values + com.threerings.getdown.net.Downloader$State valueOf(java.lang.String) -> valueOf + void (java.lang.String,int) -> + void () -> +com.threerings.getdown.net.HTTPDownloader -> com.threerings.getdown.net.HTTPDownloader: + java.net.Proxy _proxy -> _proxy + void (java.net.Proxy) -> + long checkSize(com.threerings.getdown.data.Resource) -> checkSize + void download(com.threerings.getdown.data.Resource) -> download +com.threerings.getdown.spi.ProxyAuth -> com.threerings.getdown.spi.ProxyAuth: + com.threerings.getdown.spi.ProxyAuth$Credentials loadCredentials(java.lang.String) -> loadCredentials + void saveCredentials(java.lang.String,java.lang.String,java.lang.String) -> saveCredentials +com.threerings.getdown.spi.ProxyAuth$Credentials -> com.threerings.getdown.spi.ProxyAuth$Credentials: + java.lang.String username -> username + java.lang.String password -> password + void (java.lang.String,java.lang.String) -> +com.threerings.getdown.tools.Differ -> com.threerings.getdown.tools.Differ: + void () -> + void createDiff(java.io.File,java.io.File,boolean) -> createDiff + void createPatch(java.io.File,java.util.ArrayList,java.util.ArrayList,boolean) -> createPatch + java.io.File rebuildJar(java.io.File) -> rebuildJar + void jarDiff(java.io.File,java.io.File,java.util.jar.JarOutputStream) -> jarDiff + void main(java.lang.String[]) -> main + void pipe(java.io.File,java.util.jar.JarOutputStream) -> pipe +com.threerings.getdown.tools.Digester -> com.threerings.getdown.tools.Digester: + void () -> + void main(java.lang.String[]) -> main + void createDigests(java.io.File,java.io.File,java.lang.String,java.lang.String) -> createDigests + void createDigest(int,java.io.File) -> createDigest + void signDigest(int,java.io.File,java.io.File,java.lang.String,java.lang.String) -> signDigest +com.threerings.getdown.tools.JarDiff -> com.threerings.getdown.tools.JarDiff: + int DEFAULT_READ_SIZE -> DEFAULT_READ_SIZE + byte[] newBytes -> newBytes + byte[] oldBytes -> oldBytes + boolean _debug -> _debug + void () -> + void createPatch(java.lang.String,java.lang.String,java.io.OutputStream,boolean) -> createPatch + void createIndex(java.util.jar.JarOutputStream,java.util.List,java.util.Map) -> createIndex + java.io.Writer writeEscapedString(java.io.Writer,java.lang.String) -> writeEscapedString + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,com.threerings.getdown.tools.JarDiff$JarFile2) -> writeEntry + byte[] access$000() -> access$000 + byte[] access$100() -> access$100 + boolean access$200() -> access$200 + void () -> +com.threerings.getdown.tools.JarDiff$JarFile2 -> com.threerings.getdown.tools.JarDiff$a: + java.util.jar.JarFile _jar -> a + java.util.List _entries -> b + java.util.HashMap _nameToEntryMap -> c + java.util.HashMap _crcToEntryMap -> d + void (java.lang.String) -> + java.util.jar.JarFile getJarFile() -> a + java.util.Iterator iterator() -> iterator + java.util.jar.JarEntry getEntryByName(java.lang.String) -> a + boolean differs(java.io.InputStream,java.io.InputStream) -> a + boolean contains(com.threerings.getdown.tools.JarDiff$JarFile2,java.util.jar.JarEntry) -> a + java.lang.String hasSameContent(com.threerings.getdown.tools.JarDiff$JarFile2,java.util.jar.JarEntry) -> b + void index() -> b + void close() -> close +com.threerings.getdown.tools.JarDiffCodes -> com.threerings.getdown.tools.JarDiffCodes: + java.lang.String INDEX_NAME -> INDEX_NAME + java.lang.String VERSION_HEADER -> VERSION_HEADER + java.lang.String REMOVE_COMMAND -> REMOVE_COMMAND + java.lang.String MOVE_COMMAND -> MOVE_COMMAND +com.threerings.getdown.tools.JarDiffPatcher -> com.threerings.getdown.tools.JarDiffPatcher: + int DEFAULT_READ_SIZE -> DEFAULT_READ_SIZE + byte[] newBytes -> newBytes + byte[] oldBytes -> oldBytes + void () -> + void patchJar(java.lang.String,java.lang.String,java.io.File,com.threerings.getdown.util.ProgressObserver) -> patchJar + void updateObserver(com.threerings.getdown.util.ProgressObserver,double,double) -> updateObserver + void determineNameMapping(java.util.jar.JarFile,java.util.Set,java.util.Map) -> determineNameMapping + java.util.List getSubpaths(java.lang.String) -> getSubpaths + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.util.jar.JarFile) -> writeEntry + void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.io.InputStream) -> writeEntry + void () -> +com.threerings.getdown.tools.Patcher -> com.threerings.getdown.tools.Patcher: + java.lang.String CREATE -> CREATE + java.lang.String PATCH -> PATCH + java.lang.String DELETE -> DELETE + com.threerings.getdown.util.ProgressObserver _obs -> _obs + long _complete -> _complete + long _plength -> _plength + byte[] _buffer -> _buffer + int COPY_BUFFER_SIZE -> COPY_BUFFER_SIZE + void () -> + void patch(java.io.File,java.io.File,com.threerings.getdown.util.ProgressObserver) -> patch + java.lang.String strip(java.lang.String,java.lang.String) -> strip + void createFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File) -> createFile + void patchFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File,java.lang.String) -> patchFile + void updateProgress(int) -> updateProgress + void main(java.lang.String[]) -> main +com.threerings.getdown.tools.Patcher$1 -> com.threerings.getdown.tools.a: + long val$elength -> a + com.threerings.getdown.tools.Patcher this$0 -> b + void (com.threerings.getdown.tools.Patcher,long) -> + void progress(int) -> progress +com.threerings.getdown.util.Base64 -> com.threerings.getdown.util.Base64: + int DEFAULT -> DEFAULT + int NO_PADDING -> NO_PADDING + int NO_WRAP -> NO_WRAP + int CRLF -> CRLF + int URL_SAFE -> URL_SAFE + int NO_CLOSE -> NO_CLOSE + boolean $assertionsDisabled -> $assertionsDisabled + byte[] decode(java.lang.String,int) -> decode + byte[] decode(byte[],int) -> decode + byte[] decode(byte[],int,int,int) -> decode + java.lang.String encodeToString(byte[],int) -> encodeToString + java.lang.String encodeToString(byte[],int,int,int) -> encodeToString + byte[] encode(byte[],int) -> encode + byte[] encode(byte[],int,int,int) -> encode + void () -> + void () -> +com.threerings.getdown.util.Base64$Coder -> com.threerings.getdown.util.Base64$a: + byte[] output -> a + int op -> b + void () -> +com.threerings.getdown.util.Base64$Decoder -> com.threerings.getdown.util.Base64$b: + int[] DECODE -> c + int[] DECODE_WEBSAFE -> d + int state -> e + int value -> f + int[] alphabet -> g + void (int,byte[]) -> + boolean process(byte[],int,int,boolean) -> a + void () -> +com.threerings.getdown.util.Base64$Encoder -> com.threerings.getdown.util.Base64$c: + byte[] ENCODE -> f + byte[] ENCODE_WEBSAFE -> g + byte[] tail -> h + int tailLen -> i + int count -> j + boolean do_padding -> c + boolean do_newline -> d + boolean do_cr -> e + byte[] alphabet -> k + boolean $assertionsDisabled -> l + void (int,byte[]) -> + boolean process(byte[],int,int,boolean) -> a + void () -> +com.threerings.getdown.util.Color -> com.threerings.getdown.util.Color: + int CLEAR -> CLEAR + int WHITE -> WHITE + int BLACK -> BLACK + float brightness(int) -> brightness + void () -> +com.threerings.getdown.util.Config -> com.threerings.getdown.util.Config: + com.threerings.getdown.util.Config EMPTY -> EMPTY + java.util.Map _data -> _data + com.threerings.getdown.util.Config$ParseOpts createOpts(boolean) -> createOpts + java.util.List parsePairs(java.io.File,com.threerings.getdown.util.Config$ParseOpts) -> parsePairs + java.util.List parsePairs(java.io.Reader,com.threerings.getdown.util.Config$ParseOpts) -> parsePairs + com.threerings.getdown.util.Rectangle parseRect(java.lang.String,java.lang.String) -> parseRect + java.lang.Integer parseColor(java.lang.String) -> parseColor + com.threerings.getdown.util.Config parseConfig(java.io.File,com.threerings.getdown.util.Config$ParseOpts) -> parseConfig + void (java.util.Map) -> + boolean hasValue(java.lang.String) -> hasValue + java.lang.Object getRaw(java.lang.String) -> getRaw + java.lang.String getString(java.lang.String) -> getString + java.lang.String getString(java.lang.String,java.lang.String) -> getString + boolean getBoolean(java.lang.String) -> getBoolean + java.lang.String[] getMultiValue(java.lang.String) -> getMultiValue + com.threerings.getdown.util.Rectangle getRect(java.lang.String,com.threerings.getdown.util.Rectangle) -> getRect + int getInt(java.lang.String,int) -> getInt + long getLong(java.lang.String,long) -> getLong + int getColor(java.lang.String,int) -> getColor + java.lang.String[] getList(java.lang.String) -> getList + java.lang.String getUrl(java.lang.String,java.lang.String) -> getUrl + boolean checkQualifiers(java.lang.String,java.lang.String,java.lang.String) -> checkQualifiers + boolean checkQualifier(java.lang.String,java.lang.String,java.lang.String) -> checkQualifier + void () -> +com.threerings.getdown.util.Config$ParseOpts -> com.threerings.getdown.util.Config$ParseOpts: + boolean biasToKey -> biasToKey + boolean strictComments -> strictComments + java.lang.String osname -> osname + java.lang.String osarch -> osarch + void () -> +com.threerings.getdown.util.ConnectionUtil -> com.threerings.getdown.util.ConnectionUtil: + void () -> + java.net.URLConnection open(java.net.Proxy,java.net.URL,int,int) -> open + java.net.HttpURLConnection openHttp(java.net.Proxy,java.net.URL,int,int) -> openHttp +com.threerings.getdown.util.FileUtil -> com.threerings.getdown.util.FileUtil: + void () -> + boolean renameTo(java.io.File,java.io.File) -> renameTo + boolean deleteHarder(java.io.File) -> deleteHarder + boolean deleteDirHarder(java.io.File) -> deleteDirHarder + java.util.List readLines(java.io.Reader) -> readLines + void unpackJar(java.util.jar.JarFile,java.io.File,boolean) -> unpackJar + void unpackPacked200Jar(java.io.File,java.io.File) -> unpackPacked200Jar + void copy(java.io.File,java.io.File) -> copy + void makeExecutable(java.io.File) -> makeExecutable + void walkTree(java.io.File,com.threerings.getdown.util.FileUtil$Visitor) -> walkTree +com.threerings.getdown.util.FileUtil$Visitor -> com.threerings.getdown.util.FileUtil$Visitor: + void visit(java.io.File) -> visit +com.threerings.getdown.util.HostWhitelist -> com.threerings.getdown.util.HostWhitelist: + void () -> + java.net.URL verify(java.net.URL) -> verify + java.net.URL verify(java.util.List,java.net.URL) -> verify +com.threerings.getdown.util.LaunchUtil -> com.threerings.getdown.util.LaunchUtil: + java.lang.String LOCAL_JAVA_DIR -> LOCAL_JAVA_DIR + boolean _isWindows -> _isWindows + boolean _isMacOS -> _isMacOS + boolean _isLinux -> _isLinux + void () -> + boolean updateVersionAndRelaunch(java.io.File,java.lang.String,java.lang.String) -> updateVersionAndRelaunch + java.lang.String getJVMPath(java.io.File) -> getJVMPath + java.lang.String getJVMPath(java.io.File,boolean) -> getJVMPath + void upgradeGetdown(java.io.File,java.io.File,java.io.File) -> upgradeGetdown + boolean mustMonitorChildren() -> mustMonitorChildren + boolean isWindows() -> isWindows + boolean isMacOS() -> isMacOS + boolean isLinux() -> isLinux + java.lang.String checkJVMPath(java.lang.String,boolean) -> checkJVMPath + void () -> +com.threerings.getdown.util.MessageUtil -> com.threerings.getdown.util.MessageUtil: + java.lang.String TAINT_CHAR -> TAINT_CHAR + void () -> + boolean isTainted(java.lang.String) -> isTainted + java.lang.String taint(java.lang.Object) -> taint + java.lang.String untaint(java.lang.String) -> untaint + java.lang.String compose(java.lang.String,java.lang.Object[]) -> compose + java.lang.String compose(java.lang.String,java.lang.String[]) -> compose + java.lang.String tcompose(java.lang.String,java.lang.Object[]) -> tcompose + java.lang.String tcompose(java.lang.String,java.lang.String[]) -> tcompose + java.lang.String escape(java.lang.String) -> escape + java.lang.String unescape(java.lang.String) -> unescape +com.threerings.getdown.util.ProgressAggregator -> com.threerings.getdown.util.ProgressAggregator: + com.threerings.getdown.util.ProgressObserver _target -> _target + long[] _sizes -> _sizes + int[] _progress -> _progress + void (com.threerings.getdown.util.ProgressObserver,long[]) -> + com.threerings.getdown.util.ProgressObserver startElement(int) -> startElement + void updateAggProgress() -> updateAggProgress + long sum(long[]) -> sum +com.threerings.getdown.util.ProgressAggregator$1 -> com.threerings.getdown.util.a: + int val$index -> a + com.threerings.getdown.util.ProgressAggregator this$0 -> b + void (com.threerings.getdown.util.ProgressAggregator,int) -> + void progress(int) -> progress +com.threerings.getdown.util.ProgressObserver -> com.threerings.getdown.util.ProgressObserver: + void progress(int) -> progress +com.threerings.getdown.util.Rectangle -> com.threerings.getdown.util.Rectangle: + int x -> x + int y -> y + int width -> width + int height -> height + void (int,int,int,int) -> + com.threerings.getdown.util.Rectangle union(com.threerings.getdown.util.Rectangle) -> union + java.lang.String toString() -> toString +com.threerings.getdown.util.StreamUtil -> com.threerings.getdown.util.StreamUtil: + void () -> + void close(java.io.InputStream) -> close + void close(java.io.OutputStream) -> close + void close(java.io.Reader) -> close + void close(java.io.Writer) -> close + java.io.OutputStream copy(java.io.InputStream,java.io.OutputStream) -> copy + byte[] toByteArray(java.io.InputStream) -> toByteArray +com.threerings.getdown.util.StringUtil -> com.threerings.getdown.util.StringUtil: + java.lang.String XLATE -> XLATE + void () -> + boolean couldBeValidUrl(java.lang.String) -> couldBeValidUrl + boolean isBlank(java.lang.String) -> isBlank + int[] parseIntArray(java.lang.String) -> parseIntArray + java.lang.String[] parseStringArray(java.lang.String) -> parseStringArray + java.lang.String[] parseStringArray(java.lang.String,boolean) -> parseStringArray + java.lang.String deNull(java.lang.String) -> deNull + java.lang.String hexlate(byte[],int) -> hexlate + java.lang.String hexlate(byte[]) -> hexlate + java.lang.String join(java.lang.Object[]) -> join + java.lang.String join(java.lang.Object[],boolean) -> join + java.lang.String join(java.lang.Object[],java.lang.String) -> join + java.lang.String join(java.lang.Object[],java.lang.String,boolean) -> join +com.threerings.getdown.util.VersionUtil -> com.threerings.getdown.util.VersionUtil: + void () -> + long readVersion(java.io.File) -> readVersion + void writeVersion(java.io.File,long) -> writeVersion + long parseJavaVersion(java.lang.String,java.lang.String) -> parseJavaVersion + long readReleaseVersion(java.io.File,java.lang.String) -> readReleaseVersion + int parseInt(java.lang.String) -> parseInt diff --git a/getdown/src/getdown/launcher/target/proguard_seed.txt b/getdown/src/getdown/launcher/target/proguard_seed.txt new file mode 100644 index 0000000..39b31be --- /dev/null +++ b/getdown/src/getdown/launcher/target/proguard_seed.txt @@ -0,0 +1,879 @@ +ca.beq.util.win32.registry.KeyIterator +ca.beq.util.win32.registry.KeyIterator: ca.beq.util.win32.registry.RegistryKey m_key +ca.beq.util.win32.registry.KeyIterator: int m_index +ca.beq.util.win32.registry.KeyIterator: int m_hkey +ca.beq.util.win32.registry.KeyIterator: int m_maxsize +ca.beq.util.win32.registry.KeyIterator: int m_count +ca.beq.util.win32.registry.KeyIterator: KeyIterator(ca.beq.util.win32.registry.RegistryKey) +ca.beq.util.win32.registry.KeyIterator: void initializeFields() +ca.beq.util.win32.registry.KeyIterator: boolean hasNext() +ca.beq.util.win32.registry.KeyIterator: java.lang.Object next() +ca.beq.util.win32.registry.KeyIterator: java.lang.String getNext() +ca.beq.util.win32.registry.KeyIterator: void remove() +ca.beq.util.win32.registry.RegistryException +ca.beq.util.win32.registry.RegistryException: RegistryException() +ca.beq.util.win32.registry.RegistryException: RegistryException(java.lang.String) +ca.beq.util.win32.registry.RegistryKey +ca.beq.util.win32.registry.RegistryKey: boolean c_initSucceeded +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RootKey m_root +ca.beq.util.win32.registry.RegistryKey: java.lang.String m_path +ca.beq.util.win32.registry.RegistryKey: void testInitialized() +ca.beq.util.win32.registry.RegistryKey: void initialize() +ca.beq.util.win32.registry.RegistryKey: void initialize(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: boolean isInitialized() +ca.beq.util.win32.registry.RegistryKey: void checkInitialized() +ca.beq.util.win32.registry.RegistryKey: RegistryKey() +ca.beq.util.win32.registry.RegistryKey: RegistryKey(ca.beq.util.win32.registry.RootKey) +ca.beq.util.win32.registry.RegistryKey: RegistryKey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: RegistryKey(ca.beq.util.win32.registry.RootKey,java.lang.String) +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RootKey getRootKey() +ca.beq.util.win32.registry.RegistryKey: java.lang.String getPath() +ca.beq.util.win32.registry.RegistryKey: java.lang.String makePath(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.lang.String getName() +ca.beq.util.win32.registry.RegistryKey: boolean exists() +ca.beq.util.win32.registry.RegistryKey: void create() +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RegistryKey createSubkey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: void delete() +ca.beq.util.win32.registry.RegistryKey: boolean hasSubkeys() +ca.beq.util.win32.registry.RegistryKey: boolean hasSubkey(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.util.Iterator subkeys() +ca.beq.util.win32.registry.RegistryKey: java.util.Iterator values() +ca.beq.util.win32.registry.RegistryKey: boolean hasValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: boolean hasValues() +ca.beq.util.win32.registry.RegistryKey: ca.beq.util.win32.registry.RegistryValue getValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: void setValue(ca.beq.util.win32.registry.RegistryValue) +ca.beq.util.win32.registry.RegistryKey: void deleteValue(java.lang.String) +ca.beq.util.win32.registry.RegistryKey: java.lang.String toString() +ca.beq.util.win32.registry.RegistryKey: void () +ca.beq.util.win32.registry.RegistryValue +ca.beq.util.win32.registry.RegistryValue: java.lang.String m_name +ca.beq.util.win32.registry.RegistryValue: ca.beq.util.win32.registry.ValueType m_type +ca.beq.util.win32.registry.RegistryValue: java.lang.Object m_data +ca.beq.util.win32.registry.RegistryValue: RegistryValue() +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,ca.beq.util.win32.registry.ValueType,java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,boolean) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,byte) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,int) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,long) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,float) +ca.beq.util.win32.registry.RegistryValue: RegistryValue(java.lang.String,double) +ca.beq.util.win32.registry.RegistryValue: java.lang.String getName() +ca.beq.util.win32.registry.RegistryValue: void setName(java.lang.String) +ca.beq.util.win32.registry.RegistryValue: ca.beq.util.win32.registry.ValueType getType() +ca.beq.util.win32.registry.RegistryValue: void setType(ca.beq.util.win32.registry.ValueType) +ca.beq.util.win32.registry.RegistryValue: java.lang.Object getData() +ca.beq.util.win32.registry.RegistryValue: void setData(java.lang.Object) +ca.beq.util.win32.registry.RegistryValue: void setData(byte) +ca.beq.util.win32.registry.RegistryValue: void setData(boolean) +ca.beq.util.win32.registry.RegistryValue: void setData(int) +ca.beq.util.win32.registry.RegistryValue: void setData(long) +ca.beq.util.win32.registry.RegistryValue: void setData(float) +ca.beq.util.win32.registry.RegistryValue: void setData(double) +ca.beq.util.win32.registry.RegistryValue: java.lang.String getStringValue() +ca.beq.util.win32.registry.RegistryValue: java.lang.String toString() +ca.beq.util.win32.registry.RootKey +ca.beq.util.win32.registry.RootKey: java.lang.String m_name +ca.beq.util.win32.registry.RootKey: int m_value +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CLASSES_ROOT +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CURRENT_USER +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_LOCAL_MACHINE +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_USERS +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_CURRENT_CONFIG +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_PERFORMANCE_DATA +ca.beq.util.win32.registry.RootKey: ca.beq.util.win32.registry.RootKey HKEY_DYN_DATA +ca.beq.util.win32.registry.RootKey: RootKey(java.lang.String,int) +ca.beq.util.win32.registry.RootKey: int getValue() +ca.beq.util.win32.registry.RootKey: java.lang.String toString() +ca.beq.util.win32.registry.RootKey: void () +ca.beq.util.win32.registry.ValueIterator +ca.beq.util.win32.registry.ValueIterator: ca.beq.util.win32.registry.RegistryKey m_key +ca.beq.util.win32.registry.ValueIterator: int m_index +ca.beq.util.win32.registry.ValueIterator: int m_hkey +ca.beq.util.win32.registry.ValueIterator: int m_maxsize +ca.beq.util.win32.registry.ValueIterator: int m_count +ca.beq.util.win32.registry.ValueIterator: ValueIterator(ca.beq.util.win32.registry.RegistryKey) +ca.beq.util.win32.registry.ValueIterator: void initializeFields() +ca.beq.util.win32.registry.ValueIterator: boolean hasNext() +ca.beq.util.win32.registry.ValueIterator: java.lang.Object next() +ca.beq.util.win32.registry.ValueIterator: java.lang.String getNext() +ca.beq.util.win32.registry.ValueIterator: void remove() +ca.beq.util.win32.registry.ValueType +ca.beq.util.win32.registry.ValueType: java.lang.String m_name +ca.beq.util.win32.registry.ValueType: int m_value +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_NONE +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_EXPAND_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_BINARY +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD_LITTLE_ENDIAN +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_DWORD_BIG_ENDIAN +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_MULTI_SZ +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_RESOURCE_LIST +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_LINK +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_FULL_RESOURCE_DESCRIPTOR +ca.beq.util.win32.registry.ValueType: ca.beq.util.win32.registry.ValueType REG_RESOURCE_REQUIREMENTS_LIST +ca.beq.util.win32.registry.ValueType: ValueType(java.lang.String,int) +ca.beq.util.win32.registry.ValueType: int getValue() +ca.beq.util.win32.registry.ValueType: java.lang.String toString() +ca.beq.util.win32.registry.ValueType: void () +com.threerings.getdown.Log +com.threerings.getdown.Log: com.threerings.getdown.Log$Shim log +com.threerings.getdown.Log: java.lang.String DATE_FORMAT +com.threerings.getdown.Log: java.util.logging.Level[] LEVELS +com.threerings.getdown.Log: Log() +com.threerings.getdown.Log: java.lang.String format(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log: void () +com.threerings.getdown.Log$OneLineFormatter +com.threerings.getdown.Log$OneLineFormatter: java.util.Date _date +com.threerings.getdown.Log$OneLineFormatter: java.text.SimpleDateFormat _format +com.threerings.getdown.Log$OneLineFormatter: java.text.FieldPosition _fpos +com.threerings.getdown.Log$OneLineFormatter: Log$OneLineFormatter() +com.threerings.getdown.Log$OneLineFormatter: java.lang.String format(java.util.logging.LogRecord) +com.threerings.getdown.Log$Shim +com.threerings.getdown.Log$Shim: java.util.logging.Logger _impl +com.threerings.getdown.Log$Shim: Log$Shim() +com.threerings.getdown.Log$Shim: void debug(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void info(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void warning(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void error(java.lang.Object,java.lang.Object[]) +com.threerings.getdown.Log$Shim: void doLog(int,java.lang.Object,java.lang.Object[]) +com.threerings.getdown.cache.GarbageCollector +com.threerings.getdown.cache.GarbageCollector: GarbageCollector() +com.threerings.getdown.cache.GarbageCollector: void collect(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: void collectNative(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: boolean shouldDelete(java.io.File,long) +com.threerings.getdown.cache.GarbageCollector: java.io.File getLastAccessedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: boolean isLastAccessedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File getCachedFile(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File access$000(java.io.File) +com.threerings.getdown.cache.GarbageCollector: java.io.File access$100(java.io.File) +com.threerings.getdown.cache.GarbageCollector: boolean access$200(java.io.File,long) +com.threerings.getdown.cache.ResourceCache +com.threerings.getdown.cache.ResourceCache: java.io.File _cacheDir +com.threerings.getdown.cache.ResourceCache: java.lang.String LAST_ACCESSED_FILE_SUFFIX +com.threerings.getdown.cache.ResourceCache: ResourceCache(java.io.File) +com.threerings.getdown.cache.ResourceCache: void createDirectoryIfNecessary(java.io.File) +com.threerings.getdown.cache.ResourceCache: java.io.File cacheFile(java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.cache.ResourceCache: void createNewFile(java.io.File) +com.threerings.getdown.cache.ResourceCache: java.lang.String getFileSuffix(java.io.File) +com.threerings.getdown.data.Application +com.threerings.getdown.data.Application: java.lang.String CONFIG_FILE +com.threerings.getdown.data.Application: java.lang.String VERSION_FILE +com.threerings.getdown.data.Application: java.lang.String PROP_PASSTHROUGH_PREFIX +com.threerings.getdown.data.Application: java.lang.String SIGNATURE_SUFFIX +com.threerings.getdown.data.Application: java.lang.String MANIFEST_CLASS +com.threerings.getdown.data.Application: java.net.Proxy proxy +com.threerings.getdown.data.Application: com.threerings.getdown.data.EnvConfig _envc +com.threerings.getdown.data.Application: java.io.File _config +com.threerings.getdown.data.Application: com.threerings.getdown.data.Digest _digest +com.threerings.getdown.data.Application: long _version +com.threerings.getdown.data.Application: long _targetVersion +com.threerings.getdown.data.Application: java.lang.String _appbase +com.threerings.getdown.data.Application: java.net.URL _vappbase +com.threerings.getdown.data.Application: java.net.URL _latest +com.threerings.getdown.data.Application: java.lang.String _class +com.threerings.getdown.data.Application: java.lang.String _dockName +com.threerings.getdown.data.Application: java.lang.String _dockIconPath +com.threerings.getdown.data.Application: boolean _strictComments +com.threerings.getdown.data.Application: boolean _windebug +com.threerings.getdown.data.Application: boolean _allowOffline +com.threerings.getdown.data.Application: int _maxConcDownloads +com.threerings.getdown.data.Application: java.lang.String _trackingURL +com.threerings.getdown.data.Application: java.util.Set _trackingPcts +com.threerings.getdown.data.Application: java.lang.String _trackingCookieName +com.threerings.getdown.data.Application: java.lang.String _trackingCookieProperty +com.threerings.getdown.data.Application: java.lang.String _trackingURLSuffix +com.threerings.getdown.data.Application: java.lang.String _trackingGAHash +com.threerings.getdown.data.Application: long _trackingStart +com.threerings.getdown.data.Application: int _trackingId +com.threerings.getdown.data.Application: java.lang.String _javaVersionProp +com.threerings.getdown.data.Application: java.lang.String _javaVersionRegex +com.threerings.getdown.data.Application: long _javaMinVersion +com.threerings.getdown.data.Application: long _javaMaxVersion +com.threerings.getdown.data.Application: boolean _javaExactVersionRequired +com.threerings.getdown.data.Application: java.lang.String _javaLocation +com.threerings.getdown.data.Application: java.util.List _codes +com.threerings.getdown.data.Application: java.util.List _resources +com.threerings.getdown.data.Application: boolean _useCodeCache +com.threerings.getdown.data.Application: int _codeCacheRetentionDays +com.threerings.getdown.data.Application: java.util.Map _auxgroups +com.threerings.getdown.data.Application: java.util.Map _auxactive +com.threerings.getdown.data.Application: java.util.List _jvmargs +com.threerings.getdown.data.Application: java.util.List _appargs +com.threerings.getdown.data.Application: java.lang.String[] _optimumJvmArgs +com.threerings.getdown.data.Application: java.util.List _txtJvmArgs +com.threerings.getdown.data.Application: boolean _warnedAboutSetLastModified +com.threerings.getdown.data.Application: java.nio.channels.FileLock _lock +com.threerings.getdown.data.Application: java.nio.channels.FileChannel _lockChannel +com.threerings.getdown.data.Application: java.util.Random _rando +com.threerings.getdown.data.Application: java.lang.String[] EMPTY_STRING_ARRAY +com.threerings.getdown.data.Application: java.lang.String ENV_VAR_PREFIX +com.threerings.getdown.data.Application: java.util.regex.Pattern ENV_VAR_PATTERN +com.threerings.getdown.data.Application: Application(com.threerings.getdown.data.EnvConfig) +com.threerings.getdown.data.Application: java.io.File getAppDir() +com.threerings.getdown.data.Application: boolean useCodeCache() +com.threerings.getdown.data.Application: int getCodeCacheRetentionDays() +com.threerings.getdown.data.Application: int maxConcurrentDownloads() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getConfigResource() +com.threerings.getdown.data.Application: java.util.List getCodeResources() +com.threerings.getdown.data.Application: java.util.List getResources() +com.threerings.getdown.data.Application: java.lang.String getDigest(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Application: java.util.List getAllActiveResources() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Application$AuxGroup getAuxGroup(java.lang.String) +com.threerings.getdown.data.Application: java.lang.Iterable getAuxGroups() +com.threerings.getdown.data.Application: boolean isAuxGroupActive(java.lang.String) +com.threerings.getdown.data.Application: java.util.List getActiveCodeResources() +com.threerings.getdown.data.Application: java.util.List getNativeResources() +com.threerings.getdown.data.Application: java.util.List getActiveResources() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getPatchResource(java.lang.String) +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getJavaVMResource() +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource getFullResource() +com.threerings.getdown.data.Application: java.net.URL getTrackingURL(java.lang.String) +com.threerings.getdown.data.Application: java.net.URL getTrackingProgressURL(int) +com.threerings.getdown.data.Application: java.lang.String getTrackingCookieName() +com.threerings.getdown.data.Application: java.lang.String getTrackingCookieProperty() +com.threerings.getdown.data.Application: com.threerings.getdown.util.Config init(boolean) +com.threerings.getdown.data.Application: void fillAssignmentListFromPairs(java.lang.String,java.util.List) +com.threerings.getdown.data.Application: java.net.URL getRemoteURL(java.lang.String) +com.threerings.getdown.data.Application: java.io.File getLocalPath(java.lang.String) +com.threerings.getdown.data.Application: boolean haveValidJavaVersion() +com.threerings.getdown.data.Application: boolean hasOptimumJvmArgs() +com.threerings.getdown.data.Application: boolean allowOffline() +com.threerings.getdown.data.Application: void attemptRecovery(com.threerings.getdown.data.Application$StatusDisplay) +com.threerings.getdown.data.Application: void updateMetadata() +com.threerings.getdown.data.Application: java.lang.Process createProcess(boolean) +com.threerings.getdown.data.Application: java.lang.String[] createEnvironment() +com.threerings.getdown.data.Application: void invokeDirect() +com.threerings.getdown.data.Application: java.lang.String processArg(java.lang.String) +com.threerings.getdown.data.Application: boolean verifyMetadata(com.threerings.getdown.data.Application$StatusDisplay) +com.threerings.getdown.data.Application: void verifyResources(com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void verifyResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void unpackResources(com.threerings.getdown.util.ProgressObserver,java.util.Set) +com.threerings.getdown.data.Application: void clearValidationMarkers() +com.threerings.getdown.data.Application: long getVersion() +com.threerings.getdown.data.Application: java.net.URL createVAppBase(long) +com.threerings.getdown.data.Application: void clearValidationMarkers(java.util.Iterator) +com.threerings.getdown.data.Application: void downloadConfigFile() +com.threerings.getdown.data.Application: boolean lockForUpdates() +com.threerings.getdown.data.Application: void releaseLock() +com.threerings.getdown.data.Application: void downloadDigestFiles() +com.threerings.getdown.data.Application: void downloadControlFile(java.lang.String,int) +com.threerings.getdown.data.Application: java.io.File downloadFile(java.lang.String) +com.threerings.getdown.data.Application: com.threerings.getdown.data.Resource createResource(java.lang.String,java.util.EnumSet) +com.threerings.getdown.data.Application: void addAll(java.lang.String[],java.util.List) +com.threerings.getdown.data.Application: java.util.List intsToList(int[]) +com.threerings.getdown.data.Application: java.util.List stringsToList(java.lang.String[]) +com.threerings.getdown.data.Application: void parseResources(com.threerings.getdown.util.Config,java.lang.String,java.util.EnumSet,java.util.List) +com.threerings.getdown.data.Application: java.lang.String getGATrackingCode() +com.threerings.getdown.data.Application: java.lang.String encodePath(java.lang.String) +com.threerings.getdown.data.Application: java.io.File getLocalPath(java.io.File,java.lang.String) +com.threerings.getdown.data.Application: void access$000(com.threerings.getdown.data.Application,com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver,int[],java.util.Set,java.util.Set,java.util.Set) +com.threerings.getdown.data.Application: void () +com.threerings.getdown.data.Application$AuxGroup +com.threerings.getdown.data.Application$AuxGroup: java.lang.String name +com.threerings.getdown.data.Application$AuxGroup: java.util.List codes +com.threerings.getdown.data.Application$AuxGroup: java.util.List rsrcs +com.threerings.getdown.data.Application$AuxGroup: Application$AuxGroup(java.lang.String,java.util.List,java.util.List) +com.threerings.getdown.data.Application$StatusDisplay +com.threerings.getdown.data.Application$StatusDisplay: void updateStatus(java.lang.String) +com.threerings.getdown.data.Application$UpdateInterface +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String name +com.threerings.getdown.data.Application$UpdateInterface: int background +com.threerings.getdown.data.Application$UpdateInterface: java.util.List rotatingBackgrounds +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String errorBackground +com.threerings.getdown.data.Application$UpdateInterface: java.util.List iconImages +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String backgroundImage +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String progressImage +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle progress +com.threerings.getdown.data.Application$UpdateInterface: int progressText +com.threerings.getdown.data.Application$UpdateInterface: int progressBar +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle status +com.threerings.getdown.data.Application$UpdateInterface: int statusText +com.threerings.getdown.data.Application$UpdateInterface: int textShadow +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String installError +com.threerings.getdown.data.Application$UpdateInterface: com.threerings.getdown.util.Rectangle patchNotes +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String patchNotesUrl +com.threerings.getdown.data.Application$UpdateInterface: boolean hideDecorations +com.threerings.getdown.data.Application$UpdateInterface: boolean hideProgressText +com.threerings.getdown.data.Application$UpdateInterface: int minShowSeconds +com.threerings.getdown.data.Application$UpdateInterface: java.util.Map stepPercentages +com.threerings.getdown.data.Application$UpdateInterface: java.lang.String toString() +com.threerings.getdown.data.Application$UpdateInterface: Application$UpdateInterface(com.threerings.getdown.util.Config) +com.threerings.getdown.data.Application$UpdateInterface$Step +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step UPDATE_JAVA +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_METADATA +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step DOWNLOAD +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step PATCH +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step VERIFY_RESOURCES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step REDOWNLOAD_RESOURCES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step UNPACK +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step LAUNCH +com.threerings.getdown.data.Application$UpdateInterface$Step: java.util.List defaultPercents +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step[] $VALUES +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step[] values() +com.threerings.getdown.data.Application$UpdateInterface$Step: com.threerings.getdown.data.Application$UpdateInterface$Step valueOf(java.lang.String) +com.threerings.getdown.data.Application$UpdateInterface$Step: Application$UpdateInterface$Step(java.lang.String,int,int[]) +com.threerings.getdown.data.Application$UpdateInterface$Step: void () +com.threerings.getdown.data.Build +com.threerings.getdown.data.Build: Build() +com.threerings.getdown.data.Build: java.lang.String time() +com.threerings.getdown.data.Build: java.lang.String version() +com.threerings.getdown.data.Build: java.util.List hostWhitelist() +com.threerings.getdown.data.ClassPath +com.threerings.getdown.data.ClassPath: java.util.Set _classPathEntries +com.threerings.getdown.data.ClassPath: ClassPath(java.util.LinkedHashSet) +com.threerings.getdown.data.ClassPath: java.lang.String asArgumentString() +com.threerings.getdown.data.ClassPath: java.net.URL[] asUrls() +com.threerings.getdown.data.ClassPath: java.util.Set getClassPathEntries() +com.threerings.getdown.data.ClassPath: java.net.URL getURL(java.io.File) +com.threerings.getdown.data.Digest +com.threerings.getdown.data.Digest: int VERSION +com.threerings.getdown.data.Digest: java.util.HashMap _digests +com.threerings.getdown.data.Digest: java.lang.String _metaDigest +com.threerings.getdown.data.Digest: java.lang.String FILE_NAME +com.threerings.getdown.data.Digest: java.lang.String FILE_SUFFIX +com.threerings.getdown.data.Digest: java.lang.String digestFile(int) +com.threerings.getdown.data.Digest: java.lang.String sigAlgorithm(int) +com.threerings.getdown.data.Digest: void createDigest(int,java.util.List,java.io.File) +com.threerings.getdown.data.Digest: java.security.MessageDigest getMessageDigest(int) +com.threerings.getdown.data.Digest: Digest(java.io.File,boolean) +com.threerings.getdown.data.Digest: Digest(java.io.File,int,boolean) +com.threerings.getdown.data.Digest: java.lang.String getMetaDigest() +com.threerings.getdown.data.Digest: boolean validateResource(com.threerings.getdown.data.Resource,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Digest: java.lang.String getDigest(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Digest: void note(java.lang.StringBuilder,java.lang.String,java.lang.String) +com.threerings.getdown.data.EnvConfig +com.threerings.getdown.data.EnvConfig: java.io.File appDir +com.threerings.getdown.data.EnvConfig: java.lang.String appId +com.threerings.getdown.data.EnvConfig: java.lang.String appBase +com.threerings.getdown.data.EnvConfig: java.util.List certs +com.threerings.getdown.data.EnvConfig: java.util.List appArgs +com.threerings.getdown.data.EnvConfig: java.lang.String USER_HOME_KEY +com.threerings.getdown.data.EnvConfig: com.threerings.getdown.data.EnvConfig create(java.lang.String[],java.util.List) +com.threerings.getdown.data.EnvConfig: EnvConfig(java.io.File) +com.threerings.getdown.data.EnvConfig: EnvConfig(java.io.File,java.lang.String,java.lang.String,java.util.List,java.util.List) +com.threerings.getdown.data.EnvConfig$Note +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note$Level level +com.threerings.getdown.data.EnvConfig$Note: java.lang.String message +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note info(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note warn(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: com.threerings.getdown.data.EnvConfig$Note error(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note: EnvConfig$Note(com.threerings.getdown.data.EnvConfig$Note$Level,java.lang.String) +com.threerings.getdown.data.EnvConfig$Note$Level +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level INFO +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level WARN +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level ERROR +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level[] $VALUES +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level[] values() +com.threerings.getdown.data.EnvConfig$Note$Level: com.threerings.getdown.data.EnvConfig$Note$Level valueOf(java.lang.String) +com.threerings.getdown.data.EnvConfig$Note$Level: EnvConfig$Note$Level(java.lang.String,int) +com.threerings.getdown.data.EnvConfig$Note$Level: void () +com.threerings.getdown.data.PathBuilder +com.threerings.getdown.data.PathBuilder: java.lang.String CODE_CACHE_DIR +com.threerings.getdown.data.PathBuilder: java.lang.String NATIVE_CACHE_DIR +com.threerings.getdown.data.PathBuilder: PathBuilder() +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildDefaultClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildCachedClassPath(com.threerings.getdown.data.Application) +com.threerings.getdown.data.PathBuilder: com.threerings.getdown.data.ClassPath buildLibsPath(com.threerings.getdown.data.Application,boolean) +com.threerings.getdown.data.Properties +com.threerings.getdown.data.Properties: java.lang.String GETDOWN +com.threerings.getdown.data.Properties: java.lang.String CONNECT_PORT +com.threerings.getdown.data.Properties: Properties() +com.threerings.getdown.data.Resource +com.threerings.getdown.data.Resource: java.util.EnumSet NORMAL +com.threerings.getdown.data.Resource: java.util.EnumSet UNPACK +com.threerings.getdown.data.Resource: java.util.EnumSet EXEC +com.threerings.getdown.data.Resource: java.util.EnumSet PRELOAD +com.threerings.getdown.data.Resource: java.util.EnumSet NATIVE +com.threerings.getdown.data.Resource: java.lang.String _path +com.threerings.getdown.data.Resource: java.net.URL _remote +com.threerings.getdown.data.Resource: java.io.File _local +com.threerings.getdown.data.Resource: java.io.File _localNew +com.threerings.getdown.data.Resource: java.io.File _marker +com.threerings.getdown.data.Resource: java.io.File _unpacked +com.threerings.getdown.data.Resource: java.util.EnumSet _attrs +com.threerings.getdown.data.Resource: boolean _isJar +com.threerings.getdown.data.Resource: boolean _isPacked200Jar +com.threerings.getdown.data.Resource: java.util.Comparator ENTRY_COMP +com.threerings.getdown.data.Resource: int DIGEST_BUFFER_SIZE +com.threerings.getdown.data.Resource: java.lang.String computeDigest(int,java.io.File,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Resource: Resource(java.lang.String,java.net.URL,java.io.File,java.util.EnumSet) +com.threerings.getdown.data.Resource: java.lang.String getPath() +com.threerings.getdown.data.Resource: java.io.File getLocal() +com.threerings.getdown.data.Resource: java.io.File getLocalNew() +com.threerings.getdown.data.Resource: java.io.File getUnpacked() +com.threerings.getdown.data.Resource: java.io.File getFinalTarget() +com.threerings.getdown.data.Resource: java.net.URL getRemote() +com.threerings.getdown.data.Resource: boolean shouldUnpack() +com.threerings.getdown.data.Resource: boolean shouldPredownload() +com.threerings.getdown.data.Resource: boolean isNative() +com.threerings.getdown.data.Resource: java.lang.String computeDigest(int,java.security.MessageDigest,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.data.Resource: boolean isMarkedValid() +com.threerings.getdown.data.Resource: void markAsValid() +com.threerings.getdown.data.Resource: void clearMarker() +com.threerings.getdown.data.Resource: void install(boolean) +com.threerings.getdown.data.Resource: void unpack() +com.threerings.getdown.data.Resource: void applyAttrs() +com.threerings.getdown.data.Resource: void erase() +com.threerings.getdown.data.Resource: int compareTo(com.threerings.getdown.data.Resource) +com.threerings.getdown.data.Resource: boolean equals(java.lang.Object) +com.threerings.getdown.data.Resource: int hashCode() +com.threerings.getdown.data.Resource: java.lang.String toString() +com.threerings.getdown.data.Resource: void updateProgress(com.threerings.getdown.util.ProgressObserver,long,long) +com.threerings.getdown.data.Resource: boolean isJar(java.lang.String) +com.threerings.getdown.data.Resource: boolean isPacked200Jar(java.lang.String) +com.threerings.getdown.data.Resource: int compareTo(java.lang.Object) +com.threerings.getdown.data.Resource: void () +com.threerings.getdown.data.Resource$Attr +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr UNPACK +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr CLEAN +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr EXEC +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr PRELOAD +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr NATIVE +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr[] $VALUES +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr[] values() +com.threerings.getdown.data.Resource$Attr: com.threerings.getdown.data.Resource$Attr valueOf(java.lang.String) +com.threerings.getdown.data.Resource$Attr: Resource$Attr(java.lang.String,int) +com.threerings.getdown.data.Resource$Attr: void () +com.threerings.getdown.data.SysProps +com.threerings.getdown.data.SysProps: SysProps() +com.threerings.getdown.data.SysProps: java.lang.String appDir() +com.threerings.getdown.data.SysProps: java.lang.String appId() +com.threerings.getdown.data.SysProps: java.lang.String appBase() +com.threerings.getdown.data.SysProps: boolean noLogRedir() +com.threerings.getdown.data.SysProps: java.lang.String appbaseDomain() +com.threerings.getdown.data.SysProps: java.lang.String appbaseOverride() +com.threerings.getdown.data.SysProps: boolean silent() +com.threerings.getdown.data.SysProps: boolean launchInSilent() +com.threerings.getdown.data.SysProps: boolean noUpdate() +com.threerings.getdown.data.SysProps: boolean noInstall() +com.threerings.getdown.data.SysProps: int startDelay() +com.threerings.getdown.data.SysProps: boolean noUnpack() +com.threerings.getdown.data.SysProps: boolean direct() +com.threerings.getdown.data.SysProps: int connectTimeout() +com.threerings.getdown.data.SysProps: int readTimeout() +com.threerings.getdown.data.SysProps: int threadPoolSize() +com.threerings.getdown.data.SysProps: long parseJavaVersion(java.lang.String,java.lang.String) +com.threerings.getdown.data.SysProps: java.lang.String overrideAppbase(java.lang.String) +com.threerings.getdown.data.SysProps: java.lang.String replaceDomain(java.lang.String) +com.threerings.getdown.launcher.AbortPanel +com.threerings.getdown.launcher.AbortPanel: com.threerings.getdown.launcher.Getdown _getdown +com.threerings.getdown.launcher.AbortPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.AbortPanel: AbortPanel(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) +com.threerings.getdown.launcher.AbortPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.AbortPanel: void actionPerformed(java.awt.event.ActionEvent) +com.threerings.getdown.launcher.AbortPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.Getdown +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.util.ProgressObserver _progobs +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.data.Application _app +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.data.Application$UpdateInterface _ifc +com.threerings.getdown.launcher.Getdown: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.Getdown: java.awt.Container _container +com.threerings.getdown.launcher.Getdown: javax.swing.JLayeredPane _layers +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.StatusPanel _status +com.threerings.getdown.launcher.Getdown: javax.swing.JButton _patchNotes +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.AbortPanel _abort +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.RotatingBackgrounds _background +com.threerings.getdown.launcher.Getdown: boolean _dead +com.threerings.getdown.launcher.Getdown: boolean _silent +com.threerings.getdown.launcher.Getdown: boolean _launchInSilent +com.threerings.getdown.launcher.Getdown: boolean _noUpdate +com.threerings.getdown.launcher.Getdown: long _startup +com.threerings.getdown.launcher.Getdown: java.util.Set _toInstallResources +com.threerings.getdown.launcher.Getdown: boolean _readyToInstall +com.threerings.getdown.launcher.Getdown: boolean _enableTracking +com.threerings.getdown.launcher.Getdown: int _reportedProgress +com.threerings.getdown.launcher.Getdown: int _delay +com.threerings.getdown.launcher.Getdown: int _stepMaxPercent +com.threerings.getdown.launcher.Getdown: int _stepMinPercent +com.threerings.getdown.launcher.Getdown: int _lastGlobalPercent +com.threerings.getdown.launcher.Getdown: int _uiDisplayPercent +com.threerings.getdown.launcher.Getdown: int MAX_LOOPS +com.threerings.getdown.launcher.Getdown: long FALLBACK_CHECK_TIME +com.threerings.getdown.launcher.Getdown: Getdown(com.threerings.getdown.data.EnvConfig) +com.threerings.getdown.launcher.Getdown: boolean isUpdateAvailable() +com.threerings.getdown.launcher.Getdown: void install() +com.threerings.getdown.launcher.Getdown: void run() +com.threerings.getdown.launcher.Getdown: void configProxy(java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.Getdown: boolean detectProxy() +com.threerings.getdown.launcher.Getdown: void readConfig(boolean) +com.threerings.getdown.launcher.Getdown: void doPredownloads(java.util.Collection) +com.threerings.getdown.launcher.Getdown: void getdown() +com.threerings.getdown.launcher.Getdown: void updateStatus(java.lang.String) +com.threerings.getdown.launcher.Getdown: java.awt.image.BufferedImage loadImage(java.lang.String) +com.threerings.getdown.launcher.Getdown: void updateJava() +com.threerings.getdown.launcher.Getdown: void update() +com.threerings.getdown.launcher.Getdown: void download(java.util.Collection) +com.threerings.getdown.launcher.Getdown: void launch() +com.threerings.getdown.launcher.Getdown: void createInterfaceAsync(boolean) +com.threerings.getdown.launcher.Getdown: void initInterface() +com.threerings.getdown.launcher.Getdown: com.threerings.getdown.launcher.RotatingBackgrounds getBackground() +com.threerings.getdown.launcher.Getdown: java.awt.Image getProgressImage() +com.threerings.getdown.launcher.Getdown: void handleWindowClose() +com.threerings.getdown.launcher.Getdown: void fail(java.lang.String) +com.threerings.getdown.launcher.Getdown: void setStep(com.threerings.getdown.data.Application$UpdateInterface$Step) +com.threerings.getdown.launcher.Getdown: int stepToGlobalPercent(int) +com.threerings.getdown.launcher.Getdown: void setStatusAsync(java.lang.String,int,long,boolean) +com.threerings.getdown.launcher.Getdown: void reportTrackingEvent(java.lang.String,int) +com.threerings.getdown.launcher.Getdown: java.awt.Container createContainer() +com.threerings.getdown.launcher.Getdown: void configureContainer() +com.threerings.getdown.launcher.Getdown: void showContainer() +com.threerings.getdown.launcher.Getdown: void disposeContainer() +com.threerings.getdown.launcher.Getdown: boolean invokeDirect() +com.threerings.getdown.launcher.Getdown: void showDocument(java.lang.String) +com.threerings.getdown.launcher.Getdown: void exit(int) +com.threerings.getdown.launcher.Getdown: void copyStream(java.io.InputStream,java.io.PrintStream) +com.threerings.getdown.launcher.Getdown: java.awt.Image loadImage(java.lang.String) +com.threerings.getdown.launcher.Getdown$ProgressReporter +com.threerings.getdown.launcher.Getdown$ProgressReporter: java.net.URL _url +com.threerings.getdown.launcher.Getdown$ProgressReporter: com.threerings.getdown.launcher.Getdown this$0 +com.threerings.getdown.launcher.Getdown$ProgressReporter: Getdown$ProgressReporter(com.threerings.getdown.launcher.Getdown,java.net.URL) +com.threerings.getdown.launcher.Getdown$ProgressReporter: void run() +com.threerings.getdown.launcher.GetdownApp +com.threerings.getdown.launcher.GetdownApp: GetdownApp() +com.threerings.getdown.launcher.GetdownApp: void main(java.lang.String[]) +com.threerings.getdown.launcher.GetdownApp: com.threerings.getdown.launcher.Getdown start(java.lang.String[]) +com.threerings.getdown.launcher.MultipleGetdownRunning +com.threerings.getdown.launcher.MultipleGetdownRunning: MultipleGetdownRunning() +com.threerings.getdown.launcher.ProxyPanel +com.threerings.getdown.launcher.ProxyPanel: com.threerings.getdown.launcher.Getdown _getdown +com.threerings.getdown.launcher.ProxyPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _host +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _port +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JCheckBox _useAuth +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JTextField _username +com.threerings.getdown.launcher.ProxyPanel: javax.swing.JPasswordField _password +com.threerings.getdown.launcher.ProxyPanel: ProxyPanel(com.threerings.getdown.launcher.Getdown,java.util.ResourceBundle) +com.threerings.getdown.launcher.ProxyPanel: void setProxy(java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyPanel: void addNotify() +com.threerings.getdown.launcher.ProxyPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel: void actionPerformed(java.awt.event.ActionEvent) +com.threerings.getdown.launcher.ProxyPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.ProxyPanel: java.awt.Dimension clampWidth(java.awt.Dimension,int) +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: ProxyPanel$SaneLabelField(java.lang.String) +com.threerings.getdown.launcher.ProxyPanel$SaneLabelField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: ProxyPanel$SanePasswordField() +com.threerings.getdown.launcher.ProxyPanel$SanePasswordField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyPanel$SaneTextField +com.threerings.getdown.launcher.ProxyPanel$SaneTextField: ProxyPanel$SaneTextField() +com.threerings.getdown.launcher.ProxyPanel$SaneTextField: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.ProxyUtil +com.threerings.getdown.launcher.ProxyUtil: java.lang.String PROXY_REGISTRY +com.threerings.getdown.launcher.ProxyUtil: ProxyUtil() +com.threerings.getdown.launcher.ProxyUtil: boolean autoDetectProxy(com.threerings.getdown.data.Application) +com.threerings.getdown.launcher.ProxyUtil: boolean canLoadWithoutProxy(java.net.URL) +com.threerings.getdown.launcher.ProxyUtil: void configProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyUtil: java.lang.String[] loadProxy(com.threerings.getdown.data.Application) +com.threerings.getdown.launcher.ProxyUtil: void saveProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.ProxyUtil: void initProxy(com.threerings.getdown.data.Application,java.lang.String,java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.launcher.RotatingBackgrounds +com.threerings.getdown.launcher.RotatingBackgrounds: long currentDisplayStart +com.threerings.getdown.launcher.RotatingBackgrounds: int current +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image[] images +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image errorImage +com.threerings.getdown.launcher.RotatingBackgrounds: int[] percentages +com.threerings.getdown.launcher.RotatingBackgrounds: int[] minDisplayTime +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds() +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds(java.awt.Image) +com.threerings.getdown.launcher.RotatingBackgrounds: RotatingBackgrounds(java.util.List,java.lang.String,com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader) +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image getImage(int) +com.threerings.getdown.launcher.RotatingBackgrounds: java.awt.Image getErrorImage() +com.threerings.getdown.launcher.RotatingBackgrounds: int getNumImages() +com.threerings.getdown.launcher.RotatingBackgrounds: void makeEmpty() +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader +com.threerings.getdown.launcher.RotatingBackgrounds$ImageLoader: java.awt.Image loadImage(java.lang.String) +com.threerings.getdown.launcher.StatusPanel +com.threerings.getdown.launcher.StatusPanel: java.awt.Image _barimg +com.threerings.getdown.launcher.StatusPanel: com.threerings.getdown.launcher.RotatingBackgrounds _bg +com.threerings.getdown.launcher.StatusPanel: java.awt.Dimension _psize +com.threerings.getdown.launcher.StatusPanel: java.util.ResourceBundle _msgs +com.threerings.getdown.launcher.StatusPanel: int _progress +com.threerings.getdown.launcher.StatusPanel: java.lang.String _status +com.threerings.getdown.launcher.StatusPanel: int _statusDots +com.threerings.getdown.launcher.StatusPanel: boolean _displayError +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _label +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newlab +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _plabel +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newplab +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _rlabel +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label _newrlab +com.threerings.getdown.launcher.StatusPanel: com.threerings.getdown.data.Application$UpdateInterface _ifc +com.threerings.getdown.launcher.StatusPanel: javax.swing.Timer _timer +com.threerings.getdown.launcher.StatusPanel: long[] _remain +com.threerings.getdown.launcher.StatusPanel: int _ridx +com.threerings.getdown.launcher.StatusPanel: com.samskivert.util.Throttle _rthrottle +com.threerings.getdown.launcher.StatusPanel: java.awt.Font FONT +com.threerings.getdown.launcher.StatusPanel: StatusPanel(java.util.ResourceBundle) +com.threerings.getdown.launcher.StatusPanel: void init(com.threerings.getdown.data.Application$UpdateInterface,com.threerings.getdown.launcher.RotatingBackgrounds,java.awt.Image) +com.threerings.getdown.launcher.StatusPanel: boolean imageUpdate(java.awt.Image,int,int,int,int,int) +com.threerings.getdown.launcher.StatusPanel: void setProgress(int,long) +com.threerings.getdown.launcher.StatusPanel: void setStatus(java.lang.String,boolean) +com.threerings.getdown.launcher.StatusPanel: void stopThrob() +com.threerings.getdown.launcher.StatusPanel: void addNotify() +com.threerings.getdown.launcher.StatusPanel: void removeNotify() +com.threerings.getdown.launcher.StatusPanel: void paintComponent(java.awt.Graphics) +com.threerings.getdown.launcher.StatusPanel: java.awt.Dimension getPreferredSize() +com.threerings.getdown.launcher.StatusPanel: void updateStatusLabel() +com.threerings.getdown.launcher.StatusPanel: int getStatusY(com.samskivert.swing.Label) +com.threerings.getdown.launcher.StatusPanel: com.samskivert.swing.Label createLabel(java.lang.String,java.awt.Color) +com.threerings.getdown.launcher.StatusPanel: java.lang.String xlate(java.lang.String) +com.threerings.getdown.launcher.StatusPanel: java.lang.String get(java.lang.String,java.lang.String[]) +com.threerings.getdown.launcher.StatusPanel: java.lang.String get(java.lang.String) +com.threerings.getdown.launcher.StatusPanel: void () +com.threerings.getdown.net.Downloader +com.threerings.getdown.net.Downloader: java.util.Map _sizes +com.threerings.getdown.net.Downloader: java.util.Map _downloaded +com.threerings.getdown.net.Downloader: long _start +com.threerings.getdown.net.Downloader: long _bytesPerSecond +com.threerings.getdown.net.Downloader: long _lastUpdate +com.threerings.getdown.net.Downloader: com.threerings.getdown.net.Downloader$State _state +com.threerings.getdown.net.Downloader: long UPDATE_DELAY +com.threerings.getdown.net.Downloader: Downloader() +com.threerings.getdown.net.Downloader: boolean download(java.util.Collection,int) +com.threerings.getdown.net.Downloader: void abort() +com.threerings.getdown.net.Downloader: void resolvingDownloads() +com.threerings.getdown.net.Downloader: void downloadProgress(int,long) +com.threerings.getdown.net.Downloader: void downloadFailed(com.threerings.getdown.data.Resource,java.lang.Exception) +com.threerings.getdown.net.Downloader: long checkSize(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.Downloader: void reportProgress(com.threerings.getdown.data.Resource,long,long) +com.threerings.getdown.net.Downloader: long sum(java.lang.Iterable) +com.threerings.getdown.net.Downloader: void download(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.Downloader$State +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State DOWNLOADING +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State COMPLETE +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State FAILED +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State ABORTED +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State[] $VALUES +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State[] values() +com.threerings.getdown.net.Downloader$State: com.threerings.getdown.net.Downloader$State valueOf(java.lang.String) +com.threerings.getdown.net.Downloader$State: Downloader$State(java.lang.String,int) +com.threerings.getdown.net.Downloader$State: void () +com.threerings.getdown.net.HTTPDownloader +com.threerings.getdown.net.HTTPDownloader: java.net.Proxy _proxy +com.threerings.getdown.net.HTTPDownloader: HTTPDownloader(java.net.Proxy) +com.threerings.getdown.net.HTTPDownloader: long checkSize(com.threerings.getdown.data.Resource) +com.threerings.getdown.net.HTTPDownloader: void download(com.threerings.getdown.data.Resource) +com.threerings.getdown.spi.ProxyAuth +com.threerings.getdown.spi.ProxyAuth: com.threerings.getdown.spi.ProxyAuth$Credentials loadCredentials(java.lang.String) +com.threerings.getdown.spi.ProxyAuth: void saveCredentials(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.spi.ProxyAuth$Credentials +com.threerings.getdown.spi.ProxyAuth$Credentials: java.lang.String username +com.threerings.getdown.spi.ProxyAuth$Credentials: java.lang.String password +com.threerings.getdown.spi.ProxyAuth$Credentials: ProxyAuth$Credentials(java.lang.String,java.lang.String) +com.threerings.getdown.tools.Differ +com.threerings.getdown.tools.Differ: Differ() +com.threerings.getdown.tools.Differ: void createDiff(java.io.File,java.io.File,boolean) +com.threerings.getdown.tools.Differ: void createPatch(java.io.File,java.util.ArrayList,java.util.ArrayList,boolean) +com.threerings.getdown.tools.Differ: java.io.File rebuildJar(java.io.File) +com.threerings.getdown.tools.Differ: void jarDiff(java.io.File,java.io.File,java.util.jar.JarOutputStream) +com.threerings.getdown.tools.Differ: void main(java.lang.String[]) +com.threerings.getdown.tools.Differ: void pipe(java.io.File,java.util.jar.JarOutputStream) +com.threerings.getdown.tools.Digester +com.threerings.getdown.tools.Digester: Digester() +com.threerings.getdown.tools.Digester: void main(java.lang.String[]) +com.threerings.getdown.tools.Digester: void createDigests(java.io.File,java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.tools.Digester: void createDigest(int,java.io.File) +com.threerings.getdown.tools.Digester: void signDigest(int,java.io.File,java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.tools.JarDiff +com.threerings.getdown.tools.JarDiff: int DEFAULT_READ_SIZE +com.threerings.getdown.tools.JarDiff: byte[] newBytes +com.threerings.getdown.tools.JarDiff: byte[] oldBytes +com.threerings.getdown.tools.JarDiff: boolean _debug +com.threerings.getdown.tools.JarDiff: JarDiff() +com.threerings.getdown.tools.JarDiff: void createPatch(java.lang.String,java.lang.String,java.io.OutputStream,boolean) +com.threerings.getdown.tools.JarDiff: void createIndex(java.util.jar.JarOutputStream,java.util.List,java.util.Map) +com.threerings.getdown.tools.JarDiff: java.io.Writer writeEscapedString(java.io.Writer,java.lang.String) +com.threerings.getdown.tools.JarDiff: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,com.threerings.getdown.tools.JarDiff$JarFile2) +com.threerings.getdown.tools.JarDiff: byte[] access$000() +com.threerings.getdown.tools.JarDiff: byte[] access$100() +com.threerings.getdown.tools.JarDiff: boolean access$200() +com.threerings.getdown.tools.JarDiff: void () +com.threerings.getdown.tools.JarDiffCodes +com.threerings.getdown.tools.JarDiffCodes: java.lang.String INDEX_NAME +com.threerings.getdown.tools.JarDiffCodes: java.lang.String VERSION_HEADER +com.threerings.getdown.tools.JarDiffCodes: java.lang.String REMOVE_COMMAND +com.threerings.getdown.tools.JarDiffCodes: java.lang.String MOVE_COMMAND +com.threerings.getdown.tools.JarDiffPatcher +com.threerings.getdown.tools.JarDiffPatcher: int DEFAULT_READ_SIZE +com.threerings.getdown.tools.JarDiffPatcher: byte[] newBytes +com.threerings.getdown.tools.JarDiffPatcher: byte[] oldBytes +com.threerings.getdown.tools.JarDiffPatcher: JarDiffPatcher() +com.threerings.getdown.tools.JarDiffPatcher: void patchJar(java.lang.String,java.lang.String,java.io.File,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.tools.JarDiffPatcher: void updateObserver(com.threerings.getdown.util.ProgressObserver,double,double) +com.threerings.getdown.tools.JarDiffPatcher: void determineNameMapping(java.util.jar.JarFile,java.util.Set,java.util.Map) +com.threerings.getdown.tools.JarDiffPatcher: java.util.List getSubpaths(java.lang.String) +com.threerings.getdown.tools.JarDiffPatcher: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.util.jar.JarFile) +com.threerings.getdown.tools.JarDiffPatcher: void writeEntry(java.util.jar.JarOutputStream,java.util.jar.JarEntry,java.io.InputStream) +com.threerings.getdown.tools.JarDiffPatcher: void () +com.threerings.getdown.tools.Patcher +com.threerings.getdown.tools.Patcher: java.lang.String CREATE +com.threerings.getdown.tools.Patcher: java.lang.String PATCH +com.threerings.getdown.tools.Patcher: java.lang.String DELETE +com.threerings.getdown.tools.Patcher: com.threerings.getdown.util.ProgressObserver _obs +com.threerings.getdown.tools.Patcher: long _complete +com.threerings.getdown.tools.Patcher: long _plength +com.threerings.getdown.tools.Patcher: byte[] _buffer +com.threerings.getdown.tools.Patcher: int COPY_BUFFER_SIZE +com.threerings.getdown.tools.Patcher: Patcher() +com.threerings.getdown.tools.Patcher: void patch(java.io.File,java.io.File,com.threerings.getdown.util.ProgressObserver) +com.threerings.getdown.tools.Patcher: java.lang.String strip(java.lang.String,java.lang.String) +com.threerings.getdown.tools.Patcher: void createFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File) +com.threerings.getdown.tools.Patcher: void patchFile(java.util.jar.JarFile,java.util.zip.ZipEntry,java.io.File,java.lang.String) +com.threerings.getdown.tools.Patcher: void updateProgress(int) +com.threerings.getdown.tools.Patcher: void main(java.lang.String[]) +com.threerings.getdown.util.Base64 +com.threerings.getdown.util.Base64: int DEFAULT +com.threerings.getdown.util.Base64: int NO_PADDING +com.threerings.getdown.util.Base64: int NO_WRAP +com.threerings.getdown.util.Base64: int CRLF +com.threerings.getdown.util.Base64: int URL_SAFE +com.threerings.getdown.util.Base64: int NO_CLOSE +com.threerings.getdown.util.Base64: boolean $assertionsDisabled +com.threerings.getdown.util.Base64: byte[] decode(java.lang.String,int) +com.threerings.getdown.util.Base64: byte[] decode(byte[],int) +com.threerings.getdown.util.Base64: byte[] decode(byte[],int,int,int) +com.threerings.getdown.util.Base64: java.lang.String encodeToString(byte[],int) +com.threerings.getdown.util.Base64: java.lang.String encodeToString(byte[],int,int,int) +com.threerings.getdown.util.Base64: byte[] encode(byte[],int) +com.threerings.getdown.util.Base64: byte[] encode(byte[],int,int,int) +com.threerings.getdown.util.Base64: Base64() +com.threerings.getdown.util.Base64: void () +com.threerings.getdown.util.Color +com.threerings.getdown.util.Color: int CLEAR +com.threerings.getdown.util.Color: int WHITE +com.threerings.getdown.util.Color: int BLACK +com.threerings.getdown.util.Color: float brightness(int) +com.threerings.getdown.util.Color: Color() +com.threerings.getdown.util.Config +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config EMPTY +com.threerings.getdown.util.Config: java.util.Map _data +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config$ParseOpts createOpts(boolean) +com.threerings.getdown.util.Config: java.util.List parsePairs(java.io.File,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: java.util.List parsePairs(java.io.Reader,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Rectangle parseRect(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: java.lang.Integer parseColor(java.lang.String) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Config parseConfig(java.io.File,com.threerings.getdown.util.Config$ParseOpts) +com.threerings.getdown.util.Config: Config(java.util.Map) +com.threerings.getdown.util.Config: boolean hasValue(java.lang.String) +com.threerings.getdown.util.Config: java.lang.Object getRaw(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getString(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getString(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean getBoolean(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String[] getMultiValue(java.lang.String) +com.threerings.getdown.util.Config: com.threerings.getdown.util.Rectangle getRect(java.lang.String,com.threerings.getdown.util.Rectangle) +com.threerings.getdown.util.Config: int getInt(java.lang.String,int) +com.threerings.getdown.util.Config: long getLong(java.lang.String,long) +com.threerings.getdown.util.Config: int getColor(java.lang.String,int) +com.threerings.getdown.util.Config: java.lang.String[] getList(java.lang.String) +com.threerings.getdown.util.Config: java.lang.String getUrl(java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean checkQualifiers(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: boolean checkQualifier(java.lang.String,java.lang.String,java.lang.String) +com.threerings.getdown.util.Config: void () +com.threerings.getdown.util.Config$ParseOpts +com.threerings.getdown.util.Config$ParseOpts: boolean biasToKey +com.threerings.getdown.util.Config$ParseOpts: boolean strictComments +com.threerings.getdown.util.Config$ParseOpts: java.lang.String osname +com.threerings.getdown.util.Config$ParseOpts: java.lang.String osarch +com.threerings.getdown.util.Config$ParseOpts: Config$ParseOpts() +com.threerings.getdown.util.ConnectionUtil +com.threerings.getdown.util.ConnectionUtil: ConnectionUtil() +com.threerings.getdown.util.ConnectionUtil: java.net.URLConnection open(java.net.Proxy,java.net.URL,int,int) +com.threerings.getdown.util.ConnectionUtil: java.net.HttpURLConnection openHttp(java.net.Proxy,java.net.URL,int,int) +com.threerings.getdown.util.FileUtil +com.threerings.getdown.util.FileUtil: FileUtil() +com.threerings.getdown.util.FileUtil: boolean renameTo(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: boolean deleteHarder(java.io.File) +com.threerings.getdown.util.FileUtil: boolean deleteDirHarder(java.io.File) +com.threerings.getdown.util.FileUtil: java.util.List readLines(java.io.Reader) +com.threerings.getdown.util.FileUtil: void unpackJar(java.util.jar.JarFile,java.io.File,boolean) +com.threerings.getdown.util.FileUtil: void unpackPacked200Jar(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: void copy(java.io.File,java.io.File) +com.threerings.getdown.util.FileUtil: void makeExecutable(java.io.File) +com.threerings.getdown.util.FileUtil: void walkTree(java.io.File,com.threerings.getdown.util.FileUtil$Visitor) +com.threerings.getdown.util.FileUtil$Visitor +com.threerings.getdown.util.FileUtil$Visitor: void visit(java.io.File) +com.threerings.getdown.util.HostWhitelist +com.threerings.getdown.util.HostWhitelist: HostWhitelist() +com.threerings.getdown.util.HostWhitelist: java.net.URL verify(java.net.URL) +com.threerings.getdown.util.HostWhitelist: java.net.URL verify(java.util.List,java.net.URL) +com.threerings.getdown.util.LaunchUtil +com.threerings.getdown.util.LaunchUtil: java.lang.String LOCAL_JAVA_DIR +com.threerings.getdown.util.LaunchUtil: boolean _isWindows +com.threerings.getdown.util.LaunchUtil: boolean _isMacOS +com.threerings.getdown.util.LaunchUtil: boolean _isLinux +com.threerings.getdown.util.LaunchUtil: LaunchUtil() +com.threerings.getdown.util.LaunchUtil: boolean updateVersionAndRelaunch(java.io.File,java.lang.String,java.lang.String) +com.threerings.getdown.util.LaunchUtil: java.lang.String getJVMPath(java.io.File) +com.threerings.getdown.util.LaunchUtil: java.lang.String getJVMPath(java.io.File,boolean) +com.threerings.getdown.util.LaunchUtil: void upgradeGetdown(java.io.File,java.io.File,java.io.File) +com.threerings.getdown.util.LaunchUtil: boolean mustMonitorChildren() +com.threerings.getdown.util.LaunchUtil: boolean isWindows() +com.threerings.getdown.util.LaunchUtil: boolean isMacOS() +com.threerings.getdown.util.LaunchUtil: boolean isLinux() +com.threerings.getdown.util.LaunchUtil: java.lang.String checkJVMPath(java.lang.String,boolean) +com.threerings.getdown.util.LaunchUtil: void () +com.threerings.getdown.util.MessageUtil +com.threerings.getdown.util.MessageUtil: java.lang.String TAINT_CHAR +com.threerings.getdown.util.MessageUtil: MessageUtil() +com.threerings.getdown.util.MessageUtil: boolean isTainted(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String taint(java.lang.Object) +com.threerings.getdown.util.MessageUtil: java.lang.String untaint(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String compose(java.lang.String,java.lang.Object[]) +com.threerings.getdown.util.MessageUtil: java.lang.String compose(java.lang.String,java.lang.String[]) +com.threerings.getdown.util.MessageUtil: java.lang.String tcompose(java.lang.String,java.lang.Object[]) +com.threerings.getdown.util.MessageUtil: java.lang.String tcompose(java.lang.String,java.lang.String[]) +com.threerings.getdown.util.MessageUtil: java.lang.String escape(java.lang.String) +com.threerings.getdown.util.MessageUtil: java.lang.String unescape(java.lang.String) +com.threerings.getdown.util.ProgressAggregator +com.threerings.getdown.util.ProgressAggregator: com.threerings.getdown.util.ProgressObserver _target +com.threerings.getdown.util.ProgressAggregator: long[] _sizes +com.threerings.getdown.util.ProgressAggregator: int[] _progress +com.threerings.getdown.util.ProgressAggregator: ProgressAggregator(com.threerings.getdown.util.ProgressObserver,long[]) +com.threerings.getdown.util.ProgressAggregator: com.threerings.getdown.util.ProgressObserver startElement(int) +com.threerings.getdown.util.ProgressAggregator: void updateAggProgress() +com.threerings.getdown.util.ProgressAggregator: long sum(long[]) +com.threerings.getdown.util.ProgressObserver +com.threerings.getdown.util.ProgressObserver: void progress(int) +com.threerings.getdown.util.Rectangle +com.threerings.getdown.util.Rectangle: int x +com.threerings.getdown.util.Rectangle: int y +com.threerings.getdown.util.Rectangle: int width +com.threerings.getdown.util.Rectangle: int height +com.threerings.getdown.util.Rectangle: Rectangle(int,int,int,int) +com.threerings.getdown.util.Rectangle: com.threerings.getdown.util.Rectangle union(com.threerings.getdown.util.Rectangle) +com.threerings.getdown.util.Rectangle: java.lang.String toString() +com.threerings.getdown.util.StreamUtil +com.threerings.getdown.util.StreamUtil: StreamUtil() +com.threerings.getdown.util.StreamUtil: void close(java.io.InputStream) +com.threerings.getdown.util.StreamUtil: void close(java.io.OutputStream) +com.threerings.getdown.util.StreamUtil: void close(java.io.Reader) +com.threerings.getdown.util.StreamUtil: void close(java.io.Writer) +com.threerings.getdown.util.StreamUtil: java.io.OutputStream copy(java.io.InputStream,java.io.OutputStream) +com.threerings.getdown.util.StreamUtil: byte[] toByteArray(java.io.InputStream) +com.threerings.getdown.util.StringUtil +com.threerings.getdown.util.StringUtil: java.lang.String XLATE +com.threerings.getdown.util.StringUtil: StringUtil() +com.threerings.getdown.util.StringUtil: boolean couldBeValidUrl(java.lang.String) +com.threerings.getdown.util.StringUtil: boolean isBlank(java.lang.String) +com.threerings.getdown.util.StringUtil: int[] parseIntArray(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String[] parseStringArray(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String[] parseStringArray(java.lang.String,boolean) +com.threerings.getdown.util.StringUtil: java.lang.String deNull(java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String hexlate(byte[],int) +com.threerings.getdown.util.StringUtil: java.lang.String hexlate(byte[]) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[]) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],boolean) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],java.lang.String) +com.threerings.getdown.util.StringUtil: java.lang.String join(java.lang.Object[],java.lang.String,boolean) +com.threerings.getdown.util.VersionUtil +com.threerings.getdown.util.VersionUtil: VersionUtil() +com.threerings.getdown.util.VersionUtil: long readVersion(java.io.File) +com.threerings.getdown.util.VersionUtil: void writeVersion(java.io.File,long) +com.threerings.getdown.util.VersionUtil: long parseJavaVersion(java.lang.String,java.lang.String) +com.threerings.getdown.util.VersionUtil: long readReleaseVersion(java.io.File,java.lang.String) +com.threerings.getdown.util.VersionUtil: int parseInt(java.lang.String) diff --git a/getdown/src/getdown/lib/SOURCE_HEADER b/getdown/src/getdown/lib/SOURCE_HEADER new file mode 100644 index 0000000..43271fe --- /dev/null +++ b/getdown/src/getdown/lib/SOURCE_HEADER @@ -0,0 +1,5 @@ +// +// Getdown - application installer, patcher and launcher +// Copyright (C) 2004-2018 Getdown authors +// https://github.com/threerings/getdown/blob/master/LICENSE + diff --git a/getdown/src/getdown/lib/jRegistryKey.dll b/getdown/src/getdown/lib/jRegistryKey.dll new file mode 100644 index 0000000..5746728 Binary files /dev/null and b/getdown/src/getdown/lib/jRegistryKey.dll differ diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar new file mode 100644 index 0000000..5100795 Binary files /dev/null and b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.jar differ diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom new file mode 100644 index 0000000..226a7d7 --- /dev/null +++ b/getdown/src/getdown/lib/jregistrykey/jregistrykey/1.0/jregistrykey-1.0.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + jregistrykey + jregistrykey + 1.0 + POM was created from install:install-file + diff --git a/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml b/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml new file mode 100644 index 0000000..1a8a725 --- /dev/null +++ b/getdown/src/getdown/lib/jregistrykey/jregistrykey/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + jregistrykey + jregistrykey + 1.0 + + + 1.0 + + 20101118155146 + + diff --git a/getdown/src/getdown/lib/manifest.mf b/getdown/src/getdown/lib/manifest.mf new file mode 100644 index 0000000..3be50cc --- /dev/null +++ b/getdown/src/getdown/lib/manifest.mf @@ -0,0 +1,6 @@ +Main-Class: com.threerings.getdown.launcher.Getdown +Permissions: all-permissions +Application-Name: Getdown +Codebase: * +Application-Library-Allowable-Codebase: * +Caller-Allowable-Codebase: * diff --git a/getdown/src/getdown/pom.xml b/getdown/src/getdown/pom.xml new file mode 100644 index 0000000..c6af03d --- /dev/null +++ b/getdown/src/getdown/pom.xml @@ -0,0 +1,180 @@ + + + 4.0.0 + + org.sonatype.oss + oss-parent + 7 + + + com.threerings.getdown + getdown + pom + 1.8.3-SNAPSHOT + + getdown + An application installer and updater. + https://github.com/threerings/getdown + + https://github.com/threerings/getdown/issues + + + + + The (New) BSD License + http://www.opensource.org/licenses/bsd-license.php + repo + + + + + + samskivert + Michael Bayne + mdb@samskivert.com + + + + + scm:git:git://github.com/threerings/getdown.git + scm:git:git@github.com:threerings/getdown.git + https://github.com/threerings/getdown + + + + core + launcher + ant + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + false + + ossrh-releases + https://oss.sonatype.org/ + aa555c46fc37d0 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.7 + 1.7 + true + true + true + + -Xlint + -Xlint:-serial + -Xlint:-path + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.0.2 + + UTF-8 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0-M1 + + true + public + -Xdoclint:all -Xdoclint:-missing + + + + + + + + + eclipse + + + m2e.version + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + [1.0,) + + enforce + + + + + + + + + + + + + + + + + release-sign-artifacts + + performReleasetrue + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + mdb@samskivert.com + + + + + + +