Made ConfigUtil into a Config object that wraps the data.

This allows all of the config parsing helpers to live with Config.
This commit is contained in:
Michael Bayne
2018-08-09 12:20:21 -07:00
parent 45dc04c5a7
commit 852bba52b1
6 changed files with 252 additions and 211 deletions
@@ -510,19 +510,19 @@ public class Application
public UpdateInterface init (boolean checkPlatform) public UpdateInterface init (boolean checkPlatform)
throws IOException throws IOException
{ {
Map<String,Object> cdata = null; Config config = null;
File config = _config; File cfgfile = _config;
ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(checkPlatform); Config.ParseOpts opts = Config.createOpts(checkPlatform);
try { try {
// if we have a configuration file, read the data from it // if we have a configuration file, read the data from it
if (config.exists()) { if (cfgfile.exists()) {
cdata = ConfigUtil.parseConfig(_config, opts); config = Config.parseConfig(_config, opts);
} }
// otherwise, try reading data from our backup config file; thanks to funny windows // 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 // bullshit, we have to do this backup file fiddling in case we got screwed while
// updating getdown.txt during normal operation // updating getdown.txt during normal operation
else if ((config = getLocalPath(CONFIG_FILE + "_old")).exists()) { else if ((cfgfile = getLocalPath(CONFIG_FILE + "_old")).exists()) {
cdata = ConfigUtil.parseConfig(config, opts); config = Config.parseConfig(cfgfile, opts);
} }
// otherwise, issue a warning that we found no getdown file // otherwise, issue a warning that we found no getdown file
else { else {
@@ -534,16 +534,17 @@ public class Application
// if we failed to read our config file, check for an appbase specified via a system // 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 // property; we can use that to bootstrap ourselves back into operation
if (cdata == null) { if (config == null) {
String appbase = SysProps.appBase(); String appbase = SysProps.appBase();
log.info("Attempting to obtain 'appbase' from system property", "appbase", appbase); log.info("Attempting to obtain 'appbase' from system property", "appbase", appbase);
cdata = new HashMap<>(); Map<String, Object> cdata = new HashMap<>();
cdata.put("appbase", appbase); cdata.put("appbase", appbase);
config = new Config(cdata);
} }
// first determine our application base, this way if anything goes wrong later in the // first determine our application base, this way if anything goes wrong later in the
// process, our caller can use the appbase to download a new configuration file // process, our caller can use the appbase to download a new configuration file
_appbase = (String)cdata.get("appbase"); _appbase = config.getString("appbase");
if (_appbase == null) { if (_appbase == null) {
throw new RuntimeException("m.missing_appbase"); throw new RuntimeException("m.missing_appbase");
} }
@@ -557,8 +558,7 @@ public class Application
} }
// extract our version information // extract our version information
String vstr = (String)cdata.get("version"); _version = config.getLong("version", -1L);
if (vstr != null) _version = parseLong(vstr, "m.invalid_version");
// if we are a versioned deployment, create a versioned appbase // if we are a versioned deployment, create a versioned appbase
try { try {
@@ -569,7 +569,7 @@ public class Application
} }
// check for a latest config URL // check for a latest config URL
String latest = (String)cdata.get("latest"); String latest = config.getString("latest");
if (latest != null) { if (latest != null) {
if (latest.startsWith(_appbase)) { if (latest.startsWith(_appbase)) {
latest = _appbase + latest.substring(_appbase.length()); latest = _appbase + latest.substring(_appbase.length());
@@ -586,49 +586,41 @@ public class Application
String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + "."); String appPrefix = StringUtil.isBlank(_appid) ? "" : (_appid + ".");
// determine our application class name // determine our application class name
_class = (String)cdata.get(appPrefix + "class"); _class = config.getString(appPrefix + "class");
if (_class == null) { if (_class == null) {
throw new IOException("m.missing_class"); throw new IOException("m.missing_class");
} }
// determine whether we want strict comments // determine whether we want strict comments
_strictComments = Boolean.parseBoolean((String)cdata.get("strict_comments")); _strictComments = config.getBoolean("strict_comments");
// check to see if we're using a custom java.version property and regex // check to see if we're using a custom java.version property and regex
vstr = (String)cdata.get("java_version_prop"); _javaVersionProp = config.getString("java_version_prop", _javaVersionProp);
if (vstr != null) _javaVersionProp = vstr; _javaVersionRegex = config.getString("java_version_regex", _javaVersionRegex);
vstr = (String)cdata.get("java_version_regex");
if (vstr != null) _javaVersionRegex = vstr;
// check to see if we require a particular JVM version and have a supplied JVM // check to see if we require a particular JVM version and have a supplied JVM
vstr = (String)cdata.get("java_version"); _javaMinVersion = config.getLong("java_version", _javaMinVersion);
if (vstr != null) _javaMinVersion = parseLong(vstr, "m.invalid_java_version");
// we support java_min_version as an alias of java_version; it better expresses the check // 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 // that's going on and better mirrors java_max_version
vstr = (String)cdata.get("java_min_version"); _javaMinVersion = config.getLong("java_min_version", _javaMinVersion);
if (vstr != null) _javaMinVersion = parseLong(vstr, "m.invalid_java_version");
// check to see if we require a particular max JVM version and have a supplied JVM // check to see if we require a particular max JVM version and have a supplied JVM
vstr = (String)cdata.get("java_max_version"); _javaMaxVersion = config.getLong("java_max_version", _javaMaxVersion);
if (vstr != null) _javaMaxVersion = parseLong(vstr, "m.invalid_java_version");
// check to see if we require a particular JVM version and have a supplied JVM // check to see if we require a particular JVM version and have a supplied JVM
vstr = (String)cdata.get("java_exact_version_required"); _javaExactVersionRequired = config.getBoolean("java_exact_version_required");
_javaExactVersionRequired = Boolean.parseBoolean(vstr);
// this is a little weird, but when we're run from the digester, we see a String[] which // 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 // 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! // need to know about that; when we're run in a real application there will be only one!
Object javaloc = cdata.get("java_location"); Object javaloc = config.getRaw("java_location");
if (javaloc instanceof String) { if (javaloc instanceof String) {
_javaLocation = (String)javaloc; _javaLocation = (String)javaloc;
} }
// determine whether we have any tracking configuration // determine whether we have any tracking configuration
_trackingURL = (String)cdata.get("tracking_url"); _trackingURL = config.getString("tracking_url");
// check for tracking progress percent configuration // check for tracking progress percent configuration
String trackPcts = (String)cdata.get("tracking_percents"); String trackPcts = config.getString("tracking_percents");
if (!StringUtil.isBlank(trackPcts)) { if (!StringUtil.isBlank(trackPcts)) {
_trackingPcts = new HashSet<>(); _trackingPcts = new HashSet<>();
for (int pct : StringUtil.parseIntArray(trackPcts)) { for (int pct : StringUtil.parseIntArray(trackPcts)) {
@@ -640,14 +632,14 @@ public class Application
} }
// Check for tracking cookie configuration // Check for tracking cookie configuration
_trackingCookieName = (String)cdata.get("tracking_cookie_name"); _trackingCookieName = config.getString("tracking_cookie_name");
_trackingCookieProperty = (String)cdata.get("tracking_cookie_property"); _trackingCookieProperty = config.getString("tracking_cookie_property");
// Some app may need an extra suffix added to the tracking URL // Some app may need an extra suffix added to the tracking URL
_trackingURLSuffix = (String)cdata.get("tracking_url_suffix"); _trackingURLSuffix = config.getString("tracking_url_suffix");
// Some app may need to generate google analytics code // Some app may need to generate google analytics code
_trackingGAHash = (String)cdata.get("tracking_ga_hash"); _trackingGAHash = config.getString("tracking_ga_hash");
// clear our arrays as we may be reinitializing // clear our arrays as we may be reinitializing
_codes.clear(); _codes.clear();
@@ -658,34 +650,34 @@ public class Application
_txtJvmArgs.clear(); _txtJvmArgs.clear();
// parse our code resources // parse our code resources
if (ConfigUtil.getMultiValue(cdata, "code") == null && if (config.getMultiValue("code") == null &&
ConfigUtil.getMultiValue(cdata, "ucode") == null) { config.getMultiValue("ucode") == null) {
throw new IOException("m.missing_code"); throw new IOException("m.missing_code");
} }
parseResources(cdata, "code", Resource.NORMAL, _codes); parseResources(config, "code", Resource.NORMAL, _codes);
parseResources(cdata, "ucode", Resource.UNPACK, _codes); parseResources(config, "ucode", Resource.UNPACK, _codes);
// parse our non-code resources // parse our non-code resources
parseResources(cdata, "resource", Resource.NORMAL, _resources); parseResources(config, "resource", Resource.NORMAL, _resources);
parseResources(cdata, "uresource", Resource.UNPACK, _resources); parseResources(config, "uresource", Resource.UNPACK, _resources);
parseResources(cdata, "xresource", Resource.EXEC, _resources); parseResources(config, "xresource", Resource.EXEC, _resources);
// parse our auxiliary resource groups // parse our auxiliary resource groups
for (String auxgroup : parseList(cdata, "auxgroups")) { for (String auxgroup : config.getList("auxgroups")) {
ArrayList<Resource> codes = new ArrayList<>(); ArrayList<Resource> codes = new ArrayList<>();
parseResources(cdata, auxgroup + ".code", Resource.NORMAL, codes); parseResources(config, auxgroup + ".code", Resource.NORMAL, codes);
parseResources(cdata, auxgroup + ".ucode", Resource.UNPACK, codes); parseResources(config, auxgroup + ".ucode", Resource.UNPACK, codes);
ArrayList<Resource> rsrcs = new ArrayList<>(); ArrayList<Resource> rsrcs = new ArrayList<>();
parseResources(cdata, auxgroup + ".resource", Resource.NORMAL, rsrcs); parseResources(config, auxgroup + ".resource", Resource.NORMAL, rsrcs);
parseResources(cdata, auxgroup + ".uresource", Resource.UNPACK, rsrcs); parseResources(config, auxgroup + ".uresource", Resource.UNPACK, rsrcs);
_auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs)); _auxgroups.put(auxgroup, new AuxGroup(auxgroup, codes, rsrcs));
} }
// transfer our JVM arguments (we include both "global" args and app_id-prefixed args) // transfer our JVM arguments (we include both "global" args and app_id-prefixed args)
String[] jvmargs = ConfigUtil.getMultiValue(cdata, "jvmarg"); String[] jvmargs = config.getMultiValue("jvmarg");
addAll(jvmargs, _jvmargs); addAll(jvmargs, _jvmargs);
if (appPrefix.length() > 0) { if (appPrefix.length() > 0) {
jvmargs = ConfigUtil.getMultiValue(cdata, appPrefix + "jvmarg"); jvmargs = config.getMultiValue(appPrefix + "jvmarg");
addAll(jvmargs, _jvmargs); addAll(jvmargs, _jvmargs);
} }
@@ -693,10 +685,10 @@ public class Application
addAll(_extraJvmArgs, _jvmargs); addAll(_extraJvmArgs, _jvmargs);
// get the set of optimum JVM arguments // get the set of optimum JVM arguments
_optimumJvmArgs = ConfigUtil.getMultiValue(cdata, "optimum_jvmarg"); _optimumJvmArgs = config.getMultiValue("optimum_jvmarg");
// transfer our application arguments // transfer our application arguments
String[] appargs = ConfigUtil.getMultiValue(cdata, appPrefix + "apparg"); String[] appargs = config.getMultiValue(appPrefix + "apparg");
addAll(appargs, _appargs); addAll(appargs, _appargs);
// add the launch specific application arguments // add the launch specific application arguments
@@ -706,33 +698,31 @@ public class Application
fillAssignmentListFromPairs("extra.txt", _txtJvmArgs); fillAssignmentListFromPairs("extra.txt", _txtJvmArgs);
// determine whether we want to allow offline operation (defaults to false) // determine whether we want to allow offline operation (defaults to false)
_allowOffline = Boolean.parseBoolean((String)cdata.get("allow_offline")); _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 // 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 // obtain a thread dump of the running JVM
_windebug = getLocalPath("debug.txt").exists(); _windebug = getLocalPath("debug.txt").exists();
// whether to cache code resources and launch from cache // whether to cache code resources and launch from cache
_useCodeCache = Boolean.parseBoolean((String) cdata.get("use_code_cache")); _useCodeCache = config.getBoolean("use_code_cache");
String ccRetentionDays = (String) cdata.get("code_cache_retention_days"); _codeCacheRetentionDays = config.getInt("code_cache_retention_days", 7);
_codeCacheRetentionDays = ccRetentionDays == null ? 7 :
Integer.parseInt(ccRetentionDays);
// parse and return our application config // parse and return our application config
UpdateInterface ui = new UpdateInterface(); UpdateInterface ui = new UpdateInterface();
_name = ui.name = (String)cdata.get("ui.name"); _name = ui.name = config.getString("ui.name");
ui.progress = parseRect(cdata, "ui.progress", ui.progress); ui.progress = config.getRect("ui.progress", ui.progress);
ui.progressText = parseColor(cdata, "ui.progress_text", ui.progressText); ui.progressText = config.getColor("ui.progress_text", ui.progressText);
ui.hideProgressText = Boolean.parseBoolean((String)cdata.get("ui.hide_progress_text")); ui.hideProgressText = config.getBoolean("ui.hide_progress_text");
ui.minShowSeconds = parseInt(cdata, "ui.min_show_seconds", ui.minShowSeconds); ui.minShowSeconds = config.getInt("ui.min_show_seconds", ui.minShowSeconds);
ui.progressBar = parseColor(cdata, "ui.progress_bar", ui.progressBar); ui.progressBar = config.getColor("ui.progress_bar", ui.progressBar);
ui.status = parseRect(cdata, "ui.status", ui.status); ui.status = config.getRect("ui.status", ui.status);
ui.statusText = parseColor(cdata, "ui.status_text", ui.statusText); ui.statusText = config.getColor("ui.status_text", ui.statusText);
ui.textShadow = parseColor(cdata, "ui.text_shadow", ui.textShadow); ui.textShadow = config.getColor("ui.text_shadow", ui.textShadow);
ui.hideDecorations = Boolean.parseBoolean((String)cdata.get("ui.hide_decorations")); ui.hideDecorations = config.getBoolean("ui.hide_decorations");
ui.backgroundImage = (String)cdata.get("ui.background_image"); ui.backgroundImage = config.getString("ui.background_image");
if (ui.backgroundImage == null) { // support legacy format if (ui.backgroundImage == null) { // support legacy format
ui.backgroundImage = (String)cdata.get("ui.background"); ui.backgroundImage = config.getString("ui.background");
} }
// and now ui.background can refer to the background color, but fall back to black // and now ui.background can refer to the background color, but fall back to black
// or white, depending on the brightness of the progressText // or white, depending on the brightness of the progressText
@@ -741,32 +731,32 @@ public class Application
null)[2]) null)[2])
? Color.BLACK ? Color.BLACK
: Color.WHITE; : Color.WHITE;
ui.background = parseColor(cdata, "ui.background", defaultBackground); ui.background = config.getColor("ui.background", defaultBackground);
ui.progressImage = (String)cdata.get("ui.progress_image"); ui.progressImage = config.getString("ui.progress_image");
ui.rotatingBackgrounds = ConfigUtil.getMultiValue(cdata, "ui.rotating_background"); ui.rotatingBackgrounds = config.getMultiValue("ui.rotating_background");
ui.iconImages = ConfigUtil.getMultiValue(cdata, "ui.icon"); ui.iconImages = config.getMultiValue("ui.icon");
ui.errorBackground = (String)cdata.get("ui.error_background"); ui.errorBackground = config.getString("ui.error_background");
_dockIconPath = (String)cdata.get("ui.mac_dock_icon"); _dockIconPath = config.getString("ui.mac_dock_icon");
if (_dockIconPath == null) { if (_dockIconPath == null) {
_dockIconPath = "../desktop.icns"; // use a sensible default _dockIconPath = "../desktop.icns"; // use a sensible default
} }
// On an installation error, where do we point the user. // On an installation error, where do we point the user.
String installError = parseUrl(cdata, "ui.install_error", null); String installError = config.getUrl("ui.install_error", null);
ui.installError = (installError == null) ? ui.installError = (installError == null) ?
"m.default_install_error" : MessageUtil.taint(installError); "m.default_install_error" : MessageUtil.taint(installError);
// the patch notes bits // the patch notes bits
ui.patchNotes = parseRect(cdata, "ui.patch_notes", ui.patchNotes); ui.patchNotes = config.getRect("ui.patch_notes", ui.patchNotes);
ui.patchNotesUrl = parseUrl(cdata, "ui.patch_notes_url", null); ui.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
// the play again bits // the play again bits
ui.playAgain = parseRect(cdata, "ui.play_again", ui.playAgain); ui.playAgain = config.getRect("ui.play_again", ui.playAgain);
ui.playAgainImage = (String)cdata.get("ui.play_again_image"); ui.playAgainImage = config.getString("ui.play_again_image");
// step progress percentages // step progress percentages
for (UpdateInterface.Step step : UpdateInterface.Step.values()) { for (UpdateInterface.Step step : UpdateInterface.Step.values()) {
String spec = (String)cdata.get("ui.percents." + step.name()); String spec = config.getString("ui.percents." + step.name());
if (spec != null) { if (spec != null) {
try { try {
ui.stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec))); ui.stepPercentages.put(step, intsToList(StringUtil.parseIntArray(spec)));
@@ -787,7 +777,7 @@ public class Application
File pairFile = getLocalPath(pairLocation); File pairFile = getLocalPath(pairLocation);
if (pairFile.exists()) { if (pairFile.exists()) {
try { try {
List<String[]> args = ConfigUtil.parsePairs(pairFile, ConfigUtil.createOpts(false)); List<String[]> args = Config.parsePairs(pairFile, Config.createOpts(false));
for (String[] pair : args) { for (String[] pair : args) {
if (pair[1].length() == 0) { if (pair[1].length() == 0) {
collector.add(pair[0]); collector.add(pair[0]);
@@ -1253,7 +1243,7 @@ public class Application
try { try {
in = ConnectionUtil.open(_latest, 0, 0).getInputStream(); in = ConnectionUtil.open(_latest, 0, 0).getInputStream();
BufferedReader bin = new BufferedReader(new InputStreamReader(in)); BufferedReader bin = new BufferedReader(new InputStreamReader(in));
for (String[] pair : ConfigUtil.parsePairs(bin, ConfigUtil.createOpts(false))) { for (String[] pair : Config.parsePairs(bin, Config.createOpts(false))) {
if (pair[0].equals("version")) { if (pair[0].equals("version")) {
_targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion); _targetVersion = Math.max(Long.parseLong(pair[1]), _targetVersion);
if (fileVersion != -1 && _targetVersion > fileVersion) { if (fileVersion != -1 && _targetVersion > fileVersion) {
@@ -1707,10 +1697,10 @@ public class Application
} }
/** Used to parse resources with the specified name. */ /** Used to parse resources with the specified name. */
protected void parseResources (Map<String,Object> cdata, String name, protected void parseResources (Config config, String name, EnumSet<Resource.Attr> attrs,
EnumSet<Resource.Attr> attrs, List<Resource> list) List<Resource> list)
{ {
String[] rsrcs = ConfigUtil.getMultiValue(cdata, name); String[] rsrcs = config.getMultiValue(name);
if (rsrcs == null) { if (rsrcs == null) {
return; return;
} }
@@ -1723,87 +1713,6 @@ public class Application
} }
} }
/** Used to parse rectangle specifications from the config file. */
protected Rectangle parseRect (Map<String,Object> cdata, String name, Rectangle def)
{
String value = (String)cdata.get(name);
Rectangle rect = parseRect(name, value);
return (rect == null) ? def : rect;
}
/**
* 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;
}
/** Used to parse int specifications from the config file. */
protected int parseInt (Map<String, Object> cdata, String name, int def) {
String value = (String)cdata.get(name);
try {
return value == null ? def : Integer.parseInt(value);
} catch (Exception e) {
log.warning("Ignoring invalid int '" + name + "' config '" + value + "',");
return def;
}
}
/** Used to parse color specifications from the config file. */
protected Color parseColor (Map<String, Object> cdata, String name, Color def)
{
String value = (String)cdata.get(name);
Color color = parseColor(value);
return (color == null) ? def : color;
}
/**
* Parses the given hex color value (e.g. FFFFF) and returns a Color object with that value. If
* the given value is null of not a valid hexadecimal number, this will return null.
*/
public static Color parseColor (String hexValue)
{
if (!StringUtil.isBlank(hexValue)) {
try {
int rgba = Integer.parseInt(hexValue, 16);
boolean hasAlpha = hexValue.length() > 6;
return new Color(rgba, hasAlpha);
} catch (NumberFormatException e) {
log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e);
}
}
return null;
}
/** Parses a list of strings from the config file. */
protected String[] parseList (Map<String, Object> cdata, String name)
{
String value = (String)cdata.get(name);
return (value == null) ? ArrayUtil.EMPTY_STRING : StringUtil.parseStringArray(value);
}
/**
* Parses a URL from the config file, checking first for a localized version.
*/
protected String parseUrl (Map<String, Object> cdata, String name, String def)
{
String value = (String)cdata.get(name + "." + Locale.getDefault().getLanguage());
if (!StringUtil.isBlank(value)) {
return value;
}
value = (String)cdata.get(name);
return StringUtil.isBlank(value) ? def : value;
}
/** Possibly generates and returns a google analytics tracking cookie. */ /** Possibly generates and returns a google analytics tracking cookie. */
protected String getGATrackingCode () protected String getGATrackingCode ()
{ {
@@ -1843,20 +1752,6 @@ public class Application
} }
} }
/**
* Parses and returns a long. {@code value} must be non-null.
* @throws IOException on malformed value.
*/
protected static long parseLong (String value, String errkey) throws IOException
{
try {
return Long.parseLong(value);
} catch (Exception e) {
String err = MessageUtil.tcompose(errkey, value);
throw (IOException) new IOException(err).initCause(e);
}
}
protected File _appdir; protected File _appdir;
protected String _appid; protected String _appid;
protected File _config; protected File _config;
@@ -15,7 +15,7 @@ import com.samskivert.io.StreamUtil;
import com.samskivert.text.MessageUtil; import com.samskivert.text.MessageUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.Config;
import com.threerings.getdown.util.ProgressObserver; import com.threerings.getdown.util.ProgressObserver;
import static com.threerings.getdown.Log.log; import static com.threerings.getdown.Log.log;
@@ -155,12 +155,12 @@ public class Digest
String filename = digestFile(version); String filename = digestFile(version);
StringBuilder data = new StringBuilder(); StringBuilder data = new StringBuilder();
File dfile = new File(appdir, filename); File dfile = new File(appdir, filename);
ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(false); Config.ParseOpts opts = Config.createOpts(false);
opts.strictComments = strictComments; opts.strictComments = strictComments;
// bias = toward key: the key is the filename and could conceivably contain = signs, value // bias = toward key: the key is the filename and could conceivably contain = signs, value
// is the hex encoded hash which will not contain = // is the hex encoded hash which will not contain =
opts.biasToKey = true; opts.biasToKey = true;
for (String[] pair : ConfigUtil.parsePairs(dfile, opts)) { for (String[] pair : Config.parsePairs(dfile, opts)) {
if (pair[0].equals(filename)) { if (pair[0].equals(filename)) {
_metaDigest = pair[1]; _metaDigest = pair[1];
break; break;
@@ -56,7 +56,7 @@ import com.threerings.getdown.data.SysProps;
import com.threerings.getdown.net.Downloader; import com.threerings.getdown.net.Downloader;
import com.threerings.getdown.net.HTTPDownloader; import com.threerings.getdown.net.HTTPDownloader;
import com.threerings.getdown.tools.Patcher; import com.threerings.getdown.tools.Patcher;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.Config;
import com.threerings.getdown.util.ConnectionUtil; import com.threerings.getdown.util.ConnectionUtil;
import com.threerings.getdown.util.FileUtil; import com.threerings.getdown.util.FileUtil;
import com.threerings.getdown.util.LaunchUtil; import com.threerings.getdown.util.LaunchUtil;
@@ -304,9 +304,8 @@ public abstract class Getdown extends Thread
File pfile = _app.getLocalPath("proxy.txt"); File pfile = _app.getLocalPath("proxy.txt");
if (pfile.exists()) { if (pfile.exists()) {
try { try {
Map<String, Object> pconf = Config pconf = Config.parseConfig(pfile, Config.createOpts(false));
ConfigUtil.parseConfig(pfile, ConfigUtil.createOpts(false)); setProxyProperties(pconf.getString("host"), pconf.getString("port"));
setProxyProperties((String)pconf.get("host"), (String)pconf.get("port"));
return true; return true;
} catch (IOException ioe) { } catch (IOException ioe) {
log.warning("Failed to read '" + pfile + "': " + ioe); log.warning("Failed to read '" + pfile + "': " + ioe);
@@ -25,7 +25,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.data.Application; import com.threerings.getdown.data.Application;
import com.threerings.getdown.launcher.ImageLoader; import com.threerings.getdown.launcher.ImageLoader;
import com.threerings.getdown.launcher.RotatingBackgrounds; import com.threerings.getdown.launcher.RotatingBackgrounds;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.Config;
import static com.threerings.getdown.Log.log; import static com.threerings.getdown.Log.log;
@@ -185,8 +185,8 @@ public class GetdownAppletConfig
// This allows us to configure the status panel from applet parameters in case something // This allows us to configure the status panel from applet parameters in case something
// goes horribly wrong before we get a chance to read getdown.txt (like when the user // goes horribly wrong before we get a chance to read getdown.txt (like when the user
// rejects write permission for the applet) // rejects write permission for the applet)
statusBounds = Application.parseRect("ui.status", getParameter("ui.status")); statusBounds = Config.parseRect("ui.status", getParameter("ui.status"));
statusColor = Application.parseColor(getParameter("ui.status_text")); statusColor = Config.parseColor(getParameter("ui.status_text"));
} }
/** /**
@@ -366,9 +366,8 @@ public class GetdownAppletConfig
boolean createGetdown = !gdfile.exists(); boolean createGetdown = !gdfile.exists();
if (!createGetdown) { if (!createGetdown) {
try { try {
Map<String,Object> cdata = Config config = Config.parseConfig(gdfile, Config.createOpts(false));
ConfigUtil.parseConfig(gdfile, ConfigUtil.createOpts(false)); String oappbase = StringUtil.trim(config.getString(APPBASE));
String oappbase = StringUtil.trim((String)cdata.get(APPBASE));
createGetdown = (appbase != null && !appbase.trim().equals(oappbase)); createGetdown = (appbase != null && !appbase.trim().equals(oappbase));
if (createGetdown) { if (createGetdown) {
log.warning("Recreating getdown.txt due to appbase mismatch", log.warning("Recreating getdown.txt due to appbase mismatch",
@@ -5,6 +5,9 @@
package com.threerings.getdown.util; package com.threerings.getdown.util;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@@ -14,8 +17,10 @@ import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import static com.threerings.getdown.Log.log; import static com.threerings.getdown.Log.log;
@@ -24,7 +29,7 @@ import static com.threerings.getdown.Log.log;
* Parses a file containing key/value pairs and returns a {@link HashMap} with the values. Keys may * Parses a file containing key/value pairs and returns a {@link HashMap} with the values. Keys may
* be repeated, in which case they will be made to reference an array of values. * be repeated, in which case they will be made to reference an array of values.
*/ */
public class ConfigUtil public class Config
{ {
/** Options that control the {@link #parsePairs} function. */ /** Options that control the {@link #parsePairs} function. */
public static class ParseOpts { public static class ParseOpts {
@@ -123,6 +128,40 @@ public class ConfigUtil
return pairs; 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. FFFFF) and returns a Color object with that value.
* If the given value is null of not a valid hexadecimal number, this will return null.
*/
public static Color parseColor (String hexValue)
{
if (!StringUtil.isBlank(hexValue)) {
try {
int rgba = Integer.parseInt(hexValue, 16);
boolean hasAlpha = hexValue.length() > 6;
return new Color(rgba, hasAlpha);
} 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 * Parses a configuration file containing key/value pairs. The file must be in the UTF-8
* encoding. * encoding.
@@ -130,7 +169,7 @@ public class ConfigUtil
* @return a map from keys to values, where a value will be an array of strings if more than * @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. * one key/value pair in the config file was associated with the same key.
*/ */
public static Map<String, Object> parseConfig (File source, ParseOpts opts) public static Config parseConfig (File source, ParseOpts opts)
throws IOException throws IOException
{ {
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
@@ -161,16 +200,57 @@ public class ConfigUtil
return parseConfig(source, opts); return parseConfig(source, opts);
} }
return data; return new Config(data);
}
public Config (Map<String, Object> 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 * Massages a single string into an array and leaves existing array values as is. Simplifies
* access to parameters that are expected to be arrays. * access to parameters that are expected to be arrays.
*/ */
public static String[] getMultiValue (Map<String, Object> data, String name) public String[] getMultiValue (String name)
{ {
Object value = data.get(name); Object value = _data.get(name);
if (value instanceof String) { if (value instanceof String) {
return new String[] { (String)value }; return new String[] { (String)value };
} else { } else {
@@ -178,6 +258,72 @@ public class ConfigUtil
} }
} }
/** 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 Color getColor (String name, Color def)
{
String value = getString(name);
Color 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) ? ArrayUtil.EMPTY_STRING : 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)) {
return value;
}
value = getString(name);
return StringUtil.isBlank(value) ? def : value;
}
/** /**
* A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following * A helper function for {@link #parsePairs(Reader,ParseOpts)}. Qualifiers have the following
* form: * form:
@@ -214,4 +360,6 @@ public class ConfigUtil
String os = bits[0], arch = (bits.length > 1) ? bits[1] : ""; String os = bits[0], arch = (bits.length > 1) ? bits[1] : "";
return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1); return (osname.indexOf(os) != -1) && (osarch.indexOf(arch) != -1);
} }
private final Map<String, Object> _data;
} }
@@ -15,9 +15,9 @@ import org.junit.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
/** /**
* Tests {@link ConfigUtil}. * Tests {@link Config}.
*/ */
public class ConfigUtilTest public class ConfigTest
{ {
public static class Pair { public static class Pair {
public final String key; public final String key;
@@ -38,8 +38,8 @@ public class ConfigUtilTest
@Test public void testSimplePairs () throws IOException @Test public void testSimplePairs () throws IOException
{ {
List<String[]> pairs = ConfigUtil.parsePairs( List<String[]> pairs = Config.parsePairs(
toReader(SIMPLE_PAIRS), ConfigUtil.createOpts(true)); toReader(SIMPLE_PAIRS), Config.createOpts(true));
for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) { for (int ii = 0; ii < SIMPLE_PAIRS.length; ii++) {
assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]); assertEquals(SIMPLE_PAIRS[ii].key, pairs.get(ii)[0]);
assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]); assertEquals(SIMPLE_PAIRS[ii].value, pairs.get(ii)[1]);
@@ -58,10 +58,10 @@ public class ConfigUtilTest
Pair notWin = new Pair("fifteen", "[!windows] sixteen"); Pair notWin = new Pair("fifteen", "[!windows] sixteen");
Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin }; Pair[] pairs = { linux, mac, linuxAndMac, linux64, linux64s, mac64, win64, notWin };
ConfigUtil.ParseOpts opts = ConfigUtil.createOpts(false); Config.ParseOpts opts = Config.createOpts(false);
opts.osname = "linux"; opts.osname = "linux";
opts.osarch = "i386"; opts.osarch = "i386";
List<String[]> parsed = ConfigUtil.parsePairs(toReader(pairs), opts); List<String[]> parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(exists(parsed, linux.key)); assertTrue(exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(exists(parsed, linuxAndMac.key)); assertTrue(exists(parsed, linuxAndMac.key));
@@ -72,7 +72,7 @@ public class ConfigUtilTest
assertTrue(exists(parsed, notWin.key)); assertTrue(exists(parsed, notWin.key));
opts.osarch = "x86_64"; opts.osarch = "x86_64";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(exists(parsed, linux.key)); assertTrue(exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(exists(parsed, linuxAndMac.key)); assertTrue(exists(parsed, linuxAndMac.key));
@@ -83,7 +83,7 @@ public class ConfigUtilTest
assertTrue(exists(parsed, notWin.key)); assertTrue(exists(parsed, notWin.key));
opts.osarch = "amd64"; opts.osarch = "amd64";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(exists(parsed, linux.key)); assertTrue(exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(exists(parsed, linuxAndMac.key)); assertTrue(exists(parsed, linuxAndMac.key));
@@ -95,7 +95,7 @@ public class ConfigUtilTest
opts.osname = "mac os x"; opts.osname = "mac os x";
opts.osarch = "x86_64"; opts.osarch = "x86_64";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, linux.key));
assertTrue(exists(parsed, mac.key)); assertTrue(exists(parsed, mac.key));
assertTrue(exists(parsed, linuxAndMac.key)); assertTrue(exists(parsed, linuxAndMac.key));
@@ -107,7 +107,7 @@ public class ConfigUtilTest
opts.osname = "windows"; opts.osname = "windows";
opts.osarch = "i386"; opts.osarch = "i386";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(!exists(parsed, linuxAndMac.key)); assertTrue(!exists(parsed, linuxAndMac.key));
@@ -118,7 +118,7 @@ public class ConfigUtilTest
assertTrue(!exists(parsed, notWin.key)); assertTrue(!exists(parsed, notWin.key));
opts.osarch = "x86_64"; opts.osarch = "x86_64";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(!exists(parsed, linuxAndMac.key)); assertTrue(!exists(parsed, linuxAndMac.key));
@@ -129,7 +129,7 @@ public class ConfigUtilTest
assertTrue(!exists(parsed, notWin.key)); assertTrue(!exists(parsed, notWin.key));
opts.osarch = "amd64"; opts.osarch = "amd64";
parsed = ConfigUtil.parsePairs(toReader(pairs), opts); parsed = Config.parsePairs(toReader(pairs), opts);
assertTrue(!exists(parsed, linux.key)); assertTrue(!exists(parsed, linux.key));
assertTrue(!exists(parsed, mac.key)); assertTrue(!exists(parsed, mac.key));
assertTrue(!exists(parsed, linuxAndMac.key)); assertTrue(!exists(parsed, linuxAndMac.key));