Added support for auxiliary resource groups which are sets of jar files that

are not downloaded until a control file is created in the application directory
to activate them.
This commit is contained in:
Michael Bayne
2006-07-12 02:41:08 +00:00
parent 6aa3d95c5e
commit 4e901691bf
4 changed files with 192 additions and 97 deletions
@@ -164,12 +164,70 @@ public class Application
return _resources; return _resources;
} }
/**
* 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 ()
{
return _auxgroups;
}
/**
* Returns true if the specified auxgroup has been "activated", false if
* not. Non-activated groups should be ignored, activated groups should be
* downloaded and patched along with the main resources.
*/
public boolean isAuxGroupActive (String auxgroup)
{
Boolean active = _auxactive.get(auxgroup);
if (active == null) {
// TODO: compare the contents with the MD5 hash of the auxgroup
// name and the client's machine ident
active = getLocalPath(auxgroup + ".dat").exists();
_auxactive.put(auxgroup, active);
}
return active;
}
/**
* Returns 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)
{
ArrayList<Resource> auxrsrcs = _auxrsrcs.get(group);
return (auxrsrcs == null) ? new ArrayList<Resource>() : auxrsrcs;
}
/**
* Returns all non-code resources and all resources from active auxiliary
* resource groups.
*/
public List<Resource> getActiveResources ()
{
ArrayList<Resource> rsrcs = new ArrayList<Resource>();
rsrcs.addAll(getResources());
for (String auxgroup : getAuxGroups()) {
if (isAuxGroupActive(auxgroup)) {
rsrcs.addAll(getResources(auxgroup));
}
}
return rsrcs;
}
/** /**
* Returns a resource that can be used to download a patch file that * Returns a resource that can be used to download a patch file that
* will bring this application from its current version to the target * will bring this application from its current version to the target
* version. * version.
*
* @param auxgroup the auxiliary resource group for which a patch resource
* is desired or null for the main application patch resource.
*/ */
public Resource getPatchResource () public Resource getPatchResource (String auxgroup)
{ {
if (_targetVersion <= _version) { if (_targetVersion <= _version) {
Log.warning("Requested patch resource for up-to-date or " + Log.warning("Requested patch resource for up-to-date or " +
@@ -178,7 +236,8 @@ public class Application
return null; return null;
} }
String pfile = "patch" + _version + ".dat"; String infix = (auxgroup == null) ? "" : ("-" + auxgroup);
String pfile = "patch" + infix + _version + ".dat";
try { try {
URL remote = new URL(createVAppBase(_targetVersion), pfile); URL remote = new URL(createVAppBase(_targetVersion), pfile);
return new Resource(pfile, remote, getLocalPath(pfile), false); return new Resource(pfile, remote, getLocalPath(pfile), false);
@@ -207,7 +266,7 @@ public class Application
throws IOException throws IOException
{ {
// parse our configuration file // parse our configuration file
HashMap<String, Object> cdata = null; HashMap<String,Object> cdata = null;
try { try {
cdata = ConfigUtil.parseConfig(_config, checkPlatform); cdata = ConfigUtil.parseConfig(_config, checkPlatform);
} catch (FileNotFoundException fnfe) { } catch (FileNotFoundException fnfe) {
@@ -268,44 +327,28 @@ public class Application
// clear our arrays as we may be reinitializing // clear our arrays as we may be reinitializing
_codes.clear(); _codes.clear();
_resources.clear(); _resources.clear();
_auxgroups.clear();
_auxrsrcs.clear();
_jvmargs.clear(); _jvmargs.clear();
_appargs.clear(); _appargs.clear();
// parse our code resources // parse our code resources
String[] codes = ConfigUtil.getMultiValue(cdata, "code"); if (ConfigUtil.getMultiValue(cdata, "code") == null) {
if (codes == null) {
throw new IOException("m.missing_code"); throw new IOException("m.missing_code");
} }
for (int ii = 0; ii < codes.length; ii++) { parseResources(cdata, "code", false, _codes);
try {
_codes.add(createResource(codes[ii], false));
} catch (Exception e) {
Log.warning("Invalid code resource '" + codes[ii] + "'." + e);
}
}
// parse our non-code resources // parse our non-code resources
String[] rsrcs = ConfigUtil.getMultiValue(cdata, "resource"); parseResources(cdata, "resource", false, _resources);
if (rsrcs != null) { parseResources(cdata, "uresource", true, _resources);
for (int ii = 0; ii < rsrcs.length; ii++) {
String rsrc = rsrcs[ii]; // parse our auxiliary resource groups
try { for (String auxgroup : parseList(cdata, "auxgroups")) {
_resources.add(createResource(rsrc, false)); ArrayList<Resource> rsrcs = new ArrayList<Resource>();
} catch (Exception e) { parseResources(cdata, auxgroup + ".resource", false, rsrcs);
Log.warning("Invalid resource '" + rsrcs[ii] + "'. " + e); parseResources(cdata, auxgroup + ".uresource", true, rsrcs);
} _auxrsrcs.put(auxgroup, rsrcs);
} _auxgroups.add(auxgroup);
}
rsrcs = ConfigUtil.getMultiValue(cdata, "uresource");
if (rsrcs != null) {
for (int ii = 0; ii < rsrcs.length; ii++) {
String rsrc = rsrcs[ii];
try {
_resources.add(createResource(rsrc, true));
} catch (Exception e) {
Log.warning("Invalid resource '" + rsrcs[ii] + "'. " + e);
}
}
} }
// transfer our JVM arguments // transfer our JVM arguments
@@ -324,16 +367,6 @@ public class Application
} }
} }
// TODO: make this less of a hack
String username = System.getProperty("username");
if (!StringUtil.isBlank(username)) {
_jvmargs.add("-Dusername=" + username);
}
String password = System.getProperty("password");
if (!StringUtil.isBlank(password)) {
_jvmargs.add("-Dpassword=" + password);
}
// look for custom arguments // look for custom arguments
File file = getLocalPath("extra.txt"); File file = getLocalPath("extra.txt");
if (file.exists()) { if (file.exists()) {
@@ -477,10 +510,8 @@ public class Application
} }
// pass along any pass-through arguments // pass along any pass-through arguments
for (Iterator itr = System.getProperties().entrySet().iterator(); for (Map.Entry entry : System.getProperties().entrySet()) {
itr.hasNext(); ) { String key = (String)entry.getKey();
Map.Entry entry = (Map.Entry) itr.next();
String key = (String) entry.getKey();
if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) { if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) {
key = key.substring(PROP_PASSTHROUGH_PREFIX.length()); key = key.substring(PROP_PASSTHROUGH_PREFIX.length());
args.add("-D" + key + "=" + entry.getValue()); args.add("-D" + key + "=" + entry.getValue());
@@ -603,8 +634,10 @@ public class Application
Log.info("Verifying application: " + _vappbase); Log.info("Verifying application: " + _vappbase);
Log.info("Version: " + _version); Log.info("Version: " + _version);
Log.info("Class: " + _class); Log.info("Class: " + _class);
// Log.info("Code: " + StringUtil.toString(_codes.iterator())); // Log.info("Code: " +
// Log.info("Resources: " + StringUtil.toString(_resources.iterator())); // StringUtil.toString(getCodeResources().iterator()));
// Log.info("Resources: " +
// StringUtil.toString(getActiveResources().iterator()));
// Log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator())); // Log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator()));
// Log.info("App Args: " + StringUtil.toString(_appargs.iterator())); // Log.info("App Args: " + StringUtil.toString(_appargs.iterator()));
@@ -709,8 +742,8 @@ public class Application
{ {
ArrayList<Resource> rsrcs = new ArrayList<Resource>(); ArrayList<Resource> rsrcs = new ArrayList<Resource>();
ArrayList<Resource> failures = new ArrayList<Resource>(); ArrayList<Resource> failures = new ArrayList<Resource>();
rsrcs.addAll(_codes); rsrcs.addAll(getCodeResources());
rsrcs.addAll(_resources); rsrcs.addAll(getActiveResources());
// total up the file size of the resources to validate // total up the file size of the resources to validate
long totalSize = 0L; long totalSize = 0L;
@@ -758,8 +791,8 @@ public class Application
*/ */
public void clearValidationMarkers () public void clearValidationMarkers ()
{ {
clearValidationMarkers(_codes.iterator()); clearValidationMarkers(getCodeResources().iterator());
clearValidationMarkers(_resources.iterator()); clearValidationMarkers(getActiveResources().iterator());
} }
/** /**
@@ -850,9 +883,27 @@ public class Application
path, getRemoteURL(path), getLocalPath(path), unpack); 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,
ArrayList<Resource> list)
{
String[] rsrcs = ConfigUtil.getMultiValue(cdata, name);
if (rsrcs == null) {
return;
}
for (String rsrc : rsrcs) {
try {
list.add(createResource(rsrc, unpack));
} catch (Exception e) {
Log.warning("Invalid resource '" + rsrc + "'. " + e);
}
}
}
/** Used to parse rectangle specifications from the config file. */ /** Used to parse rectangle specifications from the config file. */
protected Rectangle parseRect (HashMap<String, Object> cdata, protected Rectangle parseRect (
String name, Rectangle def) HashMap<String,Object> cdata, String name, Rectangle def)
{ {
String value = (String)cdata.get(name); String value = (String)cdata.get(name);
if (!StringUtil.isBlank(value)) { if (!StringUtil.isBlank(value)) {
@@ -868,8 +919,8 @@ public class Application
} }
/** Used to parse color specifications from the config file. */ /** Used to parse color specifications from the config file. */
protected Color parseColor (HashMap<String, Object> cdata, String name, protected Color parseColor (
Color def) HashMap<String,Object> cdata, String name, Color def)
{ {
String value = (String)cdata.get(name); String value = (String)cdata.get(name);
if (!StringUtil.isBlank(value)) { if (!StringUtil.isBlank(value)) {
@@ -883,6 +934,14 @@ public class Application
return def; return def;
} }
/** Parses a list of strings from the config file. */
protected String[] parseList (HashMap<String,Object> cdata, String name)
{
String value = (String)cdata.get(name);
return (value == null) ? new String[0] :
StringUtil.parseStringArray(value);
}
protected File _appdir; protected File _appdir;
protected String _appid; protected String _appid;
protected File _config; protected File _config;
@@ -899,6 +958,12 @@ public class Application
protected ArrayList<Resource> _codes = new ArrayList<Resource>(); protected ArrayList<Resource> _codes = new ArrayList<Resource>();
protected ArrayList<Resource> _resources = 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 ArrayList<String> _jvmargs = new ArrayList<String>(); protected ArrayList<String> _jvmargs = new ArrayList<String>();
protected ArrayList<String> _appargs = new ArrayList<String>(); protected ArrayList<String> _appargs = new ArrayList<String>();
@@ -404,31 +404,45 @@ public abstract class Getdown extends Thread
// first clear all validation markers // first clear all validation markers
_app.clearValidationMarkers(); _app.clearValidationMarkers();
// attempt to download the patch file // attempt to download the patch files
final Resource patch = _app.getPatchResource(); Resource patch = _app.getPatchResource(null);
if (patch != null) { if (patch != null) {
// download the patch file...
ArrayList<Resource> list = new ArrayList<Resource>(); ArrayList<Resource> list = new ArrayList<Resource>();
list.add(patch); list.add(patch);
// add the auxiliary group patch files for activated groups
for (String auxgroup : _app.getAuxGroups()) {
if (_app.isAuxGroupActive(auxgroup)) {
patch = _app.getPatchResource(auxgroup);
if (patch != null) {
list.add(patch);
}
}
}
// download the patch files...
download(list); download(list);
// and apply it... // and apply them...
updateStatus("m.patching"); updateStatus("m.patching");
try { for (Resource prsrc : list) {
Patcher patcher = new Patcher(); try {
patcher.patch(patch.getLocal().getParentFile(), Patcher patcher = new Patcher();
patch.getLocal(), _progobs); patcher.patch(prsrc.getLocal().getParentFile(),
} catch (Exception e) { prsrc.getLocal(), _progobs);
Log.warning("Failed to apply patch."); } catch (Exception e) {
Log.logStackTrace(e); Log.warning("Failed to apply patch [prsrc=" + prsrc + "].");
} Log.logStackTrace(e);
}
// lastly clean up the patch file // clean up the patch file
if (!patch.getLocal().delete()) { if (!prsrc.getLocal().delete()) {
Log.warning("Failed to delete '" + patch + "'."); Log.warning("Failed to delete '" + prsrc + "'.");
patch.getLocal().deleteOnExit(); prsrc.getLocal().deleteOnExit();
}
} }
} }
// if the patch resource is null, that means something was booched // if the patch resource is null, that means something was booched
// in the application, so we skip the patching process but update // in the application, so we skip the patching process but update
// the metadata which will result in a "brute force" upgrade // the metadata which will result in a "brute force" upgrade
@@ -83,20 +83,36 @@ public class Differ
nrsrcs.addAll(napp.getCodeResources()); nrsrcs.addAll(napp.getCodeResources());
nrsrcs.addAll(napp.getResources()); nrsrcs.addAll(napp.getResources());
MessageDigest md = Digest.getMessageDigest(); // first create a patch for the main application
File patch = new File(nvdir, "patch" + overs + ".dat"); File patch = new File(nvdir, "patch" + overs + ".dat");
createPatch(patch, orsrcs, nrsrcs, verbose);
// next create patches for any auxiliary resource groups
for (String auxgroup : napp.getAuxGroups()) {
orsrcs = new ArrayList<Resource>();
orsrcs.addAll(oapp.getResources(auxgroup));
nrsrcs = new ArrayList<Resource>();
nrsrcs.addAll(napp.getResources(auxgroup));
patch = new File(nvdir, "patch-" + auxgroup + overs + ".dat");
createPatch(patch, orsrcs, nrsrcs, verbose);
}
}
protected void createPatch (File patch, ArrayList<Resource> orsrcs,
ArrayList<Resource> nrsrcs, boolean verbose)
throws IOException
{
MessageDigest md = Digest.getMessageDigest();
JarOutputStream jout = null; JarOutputStream jout = null;
try { try {
jout = new JarOutputStream( jout = new JarOutputStream(
new BufferedOutputStream(new FileOutputStream(patch))); new BufferedOutputStream(new FileOutputStream(patch)));
// for each file in the new application, it either already // for each file in the new application, it either already exists
// exists in the old application, or it is new // in the old application, or it is new
for (int ii = 0; ii < nrsrcs.size(); ii++) { for (Resource rsrc : nrsrcs) {
Resource rsrc = nrsrcs.get(ii);
int oidx = orsrcs.indexOf(rsrc); int oidx = orsrcs.indexOf(rsrc);
Resource orsrc = (oidx == -1) ? Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx);
null : (Resource)orsrcs.remove(oidx);
if (orsrc != null) { if (orsrc != null) {
// first see if they are the same // first see if they are the same
String odig = orsrc.computeDigest(md, null); String odig = orsrc.computeDigest(md, null);
@@ -105,8 +121,8 @@ public class Differ
if (verbose) { if (verbose) {
System.out.println("Unchanged: " + rsrc.getPath()); System.out.println("Unchanged: " + rsrc.getPath());
} }
// by leaving it out, it will be left as is during // by leaving it out, it will be left as is during the
// the patching process // patching process
continue; continue;
} }
@@ -115,17 +131,16 @@ public class Differ
if (verbose) { if (verbose) {
System.out.println("JarDiff: " + rsrc.getPath()); System.out.println("JarDiff: " + rsrc.getPath());
} }
// here's a juicy one: JarDiff blindly pulls // here's a juicy one: JarDiff blindly pulls ZipEntry
// ZipEntry objects out of one jar file and stuffs // objects out of one jar file and stuffs them into
// them into another without clearing out things // another without clearing out things like the
// like the compressed size, so if, for whatever // compressed size, so if, for whatever reason (like
// reason (like different JRE versions or phase of // different JRE versions or phase of the moon) the
// the moon) the compressed size in the old jar // compressed size in the old jar file is different
// file is different than the compressed size // than the compressed size generated when creating the
// generated when creating the jardiff jar file, // jardiff jar file, ZipOutputStream will choke and
// ZipOutputStream will choke and we'll be hosed; // we'll be hosed; so we recreate the jar files in
// so we recreate the jar files in their entirety // their entirety before running jardiff on 'em
// before running jardiff on 'em
File otemp = rebuildJar(orsrc.getLocal()); File otemp = rebuildJar(orsrc.getLocal());
File temp = rebuildJar(rsrc.getLocal()); File temp = rebuildJar(rsrc.getLocal());
jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH)); jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH));
@@ -144,10 +159,8 @@ public class Differ
} }
// now any file remaining in orsrcs needs to be removed // now any file remaining in orsrcs needs to be removed
for (int ii = 0; ii < orsrcs.size(); ii++) { for (Resource rsrc : orsrcs) {
Resource rsrc = orsrcs.get(ii); // add an entry with the resource name and the deletion suffix
// simply add an entry with the resource name and the
// deletion suffix
if (verbose) { if (verbose) {
System.out.println("Removal: " + rsrc.getPath()); System.out.println("Removal: " + rsrc.getPath());
} }
@@ -65,6 +65,9 @@ public class DigesterTask extends Task
rsrcs.add(app.getConfigResource()); rsrcs.add(app.getConfigResource());
rsrcs.addAll(app.getCodeResources()); rsrcs.addAll(app.getCodeResources());
rsrcs.addAll(app.getResources()); rsrcs.addAll(app.getResources());
for (String auxgroup : app.getAuxGroups()) {
rsrcs.addAll(app.getResources(auxgroup));
}
// now generate the digest file // now generate the digest file
Digest.createDigest(rsrcs, target); Digest.createDigest(rsrcs, target);