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;
}
/**
* 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
* will bring this application from its current version to the target
* version.
*
* @param auxgroup the auxiliary resource group for which a patch resource
* is desired or null for the main application patch resource.
*/
public Resource getPatchResource ()
public Resource getPatchResource (String auxgroup)
{
if (_targetVersion <= _version) {
Log.warning("Requested patch resource for up-to-date or " +
@@ -178,7 +236,8 @@ public class Application
return null;
}
String pfile = "patch" + _version + ".dat";
String infix = (auxgroup == null) ? "" : ("-" + auxgroup);
String pfile = "patch" + infix + _version + ".dat";
try {
URL remote = new URL(createVAppBase(_targetVersion), pfile);
return new Resource(pfile, remote, getLocalPath(pfile), false);
@@ -207,7 +266,7 @@ public class Application
throws IOException
{
// parse our configuration file
HashMap<String, Object> cdata = null;
HashMap<String,Object> cdata = null;
try {
cdata = ConfigUtil.parseConfig(_config, checkPlatform);
} catch (FileNotFoundException fnfe) {
@@ -268,44 +327,28 @@ public class Application
// clear our arrays as we may be reinitializing
_codes.clear();
_resources.clear();
_auxgroups.clear();
_auxrsrcs.clear();
_jvmargs.clear();
_appargs.clear();
// parse our code resources
String[] codes = ConfigUtil.getMultiValue(cdata, "code");
if (codes == null) {
if (ConfigUtil.getMultiValue(cdata, "code") == null) {
throw new IOException("m.missing_code");
}
for (int ii = 0; ii < codes.length; ii++) {
try {
_codes.add(createResource(codes[ii], false));
} catch (Exception e) {
Log.warning("Invalid code resource '" + codes[ii] + "'." + e);
}
}
parseResources(cdata, "code", false, _codes);
// parse our non-code resources
String[] rsrcs = ConfigUtil.getMultiValue(cdata, "resource");
if (rsrcs != null) {
for (int ii = 0; ii < rsrcs.length; ii++) {
String rsrc = rsrcs[ii];
try {
_resources.add(createResource(rsrc, false));
} catch (Exception e) {
Log.warning("Invalid resource '" + rsrcs[ii] + "'. " + e);
}
}
}
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);
}
}
parseResources(cdata, "resource", false, _resources);
parseResources(cdata, "uresource", true, _resources);
// parse our auxiliary resource groups
for (String auxgroup : parseList(cdata, "auxgroups")) {
ArrayList<Resource> rsrcs = new ArrayList<Resource>();
parseResources(cdata, auxgroup + ".resource", false, rsrcs);
parseResources(cdata, auxgroup + ".uresource", true, rsrcs);
_auxrsrcs.put(auxgroup, rsrcs);
_auxgroups.add(auxgroup);
}
// 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
File file = getLocalPath("extra.txt");
if (file.exists()) {
@@ -477,10 +510,8 @@ public class Application
}
// pass along any pass-through arguments
for (Iterator itr = System.getProperties().entrySet().iterator();
itr.hasNext(); ) {
Map.Entry entry = (Map.Entry) itr.next();
String key = (String) entry.getKey();
for (Map.Entry entry : System.getProperties().entrySet()) {
String key = (String)entry.getKey();
if (key.startsWith(PROP_PASSTHROUGH_PREFIX)) {
key = key.substring(PROP_PASSTHROUGH_PREFIX.length());
args.add("-D" + key + "=" + entry.getValue());
@@ -603,8 +634,10 @@ public class Application
Log.info("Verifying application: " + _vappbase);
Log.info("Version: " + _version);
Log.info("Class: " + _class);
// Log.info("Code: " + StringUtil.toString(_codes.iterator()));
// Log.info("Resources: " + StringUtil.toString(_resources.iterator()));
// Log.info("Code: " +
// StringUtil.toString(getCodeResources().iterator()));
// Log.info("Resources: " +
// StringUtil.toString(getActiveResources().iterator()));
// Log.info("JVM Args: " + StringUtil.toString(_jvmargs.iterator()));
// Log.info("App Args: " + StringUtil.toString(_appargs.iterator()));
@@ -709,8 +742,8 @@ public class Application
{
ArrayList<Resource> rsrcs = new ArrayList<Resource>();
ArrayList<Resource> failures = new ArrayList<Resource>();
rsrcs.addAll(_codes);
rsrcs.addAll(_resources);
rsrcs.addAll(getCodeResources());
rsrcs.addAll(getActiveResources());
// total up the file size of the resources to validate
long totalSize = 0L;
@@ -758,8 +791,8 @@ public class Application
*/
public void clearValidationMarkers ()
{
clearValidationMarkers(_codes.iterator());
clearValidationMarkers(_resources.iterator());
clearValidationMarkers(getCodeResources().iterator());
clearValidationMarkers(getActiveResources().iterator());
}
/**
@@ -850,9 +883,27 @@ public class Application
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. */
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)) {
@@ -868,8 +919,8 @@ public class Application
}
/** 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)) {
@@ -883,6 +934,14 @@ public class Application
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 String _appid;
protected File _config;
@@ -899,6 +958,12 @@ public class Application
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 ArrayList<String> _jvmargs = 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
_app.clearValidationMarkers();
// attempt to download the patch file
final Resource patch = _app.getPatchResource();
// attempt to download the patch files
Resource patch = _app.getPatchResource(null);
if (patch != null) {
// download the patch file...
ArrayList<Resource> list = new ArrayList<Resource>();
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);
// and apply it...
// and apply them...
updateStatus("m.patching");
try {
Patcher patcher = new Patcher();
patcher.patch(patch.getLocal().getParentFile(),
patch.getLocal(), _progobs);
} catch (Exception e) {
Log.warning("Failed to apply patch.");
Log.logStackTrace(e);
}
for (Resource prsrc : list) {
try {
Patcher patcher = new Patcher();
patcher.patch(prsrc.getLocal().getParentFile(),
prsrc.getLocal(), _progobs);
} catch (Exception e) {
Log.warning("Failed to apply patch [prsrc=" + prsrc + "].");
Log.logStackTrace(e);
}
// lastly clean up the patch file
if (!patch.getLocal().delete()) {
Log.warning("Failed to delete '" + patch + "'.");
patch.getLocal().deleteOnExit();
// clean up the patch file
if (!prsrc.getLocal().delete()) {
Log.warning("Failed to delete '" + prsrc + "'.");
prsrc.getLocal().deleteOnExit();
}
}
}
// 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
@@ -83,20 +83,36 @@ public class Differ
nrsrcs.addAll(napp.getCodeResources());
nrsrcs.addAll(napp.getResources());
MessageDigest md = Digest.getMessageDigest();
// first create a patch for the main application
File patch = new File(nvdir, "patch" + overs + ".dat");
createPatch(patch, orsrcs, nrsrcs, verbose);
// next create patches for any auxiliary resource groups
for (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;
try {
jout = new JarOutputStream(
new BufferedOutputStream(new FileOutputStream(patch)));
// for each file in the new application, it either already
// exists in the old application, or it is new
for (int ii = 0; ii < nrsrcs.size(); ii++) {
Resource rsrc = nrsrcs.get(ii);
// for each file in the new application, it either already exists
// in the old application, or it is new
for (Resource rsrc : nrsrcs) {
int oidx = orsrcs.indexOf(rsrc);
Resource orsrc = (oidx == -1) ?
null : (Resource)orsrcs.remove(oidx);
Resource orsrc = (oidx == -1) ? null : orsrcs.remove(oidx);
if (orsrc != null) {
// first see if they are the same
String odig = orsrc.computeDigest(md, null);
@@ -105,8 +121,8 @@ public class Differ
if (verbose) {
System.out.println("Unchanged: " + rsrc.getPath());
}
// by leaving it out, it will be left as is during
// the patching process
// by leaving it out, it will be left as is during the
// patching process
continue;
}
@@ -115,17 +131,16 @@ public class Differ
if (verbose) {
System.out.println("JarDiff: " + rsrc.getPath());
}
// here's a juicy one: JarDiff blindly pulls
// ZipEntry objects out of one jar file and stuffs
// them into another without clearing out things
// like the compressed size, so if, for whatever
// reason (like different JRE versions or phase of
// the moon) the compressed size in the old jar
// file is different than the compressed size
// generated when creating the jardiff jar file,
// ZipOutputStream will choke and we'll be hosed;
// so we recreate the jar files in their entirety
// before running jardiff on 'em
// here's a juicy one: JarDiff blindly pulls ZipEntry
// objects out of one jar file and stuffs them into
// another without clearing out things like the
// compressed size, so if, for whatever reason (like
// different JRE versions or phase of the moon) the
// compressed size in the old jar file is different
// than the compressed size generated when creating the
// jardiff jar file, ZipOutputStream will choke and
// we'll be hosed; so we recreate the jar files in
// their entirety before running jardiff on 'em
File otemp = rebuildJar(orsrc.getLocal());
File temp = rebuildJar(rsrc.getLocal());
jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH));
@@ -144,10 +159,8 @@ public class Differ
}
// now any file remaining in orsrcs needs to be removed
for (int ii = 0; ii < orsrcs.size(); ii++) {
Resource rsrc = orsrcs.get(ii);
// simply add an entry with the resource name and the
// deletion suffix
for (Resource rsrc : orsrcs) {
// add an entry with the resource name and the deletion suffix
if (verbose) {
System.out.println("Removal: " + rsrc.getPath());
}
@@ -65,6 +65,9 @@ public class DigesterTask extends Task
rsrcs.add(app.getConfigResource());
rsrcs.addAll(app.getCodeResources());
rsrcs.addAll(app.getResources());
for (String auxgroup : app.getAuxGroups()) {
rsrcs.addAll(app.getResources(auxgroup));
}
// now generate the digest file
Digest.createDigest(rsrcs, target);