Cleaned up the applet initialization process. If we are not granted privileges,

we will not display our background image because will be unable to read the
necessary metadata that tells us where to display status on that image. Also
clarified the language a bit in the "user failed to grant is privileges" case.
This commit is contained in:
Michael Bayne
2007-01-24 03:19:13 +00:00
parent 28da206181
commit 1455088ac3
5 changed files with 391 additions and 451 deletions
@@ -64,8 +64,8 @@ import com.threerings.getdown.util.MetaProgressObserver;
import com.threerings.getdown.util.ProgressObserver;
/**
* Parses and provide access to the information contained in the
* <code>getdown.txt</code> configuration file.
* Parses and provide access to the information contained in the <code>getdown.txt</code>
* configuration file.
*/
public class Application
{
@@ -75,12 +75,11 @@ public class Application
/** 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). */
/** 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.";
/** Used to communicate information about the UI displayed when
* updating the application. */
/** Used to communicate information about the UI displayed when updating the application. */
public static class UpdateInterface
{
/** The human readable name of this application. */
@@ -116,16 +115,17 @@ public class Application
/** Generates a string representation of this instance. */
public String toString ()
{
return "[name=" + name + ", bg=" + backgroundImage +
", pi=" + progressImage + ", prect=" + progress +
", pt=" + progressText + ", pb=" + progressBar +
", srect=" + status + ", st=" + statusText +
", shadow=" + textShadow + ", err=" + installError + "]";
return "[name=" + name + ", bg=" + backgroundImage + ", pi=" + progressImage +
", prect=" + progress + ", pt=" + progressText + ", pb=" + progressBar +
", srect=" + status + ", st=" + statusText + ", shadow=" + textShadow +
", err=" + installError + "]";
}
}
/** Used by {@link #verifyMetadata} to communicate status in
* circumstances where it needs to take network actions. */
/**
* 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. */
@@ -133,15 +133,13 @@ public class Application
}
/**
* Creates an application instance which records the location of the
* <code>getdown.txt</code> configuration file from the supplied
* application directory.
* Creates an application instance which records the location of the <code>getdown.txt</code>
* configuration file from the supplied application directory.
*
* @param appid usually null but a string identifier if a secondary
* application is desired to be launched. That application will use *
* <code>appid.class</code> and <code>appid.apparg</code> to configure
* itself but all other parameters will be the same as the primary
* application.
* @param appid usually null but a string identifier if a secondary application is desired to
* be launched. That application will use <code>appid.class</code> and
* <code>appid.apparg</code> to configure itself but all other parameters will be the same as
* the primary application.
*/
public Application (File appdir, String appid)
{
@@ -151,8 +149,8 @@ public class Application
}
/**
* Indicates whether or not we support downloading of our resources using
* the Bittorrent protocol.
* Indicates whether or not we support downloading of our resources using the Bittorrent
* protocol.
*/
public boolean getUseTorrent ()
{
@@ -160,8 +158,7 @@ public class Application
}
/**
* Returns a resource that refers to the application configuration
* file itself.
* Returns a resource that refers to the application configuration file itself.
*/
public Resource getConfigResource ()
{
@@ -173,8 +170,7 @@ public class Application
}
/**
* Returns a list of the code {@link Resource} objects used by this
* application.
* Returns a list of the code {@link Resource} objects used by this application.
*/
public List<Resource> getCodeResources ()
{
@@ -182,8 +178,7 @@ public class Application
}
/**
* Returns a list of the non-code {@link Resource} objects used by
* this application.
* Returns a list of the non-code {@link Resource} objects used by this application.
*/
public List<Resource> getResources ()
{
@@ -191,8 +186,7 @@ public class Application
}
/**
* Returns a list of all the {@link Resource} objects used by
* this application.
* Returns a list of all the {@link Resource} objects used by this application.
*/
public List<Resource> getAllResources ()
{
@@ -203,10 +197,9 @@ public class Application
}
/**
* Returns a list 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.
* Returns a list 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 List<String> getAuxGroups ()
{
@@ -214,16 +207,16 @@ public class Application
}
/**
* 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.
* 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
// 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);
}
@@ -231,9 +224,9 @@ public class Application
}
/**
* Returns a list of the non-code {@link Resource} objects included in the
* specified auxiliary resource group. If the group does not exist or has
* no resources, an empty list will be returned.
* Returns a list of the non-code {@link Resource} objects included in the specified auxiliary
* resource group. If the group does not exist or has no resources, an empty list will be
* returned.
*/
public List<Resource> getResources (String group)
{
@@ -242,8 +235,7 @@ public class Application
}
/**
* Returns all non-code resources and all resources from active auxiliary
* resource groups.
* Returns all non-code resources and all resources from active auxiliary resource groups.
*/
public List<Resource> getActiveResources ()
{
@@ -258,19 +250,17 @@ public class Application
}
/**
* 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.
* 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.
* @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 + "].");
Log.warning("Requested patch resource for up-to-date or non-versioned application " +
"[cvers=" + _version + ", tvers=" + _targetVersion + "].");
return null;
}
@@ -288,8 +278,8 @@ public class Application
}
/**
* Returns a resource that can be used to download an archive containing
* all files belonging to the application.
* Returns a resource that can be used to download an archive containing all files belonging to
* the application.
*/
public Resource getFullResource ()
{
@@ -306,17 +296,15 @@ public class Application
}
/**
* Instructs the application to parse its <code>getdown.txt</code>
* 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>config.txt</code> file and try again.
* Instructs the application to parse its <code>getdown.txt</code> 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>config.txt</code> file and try again.
*
* @return a configured UpdateInterface instance that will be used to
* configure the update UI.
* @return a configured UpdateInterface instance that will be used to configure the update UI.
*
* @exception IOException thrown if there is an error reading the file
* or an error encountered during its parsing.
* @exception IOException thrown if there is an error reading the file or an error encountered
* during its parsing.
*/
public UpdateInterface init (boolean checkPlatform)
throws IOException
@@ -326,9 +314,8 @@ public class Application
try {
cdata = ConfigUtil.parseConfig(_config, checkPlatform);
} catch (FileNotFoundException fnfe) {
// thanks to funny windows bullshit, we have to do this backup
// file fiddling in case we got screwed while updating our
// very critical getdown config file
// thanks to funny windows bullshit, we have to do this backup file fiddling in case we
// got screwed while updating our very critical getdown config file
File cbackup = getLocalPath(CONFIG_FILE + "_old");
if (cbackup.exists()) {
cdata = ConfigUtil.parseConfig(cbackup, checkPlatform);
@@ -337,9 +324,8 @@ public class Application
}
}
// 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
// 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 = (String)cdata.get("appbase");
if (_appbase == null) {
throw new IOException("m.missing_appbase");
@@ -380,6 +366,18 @@ public class Application
throw new IOException("m.missing_class");
}
// check to see if we require a particular JVM version and have a supplied JVM
vstr = (String)cdata.get("java_version");
if (vstr != null) {
try {
_javaVersion = Integer.parseInt(vstr);
} catch (Exception e) {
String err = MessageUtil.tcompose("m.invalid_java_version", vstr);
throw (IOException) new IOException(err).initCause(e);
}
}
_javaLocation = (String)cdata.get("java_location");
// clear our arrays as we may be reinitializing
_codes.clear();
_resources.clear();
@@ -428,8 +426,7 @@ public class Application
if (file.exists()) {
try {
List<String[]> args = ConfigUtil.parsePairs(file, false);
for (Iterator<String[]> iter = args.iterator();
iter.hasNext();) {
for (Iterator<String[]> iter = args.iterator(); iter.hasNext();) {
String[] pair = iter.next();
_jvmargs.add(pair[0] + "=" + pair[1]);
}
@@ -439,21 +436,18 @@ public class Application
}
// determine whether or not we should be using bit torrent
_useTorrent = (cdata.get("torrent") != null) ||
(System.getProperty("torrent") != null);
_useTorrent = (cdata.get("torrent") != null) || (System.getProperty("torrent") != null);
// 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
// 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();
// parse and return our application config
UpdateInterface ui = new UpdateInterface();
_name = ui.name = (String)cdata.get("ui.name");
ui.progress = parseRect(cdata, "ui.progress", ui.progress);
ui.progressText = parseColor(
cdata, "ui.progress_text", ui.progressText);
ui.progressBar = parseColor(
cdata, "ui.progress_bar", ui.progressBar);
ui.progressText = parseColor(cdata, "ui.progress_text", ui.progressText);
ui.progressBar = parseColor(cdata, "ui.progress_bar", ui.progressBar);
ui.status = parseRect(cdata, "ui.status", ui.status);
ui.statusText = parseColor(cdata, "ui.status_text", ui.statusText);
ui.textShadow = parseColor(cdata, "ui.text_shadow", ui.textShadow);
@@ -475,9 +469,8 @@ public class Application
}
/**
* Returns a URL from which the specified path can be fetched. Our
* application base URL is properly versioned and combined with the
* supplied path.
* 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
@@ -494,8 +487,18 @@ public class Application
}
/**
* Attempts to redownload the <code>getdown.txt</code> file based on
* information parsed from a previous call to {@link #init}.
* 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 ()
{
return true; // TODO
}
/**
* Attempts to redownload the <code>getdown.txt</code> file based on information parsed from a
* previous call to {@link #init}.
*/
public void attemptRecovery (StatusDisplay status)
throws IOException
@@ -505,9 +508,8 @@ public class Application
}
/**
* Downloads and replaces the <code>getdown.txt</code> and
* <code>digest.txt</code> files with those for the target version of
* our application.
* Downloads and replaces the <code>getdown.txt</code> and <code>digest.txt</code> files with
* those for the target version of our application.
*/
public void updateMetadata ()
throws IOException
@@ -520,10 +522,9 @@ public class Application
throw (IOException) new IOException(err).initCause(mue);
}
// 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
// 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
downloadControlFile(Digest.DIGEST_FILE);
downloadControlFile(CONFIG_FILE);
}
@@ -553,11 +554,9 @@ public class Application
args.add("-classpath");
args.add(cpbuf.toString());
// we love our Mac users, so we do nice things to preserve our
// application identity
// we love our Mac users, so we do nice things to preserve our application identity
if (RunAnywhere.isMacOS()) {
args.add("-Xdock:icon=" + _appdir.getAbsolutePath() +
"/../desktop.icns");
args.add("-Xdock:icon=" + _appdir.getAbsolutePath() + "/../desktop.icns");
args.add("-Xdock:name=" + _name);
}
@@ -565,8 +564,7 @@ public class Application
String proxyHost;
if ((proxyHost = System.getProperty("http.proxyHost")) != null) {
args.add("-Dhttp.proxyHost=" + proxyHost);
args.add("-Dhttp.proxyPort=" +
System.getProperty("http.proxyPort"));
args.add("-Dhttp.proxyPort=" + System.getProperty("http.proxyPort"));
}
// pass along any pass-through arguments
@@ -607,8 +605,7 @@ public class Application
ArrayList<URL> jars = new ArrayList<URL>();
for (Resource rsrc : _codes) {
try {
jars.add(
new URL("file", "", rsrc.getLocal().getAbsolutePath()));
jars.add(new URL("file", "", rsrc.getLocal().getAbsolutePath()));
} catch (Exception e) {
e.printStackTrace(System.err);
}
@@ -631,8 +628,7 @@ public class Application
if (eqidx == -1) {
Log.warning("Bogus system property: '" + jvmarg + "'?");
} else {
System.setProperty(jvmarg.substring(0, eqidx),
jvmarg.substring(eqidx+1));
System.setProperty(jvmarg.substring(0, eqidx), jvmarg.substring(eqidx+1));
}
}
}
@@ -655,8 +651,7 @@ public class Application
Method main;
try {
// first see if the class has a special applet-aware main
main = appclass.getMethod(
"main", JApplet.class, SA_PROTO.getClass());
main = appclass.getMethod("main", JApplet.class, SA_PROTO.getClass());
main.invoke(null, new Object[] { applet, args });
} catch (NoSuchMethodException nsme) {
main = appclass.getMethod("main", SA_PROTO.getClass());
@@ -676,17 +671,16 @@ public class Application
}
/**
* Loads the <code>digest.txt</code> file and verifies the contents of
* both that file and the <code>getdown.text</code> file. Then it
* loads the <code>version.txt</code> and decides whether or not the
* application needs to be updated or whether we can proceed to
* verification and execution.
* Loads the <code>digest.txt</code> file and verifies the contents of both that file and the
* <code>getdown.text</code> file. Then it loads the <code>version.txt</code> and decides
* 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.
* @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.
* @exception IOException thrown if we encounter an unrecoverable error while verifying the
* metadata.
*/
public boolean verifyMetadata (StatusDisplay status)
throws IOException
@@ -701,21 +695,18 @@ public class Application
// Log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator()));
// Log.info("App Args: " + StringUtil.toString(_appargs.iterator()));
// create our digester which will read in the contents of the
// digest file and validate itself
// this will read in the contents of the digest file and validate itself
try {
_digest = new Digest(_appdir);
} catch (IOException ioe) {
Log.info("Failed to load digest: " + ioe.getMessage() + ". " +
"Attempting recovery...");
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 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
// 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");
@@ -732,10 +723,9 @@ public class Application
}
}
// 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
// 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");
downloadControlFile(Digest.DIGEST_FILE);
@@ -746,16 +736,14 @@ public class Application
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
// 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
downloadControlFile(CONFIG_FILE);
downloadControlFile(Digest.DIGEST_FILE);
_digest = new Digest(_appdir);
// revalidate everything if we end up downloading new metadata
clearValidationMarkers();
// if the new copy validates, reinitialize ourselves;
// otherwise report baffling hoseage
// if the new copy validates, reinitialize ourselves; otherwise report baffling hoseage
if (_digest.validateResource(crsrc, null)) {
init(true);
} else {
@@ -767,15 +755,13 @@ public class Application
// 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
// if we are a versioned application, read in the contents of the version.txt file
if (_version != -1) {
File vfile = getLocalPath(VERSION_FILE);
FileInputStream fin = null;
try {
fin = new FileInputStream(vfile);
BufferedReader bin = new BufferedReader(
new InputStreamReader(fin));
BufferedReader bin = new BufferedReader(new InputStreamReader(fin));
String vstr = bin.readLine();
if (!StringUtil.isBlank(vstr)) {
_targetVersion = Long.parseLong(vstr);
@@ -792,11 +778,9 @@ public class Application
}
/**
* 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.
* 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.
*/
public List<Resource> verifyResources (ProgressObserver obs)
{
@@ -830,8 +814,8 @@ public class Application
}
} catch (Exception e) {
Log.info("Failure validating resource [rsrc=" + rsrc +
", error=" + e + "]. Requesting redownload...");
Log.info("Failure validating resource [rsrc=" + rsrc + ", error=" + e + "]. " +
"Requesting redownload...");
} finally {
mpobs.progress(100);
@@ -856,12 +840,12 @@ public class Application
protected URL createVAppBase (long version)
throws MalformedURLException
{
return new URL(
StringUtil.replace(_appbase, "%VERSION%", "" + version));
return new URL(StringUtil.replace(_appbase, "%VERSION%", "" + version));
}
/** Clears all validation marker files for the resources in the
* supplied iterator. */
/**
* Clears all validation marker files for the resources in the supplied iterator.
*/
protected void clearValidationMarkers (Iterator<Resource> iter)
{
while (iter.hasNext()) {
@@ -870,9 +854,8 @@ public class Application
}
/**
* Downloads a new copy of the specified control file and, if the
* download is successful, moves it over the old file on the
* filesystem.
* Downloads a new copy of the specified control file and, if the download is successful, moves
* it over the old file on the filesystem.
*/
protected void downloadControlFile (String path)
throws IOException
@@ -882,15 +865,12 @@ public class Application
try {
targetURL = getRemoteURL(path);
} catch (Exception e) {
Log.warning("Requested to download invalid control file " +
"[appbase=" + _vappbase + ", path=" + path +
", error=" + e + "].");
String msg = "Invalid path '" + path + "'.";
throw (IOException) new IOException(msg).initCause(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 + "'.");
Log.info("Attempting to refetch '" + path + "' from '" + targetURL + "'.");
// stream the URL into our temporary file
InputStream fin = null;
@@ -904,12 +884,10 @@ public class Application
StreamUtil.close(fout);
}
// Windows is a wonderful operating system, it won't let you
// rename a file overtop of another one; thus to avoid running the
// risk of getting royally fucked, we have to do this complicated
// backup bullshit; this way if the shit hits the fan before we
// get the new copy into place, we should be able to read from the
// backup copy; yay!
// Windows is a wonderful operating system, it won't let you rename a file overtop of
// another one; thus to avoid running the risk of getting royally fucked, we have to do
// this complicated backup bullshit; this way if the shit hits the fan before we get the
// new copy into place, we should be able to read from the backup copy; yay!
File original = getLocalPath(path);
if (RunAnywhere.isWindows() && original.exists()) {
File backup = getLocalPath(path + "_old");
@@ -917,16 +895,14 @@ public class Application
Log.warning("Failed to delete " + backup + ".");
}
if (!original.renameTo(backup)) {
Log.warning("Failed to move " + original + " to backup. " +
"We will likely fail to replace it with " +
target + ".");
Log.warning("Failed to move " + original + " to backup. We will likely fail " +
"to replace it with " + target + ".");
}
}
// now attempt to replace the current file with the new one
if (!target.renameTo(original)) {
throw new IOException(
"Failed to rename(" + target + ", " + original + ")");
throw new IOException("Failed to rename(" + target + ", " + original + ")");
}
}
@@ -934,13 +910,11 @@ public class Application
protected Resource createResource (String path, boolean unpack)
throws MalformedURLException
{
return new Resource(
path, getRemoteURL(path), getLocalPath(path), unpack);
return new Resource(path, getRemoteURL(path), getLocalPath(path), unpack);
}
/** Used to parse resources with the specfied name. */
protected void parseResources (
HashMap<String,Object> cdata, String name, boolean unpack,
protected void parseResources (HashMap<String,Object> cdata, String name, boolean unpack,
ArrayList<Resource> list)
{
String[] rsrcs = ConfigUtil.getMultiValue(cdata, name);
@@ -957,8 +931,7 @@ public class Application
}
/** Used to parse rectangle specifications from the config file. */
protected Rectangle parseRect (
HashMap<String,Object> cdata, String name, Rectangle def)
protected Rectangle parseRect (HashMap<String,Object> cdata, String name, Rectangle def)
{
String value = (String)cdata.get(name);
if (!StringUtil.isBlank(value)) {
@@ -966,24 +939,21 @@ public class Application
if (v != null && v.length == 4) {
return new Rectangle(v[0], v[1], v[2], v[3]);
} else {
Log.warning("Ignoring invalid '" + name + "' config '" +
value + "'.");
Log.warning("Ignoring invalid '" + name + "' config '" + value + "'.");
}
}
return def;
}
/** Used to parse color specifications from the config file. */
protected Color parseColor (
HashMap<String,Object> cdata, String name, Color def)
protected Color parseColor (HashMap<String,Object> cdata, String name, Color def)
{
String value = (String)cdata.get(name);
if (!StringUtil.isBlank(value)) {
try {
return new Color(Integer.parseInt(value, 16));
} catch (Exception e) {
Log.warning("Ignoring invalid '" + name + "' config '" +
value + "'.");
Log.warning("Ignoring invalid '" + name + "' config '" + value + "'.");
}
}
return def;
@@ -993,8 +963,7 @@ public class Application
protected String[] parseList (HashMap<String,Object> cdata, String name)
{
String value = (String)cdata.get(name);
return (value == null) ? new String[0] :
StringUtil.parseStringArray(value);
return (value == null) ? new String[0] : StringUtil.parseStringArray(value);
}
protected File _appdir;
@@ -1011,14 +980,16 @@ public class Application
protected boolean _windebug;
protected boolean _useTorrent = false;
protected int _javaVersion;
protected String _javaLocation;
protected ArrayList<Resource> _codes = new ArrayList<Resource>();
protected ArrayList<Resource> _resources = new ArrayList<Resource>();
protected ArrayList<String> _auxgroups = new ArrayList<String>();
protected HashMap<String,ArrayList<Resource>> _auxrsrcs =
new HashMap<String,ArrayList<Resource>>();
protected HashMap<String,Boolean> _auxactive =
new HashMap<String,Boolean>();
protected HashMap<String,Boolean> _auxactive = new HashMap<String,Boolean>();
protected ArrayList<String> _jvmargs = new ArrayList<String>();
protected ArrayList<String> _appargs = new ArrayList<String>();
@@ -68,8 +68,7 @@ import com.threerings.getdown.util.LaunchUtil;
import com.threerings.getdown.util.ProgressObserver;
/**
* Manages the main control for the Getdown application updater and
* deployment system.
* Manages the main control for the Getdown application updater and deployment system.
*/
public abstract class Getdown extends Thread
implements Application.StatusDisplay
@@ -86,16 +85,14 @@ public abstract class Getdown extends Thread
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
// 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 = appDir.toString();
if (dir.equals(".")) {
dir = System.getProperty("user.dir");
}
String errmsg = "The directory in which this application is " +
"installed:\n" + dir + "\nis invalid. The directory " +
"must not contain the '!' character. Please reinstall.";
String errmsg = "The directory in which this application is installed:\n" + dir +
"\nis invalid. The directory must not contain the '!' character. Please reinstall.";
updateStatus(errmsg);
_dead = true;
}
@@ -104,8 +101,8 @@ public abstract class Getdown extends Thread
}
/**
* This is used by the applet which always needs a user interface and wants
* to load it as soon as possible.
* This is used by the applet which always needs a user interface and wants to load it as soon
* as possible.
*/
public void preInit ()
{
@@ -120,8 +117,8 @@ public abstract class Getdown extends Thread
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 we have no messages, just bail because we're hosed; the error message will be
// displayed to the user already
if (_msgs == null) {
return;
}
@@ -145,11 +142,9 @@ public abstract class Getdown extends Thread
} else {
// create a panel they can use to configure the proxy settings
_container = createContainer();
_container.add(
new ProxyPanel(this, _msgs), BorderLayout.CENTER);
_container.add(new ProxyPanel(this, _msgs), BorderLayout.CENTER);
showContainer();
// allow them to close the window to abort the proxy
// configuration
// allow them to close the window to abort the proxy configuration
_dead = true;
}
@@ -161,11 +156,11 @@ public abstract class Getdown extends Thread
} 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);
msg = MessageUtil.compose(
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
} else {
msg = MessageUtil.compose("m.init_error",
MessageUtil.taint(msg), _ifc.installError);
msg = MessageUtil.compose(
"m.init_error", MessageUtil.taint(msg), _ifc.installError);
}
}
updateStatus(msg);
@@ -174,13 +169,11 @@ public abstract class Getdown extends Thread
}
/**
* Configures our proxy settings (called by {@link ProxyPanel}) and
* fires up the launcher.
* Configures our proxy settings (called by {@link ProxyPanel}) and fires up the launcher.
*/
public void configureProxy (String host, String port)
{
Log.info("User configured proxy [host=" + host +
", port=" + port + "].");
Log.info("User configured proxy [host=" + host + ", port=" + port + "].");
// if we're provided with valid values, create a proxy.txt file
if (!StringUtil.isBlank(host)) {
@@ -193,8 +186,7 @@ public abstract class Getdown extends Thread
}
pout.close();
} catch (IOException ioe) {
Log.warning("Error creating proxy file '" + pfile +
"': " + ioe);
Log.warning("Error creating proxy file '" + pfile + "': " + ioe);
}
// also configure them in the JVM
@@ -212,8 +204,8 @@ public abstract class Getdown extends Thread
/**
* Reads and/or autodetects our proxy settings.
*
* @return true if we should proceed with running the launcher, false
* if we need to wait for the user to enter proxy settings.
* @return true if we should proceed with running the launcher, false if we need to wait for
* the user to enter proxy settings.
*/
protected boolean detectProxy ()
{
@@ -228,8 +220,7 @@ public abstract class Getdown extends Thread
String host = null, port = null;
boolean enabled = false;
RegistryKey.initialize();
RegistryKey r = new RegistryKey(
RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
RegistryKey r = new RegistryKey(RootKey.HKEY_CURRENT_USER, PROXY_REGISTRY);
for (Iterator iter = r.values(); iter.hasNext(); ) {
RegistryValue value = (RegistryValue)iter.next();
if (value.getName().equals("ProxyEnable")) {
@@ -254,8 +245,7 @@ public abstract class Getdown extends Thread
}
} catch (Throwable t) {
Log.info("Failed to find proxy settings in Windows registry " +
"[error=" + t + "].");
Log.info("Failed to find proxy settings in Windows registry [error=" + t + "].");
}
}
@@ -264,18 +254,15 @@ public abstract class Getdown extends Thread
if (pfile.exists()) {
try {
HashMap pconf = ConfigUtil.parseConfig(pfile, false);
setProxyProperties((String)pconf.get("host"),
(String)pconf.get("port"));
setProxyProperties((String)pconf.get("host"), (String)pconf.get("port"));
return true;
} catch (IOException ioe) {
Log.warning("Failed to read '" + pfile + "': " + ioe);
}
}
// 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
// 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 {
_ifc = _app.init(true);
@@ -287,26 +274,23 @@ public abstract class Getdown extends Thread
URL rurl = _app.getConfigResource().getRemote();
try {
// try to make a HEAD request for this URL
HttpURLConnection ucon = (HttpURLConnection)
rurl.openConnection();
HttpURLConnection ucon = (HttpURLConnection)rurl.openConnection();
ucon.setRequestMethod("HEAD");
ucon.connect();
// make sure we got a satisfactory response code
if (ucon.getResponseCode() != HttpURLConnection.HTTP_OK) {
Log.warning("Got a non-200 response but assuming we're OK " +
"because we got something... [url=" + rurl +
", rsp=" + ucon.getResponseCode() + "].");
Log.warning("Got a non-200 response but assuming we're OK because we got " +
"something... [url=" + rurl + ", rsp=" + ucon.getResponseCode() + "].");
}
// we got through, so we appear not to require a proxy; make a
// blank proxy config and get on gettin' down
// we got through, so we appear not to require a proxy; make a blank proxy config and
// get on gettin' down
Log.info("No proxy appears to be needed.");
try {
pfile.createNewFile();
} catch (IOException ioe) {
Log.warning("Failed to create blank proxy file '" +
pfile + "': " + ioe);
Log.warning("Failed to create blank proxy file '" + pfile + "': " + ioe);
}
return true;
@@ -334,8 +318,7 @@ public abstract class Getdown extends Thread
}
/**
* Does the actual application validation, update and launching
* business.
* Does the actual application validation, update and launching business.
*/
protected void getdown ()
{
@@ -357,9 +340,11 @@ public abstract class Getdown extends Thread
createInterface(true);
}
// if we aren't running in a JVM that meets our version requirements, either complain
// or attempt to download and install the appropriate version
for (int ii = 0; ii < MAX_LOOPS; ii++) {
// make sure we have the desired version and that the
// metadata files are valid...
// make sure we have the desired version and that the metadata files are valid...
setStatus("m.validating", -1, -1L, false);
if (_app.verifyMetadata(this)) {
Log.info("Application requires update.");
@@ -394,11 +379,11 @@ public abstract class Getdown extends Thread
} 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);
msg = MessageUtil.compose(
"m.missing_resource", MessageUtil.taint(msg), _ifc.installError);
} else {
msg = MessageUtil.compose("m.init_error", msg,
MessageUtil.taint(msg), _ifc.installError);
msg = MessageUtil.compose(
"m.init_error", msg, MessageUtil.taint(msg), _ifc.installError);
}
}
updateStatus(msg);
@@ -445,8 +430,7 @@ public abstract class Getdown extends Thread
for (Resource prsrc : list) {
try {
Patcher patcher = new Patcher();
patcher.patch(prsrc.getLocal().getParentFile(),
prsrc.getLocal(), _progobs);
patcher.patch(prsrc.getLocal().getParentFile(), prsrc.getLocal(), _progobs);
} catch (Exception e) {
Log.warning("Failed to apply patch [prsrc=" + prsrc + "].");
Log.logStackTrace(e);
@@ -460,9 +444,9 @@ public abstract class Getdown extends Thread
}
}
// 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
// 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();
@@ -471,8 +455,7 @@ public abstract class Getdown extends Thread
}
/**
* Called if the application is determined to require resource
* downloads.
* Called if the application is determined to require resource downloads.
*/
protected void download (List<Resource> resources)
{
@@ -510,23 +493,19 @@ public abstract class Getdown extends Thread
// assume we're going to use an HTTP downloader
Downloader dl = new HTTPDownloader(resources, obs);
// if torrent downloading is enabled and we are downloading the right
// set of resources (a single patch file or the entire app from
// scratch), then use a torrent downloader instead.
// Because many of our installers also bundle background.png,
// and might bundle more required files, we need to allow a
// 'fudge factor' threshhold for determining at which point it is
// faster to torrent, and at which point we should use HTTP.
// if torrent downloading is enabled and we are downloading the right set of resources (a
// single patch file or the entire app from scratch), then use a torrent downloader
// instead. Because many of our installers also bundle background.png, and might bundle
// more required files, we need to allow a 'fudge factor' threshhold for determining at
// which point it is faster to torrent, and at which point we should use HTTP.
if (_app.getUseTorrent()) {
int verifiedResources = _app.getAllResources().size() -
resources.size();
int verifiedResources = _app.getAllResources().size() - resources.size();
if (verifiedResources <= MAX_TORRENT_VERIFIED_RESOURCES) {
ArrayList<Resource> full = new ArrayList<Resource>();
full.add(_app.getFullResource());
full.addAll(resources);
dl = new TorrentDownloader(full, obs);
} else if (resources.size() == 1 &&
resources.get(0).getPath().startsWith("patch")) {
} else if (resources.size() == 1 && resources.get(0).getPath().startsWith("patch")) {
dl = new TorrentDownloader(resources, obs);
}
}
@@ -543,8 +522,7 @@ public abstract class Getdown extends Thread
}
/**
* Called to launch the application if everything is determined to be
* ready to go.
* Called to launch the application if everything is determined to be ready to go.
*/
protected void launch ()
{
@@ -557,28 +535,27 @@ public abstract class Getdown extends Thread
} else {
Process proc = _app.createProcess();
// on Windows 98 and ME we need to stick around and read the
// output of stderr lest the process fill its output buffer and
// choke, yay!
// 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();
_status = null;
BufferedReader reader = new BufferedReader(
new InputStreamReader(stderr));
BufferedReader reader = new BufferedReader(new InputStreamReader(stderr));
while (reader.readLine() != null) {
// nothing doing!
}
Log.info("Process exited: " + proc.waitFor());
} else {
// spawn a daemon thread that will catch the early bits of
// stderr in case the launch fails
// spawn a daemon thread that will catch the early bits of stderr in case the
// launch fails
Thread t = new Thread() {
public void run () {
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(stderr));
BufferedReader reader =
new BufferedReader(new InputStreamReader(stderr));
String line;
while ((line = reader.readLine()) != null) {
Log.warning(line);
@@ -593,9 +570,9 @@ public abstract class Getdown extends Thread
}
}
// if we have a UI open and we haven't been around for at least 5
// seconds, don't stick a fork in ourselves straight away but give
// our lovely user a chance to see what we're doing
// if we have a UI open and we haven't been around for at least 5 seconds, don't stick
// a fork in ourselves straight away but give our lovely user a chance to see what
// we're doing
long uptime = System.currentTimeMillis() - _startup;
if (_container != null && uptime < MIN_EXIST_TIME) {
try {
@@ -611,8 +588,8 @@ public abstract class Getdown extends Thread
}
/**
* Creates our user interface, which we avoid doing unless we actually
* have to update something.
* Creates our user interface, which we avoid doing unless we actually have to update
* something.
*/
protected void createInterface (boolean force)
{
@@ -627,8 +604,7 @@ public abstract class Getdown extends Thread
_status = new StatusPanel(_msgs);
_container.add(_status, BorderLayout.CENTER);
}
_status.init(_ifc, getBackgroundImage(),
getProgressImage());
_status.init(_ifc, getBackgroundImage(), getProgressImage());
showContainer();
}
});
@@ -660,8 +636,8 @@ public abstract class Getdown extends Thread
}
}
protected void setStatus (final String message, final int percent,
final long remaining, boolean createUI)
protected void setStatus (
final String message, final int percent, final long remaining, boolean createUI)
{
if (_status == null && createUI) {
createInterface(false);
@@ -712,8 +688,7 @@ public abstract class Getdown extends Thread
imgpath = _app.getLocalPath(path);
return ImageIO.read(imgpath);
} catch (IOException ioe2) {
Log.warning("Failed to load image [path=" + imgpath +
", error=" + ioe2 + "].");
Log.warning("Failed to load image [path=" + imgpath + ", error=" + ioe2 + "].");
return null;
}
}
@@ -734,9 +709,8 @@ public abstract class Getdown extends Thread
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.
* 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 ()
{
@@ -744,8 +718,8 @@ public abstract class Getdown extends Thread
}
/**
* Provides access to the applet that we'll pass on to our application when
* we're in "invoke direct" mode.
* Provides access to the applet that we'll pass on to our application when we're in "invoke
* direct" mode.
*/
protected JApplet getApplet ()
{
@@ -765,8 +739,7 @@ public abstract class Getdown extends Thread
};
protected Application _app;
protected Application.UpdateInterface _ifc =
new Application.UpdateInterface();
protected Application.UpdateInterface _ifc = new Application.UpdateInterface();
protected ResourceBundle _msgs;
protected Container _container;
@@ -776,10 +749,7 @@ public abstract class Getdown extends Thread
protected boolean _dead;
protected long _startup;
/**
* The maximum number of resources that can be already present for
* bittorrent to be used.
*/
/** The maximum number of resources that can be already present for bittorrent to be used. */
protected static final int MAX_TORRENT_VERIFIED_RESOURCES = 1;
protected static final int MAX_LOOPS = 5;
@@ -54,23 +54,8 @@ public class GetdownApplet extends JApplet
@Override // documentation inherited
public void init ()
{
// Getdown absolutely requires full read/write permissions to the system. If we don't
// have this, then we need to not do anything unsafe, and display a message to the user
// telling them they need to (groan) close out of the web browser entirely and re-launch
// the browser, go to our site, and accept the certificate.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkWrite("getdown");
sm.checkPropertiesAccess();
} catch (SecurityException se) {
Log.warning("Signed applet rejected by user [se=" + se + "].");
_permissioned = false;
}
}
// First off, verify that we are not being hijacked to execute
// malicious code in the name of the signer.
// verify that we are not being hijacked to execute malicious code in the name of the
// signer
String appbase = getParameter("appbase");
String appname = getParameter("appname");
String imgpath = getParameter("bgimage");
@@ -83,100 +68,10 @@ public class GetdownApplet extends JApplet
if (imgpath == null) {
imgpath = "";
}
String params = appbase + appname + imgpath;
String signature = getParameter("signature");
if (signature == null) {
signature = "";
}
Object[] signers = GetdownApplet.class.getSigners();
if (signers.length == 0) {
_safe = true;
}
for (Object signer : signers) {
if (!_safe && signer instanceof Certificate) {
Certificate cert = (Certificate)signer;
File appdir = null;
try {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(cert);
sig.update(params.getBytes());
if (sig.verify(Base64.decodeBase64(
signature.getBytes()))) {
_safe = true;
}
} catch (GeneralSecurityException gse) {
// ignore the error - the default is to not launch.
}
}
}
if (!_safe) {
Log.warning("Signed getdown invoked on unsigned application; " +
"aborting installation.");
}
// Pass through properties parameter.
String properties = getParameter("properties");
if (properties != null && _permissioned) {
String[] proparray = properties.split(" ");
for (String property : proparray) {
String key = property.substring(property.indexOf("-D") + 2,
property.indexOf("="));
String value = property.substring(property.indexOf("=") + 1);
System.setProperty(key, value);
}
}
// when run from an applet, we install
String root;
if (RunAnywhere.isWindows()) {
root = "Application Data";
} else if (RunAnywhere.isMacOS()) {
root = "Library" + File.separator + "Application Support";
} else /* isLinux() or something wacky */ {
root = ".getdown";
}
String appdir = root + File.separator + appname;
if (_permissioned) {
appdir = System.getProperty("user.home") + File.separator + appdir;
}
// if our application directory does not exist, auto-create it
File appDir = new File(appdir);
if (_permissioned && (!appDir.exists() || !appDir.isDirectory())) {
if (!appDir.mkdirs()) {
Log.warning("Unable to create app_dir '" + appdir + "'.");
// TODO: report error
return;
}
}
// if an installer.txt file is desired, create that
String inststr = getParameter("installer");
if (_permissioned && !StringUtil.isBlank(inststr)) {
File infile = new File(appDir, "installer.txt");
if (!infile.exists()) {
writeToFile(infile, inststr);
}
}
// if our getdown.txt file does not exist, auto-create it
if (_permissioned) {
File gdfile = new File(appDir, "getdown.txt");
if (!gdfile.exists()) {
if (StringUtil.isBlank(appbase)) {
Log.warning("Missing 'appbase' cannot auto-create " +
"application directory.");
// TODO: report
return;
}
if (!writeToFile(gdfile, "appbase = " + appbase)) {
// TODO: report the error
return;
}
}
}
appdir = initGetdown(appbase, appname, imgpath);
// if a background image was specified, grabbit
try {
@@ -194,16 +89,18 @@ public class GetdownApplet extends JApplet
Log.info("-- OS Arch: " + System.getProperty("os.arch"));
Log.info("-- OS Vers: " + System.getProperty("os.version"));
Log.info("-- Java Vers: " + System.getProperty("java.version"));
if (_permissioned) {
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("---------------------------------------------");
} catch (Exception e) {
_errmsg = e.getMessage();
}
try {
_getdown = new Getdown(appDir, null) {
_getdown = new Getdown(appdir, null) {
protected Container createContainer () {
getContentPane().removeAll();
return getContentPane();
@@ -227,14 +124,13 @@ public class GetdownApplet extends JApplet
protected void exit (int exitCode) {
// don't exit as we're in an applet
}
@Override // documentation inherited
protected void setStatus (final String message, final int percent,
final long remaining, boolean createUI)
@Override protected void setStatus (
final String message, final int percent, final long remaining, boolean createUI)
{
super.setStatus(message, percent, remaining, createUI);
try {
JSObject.getWindow(GetdownApplet.this).call("getdownStatus",
new Object[] {message, percent, remaining});
JSObject.getWindow(GetdownApplet.this).call(
"getdownStatus", new Object[] { message, percent, remaining });
} catch (JSException jse) {
// don't sweat it.
}
@@ -252,20 +148,16 @@ public class GetdownApplet extends JApplet
@Override // documentation inherited
public void start ()
{
if (!_safe) {
_getdown.updateStatus("m.corrupt_param_signature_error");
return;
}
if (!_permissioned) {
_getdown.updateStatus("m.insufficient_permissions_error");
return;
}
if (_errmsg != null) {
_getdown.updateStatus(_errmsg);
} else {
try {
_getdown.start();
} catch (Exception e) {
Log.logStackTrace(e);
}
}
}
@Override // documentation inherited
public void stop ()
@@ -273,6 +165,110 @@ public class GetdownApplet extends JApplet
// TODO
}
/**
* Does all the fiddly initialization of Getdown and throws an exception if something goes
* horribly wrong. If an exception is thrown we will abort the whole process and display an
* error message to the user.
*/
protected File initGetdown (String appbase, String appname, String imgpath)
throws Exception
{
// getdown requires full read/write permissions to the system; if we don't have this, then
// we need to not do anything unsafe, and display a message to the user telling them they
// need to (groan) close out of the web browser entirely and re-launch the browser, go to
// our site, and accept the certificate
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
try {
sm.checkWrite("getdown");
sm.checkPropertiesAccess();
} catch (SecurityException se) {
Log.warning("Signed applet rejected by user [se=" + se + "].");
throw new Exception("m.insufficient_permissions_error");
}
}
Object[] signers = GetdownApplet.class.getSigners();
if (signers.length == 0) {
Log.info("No signers, not verifying param signature.");
} else {
String signature = getParameter("signature");
if (signature == null) {
signature = "";
}
String params = appbase + appname + imgpath;
for (Object signer : signers) {
if (signer instanceof Certificate) {
Certificate cert = (Certificate)signer;
try {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(cert);
sig.update(params.getBytes());
if (!sig.verify(Base64.decodeBase64(signature.getBytes()))) {
throw new Exception("m.corrupt_param_signature_error");
}
} catch (GeneralSecurityException gse) {
throw new Exception("m.corrupt_param_signature_error");
}
}
}
}
// pass through properties parameters
String properties = getParameter("properties");
if (properties != null) {
String[] proparray = properties.split(" ");
for (String property : proparray) {
String key = property.substring(property.indexOf("-D") + 2, property.indexOf("="));
String value = property.substring(property.indexOf("=") + 1);
System.setProperty(key, value);
}
}
// when run from an applet, we install to the user's home directory
String root;
if (RunAnywhere.isWindows()) {
root = "Application Data";
} else if (RunAnywhere.isMacOS()) {
root = "Library" + File.separator + "Application Support";
} else /* isLinux() or something wacky */ {
root = ".getdown";
}
File appdir = new File(System.getProperty("user.home") + File.separator + root +
File.separator + appname);
// if our application directory does not exist, auto-create it
if (!appdir.exists() || !appdir.isDirectory()) {
if (!appdir.mkdirs()) {
throw new Exception("m.create_appdir_failed");
}
}
// if an installer.txt file is desired, create that
String inststr = getParameter("installer");
if (!StringUtil.isBlank(inststr)) {
File infile = new File(appdir, "installer.txt");
if (!infile.exists()) {
writeToFile(infile, inststr);
}
}
// if our getdown.txt file does not exist, auto-create it
File gdfile = new File(appdir, "getdown.txt");
if (!gdfile.exists()) {
if (StringUtil.isBlank(appbase)) {
throw new Exception("m.missing_appbase");
}
if (!writeToFile(gdfile, "appbase = " + appbase)) {
throw new Exception("m.create_getdown_failed");
}
}
return appdir;
}
/**
* Creates the specified file and writes the supplied contents to it.
*/
@@ -290,15 +286,12 @@ public class GetdownApplet extends JApplet
}
}
/** Handles all the actual getting down. */
protected Getdown _getdown;
/** A background image drawn to make things look purdy. */
protected Image _bgimage;
/**
* Getdown will refuse to initialize if the jar is signed but the
* parameters are not validated to prevent malicious code from being run.
*/
protected boolean _safe = false;
/** Whether Getdown has been trusted by the user with system access */
protected boolean _permissioned = true;
/** An error encountered during initialization. */
protected String _errmsg;
}
@@ -118,7 +118,7 @@ public class StatusPanel extends JComponent
{
status = xlate(status);
_newlab = new Label(status, _ifc.statusText, _font);
_newlab.setTargetWidth(_ifc.status.width);
_newlab.setTargetWidth(Math.min(_ifc.status.width, getWidth() - _ifc.status.x*2));
if (_ifc.textShadow != null) {
_newlab.setAlternateColor(_ifc.textShadow);
_newlab.setStyle(Label.SHADOW);
@@ -64,14 +64,20 @@ m.readonly_error = The directory in which this application is installed: \
\n{0}\nis read-only. Please install the applicaton 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.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 = \n\n\n\n\n\n\nYou did not accept the application's digital signature. \
\nPlease quit and re-open your web browser and attempt to \nre-launch the application, \
making sure that you accept \nthe application's digital signature.
m.corrupt_param_signature_error = We couldn't verify the application's digital signature.\n\
Please check that you are launching the application from \nthe application's website.
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_param_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