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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user