From 1e5402ea82b09b18eb1439f7118910f63684e829 Mon Sep 17 00:00:00 2001
From: Norberto Lopez <106690103+nobes888@users.noreply.github.com>
Date: Sat, 7 May 2016 03:57:18 +0000
Subject: [PATCH] Updated logic to support - Code cleanup / refactor - Improved
Java versioning related logic - Added support for Java version range (rather
than just minimum version) - Added support for JRE updating - Improved update
revert logic - Added support to untar jvm tar.gz archives
---
src/distMaker/DistMakerEngine.java | 624 ++++++++++++++++------
src/distMaker/DistUtils.java | 70 ++-
src/distMaker/ErrorDM.java | 37 ++
src/distMaker/MiscUtils.java | 182 ++++++-
src/distMaker/gui/MemoryConfigPanel.java | 25 +-
src/distMaker/jre/JreUtils.java | 35 +-
src/distMaker/jre/JreVersion.java | 8 +-
src/distMaker/node/AppCatalog.java | 91 +++-
src/distMaker/node/AppRelease.java | 4 +-
src/distMaker/platform/AppleUtils.java | 278 ++++++----
src/distMaker/platform/LinuxUtils.java | 194 ++++---
src/distMaker/platform/PlatformUtils.java | 161 ++++--
src/distMaker/platform/WindowsUtils.java | 175 +++---
template/appLauncher.jar | Bin 17850 -> 22751 bytes
14 files changed, 1337 insertions(+), 547 deletions(-)
create mode 100644 src/distMaker/ErrorDM.java
diff --git a/src/distMaker/DistMakerEngine.java b/src/distMaker/DistMakerEngine.java
index 1163313..0bc5eff 100644
--- a/src/distMaker/DistMakerEngine.java
+++ b/src/distMaker/DistMakerEngine.java
@@ -10,10 +10,12 @@ import glum.task.*;
import glum.unit.DateUnit;
import glum.util.ThreadUtil;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
-import java.nio.file.Files;
+import java.nio.file.*;
import java.security.MessageDigest;
import java.util.*;
@@ -21,13 +23,13 @@ import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
import distMaker.digest.Digest;
import distMaker.digest.DigestUtils;
import distMaker.gui.PickReleasePanel;
import distMaker.jre.*;
import distMaker.node.*;
-import distMaker.platform.AppleUtils;
import distMaker.platform.PlatformUtils;
public class DistMakerEngine
@@ -110,6 +112,14 @@ public class DistMakerEngine
return currRelease;
}
+ /**
+ * Returns the URL where software updates for this application are retrieved from.
+ */
+ public URL getUpdateSite()
+ {
+ return updateSiteUrl;
+ }
+
/**
* returns
*
@@ -154,7 +164,6 @@ public class DistMakerEngine
private void initialize()
{
File appPath, cfgFile;
- BufferedReader br;
DateUnit dateUnit;
String currInstr, strLine;
String appName, verName, buildStr;
@@ -180,11 +189,8 @@ public class DistMakerEngine
}
// Read in the configuration file
- br = null;
- try
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfgFile))))
{
- br = new BufferedReader(new InputStreamReader(new FileInputStream(cfgFile)));
-
// Read the lines
currInstr = "None";
while (true)
@@ -215,10 +221,6 @@ public class DistMakerEngine
{
aExp.printStackTrace();
}
- finally
- {
- IoUtil.forceClose(br);
- }
if (appName == null || verName == null)
{
@@ -343,7 +345,7 @@ public class DistMakerEngine
}
// Download the release
- isPass = downloadRelease(aTask, chosenItem, deltaPath);
+ isPass = downloadAppRelease(aTask, chosenItem, deltaPath);
if (isPass == false || aTask.isActive() == false)
{
IoUtil.deleteDirectory(deltaPath);
@@ -370,6 +372,29 @@ public class DistMakerEngine
aMsg += "Please check installation configuration.";
}
+ // If the parentFrame is not visible then delay the showing until it is visible
+ if (parentFrame.isVisible() == false)
+ {
+ final String tmpMsg = aMsg;
+ parentFrame.addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentShown(ComponentEvent aEvent)
+ {
+ msgPanel.setTitle("Application Updater");
+ msgPanel.setInfo(tmpMsg);
+ msgPanel.setVisible(true);
+
+ // Deregister for events after the parentFrame is made visible
+ parentFrame.removeComponentListener(this);
+ }
+ });
+
+ return;
+ }
+
+ // Transform tabs to 3 spaces
+ aMsg = aMsg.replace("\t", " ");
+
// Display the message
msgPanel.setTitle("Application Updater");
msgPanel.setInfo(aMsg);
@@ -381,7 +406,7 @@ public class DistMakerEngine
*
* Returns true if the release was downloaded properly.
*/
- private boolean downloadRelease(Task aTask, AppRelease aRelease, File destPath)
+ private boolean downloadAppRelease(Task aTask, AppRelease aRelease, File destPath)
{
AppCatalog staleCat, updateCat;
Node staleNode, updateNode;
@@ -390,7 +415,6 @@ public class DistMakerEngine
Task mainTask, tmpTask;
double progressVal;
long tmpFileLen;
- boolean isPass;
try
{
@@ -404,20 +428,21 @@ public class DistMakerEngine
return false;
}
- // Download the update catalog to the (local) delta location (Progress -> [0% - 1%])
- catUrl = IoUtil.createURL(updateUrl.toString() + "/catalog.txt");
- catalogFile = new File(destPath, "catalog.txt");
- if (DistUtils.downloadFile(new PartialTask(aTask, 0.00, 0.01), catUrl, catalogFile, refCredential, -1L, null) == false)
- return false;
-
// Load the stale catalog
catalogFile = new File(DistUtils.getAppPath(), "catalog.txt");
staleCat = DistUtils.readAppCatalog(aTask, catalogFile, staleUrl);
if (staleCat == null)
return false;
+ // Download the update app catalog to the (local) delta location (Progress -> [0% - 1%])
+ File appNewPath = new File(destPath, "app");
+ appNewPath.mkdirs();
+ catUrl = IoUtil.createURL(updateUrl.toString() + "/catalog.txt");
+ catalogFile = new File(appNewPath, "catalog.txt");
+ if (DistUtils.downloadFile(new PartialTask(aTask, 0.00, 0.01), catUrl, catalogFile, refCredential, -1L, null) == false)
+ return false;
+
// Load the update catalog
- catalogFile = new File(destPath, "catalog.txt");
updateCat = DistUtils.readAppCatalog(aTask, catalogFile, updateUrl);
if (updateCat == null)
return false;
@@ -433,136 +458,29 @@ public class DistMakerEngine
// Set up the mainTask for downloading of remote content (Progress -> [1% - 95%])
mainTask = new PartialTask(aTask, 0.01, 0.94);
- // Ensure our JRE version is sufficient for this release
+ // Ensure our JRE version is compatible for this release
+ JreRelease targJre = null;
JreVersion currJreVer = DistUtils.getJreVersion();
- JreVersion targJreVer = updateCat.getJreVersion();
- if (targJreVer != null && JreVersion.getBetterVersion(targJreVer, currJreVer) != currJreVer)
+ if (updateCat.isJreVersionCompatible(currJreVer) == false)
{
- List jreList;
-
- aTask.infoAppendln("Your current JRE is too old. It will need to be updated!");
- aTask.infoAppendln("\tCurrent JRE: " + currJreVer.getLabel());
- aTask.infoAppendln("\tMinimun JRE: " + targJreVer.getLabel() + "\n");
-
- // Get list of all available JREs
- jreList = JreUtils.getAvailableJreReleases(aTask, updateSiteUrl, refCredential);
- if (jreList == null)
- {
- aTask.infoAppendln("The update site has not had any JREs deployed.");
- aTask.infoAppendln("Please contact the update site adminstartor.");
+ // Bail if we failed to download a compatible JRE
+ targJre = downloadJreUpdate(mainTask, updateCat, destPath, releaseSizeFull);
+ if (targJre == null)
return false;
- }
- if (jreList.size() == 0)
- {
- aTask.infoAppendln("No JRE releases found!");
- aTask.infoAppendln("Please contact the update site adminstartor.");
- return false;
- }
- // Retrieve the latest appropriate JreRelease
- String platform = DistUtils.getPlatform();
- jreList = JreUtils.getMatchingPlatforms(jreList, platform);
- if (jreList.size() == 0)
- {
- aTask.infoAppendln("There are no JRE releases available for the platform: " + platform + "!");
- return false;
- }
-
- JreRelease pickJre = jreList.get(0);
- JreVersion pickJreVer = pickJre.getVersion();
-
- if (JreVersion.getBetterVersion(pickJreVer, targJreVer) == currJreVer)
- {
- aTask.infoAppendln("The latest available JRE on the update site is not recent enought!");
- aTask.infoAppendln("Minimun required JRE: " + targJreVer.getLabel());
- aTask.infoAppendln("Latest available JRE: " + pickJreVer.getLabel());
- return false;
- }
-
- // Update the number of bytes to be retrieved to take into account the JRE which we will be downloading
- // and form the appropriate tmpTask
- tmpFileLen = pickJre.getFileLen();
- releaseSizeFull += tmpFileLen;
- tmpTask = new PartialTask(mainTask, mainTask.getProgress(), tmpFileLen / (releaseSizeFull + 0.00));
-
- // Download the JRE
- Digest targDigest, testDigest;
- targDigest = pickJre.getDigest();
- MessageDigest msgDigest;
- msgDigest = DigestUtils.getDigest(targDigest.getType());
- mainTask.infoAppendln("Downloading JRE... Version: " + pickJre.getVersion().getLabel());
- URL srcUrl = IoUtil.createURL(updateSiteUrl.toString() + "/jre/" + pickJre.getFileName());
- File dstFile = new File(new File(destPath, "jre"), pickJre.getFileName());
- DistUtils.downloadFile(tmpTask, srcUrl, dstFile, refCredential, tmpFileLen, msgDigest);
-
- // Validate that the JRE was downloaded successfully
- testDigest = new Digest(targDigest.getType(), msgDigest.digest());
- if (targDigest.equals(testDigest) == false)
- {
- aTask.infoAppendln("The download of the JRE appears to be corrupted.");
- aTask.infoAppendln("\tFile: " + dstFile);
- aTask.infoAppendln("\t\tExpected " + targDigest.getDescr());
- aTask.infoAppendln("\t\tRecieved " + testDigest.getDescr() + "\n");
- return false;
- }
- releaseSizeCurr += tmpFileLen;
+ // Update the progress to reflect the downloaded / updated JRE
+ releaseSizeCurr += targJre.getFileLen();
+ releaseSizeFull += targJre.getFileLen();
progressVal = releaseSizeCurr / (releaseSizeFull + 0.00);
mainTask.setProgress(progressVal);
-
- // TODO: Unpack the JRE at the proper location and then delete it
- File jrePath = PlatformUtils.getJreLocation(pickJre);
- try
- {
- int finish_me_and_clean_me;
-// Files.createTempDirectory(dstFile, prefix, attrs)
-// Files.createTempDir();
-
-
- File unpackPath;
- unpackPath = new File(destPath, "jre/unpack");
- unpackPath.mkdirs();
- MiscUtils.unTar(dstFile, unpackPath);
-
- File[] fileArr;
- fileArr = unpackPath.listFiles();
- if (fileArr.length != 1 && fileArr[0].isDirectory() == false)
- throw new Exception("Expected only one (top level) folder to be unpacked. Items extracted: " + fileArr.length + " Path: " + unpackPath);
-
- File jreUnpackedPath;
- jreUnpackedPath = fileArr[0];
-
- // Moved the unpacked file to the "standardized" jrePath
- jreUnpackedPath.renameTo(jrePath);
-// MiscUtils.unTar(dstFile, jrePath);
-
- // TODO: Remove the unpacked folder...
- }
- catch(Exception aExp)
- {
- aTask.infoAppendln("Failed to untar archive. The update has been aborted.");
- aTask.infoAppendln("\tTar File: " + dstFile);
- aTask.infoAppendln("\tDestination: " + jrePath);
- return false;
- }
-
- // TODO: Update the launch script or launch config file to point to the proper JRE
- if (PlatformUtils.setJreLocation(jrePath) == false)
- {
- aTask.infoAppendln("Failed to update the configuration to point to the updated JRE!");
- aTask.infoAppendln("\tCurrent JRE: " + currJreVer.getLabel());
- aTask.infoAppendln("\t Chosen JRE: " + pickJreVer.getLabel());
- return false;
- }
-
- return true;
-
- // TODO: Eventually remove the old JRE - perhaps from the app launcher
}
// Download the individual application files
mainTask.infoAppendln("Downloading release: " + aRelease.getVersion() + " Nodes: " + updateCat.getAllNodesList().size());
for (Node aNode : updateCat.getAllNodesList())
{
+ boolean isPass;
+
// Bail if we have been aborted
if (mainTask.isActive() == false)
return false;
@@ -580,9 +498,9 @@ public class DistMakerEngine
{
// Note we pass the SilentTask since
// - This should be fairly fast since this should result in a local disk copy
- // - This may fail, (but the failuer is recoverable and this serves just as an optimization)
+ // - This may fail, (but the failure is recoverable and this serves just as an optimization)
// isPass = staleNode.transferContentTo(tmpTask, refCredential, destPath);
- isPass = staleNode.transferContentTo(new SilentTask(), refCredential, destPath);
+ isPass = staleNode.transferContentTo(new SilentTask(), refCredential, appNewPath);
if (isPass == true)
mainTask.infoAppendln("\t(L) " + staleNode.getFileName());
}
@@ -590,7 +508,7 @@ public class DistMakerEngine
// Use the remote update copy, if we were not able to use a local stale copy
if (isPass == false && mainTask.isActive() == true)
{
- isPass = updateNode.transferContentTo(tmpTask, refCredential, destPath);
+ isPass = updateNode.transferContentTo(tmpTask, refCredential, appNewPath);
if (isPass == true)
mainTask.infoAppendln("\t(R) " + updateNode.getFileName());
}
@@ -601,7 +519,7 @@ public class DistMakerEngine
mainTask.infoAppendln("Failed to download from update site.");
mainTask.infoAppendln("\tSite: " + updateUrl);
mainTask.infoAppendln("\tFile: " + updateNode.getFileName());
- mainTask.infoAppendln("\tDest: " + destPath);
+ mainTask.infoAppendln("\tDest: " + appNewPath);
return false;
}
@@ -610,11 +528,246 @@ public class DistMakerEngine
progressVal = releaseSizeCurr / (releaseSizeFull + 0.00);
mainTask.setProgress(progressVal);
}
+ mainTask.infoAppendln("Finished downloading release.\n");
// Update the platform configuration files
- isPass = updatePlatformConfigFiles(aTask, aRelease);
+ try
+ {
+ PlatformUtils.updateAppRelease(aRelease);
+ }
+ catch(ErrorDM aExp)
+ {
+ aTask.infoAppendln("Failed updating application configuration.");
+ printErrorDM(aTask, aExp, 1);
+ return false;
+ }
- return isPass;
+ // Retrieve the reference to the appCfgFile
+ File appCfgFile = PlatformUtils.getConfigurationFile();
+
+ // Create the delta.cmd file which provides the Updater with the clean activities to perform (based on fail / pass conditions)
+ File deltaCmdFile = new File(destPath, "delta.cmd");
+ try (FileWriter tmpFW = new FileWriter(deltaCmdFile))
+ {
+ if (targJre != null)
+ {
+ File rootPath = DistUtils.getAppPath().getParentFile();
+
+ JreVersion targJreVer = targJre.getVersion();
+ tmpFW.write("# Define the fail section (clean up for failure)\n");
+ tmpFW.write("sect,fail\n");
+ tmpFW.write("copy," + "delta/" + appCfgFile.getName() + ".old," + MiscUtils.getRelativePath(rootPath, appCfgFile) + "\n");
+ tmpFW.write("move,jre" + targJreVer.getLabel() + ",delta" + "\n");
+ tmpFW.write("reboot\n\n");
+
+ tmpFW.write("# Define the pass section (clean up for success)\n");
+ tmpFW.write("sect,pass\n");
+ tmpFW.write("trash,jre" + currJreVer.getLabel() + "\n");
+ tmpFW.write("exit\n\n");
+ }
+ else
+ {
+ tmpFW.write("# Define the fail section (clean up for failure)\n");
+ tmpFW.write("sect,fail\n");
+ tmpFW.write("exit\n\n");
+
+ tmpFW.write("# Define the pass section (clean up for success)\n");
+ tmpFW.write("sect,pass\n");
+ tmpFW.write("exit\n\n");
+ }
+
+ tmpFW.write("# Define the reboot section\n");
+ tmpFW.write("sect,reboot\n");
+ tmpFW.write("exit\n\n");
+
+ tmpFW.write("# Define the test section\n");
+ tmpFW.write("sect,test\n");
+ tmpFW.write("exit\n\n");
+ }
+ catch(IOException aExp)
+ {
+ aTask.infoAppendln("Failed to generate the delta.cfg file.");
+ aTask.infoAppendln(ThreadUtil.getStackTrace(aExp));
+ return false;
+ }
+
+ // We are done if there was no updated JRE
+ if (targJre == null)
+ return true;
+
+ // Since an updated JRE was needed...
+ // Moved the JRE (unpacked folder) from its drop path to the proper location
+ File jreDropPath = new File(destPath, "jre" + targJre.getVersion().getLabel());
+ File jreTargPath = PlatformUtils.getJreLocation(targJre);
+ jreTargPath.getParentFile().setWritable(true);
+ if (jreDropPath.renameTo(jreTargPath) == false)
+ {
+ aTask.infoAppendln("Failed to move the updated JRE to its target location!");
+ aTask.infoAppendln("\t Current path: " + jreDropPath);
+ aTask.infoAppendln("\tOfficial path: " + jreTargPath);
+ return false;
+ }
+
+ // Backup the application configuration
+ try
+ {
+ Files.copy(appCfgFile.toPath(), new File(destPath, appCfgFile.getName() + ".old").toPath(), StandardCopyOption.COPY_ATTRIBUTES);
+ }
+ catch(IOException aExp)
+ {
+ aTask.infoAppendln("Failed to backup application configuration file: " + deltaCmdFile);
+ aTask.infoAppendln(ThreadUtil.getStackTrace(aExp));
+ return false;
+ }
+
+ // Update the application configuration to reflect the proper JRE
+ try
+ {
+ PlatformUtils.setJreVersion(targJre.getVersion());
+ }
+ catch(ErrorDM aExp)
+ {
+ aTask.infoAppendln("Failed to update the configuration to point to the updated JRE!");
+ aTask.infoAppendln("\tCurrent JRE: " + currJreVer.getLabel());
+ aTask.infoAppendln("\t Chosen JRE: " + targJre.getVersion().getLabel());
+ printErrorDM(aTask, aExp, 1);
+
+ // Remove the just installed JRE
+ IoUtil.deleteDirectory(jreTargPath);
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Helper method to download a compatible JreRelease for the AppCatalog to the specified destPath.
+ *
+ * On success the JreVersion that was downloaded is returned.
+ */
+ private JreRelease downloadJreUpdate(Task aTask, AppCatalog aUpdateCat, File aDestPath, long releaseSizeFull)
+ {
+ List jreList;
+
+ // Ensure our JRE version is compatible for this release
+ JreVersion currJreVer = DistUtils.getJreVersion();
+
+ // Let the user know why their version is not compatible
+ String updnStr = "downgraded";
+ if (aUpdateCat.isJreVersionTooOld(currJreVer) == true)
+ updnStr = "upgraded";
+ aTask.infoAppendln("Your current JRE is not compatible with this release. It will need to be " + updnStr + "!");
+ aTask.infoAppendln("\tCurrent JRE: " + currJreVer.getLabel());
+ aTask.infoAppendln("\tMinimun JRE: " + aUpdateCat.getMinJreVersion().getLabel());
+ JreVersion tmpJreVer = aUpdateCat.getMaxJreVersion();
+ if (tmpJreVer != null)
+ aTask.infoAppendln("\tMaximun JRE: " + tmpJreVer.getLabel());
+ aTask.infoAppendln("");
+
+ // Bail if we are running a bundled JRE
+ if (DistUtils.isJreBundled() == false)
+ {
+ aTask.infoAppend("This is the non bundled JRE version of the application. You are running the system JRE. ");
+ aTask.infoAppendln("Please update the JRE (or path) to reflect a compatible JRE version.\n");
+ return null;
+ }
+
+ // Get list of all available JREs
+ jreList = JreUtils.getAvailableJreReleases(aTask, updateSiteUrl, refCredential);
+ if (jreList == null)
+ {
+ aTask.infoAppendln("The update site has not had any JREs deployed.");
+ aTask.infoAppendln("Please contact the update site adminstartor.");
+ return null;
+ }
+ if (jreList.size() == 0)
+ {
+ aTask.infoAppendln("No JRE releases found!");
+ aTask.infoAppendln("Please contact the update site adminstartor.");
+ return null;
+ }
+
+ // Retrieve the latest appropriate JreRelease
+ String platform = PlatformUtils.getPlatform();
+ jreList = JreUtils.getMatchingPlatforms(jreList, platform);
+ if (jreList.size() == 0)
+ {
+ aTask.infoAppendln("There are no JRE releases available for the platform: " + platform + "!");
+ return null;
+ }
+
+ // Retrieve the JRE that is compatible from the list
+ JreRelease pickJre = aUpdateCat.getCompatibleJre(jreList);
+ if (pickJre == null)
+ {
+ aTask.infoAppendln("There are no compatible JREs found on the deploy site. Available JREs: " + jreList.size());
+ for (JreRelease aJreRelease : jreList)
+ aTask.infoAppendln("\t" + aJreRelease.getFileName() + " ---> (JRE: " + aJreRelease.getVersion().getLabel() + ")");
+ aTask.infoAppendln("\nPlease contact the update site adminstartor.");
+ return null;
+ }
+ JreVersion pickJreVer = pickJre.getVersion();
+
+ // Update the number of bytes to be retrieved to take into account the JRE which we will be downloading
+ long tmpFileLen = pickJre.getFileLen();
+ releaseSizeFull += tmpFileLen;
+
+ // Download the JRE
+ Digest targDigest, testDigest;
+ targDigest = pickJre.getDigest();
+ MessageDigest msgDigest;
+ msgDigest = DigestUtils.getDigest(targDigest.getType());
+ aTask.infoAppendln("Downloading JRE... Version: " + pickJreVer.getLabel());
+ URL srcUrl = IoUtil.createURL(updateSiteUrl.toString() + "/jre/" + pickJreVer.getLabel() + "/" + pickJre.getFileName());
+ File dstFile = new File(aDestPath, pickJre.getFileName());
+ Task tmpTask = new PartialTask(aTask, aTask.getProgress(), (tmpFileLen * 0.75) / (releaseSizeFull + 0.00));
+ if (DistUtils.downloadFile(tmpTask, srcUrl, dstFile, refCredential, tmpFileLen, msgDigest) == false)
+ return null;
+
+ // Validate that the JRE was downloaded successfully
+ testDigest = new Digest(targDigest.getType(), msgDigest.digest());
+ if (targDigest.equals(testDigest) == false)
+ {
+ aTask.infoAppendln("The download of the JRE appears to be corrupted.");
+ aTask.infoAppendln("\tFile: " + dstFile);
+ aTask.infoAppendln("\t\tExpected " + targDigest.getDescr());
+ aTask.infoAppendln("\t\tRecieved " + testDigest.getDescr() + "\n");
+ return null;
+ }
+
+ // Unpack the JRE at the unpack location
+ aTask.infoAppendln("Finshed downloading JRE. Unpacking JRE...");
+ File jreRootPath = null;
+ File jreTargPath = new File(aDestPath, "jre" + pickJreVer.getLabel());
+ try
+ {
+ // Create the working unpack folder where the JRE will be initially unpacked to.
+ File unpackPath = new File(aDestPath, "unpack");
+ unpackPath.mkdirs();
+
+ // Unpack the JRE to the working unpack folder and ensure that the unpacked JRE results in a 1 top level root folder.
+ tmpTask = new PartialTask(aTask, aTask.getProgress(), (tmpFileLen * 0.25) / (releaseSizeFull + 0.00));
+ MiscUtils.unTar(tmpTask, dstFile, unpackPath);
+ File[] fileArr = unpackPath.listFiles();
+ if (fileArr.length != 1 && fileArr[0].isDirectory() == false)
+ throw new Exception("Expected only one (top level) folder to be unpacked. Items extracted: " + fileArr.length + " Path: " + unpackPath);
+ jreRootPath = fileArr[0];
+
+ // Moved the unpacked JRE to aDestPath/jre/ folder and remove the working unpack folder and the tar.gz file
+ jreRootPath.renameTo(jreTargPath);
+ unpackPath.delete();
+ dstFile.delete();
+ }
+ catch(Exception aExp)
+ {
+ aTask.infoAppendln("Failed to properly untar archive. The update has been aborted.");
+ aTask.infoAppendln("\tTar File: " + dstFile);
+ aTask.infoAppendln("\tDestination: " + jreTargPath);
+ return null;
+ }
+
+ return pickJre;
}
/**
@@ -652,6 +805,39 @@ public class DistMakerEngine
displayNotice(msg);
}
+ /**
+ * Helper method that prints the exception of ErrorDM in an intelligent fashion to the specified task.
+ *
+ * All ErrorDM exceptions (and their causes) will be printed. If the cause is not of type ErrorDM then the stack trace will be printed as well.
+ */
+ private void printErrorDM(Task aTask, ErrorDM aErrorDM, int numTabs)
+ {
+ Throwable cause;
+ String tabStr;
+
+ tabStr = Strings.repeat("\t", numTabs);
+
+ aTask.infoAppendln(tabStr + "Reason: " + aErrorDM.getMessage());
+ cause = aErrorDM.getCause();
+ while (cause != null)
+ {
+ if (cause instanceof ErrorDM)
+ {
+ aTask.infoAppendln(tabStr + "Reason: " + cause.getMessage());
+ }
+ else
+ {
+ aTask.infoAppendln(tabStr + "StackTrace: ");
+ aTask.infoAppendln(ThreadUtil.getStackTrace(cause));
+ break;
+ }
+
+ cause = aErrorDM.getCause();
+ }
+
+ aTask.infoAppendln("");
+ }
+
/**
* Helper method that prompts the user for forms of input depending on the state of the App
*
@@ -675,11 +861,8 @@ public class DistMakerEngine
return;
}
- // Remove the retrieved update, and restore the platform configuration files to this (running) release
- // It is necessary to do this, since the user may later cancel the update request and it is important to
- // leave the program and configuration files in a stable state.
- IoUtil.deleteDirectory(deltaPath);
- updatePlatformConfigFiles(aTask, currRelease);
+ // Revert the update
+ revertUpdate(aTask);
}
// Query the user of the version to update to
@@ -695,37 +878,126 @@ public class DistMakerEngine
}
/**
- * Helper method to update platform specific configuration files
+ * Helper method that "reverts" an update. After this method is called the DistMaker application's configuration should be in the same state as before an
+ * update was applied. Reverting consists of the following:
+ *
+ * - Removal of any downloaded and installed JRE
+ *
- Removing the delta directory
+ *
- Removing the delta.cfg file
+ *
+ *
+ * There should not be any issues with this roll back process. However if there are a best effort will be made to continue rolling back the updates - note
+ * that the application might be in an unstable state - and may not be able to be restarted.
*/
- private boolean updatePlatformConfigFiles(Task aTask, AppRelease aRelease)
+ private void revertUpdate(Task aTask)
{
- File installPath, pFile;
- String errMsg;
-
- // Get the top level install path
- installPath = DistUtils.getAppPath().getParentFile();
-
- // Apple specific platform files
- pFile = new File(installPath, "Info.plist");
- if (pFile.isFile() == false)
- pFile = new File(installPath.getParentFile(), "Info.plist");
-
- if (pFile.isFile() == true)
+ // Revert our application's configuration (which will be loaded when it is restarted) to reflect the proper JRE
+ try
{
- errMsg = null;
- if (pFile.setWritable(true) == false)
- errMsg = "Failure. No writable permmisions for file: " + pFile;
- else
- errMsg = AppleUtils.updateVersion(pFile, aRelease.getVersion());
-
- if (errMsg != null)
- {
- aTask.infoAppendln(errMsg);
- return false;
- }
+ JreVersion currJreVer = DistUtils.getJreVersion();
+ PlatformUtils.setJreVersion(currJreVer);
+ }
+ catch(ErrorDM aExp)
+ {
+ aTask.infoAppendln("Failed to revert application's JRE!");
+ aTask.infoAppendln("\tApplication may be in an unstable state.");
+ printErrorDM(aTask, aExp, 1);
}
- return true;
+ // Revert any platform specific config files
+ try
+ {
+ PlatformUtils.updateAppRelease(currRelease);
+ }
+ catch(ErrorDM aExp)
+ {
+ aTask.infoAppendln("Failed to revert application configuration!");
+ aTask.infoAppendln("\tApplication may be in an unstable state.");
+ printErrorDM(aTask, aExp, 1);
+ }
+
+ // Determine the path to the delta (update) folder
+ File rootPath = DistUtils.getAppPath().getParentFile();
+ File deltaPath = new File(rootPath, "delta");
+
+ // It is necessary to do this, since the user may later cancel the update request and it is important to
+ // leave the program and configuration files in a stable state.
+
+ // Execute any trash commands from the "fail" section of the delta.cmd file
+ File deltaCmdFile = new File(deltaPath, "delta.cmd");
+ try (BufferedReader br = MiscUtils.openFileAsBufferedReader(deltaCmdFile))
+ {
+ String currSect = null;
+ while (true)
+ {
+ String inputStr = br.readLine();
+
+ // Delta command files should always have a proper exit (or reboot) and thus never arrive here
+ if (inputStr == null)
+ throw new ErrorDM("Command file (" + deltaCmdFile + ") is incomplete.");
+
+ // Ignore empty lines and comments
+ if (inputStr.isEmpty() == true || inputStr.startsWith("#") == true)
+ continue;
+
+ // Tokenize the input and retrieve the command
+ String[] strArr = inputStr.split(",");
+ String cmdStr = strArr[0];
+
+ // Skip to next line when we read a new section
+ if (strArr.length == 2 && cmdStr.equals("sect") == true)
+ {
+ currSect = strArr[1];
+ continue;
+ }
+
+ // Skip to the next line if we are not in the "fail" section
+ if (currSect != null && currSect.equals("fail") == false)
+ continue;
+ else if (currSect == null)
+ throw new ErrorDM("Command specified outside of section. Command: " + inputStr);
+
+ // Exit if we reach the exit or reboot command
+ if (strArr.length == 1 && cmdStr.equals("exit") == true)
+ break;
+ else if (strArr.length == 1 && cmdStr.equals("reboot") == true)
+ break;
+
+ // Execute the individual (trash) commands
+ if (strArr.length == 2 && cmdStr.equals("trash") == true)
+ {
+ // Resolve the argument to the corresponding file and ensure it is relative to our rootPath
+ File tmpFile = new File(rootPath, strArr[1]).getCanonicalFile();
+ if (MiscUtils.getRelativePath(rootPath, tmpFile) == null)
+ throw new ErrorDM("File (" + tmpFile + ") is not relative to folder: " + rootPath);
+
+ if (tmpFile.isFile() == true)
+ {
+ if (tmpFile.delete() == false)
+ throw new ErrorDM("Failed to delete file: " + tmpFile);
+ }
+ else if (tmpFile.isDirectory() == true)
+ {
+ if (IoUtil.deleteDirectory(tmpFile) == false)
+ throw new ErrorDM("Failed to delete folder: " + tmpFile);
+ }
+ else
+ {
+ throw new ErrorDM("File type is not recognized: " + tmpFile);
+ }
+ }
+ }
+ }
+ catch(IOException aExp)
+ {
+ aTask.infoAppendln("Failed to revert application configuration!");
+ aTask.infoAppendln("\tApplication may be in an unstable state.");
+ aTask.infoAppendln(ThreadUtil.getStackTrace(aExp));
+ }
+
+ // Remove the entire delta folder
+ if (IoUtil.deleteDirectory(deltaPath) == false)
+ throw new ErrorDM("Failed to delete folder: " + deltaPath);
}
}
diff --git a/src/distMaker/DistUtils.java b/src/distMaker/DistUtils.java
index f10e31c..a3f6c7b 100644
--- a/src/distMaker/DistUtils.java
+++ b/src/distMaker/DistUtils.java
@@ -12,6 +12,8 @@ import glum.util.ThreadUtil;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.*;
@@ -75,24 +77,6 @@ public class DistUtils
return new JreVersion(jreVer);
}
- /**
- * Returns the platform (Apple, Linux, or Windows) on which the current JRE is running on.
- */
- public static String getPlatform()
- {
- String osName;
-
- osName = System.getProperty("os.name").toUpperCase();
- if (osName.startsWith("LINUX") == true)
- return "Linux";
- if (osName.startsWith("MAC OS X") == true)
- return "Apple";
- if (osName.startsWith("WINDOWS") == true)
- return "Windows";
-
- return System.getProperty("os.name");
- }
-
/**
* Returns the code associated with the update.
*/
@@ -117,6 +101,26 @@ public class DistUtils
return isDevelopersEnvironment;
}
+ /**
+ * Utility method to determine if the JRE is embedded with this application.
+ */
+ public static boolean isJreBundled()
+ {
+ Path rootPath, jrePath;
+
+ // Get the top level folder of our installation
+ rootPath = getAppPath().toPath().getParent();
+
+ // Get the path to the JRE
+ jrePath = Paths.get(System.getProperty("java.home"));
+
+ // Determine if the java.home JRE is a subdirectory of the top level app folder
+ if (jrePath.startsWith(rootPath) == true)
+ return true;
+
+ return false;
+ }
+
/**
* Downloads the specified file from srcUrl to destFile. Returns true on success
*
@@ -383,28 +387,23 @@ public class DistUtils
public static AppCatalog readAppCatalog(Task aTask, File aCatalogFile, URL aUpdateUrl)
{
List nodeList;
- JreVersion jreVersion;
- InputStream inStream;
- BufferedReader bufReader;
+ JreVersion minJreVersion, maxJreVersion;
DigestType digestType;
String errMsg, strLine;
errMsg = null;
nodeList = new ArrayList<>();
- jreVersion = null;
+ minJreVersion = null;
+ maxJreVersion = null;
// Default to DigestType of MD5
digestType = DigestType.MD5;
- inStream = null;
- bufReader = null;
- try
+ try (BufferedReader bufReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(aCatalogFile))));)
{
String[] tokens;
// Read the contents of the file
- inStream = new FileInputStream(aCatalogFile);
- bufReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(inStream)));
while (true)
{
strLine = bufReader.readLine();
@@ -447,16 +446,18 @@ public class DistUtils
else
digestType = tmpDigestType;
}
- else if (tokens.length == 2 && tokens[0].equals("jre") == true)
+ else if ((tokens.length == 2 || tokens.length == 3) && tokens[0].equals("jre") == true)
{
- if (jreVersion != null)
+ if (minJreVersion != null)
{
- aTask.infoAppendln("JRE version has already been specified. Current ver: " + jreVersion.getLabel() + " Requested ver: " + tokens[1]
+ aTask.infoAppendln("JRE version has already been specified. Current ver: " + minJreVersion.getLabel() + " Requested ver: " + tokens[1]
+ ". Skipping...");
continue;
}
- jreVersion = new JreVersion(tokens[1]);
+ minJreVersion = new JreVersion(tokens[1]);
+ if (tokens.length == 3)
+ maxJreVersion = new JreVersion(tokens[2]);
}
else
{
@@ -468,11 +469,6 @@ public class DistUtils
{
errMsg = ThreadUtil.getStackTrace(aExp);
}
- finally
- {
- IoUtil.forceClose(inStream);
- IoUtil.forceClose(bufReader);
- }
// See if we are in a valid state
if (errMsg != null)
@@ -487,7 +483,7 @@ public class DistUtils
return null;
}
- return new AppCatalog(jreVersion, nodeList);
+ return new AppCatalog(nodeList, minJreVersion, maxJreVersion);
}
/**
diff --git a/src/distMaker/ErrorDM.java b/src/distMaker/ErrorDM.java
new file mode 100644
index 0000000..5f5e45a
--- /dev/null
+++ b/src/distMaker/ErrorDM.java
@@ -0,0 +1,37 @@
+package distMaker;
+
+/**
+ * Generic runtime exception thrown by various DistMaker modules/routines.
+ */
+public class ErrorDM extends RuntimeException
+{
+ private static final long serialVersionUID = 1L;
+
+ // State vars
+ private String subject;
+
+ public ErrorDM(Throwable aCause, String aMessage, String aSubject)
+ {
+ super(aMessage, aCause);
+ subject = aSubject;
+ }
+
+ public ErrorDM(Throwable aCause, String aMessage)
+ {
+ this(aCause, aMessage, null);
+ }
+
+ public ErrorDM(String aMessage)
+ {
+ this(null, aMessage, null);
+ }
+
+ /**
+ * Returns the subject specific to this error. May be null if this is just a generic error.
+ */
+ public String getSubject()
+ {
+ return subject;
+ }
+
+}
diff --git a/src/distMaker/MiscUtils.java b/src/distMaker/MiscUtils.java
index bef57df..ddaa81a 100644
--- a/src/distMaker/MiscUtils.java
+++ b/src/distMaker/MiscUtils.java
@@ -1,47 +1,54 @@
package distMaker;
+import glum.task.Task;
+
import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.commons.compress.archivers.ArchiveException;
+import org.apache.commons.compress.archivers.ArchiveStreamFactory;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.utils.IOUtils;
+
+import com.google.common.io.CountingInputStream;
/**
- * Collection of generic utility methods that should be migrated to another library.
+ * Collection of generic utility methods that should be migrated to another library / class.
*/
public class MiscUtils
{
- public static void unTar(final File inputFile, final File aDestPath) throws Exception
- {
- throw new Exception("Incomplete...");
- }
-
/**
- * Helper method to convert a Unix base-10 mode into the equivalent string.
+ * Utility method to convert a Unix base-10 mode into the equivalent string.
*
* Example: 493 -> 'rwxr-xr-x'
*
- * The returned string will always be of length 9
+ * The returned string will always be of length 9
*/
public static String convertUnixModeToStr(int aMode)
{
- char permArr[] = {'r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x'};
-
+ char permArr[] = { 'r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x' };
+
for (int c1 = 8; c1 >= 0; c1--)
{
if (((aMode >> c1) & 0x01) != 1)
permArr[8 - c1] = '-';
}
-
+
return new String(permArr);
}
/**
- * Helper method to convert a Unix base-10 mode into the Set.
+ * Utility method to convert a Unix base-10 mode into the Set.
*
* Example: 493 -> 'rwxr-xr-x'
*
- * The returned string will always be of length 9
+ * The returned string will always be of length 9
*/
public static Set convertUnixModeToPFP(int aMode)
{
@@ -49,9 +56,143 @@ public class MiscUtils
}
/**
- * Helper method to output the specified strings to aFile
+ * Returns the relative path component of aAbsolutePath relative to aBasePath.
+ *
+ * Returns null if aAbsolutePath does not start with aBasePath.
*/
- public static boolean writeDoc(File aFile, List strList)
+ public static String getRelativePath(File aBasePath, File aAbsolutePath)
+ {
+ Path relPath;
+
+ // Bail if aAbsolutePath does not start with aBasePath
+ if (aAbsolutePath.toPath().startsWith(aBasePath.toPath()) == false)
+ return null;
+
+ relPath = aBasePath.toPath().relativize(aAbsolutePath.toPath());
+ return relPath.toString();
+ }
+
+ /**
+ * Utility method that returns a BufferedReader corresponding to the specified file.
+ *
+ * This helps reduce boiler plate code.
+ */
+ public static BufferedReader openFileAsBufferedReader(File aFile) throws IOException
+ {
+ return new BufferedReader(new InputStreamReader(new FileInputStream(aFile)));
+ }
+
+ /**
+ * Untar an input file into an output file.
+ *
+ * Source based off of:
+ * http://stackoverflow.com/questions/315618/how-do-i-extract-a-tar-file-in-java/7556307#7556307
+ *
+ *
+ * The output file is created in the output folder, having the same name as the input file, minus the '.tar' extension.
+ *
+ * @param inputFile
+ * the input .tar file
+ * @param aDestPath
+ * The destination folder where the content will be dumped.
+ * @throws IOException
+ * @throws FileNotFoundException
+ *
+ * @return The {@link List} of {@link File}s with the untared content.
+ * @throws ArchiveException
+ */
+ public static List unTar(Task aTask, final File inputFile, final File aDestPath) throws FileNotFoundException, IOException, ArchiveException
+ {
+ Map pathMap;
+ InputStream iStream;
+
+ final List untaredFiles = new ArrayList<>();
+ long fullLen = inputFile.length();
+
+ // Open up the stream to the tar file (set up a counting stream to allow for progress updates)
+ CountingInputStream cntStream = new CountingInputStream(new FileInputStream(inputFile));
+ iStream = cntStream;
+ if (inputFile.getName().toUpperCase().endsWith(".GZ") == true)
+ iStream = new GZIPInputStream(iStream);
+
+ pathMap = new LinkedHashMap<>();
+ final TarArchiveInputStream debInputStream = (TarArchiveInputStream)new ArchiveStreamFactory().createArchiveInputStream("tar", iStream);
+ TarArchiveEntry entry = null;
+ while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null)
+ {
+ final File outputFile = new File(aDestPath, entry.getName());
+ if (entry.isDirectory())
+ {
+ if (!outputFile.exists())
+ {
+ if (!outputFile.mkdirs())
+ {
+ throw new IllegalStateException(String.format("Couldn't create directory %s.", outputFile.getAbsolutePath()));
+ }
+ }
+
+ long tmpUtc = entry.getModTime().getTime();
+ outputFile.setLastModified(tmpUtc);
+ pathMap.put(outputFile, tmpUtc);
+ }
+ else if (entry.isSymbolicLink() == true)
+ {
+ File tmpFile = new File(entry.getLinkName());
+ Path outLink = Files.createSymbolicLink(outputFile.toPath(), tmpFile.toPath());
+
+ long tmpUtc = entry.getModTime().getTime();
+ outputFile.setLastModified(tmpUtc);
+ }
+ else if (entry.isFile() == true)
+ {
+ final OutputStream outputFileStream = new FileOutputStream(outputFile);
+ IOUtils.copy(debInputStream, outputFileStream);
+ outputFileStream.close();
+
+ // Update the modified time of the file
+ long tmpUtc = entry.getModTime().getTime();
+ outputFile.setLastModified(tmpUtc);
+ }
+ else
+ {
+ System.err.println(String.format("Unrecognized entry: %s", entry.getName()));
+ }
+
+ // Update the mode on all (non symbolic) files / paths
+ int mode = entry.getMode();
+ if (entry.isSymbolicLink() == false)
+ {
+ String permStr;
+
+ permStr = convertUnixModeToStr(mode);
+ Files.setPosixFilePermissions(outputFile.toPath(), PosixFilePermissions.fromString(permStr));
+// System.out.println(String.format("\tMode: %d %x %s %s name: %s", mode, mode, Integer.toOctalString(mode), permStr, entry.getName()));
+ }
+
+ untaredFiles.add(outputFile);
+
+ // Update the progress bar
+ aTask.infoUpdate("\tUnpacked: " + entry.getName());
+ long currLen = cntStream.getCount();
+ aTask.setProgress(currLen / (fullLen + 0.0));
+ }
+ debInputStream.close();
+
+ // Update all of the times on the folders last
+ for (File aDir : pathMap.keySet())
+ aDir.setLastModified(pathMap.get(aDir));
+
+ aTask.infoAppendln("\tUnpacked: " + untaredFiles.size() + " files\n");
+
+ return untaredFiles;
+ }
+
+ /**
+ * Helper method to output the specified strings to aFile
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void writeDoc(File aFile, List strList)
{
// Output the strList
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(aFile)));)
@@ -60,13 +201,10 @@ public class MiscUtils
for (String aStr : strList)
bw.write(aStr + '\n');
}
- catch(Exception aExp)
+ catch(IOException aExp)
{
- aExp.printStackTrace();
- return false;
+ throw new ErrorDM(aExp, "Failed to write the file: " + aFile);
}
-
- return true;
}
}
diff --git a/src/distMaker/gui/MemoryConfigPanel.java b/src/distMaker/gui/MemoryConfigPanel.java
index 0d4c8a4..e6402bc 100644
--- a/src/distMaker/gui/MemoryConfigPanel.java
+++ b/src/distMaker/gui/MemoryConfigPanel.java
@@ -8,6 +8,7 @@ import glum.gui.component.GSlider;
import glum.gui.panel.GlassPanel;
import glum.gui.panel.generic.MessagePanel;
import glum.unit.ByteUnit;
+import glum.util.ThreadUtil;
import glum.zio.raw.ZioRaw;
import java.awt.*;
@@ -22,6 +23,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.miginfocom.swing.MigLayout;
+import distMaker.ErrorDM;
import distMaker.platform.MemUtils;
import distMaker.platform.PlatformUtils;
import static distMaker.platform.MemUtils.KB_SIZE;
@@ -123,9 +125,26 @@ public class MemoryConfigPanel extends GlassPanel implements ActionListener, Zio
targMemSize = (long)targMemS.getModelValue();
targMemSize = roundToMB(targMemSize);
- // Bail if we are not able to set the DistMaker max heap memory
- if (PlatformUtils.setMaxHeapMem(warnPanel, targMemSize) == false)
- return;
+ try
+ {
+ // Delegate the updating of the memory
+ PlatformUtils.setMaxHeapMem(targMemSize);
+ }
+ catch(ErrorDM aExp)
+ {
+ String subjectStr = aExp.getSubject();
+ if (subjectStr == null)
+ subjectStr = "Application Configuration Error";
+
+ String messageStr = aExp.getMessage();
+ if (aExp.getCause() != null)
+ messageStr += "\n\n" + ThreadUtil.getStackTrace(aExp.getCause());
+
+ // Show the user the details of the failure
+ warnPanel.setTitle(subjectStr);
+ warnPanel.setInfo(messageStr);
+ warnPanel.setVisible(true);
+ }
// Update our state vars
instMemSize = targMemSize;
diff --git a/src/distMaker/jre/JreUtils.java b/src/distMaker/jre/JreUtils.java
index 99cd99e..07ee1b3 100644
--- a/src/distMaker/jre/JreUtils.java
+++ b/src/distMaker/jre/JreUtils.java
@@ -14,6 +14,7 @@ import java.util.*;
import distMaker.digest.Digest;
import distMaker.digest.DigestType;
+import distMaker.platform.PlatformUtils;
public class JreUtils
{
@@ -29,6 +30,7 @@ public class JreUtils
BufferedReader bufReader;
DigestType digestType;
String errMsg, strLine;
+ String version;
errMsg = null;
retList = new ArrayList<>();
@@ -36,6 +38,7 @@ public class JreUtils
// Default to DigestType of MD5
digestType = DigestType.MD5;
+ version = null;
inStream = null;
bufReader = null;
@@ -62,6 +65,8 @@ public class JreUtils
; // Nothing to do
else if (tokens.length >= 1 && tokens[0].equals("exit") == true)
break; // Bail once we get the 'exit' command
+ else if (tokens.length == 2 && tokens[0].equals("name") == true && tokens[1].equals("JRE") == true)
+ ; // Nothing to do - we just entered the "JRE" section
else if (tokens.length == 2 && tokens[0].equals("digest") == true)
{
DigestType tmpDigestType;
@@ -72,17 +77,35 @@ public class JreUtils
else
digestType = tmpDigestType;
}
- else if (tokens.length == 6 && tokens[0].equals("jre") == true)
+ else if (tokens.length == 2 && tokens[0].equals("jre") == true)
{
- String platform, version, filename, digestStr;
+ version = tokens[1];
+ }
+ else if (tokens.length == 4 && tokens[0].equals("F") == true)
+ {
+ String platform, filename, digestStr;
long fileLen;
+ if (version == null)
+ {
+ aTask.infoAppendln("Skipping input: " + strLine);
+ aTask.infoAppendln("\tJRE version has not been specifed. Missing input line: jre,");
+ continue;
+ }
+
// Form the JreRelease
digestStr = tokens[1];
- platform = tokens[2];
- version = tokens[3];
- fileLen = GuiUtil.readLong(tokens[4], -1);
- filename = tokens[5];
+ fileLen = GuiUtil.readLong(tokens[2], -1);
+ filename = tokens[3];
+
+ platform = PlatformUtils.getPlatformOfJreTarGz(filename);
+ if (platform == null)
+ {
+ aTask.infoAppendln("Skipping input: " + strLine);
+ aTask.infoAppendln("\tFailed to determine the target platform of the JRE.");
+ continue;
+ }
+
retList.add(new JreRelease(platform, version, filename, new Digest(digestType, digestStr), fileLen));
}
else
diff --git a/src/distMaker/jre/JreVersion.java b/src/distMaker/jre/JreVersion.java
index af2c641..a443425 100644
--- a/src/distMaker/jre/JreVersion.java
+++ b/src/distMaker/jre/JreVersion.java
@@ -24,7 +24,7 @@ public class JreVersion implements Comparable
*
* The better version is defined as the later version (and the more specific version).
*
- * Returns null if the better version can not be determined
+ * Returns null if the better version can not be determined or if the versions are equal.
*/
public static JreVersion getBetterVersion(JreVersion verA, JreVersion verB)
{
@@ -35,10 +35,12 @@ public class JreVersion implements Comparable
tokenA = verA.getLabel().split("[._]");
tokenB = verB.getLabel().split("[._]");
- // Default return JreVersion is verA or the version that is more specific
- defaultVer = verA;
+ // Default JreVersion is the version that is more specific
+ defaultVer = null;
if (tokenA.length < tokenB.length)
defaultVer = verB;
+ else if (tokenB.length < tokenA.length)
+ defaultVer = verA;
// Set the idxCnt to the less specific JreVersion
idxCnt = tokenA.length;
diff --git a/src/distMaker/node/AppCatalog.java b/src/distMaker/node/AppCatalog.java
index c2b2932..b0c7f77 100644
--- a/src/distMaker/node/AppCatalog.java
+++ b/src/distMaker/node/AppCatalog.java
@@ -5,7 +5,7 @@ import java.util.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import distMaker.jre.JreVersion;
+import distMaker.jre.*;
/**
* Object that describes the structure (files, folders, and JRE version) of a Java application.
@@ -13,23 +13,98 @@ import distMaker.jre.JreVersion;
public class AppCatalog
{
/** The minimum JRE version required. */
- private JreVersion jreVersion;
+ private JreVersion minJreVer;
+
+ /** The maximum JRE version allowed. This will be null if there is no maximum. */
+ private JreVersion maxJreVer;
/** A mapping of filename to to corresponding Node */
private ImmutableMap nodeMap;
- public AppCatalog(JreVersion aJreVersion, List aNodeList)
+ public AppCatalog(List aNodeList, JreVersion aMinJreVer, JreVersion aMaxJreVer)
{
- jreVersion = aJreVersion;
+ minJreVer = aMinJreVer;
+ maxJreVer = aMaxJreVer;
nodeMap = ImmutableMap.copyOf(formNameMap(aNodeList));
}
/**
- * Returns the minimum JreVersion required.
+ * Returns the most recent JRE from the specified release that is compatible with this Appcatalog.
+ *
+ * Returns null if there are no JREs that are compatible.
*/
- public JreVersion getJreVersion()
+ public JreRelease getCompatibleJre(List aJreList)
{
- return jreVersion;
+ // Sort the platforms, but reverse the order so that the newest version is first
+ Collections.sort(aJreList);
+ Collections.reverse(aJreList);
+
+ for (JreRelease aRelease : aJreList)
+ {
+ if (isJreVersionTooNew(aRelease.getVersion()) == true)
+ continue;
+
+ if (isJreVersionTooOld(aRelease.getVersion()) == true)
+ continue;
+
+ return aRelease;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the minimum JreVersion that is compatible.
+ */
+ public JreVersion getMinJreVersion()
+ {
+ return minJreVer;
+ }
+
+ /**
+ * Returns the maximum JreVersion that is compatible.
+ */
+ public JreVersion getMaxJreVersion()
+ {
+ return maxJreVer;
+ }
+
+ public boolean isJreVersionCompatible(JreVersion aJreVer)
+ {
+ // Check to make sure aJreVer is not too old
+ if (isJreVersionTooOld(aJreVer) == true)
+ return false;
+
+ // Check to make sure aJreVer is not too new
+ if (isJreVersionTooNew(aJreVer) == true)
+ return false;
+
+ // The version aJreVer must be compatible
+ return true;
+ }
+
+ /**
+ * Returns true if the specified version is not compatible (too new) with this AppCatalog.
+ */
+ public boolean isJreVersionTooNew(JreVersion aJreVer)
+ {
+ // Check to make sure aJreVer is not too old
+ if (maxJreVer != null && JreVersion.getBetterVersion(maxJreVer, aJreVer) == aJreVer)
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns true if the specified version is not compatible (too old) with this AppCatalog.
+ */
+ public boolean isJreVersionTooOld(JreVersion aJreVer)
+ {
+ // Check to make sure aJreVer is not too old
+ if (minJreVer != null && JreVersion.getBetterVersion(minJreVer, aJreVer) == minJreVer)
+ return true;
+
+ return false;
}
/**
@@ -54,7 +129,7 @@ public class AppCatalog
* TODO: This should be renamed formNameMap to formDigestMap
* TODO: This should probably be a mapping of Digest to Node rather than filename to Node
*/
- public Map formNameMap(List aNodeList)
+ private Map formNameMap(List aNodeList)
{
Map retMap;
diff --git a/src/distMaker/node/AppRelease.java b/src/distMaker/node/AppRelease.java
index 3b23bae..30aa535 100644
--- a/src/distMaker/node/AppRelease.java
+++ b/src/distMaker/node/AppRelease.java
@@ -71,9 +71,9 @@ public class AppRelease implements Comparable, QueryItem
}
@Override
- public void setValue(@SuppressWarnings("unused") LookUp aEnum, @SuppressWarnings("unused") Object aObj)
+ public void setValue(LookUp aEnum, Object aObj)
{
- throw new RuntimeException("Unsupported operation");
+ throw new RuntimeException("Unsupported operation setValue(aEnum: " + aEnum + " aObj: " + aObj + ")");
}
@Override
diff --git a/src/distMaker/platform/AppleUtils.java b/src/distMaker/platform/AppleUtils.java
index a3fc716..fc49dd9 100644
--- a/src/distMaker/platform/AppleUtils.java
+++ b/src/distMaker/platform/AppleUtils.java
@@ -1,7 +1,8 @@
package distMaker.platform;
-import java.io.File;
-import java.io.FileOutputStream;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -11,30 +12,22 @@ import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
-import distMaker.DistUtils;
+import distMaker.*;
+import distMaker.jre.JreVersion;
/**
* Utility class which contains a set of methods to interact with an Apple Info.plist file.
*/
public class AppleUtils
{
- /**
- * Utility method to update the JRE to reflect the specified path.
- *
- * TODO: Complete this comment and method.
- */
- public static boolean updateJrePath(File aPath)
- {
- int zios_finish;
- return false;
- }
-
/**
* Returns the plist file used to configure apple applications.
*
* Two locations will be searched.... TODO: Add more details of those locations.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- private static File getPlistFile()
+ public static File getPlistFile()
{
File installPath;
File pFile;
@@ -42,58 +35,54 @@ public class AppleUtils
// Get the top level install path
installPath = DistUtils.getAppPath().getParentFile();
- // Attempt to locate the pList file
+ // Attempt to locate the pList file (from one of the possible locations)
pFile = new File(installPath, "Info.plist");
if (pFile.isFile() == false)
pFile = new File(installPath.getParentFile(), "Info.plist");
+
+ // Bail if we failed to locate the plist file
+ if (pFile.exists() == false)
+ throw new ErrorDM("The plist file could not be located.");
+
+ // Bail if the plist file is not a regular file.
if (pFile.isFile() == false)
- pFile = null;
+ throw new ErrorDM("The plist file does not appear to be a regular file: " + pFile);
return pFile;
}
/**
- * Utility method to update the specified max memory (-Xmx) value in the plist file (aFile) to the specified maxMemVal.
+ * Utility method to update the specified version in the plist file (pFile) to the new version.
*
- * In order for this method to succeed there must be a valid JVMOptions section followed by an array of string elements of JVM arguments. The array element
- * may be empty but must be specified.
- *
- * @return Returns null on success or an error message describing the issue.
+ * Note this method is very brittle, and assumes that the version will occur in the sibling node which immediately follows the node with a value of
+ * CFBundleVersion. TODO: Consider reducing brittleness.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static String updateMaxMem(long numBytes)
+ public static void updateAppVersion(String aNewVersion)
{
// Utilize the system pList file and delegate.
- return updateMaxMem(numBytes, getPlistFile());
+ updateAppVersion(aNewVersion, getPlistFile());
}
/**
- * Utility method to update the specified max memory (-Xmx) value in the plist file (aFile) to the specified maxMemVal.
+ * Utility method to update the specified version in the plist file (pFile) to the new version.
*
- * In order for this method to succeed there must be a valid JVMOptions section followed by an array of string elements of JVM arguments. The array element
- * may be empty but must be specified.
- *
- * @return Returns null on success or an error message describing the issue.
+ * Note this method is very brittle, and assumes that the version will occur in the sibling node which immediately follows the node with a value of
+ * CFBundleVersion. TODO: Consider reducing brittleness.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static String updateMaxMem(long numBytes, File pFile)
+ public static void updateAppVersion(String aNewVersin, File pFile)
{
Document doc;
Element docElement;
- String evalStr, updateStr;
- NodeList dictList, childList;
- Node childNode, targNode;
- Element evalE, arrE, memE;
- String tagStr, valStr, currKeyVal;
- int zios_Clean;
+ NodeList nodeList;
+ Node keyNode, strNode;
- // Bail if we failed to locate the pList file.
- if (pFile == null)
- return "The plist file could not be located.";
- // Bail if the plist file is not a regular file.
- if (pFile.isFile() == false)
- return "The plist file does not appear to be a regular file: " + pFile;
- // Bail if the plist file is not writeable.
+ // Bail if the pFile is not writable.
if (pFile.setWritable(true) == false)
- return "The plist file is not writeable: " + pFile;
+ throw new ErrorDM("The plist file is not writeable: " + pFile);
// Load the XML document via the javax.xml.parsers.* package
try
@@ -103,14 +92,148 @@ public class AppleUtils
}
catch(Exception aExp)
{
- aExp.printStackTrace();
- return "Failed to parse XML document. File: " + pFile;
+ throw new ErrorDM(aExp, "Failed to parse XML document. File: " + pFile);
+ }
+
+ nodeList = docElement.getElementsByTagName("*");
+ for (int c1 = 0; c1 < nodeList.getLength(); c1++)
+ {
+ keyNode = nodeList.item(c1).getFirstChild();
+ if (keyNode != null && keyNode.getNodeValue().equals("CFBundleVersion") == true)
+ {
+ System.out.println("Updating contents of file: " + pFile);
+
+ strNode = nodeList.item(c1 + 1).getFirstChild();
+ System.out.println(" Old Version: " + strNode.getNodeValue());
+
+ strNode.setNodeValue(aNewVersin);
+ System.out.println(" New Version: " + strNode.getNodeValue());
+ }
+ }
+
+ // Update the file with the changed document
+ saveDoc(pFile, doc);
+ }
+
+ /**
+ * Utility method to update the JRE to point to the specified path in the system plist file.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion)
+ {
+ // Utilize the system pList file and delegate.
+ updateJreVersion(aJreVersion, getPlistFile());
+ }
+
+ /**
+ * Utility method to update the JRE to point to the specified path in the plist file (pFile).
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion, File pFile)
+ {
+ List inputList;
+ String evalStr, tmpStr;
+ int currLineNum, targLineNum;
+
+ // Bail if the pFile is not writable
+ if (pFile.setWritable(true) == false)
+ throw new ErrorDM("The pFile is not writeable: " + pFile);
+
+ // Process our input
+ inputList = new ArrayList<>();
+ try (BufferedReader br = MiscUtils.openFileAsBufferedReader(pFile))
+ {
+ // Read the lines
+ currLineNum = 0;
+ targLineNum = -1;
+ while (true)
+ {
+ evalStr = br.readLine();
+ if (evalStr == null)
+ break;
+
+ // Record the start of the JVMRunitme section. Note the JRE should be specified on the next line
+ tmpStr = evalStr.trim();
+ if (tmpStr.equals("JVMRuntime") == true)
+ targLineNum = currLineNum + 1;
+
+ inputList.add(evalStr);
+ currLineNum++;
+ }
+ }
+ catch(IOException aExp)
+ {
+ throw new ErrorDM(aExp, "Failed while processing the pFile: " + pFile);
+ }
+
+ // Update the pFile
+ String regex = "(.*?)";
+ String repStr = "jre" + aJreVersion.getLabel() + "";
+ if (targLineNum == -1)
+ throw new ErrorDM("[" + pFile + "] The pFile does not specify a 'JVMRuntime' section.");
+ else if (targLineNum >= inputList.size())
+ throw new ErrorDM("[" + pFile + "] The pFile appears to be incomplete!");
+ else
+ inputList.set(targLineNum, inputList.get(targLineNum).replaceFirst(regex, repStr));
+
+ // Write the pFile
+ MiscUtils.writeDoc(pFile, inputList);
+ }
+
+ /**
+ * Utility method to update the specified max memory (-Xmx) value in the system plist file to the specified maxMemVal.
+ *
+ * In order for this method to succeed there must be a valid JVMOptions section followed by an array of string elements of JVM arguments. The array element
+ * may be empty but must be specified.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateMaxMem(long numBytes)
+ {
+ // Utilize the system pList file and delegate.
+ updateMaxMem(numBytes, getPlistFile());
+ }
+
+ /**
+ * Utility method to update the specified max memory (-Xmx) value in the plist file (pFile) to the specified maxMemVal.
+ *
+ * In order for this method to succeed there must be a valid JVMOptions section followed by an array of string elements of JVM arguments. The array element
+ * may be empty but must be specified.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateMaxMem(long numBytes, File pFile)
+ {
+ Document doc;
+ Element docElement;
+ String evalStr, updateStr;
+ NodeList dictList, childList;
+ Node childNode, targNode;
+ Element evalE, arrE, memE;
+ String tagStr, valStr, currKeyVal;
+ int zzz_Clean_this;
+
+ // Bail if the pFile is not writable.
+ if (pFile.setWritable(true) == false)
+ throw new ErrorDM("The plist file is not writeable: " + pFile);
+
+ // Load the XML document via the javax.xml.parsers.* package
+ try
+ {
+ doc = loadDoc(pFile);
+ docElement = doc.getDocumentElement();
+ }
+ catch(Exception aExp)
+ {
+ throw new ErrorDM(aExp, "Failed to parse XML document. File: " + pFile);
}
// Locate the element
dictList = docElement.getElementsByTagName("dict");
if (dictList.getLength() == 0)
- return "No element found! File: " + pFile;
+ throw new ErrorDM("No element found! File: " + pFile);
arrE = null;
currKeyVal = null;
@@ -148,7 +271,7 @@ public class AppleUtils
// Bail if we failed to locate the array element
if (arrE == null)
- return "Failed to locate the element following the element: JVMOptions\nFile: " + pFile;
+ throw new ErrorDM("Failed to locate the element following the element: JVMOptions\nFile: " + pFile);
memE = null;
childList = arrE.getChildNodes();
@@ -192,57 +315,11 @@ public class AppleUtils
evalStr = targNode.getNodeValue();
updateStr = MemUtils.transformMaxMemHeapString(evalStr, numBytes);
if (updateStr == null)
- return "Failed to transform the memory spec value. Original value: " + evalStr + "\nFile: " + pFile;
+ throw new ErrorDM("Failed to transform the memory spec value. Original value: " + evalStr + "\nFile: " + pFile);
targNode.setNodeValue(updateStr);
// Update the file with the changed document
- System.out.println("Updating contents of file: " + pFile);
- return saveDoc(pFile, doc);
- }
-
- /**
- * Utility method to update the specified version in the plist file (aFile) to the new version.
- *
- * Note this method is very brittle, and assumes that the version will occur in the sibling node which immediately follows the node with a value of
- * CFBundleVersion.
- */
- public static String updateVersion(File aFile, String aNewVersin)
- {
- Document doc;
- Element docElement;
- NodeList nodeList;
- Node keyNode, strNode;
-
- // Load the XML document via the javax.xml.parsers.* package
- try
- {
- doc = loadDoc(aFile);
- docElement = doc.getDocumentElement();
- }
- catch(Exception aExp)
- {
- aExp.printStackTrace();
- return "Failed to parse XML document. File: " + aFile;
- }
-
- nodeList = docElement.getElementsByTagName("*");
- for (int c1 = 0; c1 < nodeList.getLength(); c1++)
- {
- keyNode = nodeList.item(c1).getFirstChild();
- if (keyNode != null && keyNode.getNodeValue().equals("CFBundleVersion") == true)
- {
- System.out.println("Updating contents of file: " + aFile);
-
- strNode = nodeList.item(c1 + 1).getFirstChild();
- System.out.println(" Old Version: " + strNode.getNodeValue());
-
- strNode.setNodeValue(aNewVersin);
- System.out.println(" New Version: " + strNode.getNodeValue());
- }
- }
-
- // Update the file with the changed document
- return saveDoc(aFile, doc);
+ saveDoc(pFile, doc);
}
/**
@@ -264,8 +341,10 @@ public class AppleUtils
/**
* Helper method to output aDoc to the specified file.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- private static String saveDoc(File aFile, Document aDoc)
+ private static void saveDoc(File aFile, Document aDoc)
{
try (FileOutputStream oStream = new FileOutputStream(aFile);)
{
@@ -282,11 +361,8 @@ public class AppleUtils
}
catch(Exception aExp)
{
- aExp.printStackTrace();
- return "Failed to write the file: " + aFile;
+ throw new ErrorDM(aExp, "Failed to write the file: " + aFile);
}
-
- return null;
}
}
diff --git a/src/distMaker/platform/LinuxUtils.java b/src/distMaker/platform/LinuxUtils.java
index 025c66c..ab0dcf8 100644
--- a/src/distMaker/platform/LinuxUtils.java
+++ b/src/distMaker/platform/LinuxUtils.java
@@ -5,20 +5,114 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
-import distMaker.DistUtils;
-import distMaker.MiscUtils;
+import distMaker.*;
+import distMaker.jre.JreVersion;
public class LinuxUtils
{
/**
- * Utility method to update the JRE to reflect the specified path.
+ * Returns the executable script used to launch the JVM.
*
- * TODO: Complete this comment and method.
+ * If there are multiple launch scripts then this method may grab the wrong file and fail.
+ *
+ * TODO: In the future the launch script should pass itself as an argument to the JVM and DistMaker should keep track of that. If the script is significantly
+ * manipulated from the original the launch file may be improperly detected.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static boolean updateJrePath(File aPath)
+ public static File getScriptFile()
{
- int zios_finish;
- return false;
+ File[] fileArr;
+ File installPath;
+ File retFile;
+
+ installPath = DistUtils.getAppPath().getParentFile();
+ fileArr = installPath.listFiles();
+
+ // Attempt to locate the path that matches run* file
+ retFile = null;
+ for (File aFile : fileArr)
+ {
+ if (aFile.getName().startsWith("run") == true)
+ retFile = aFile;
+ }
+
+ // Bail if we failed to locate a regular file
+ if (retFile == null)
+ throw new ErrorDM("The script file could not be located.");
+
+ // Ensure the file is a regular fie
+ if (retFile.isFile() == false)
+ throw new ErrorDM("The script file is NOT a regular file.");
+
+ // Ensure the file is executable. If this is really the script file used to launch us then it should be executable!
+ if (Files.isExecutable(retFile.toPath()) == false)
+ throw new ErrorDM("The script file is NOT executable.");
+
+ return retFile;
+ }
+
+ /**
+ * Utility method to update the configuration to reflect the specified JRE version.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion)
+ {
+ // Utilize the system scriptFile and delegate.
+ updateJreVersion(aJreVersion, getScriptFile());
+ }
+
+ /**
+ * Utility method to update the configuration to reflect the specified JRE version.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion, File aScriptFile)
+ {
+ List inputList;
+ String evalStr, tmpStr;
+ int currLineNum, targLineNum;
+
+ // Bail if the scritpFile is not writable
+ if (aScriptFile.setWritable(true) == false)
+ throw new ErrorDM("The script file is not writeable: " + aScriptFile);
+
+ // Process our input
+ inputList = new ArrayList<>();
+ try (BufferedReader br = MiscUtils.openFileAsBufferedReader(aScriptFile))
+ {
+ // Read the lines
+ currLineNum = 0;
+ targLineNum = -1;
+ while (true)
+ {
+ evalStr = br.readLine();
+ if (evalStr == null)
+ break;
+
+ // Locate where the java executable is specified
+ tmpStr = evalStr.trim();
+ if (tmpStr.startsWith("javaExe=") == true)
+ targLineNum = currLineNum;
+
+ inputList.add(evalStr);
+ currLineNum++;
+ }
+ }
+ catch(IOException aExp)
+ {
+ throw new ErrorDM(aExp, "Failed while processing the script file: " + aScriptFile);
+ }
+
+ // Update the script
+ if (targLineNum != -1)
+ inputList.set(targLineNum, "javaExe=../jre" + aJreVersion.getLabel() + "/bin/java");
+ else
+ throw new ErrorDM("[" + aScriptFile + "] The script does not specify 'javaExe'.");
+
+ // Write the scriptFile
+ MiscUtils.writeDoc(aScriptFile, inputList);
}
/**
@@ -29,28 +123,39 @@ public class LinuxUtils
*
* If the maxMem var definition is moved in the script file to after the launch of the application then this method will (silently) fail to configure the
* value needed to launch the JVM.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static String updateMaxMem(long numBytes)
+ public static void updateMaxMem(long numBytes)
+ {
+ // Utilize the system scriptFile and delegate.
+ updateMaxMem(numBytes, getScriptFile());
+ }
+
+ /**
+ * Utility method to update the specified maxMem var in the script (aFile) to the requested number of bytes.
+ *
+ * Note this method assumes the specified file is a shell script built by DistMaker where the var maxMem holds the proper (right side) specification for the
+ * JVM's -Xmx value.
+ *
+ * If the maxMem var definition is moved in the script file to after the launch of the application then this method will (silently) fail to configure the
+ * value needed to launch the JVM.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateMaxMem(long numBytes, File aScriptFile)
{
List inputList;
- File scriptFile;
String evalStr, memStr, tmpStr;
int currLineNum, injectLineNum, targLineNum;
- // Bail if we fail to locate the scriptFile.
- scriptFile = getScriptFile();
- if (scriptFile == null)
- return "The script file could not be located.";
- // Bail if the script file is not a regular file.
- if (scriptFile.isFile() == false)
- return "The script file does not appear to be a regular file: " + scriptFile;
- // Bail if the script file is not writeable.
- if (scriptFile.setWritable(true) == false)
- return "The script file is not writeable: " + scriptFile;
+ // Bail if the scriptFile is not writable
+ if (aScriptFile.setWritable(true) == false)
+ throw new ErrorDM("The script file is not writeable: " + aScriptFile);
// Process our input
inputList = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(scriptFile)));)
+ try (BufferedReader br = MiscUtils.openFileAsBufferedReader(aScriptFile))
{
// Read the lines
currLineNum = 0;
@@ -73,10 +178,9 @@ public class LinuxUtils
currLineNum++;
}
}
- catch(Exception aExp)
+ catch(IOException aExp)
{
- aExp.printStackTrace();
- return "Failed while processing the script file: " + scriptFile;
+ throw new ErrorDM(aExp, "Failed while processing the script file: " + aScriptFile);
}
// Determine the memStr to use
@@ -96,48 +200,8 @@ public class LinuxUtils
inputList.add(1, "maxMem=" + memStr + "\n");
}
- // Update the script
- System.out.println("Updating contents of file: " + scriptFile);
- if (MiscUtils.writeDoc(scriptFile, inputList) == false)
- return "Failed to write the script file: " + scriptFile;
-
- // On success return null
- return null;
- }
-
- /**
- * Returns the executable script used to launch the JVM. If one can not be determined then this method will return null.
- *
- * If there are multiple launch scripts then this method may grab the wrong file and fail.
- *
- * TODO: In the future the launch script should pass itself as an argument to the JVM and DistMaker should keep track of that. If the script is significantly
- * manipulated from the original the launch file may be improperly detected.
- */
- private static File getScriptFile()
- {
- File[] fileArr;
- File installPath;
- File retFile;
-
- installPath = DistUtils.getAppPath().getParentFile();
- fileArr = installPath.listFiles();
-
- // Attempt to locate the path that matches run* file
- retFile = null;
- for (File aFile : fileArr)
- {
- if (aFile.getName().startsWith("run") == true)
- retFile = aFile;
- }
-
- if (retFile == null)
- return null;
-
- if (retFile.isFile() == false && Files.isExecutable(retFile.toPath()) == false)
- return null;
-
- System.out.println("Linux launch file: " + retFile);
- return retFile;
+ // Write the scriptFile
+ MiscUtils.writeDoc(aScriptFile, inputList);
}
}
diff --git a/src/distMaker/platform/PlatformUtils.java b/src/distMaker/platform/PlatformUtils.java
index 550581b..ac9e6ab 100644
--- a/src/distMaker/platform/PlatformUtils.java
+++ b/src/distMaker/platform/PlatformUtils.java
@@ -1,104 +1,171 @@
package distMaker.platform;
-import glum.gui.panel.generic.MessagePanel;
-
import java.io.File;
import distMaker.DistUtils;
+import distMaker.ErrorDM;
import distMaker.jre.JreRelease;
+import distMaker.jre.JreVersion;
+import distMaker.node.AppRelease;
public class PlatformUtils
{
+ /**
+ * Utility method that returns the platform specific configuration file for the java application.
+ */
+ public static File getConfigurationFile()
+ {
+ File cfgFile;
+ String platform;
+
+ platform = PlatformUtils.getPlatform().toUpperCase();
+ if (platform.equals("APPLE") == true)
+ cfgFile = AppleUtils.getPlistFile();
+ else if (platform.equals("LINUX") == true)
+ cfgFile = LinuxUtils.getScriptFile();
+ else if (platform.equals("WINDOWS") == true)
+ cfgFile = WindowsUtils.getConfigFile();
+ else
+ throw new ErrorDM("Unsupported platform: " + platform);
+
+ return cfgFile;
+ }
+
/**
* Utility method that returns the path where the specified JRE should be unpacked to.
*/
public static File getJreLocation(JreRelease aJreRelease)
+ {
+ // Delegate to actual worker method
+ return getJreLocation(aJreRelease.getVersion());
+ }
+
+ /**
+ * Utility method that returns the path where the specified JRE should be unpacked to.
+ */
+ public static File getJreLocation(JreVersion aJreVersion)
{
String platform, versionStr;
File jrePath, installPath;
jrePath = null;
installPath = DistUtils.getAppPath();
- versionStr = aJreRelease.getVersion().getLabel();
+ versionStr = aJreVersion.getLabel();
- platform = DistUtils.getPlatform().toUpperCase();
+ platform = PlatformUtils.getPlatform().toUpperCase();
if (platform.equals("APPLE") == true)
jrePath = new File(installPath.getParentFile(), "PlugIns/jre" + versionStr);
else if (platform.equals("LINUX") == true)
jrePath = new File(installPath.getParentFile(), "jre" + versionStr);
- else if (platform.equals("Windows") == true)
+ else if (platform.equals("WINDOWS") == true)
jrePath = new File(installPath.getParentFile(), "jre" + versionStr);
return jrePath;
}
/**
- * Utility method to configure the JRE location used by the active DistMaker distribution.
- *
- * Note this will only take effect after the application has been relaunched.
+ * Returns the platform (Apple, Linux, or Windows) on which the current JRE is running on.
*/
- public static boolean setJreLocation(File aPath)
+ public static String getPlatform()
+ {
+ String osName;
+
+ osName = System.getProperty("os.name").toUpperCase();
+ if (osName.startsWith("LINUX") == true)
+ return "Linux";
+ if (osName.startsWith("MAC OS X") == true)
+ return "Apple";
+ if (osName.startsWith("WINDOWS") == true)
+ return "Windows";
+
+ return System.getProperty("os.name");
+ }
+
+ /**
+ * Returns the platform of the JRE file.
+ *
+ * This only examines the filename to determine the platform.
+ */
+ public static String getPlatformOfJreTarGz(String aFileName)
+ {
+ aFileName = aFileName.toUpperCase();
+ if (aFileName.contains("LINUX") == true)
+ return "Linux";
+ if (aFileName.contains("MACOSX") == true)
+ return "Apple";
+ if (aFileName.contains("WINDOWS") == true)
+ return "Windows";
+
+ return null;
+ }
+
+ /**
+ * Utility method to configure the JRE version used by the (active) DistMaker distribution.
+ *
+ * Note this will only take effect after the application has been restarted.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ *
+ * @param aJrePath
+ * Path to top of the JRE.
+ */
+ public static void setJreVersion(JreVersion aJreVersion)
{
String platform;
- boolean isPass;
- isPass = false;
- platform = DistUtils.getPlatform().toUpperCase();
+ // Delegate to the proper platform code
+ platform = PlatformUtils.getPlatform().toUpperCase();
if (platform.equals("APPLE") == true)
- isPass = AppleUtils.updateJrePath(aPath);
+ AppleUtils.updateJreVersion(aJreVersion);
else if (platform.equals("LINUX") == true)
- isPass = LinuxUtils.updateJrePath(aPath);
- else if (platform.equals("Windows") == true)
- isPass = WindowsUtils.updateJrePath(aPath);
-
- return isPass;
+ LinuxUtils.updateJreVersion(aJreVersion);
+ else if (platform.equals("WINDOWS") == true)
+ WindowsUtils.updateJreVersion(aJreVersion);
+ else
+ throw new ErrorDM("Unrecognized platform: " + platform);
}
/**
* Utility method to configure the (active) DistMaker distribution to use the specified maxMem.
*
- * Method will return false on failure.
+ * Note this will only take effect after the application has been restarted.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*
- * @param warnPanel
- * GUI message panel to route error messages.
* @param maxMemSize
* Maximum heap memory in bytes.
*/
- public static boolean setMaxHeapMem(MessagePanel warnPanel, long maxMemSize)
+ public static void setMaxHeapMem(long maxMemSize)
{
String platform;
- String errMsg;
// Delegate to the proper platform code
- errMsg = null;
- platform = DistUtils.getPlatform().toUpperCase();
+ platform = PlatformUtils.getPlatform().toUpperCase();
if (platform.equals("APPLE") == true)
- errMsg = AppleUtils.updateMaxMem(maxMemSize);
+ AppleUtils.updateMaxMem(maxMemSize);
else if (platform.equals("LINUX") == true)
- errMsg = LinuxUtils.updateMaxMem(maxMemSize);
- else if (platform.equals("Windows") == true)
- errMsg = WindowsUtils.updateMaxMem(maxMemSize);
+ LinuxUtils.updateMaxMem(maxMemSize);
+ else if (platform.equals("WINDOWS") == true)
+ WindowsUtils.updateMaxMem(maxMemSize);
else
- errMsg = "Unrecognized platform: " + platform;
+ throw new ErrorDM(null, "Unrecognized platform: " + platform, "Unsupported Platform");
+ }
- // Display the warnPanel with the the error condition
- if (errMsg != null)
- {
- if (platform.equals("APPLE") == true)
- warnPanel.setTitle("Failed setting Apple properties.");
- else if (platform.equals("LINUX") == true)
- warnPanel.setTitle("Failed setting Linux configuration.");
- else if (platform.equals("Windows") == true)
- warnPanel.setTitle("Failed setting Windows configuration.");
- else
- warnPanel.setTitle("Platform is not supported.");
+ /**
+ * Utility method to update the (active) DistMaker distribution to reflect the specified AppRelease.
+ *
+ * Note this will only take effect after the application has been restarted.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateAppRelease(AppRelease aRelease)
+ {
+ String platform;
- warnPanel.setInfo(errMsg);
- warnPanel.setVisible(true);
- return false;
- }
-
- return true;
+ // Delegate to the proper platform code
+ platform = getPlatform().toUpperCase();
+ if (platform.equals("APPLE") == true)
+ AppleUtils.updateAppVersion(aRelease.getVersion());
}
}
diff --git a/src/distMaker/platform/WindowsUtils.java b/src/distMaker/platform/WindowsUtils.java
index b4db704..7bc52dc 100644
--- a/src/distMaker/platform/WindowsUtils.java
+++ b/src/distMaker/platform/WindowsUtils.java
@@ -4,20 +4,87 @@ import java.io.*;
import java.util.ArrayList;
import java.util.List;
-import distMaker.DistUtils;
-import distMaker.MiscUtils;
+import distMaker.*;
+import distMaker.jre.JreVersion;
public class WindowsUtils
{
/**
- * Utility method to update the JRE to reflect the specified path.
+ * Returns the l4j runtime configuration file. If one can not be determined then this method will return null.
*
- * TODO: Complete this comment and method.
+ * If the configuration file is determined but does not exist, then an empty configuration file will be created.
+ *
+ * Note this method looks for a file that ends in .l4j.cfg, or an exe file and creates the corresponding config file.
+ *
+ * If there are multiple .exe or .l4j.cfg files, then this method may grab the wrong file and fail.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static boolean updateJrePath(File aPath)
+ public static File getConfigFile()
{
- int zios_finish;
- return false;
+ File[] fileArr;
+ File installPath;
+ File retFile;
+
+ installPath = DistUtils.getAppPath().getParentFile();
+ fileArr = installPath.listFiles();
+
+ // Attempt to locate the .l4j.ini file
+ retFile = null;
+ for (File aFile : fileArr)
+ {
+ if (aFile.getName().endsWith(".l4j.ini") == true)
+ retFile = aFile;
+ }
+
+ if (retFile == null)
+ {
+ for (File aFile : fileArr)
+ {
+ if (aFile.getName().endsWith(".exe") == true)
+ retFile = new File(aFile.getParentFile(), aFile.getName().substring(0, aFile.getName().length() - 4) + ".l4j.ini");
+ }
+ }
+
+ if (retFile == null)
+ throw new ErrorDM("The config file could not be located.");
+
+ if (retFile.isFile() == false)
+ {
+ try
+ {
+ retFile.createNewFile();
+ }
+ catch(IOException aExp)
+ {
+ throw new ErrorDM(aExp, "A default config file could not be created.");
+ }
+ }
+
+ System.out.println("Windows config file: " + retFile);
+ return retFile;
+ }
+
+ /**
+ * Utility method to update the configuration to reflect the specified JRE version.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion)
+ {
+ // Utilize the system configFile and delegate.
+ updateJreVersion(aJreVersion, getConfigFile());
+ }
+
+ /**
+ * Utility method to update the configuration to reflect the specified JRE version.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateJreVersion(JreVersion aJreVersion, File aConfigFile)
+ {
+ int zzz_incomplete_logic;
+ throw new ErrorDM("The logic is incomplete.");
}
/**
@@ -25,24 +92,39 @@ public class WindowsUtils
*
* Note this method is very brittle, and assumes that there is a single value where the string, -Xmx, is specified in the script. It assumes this string will
* be surrounded by a single space character on each side.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
*/
- public static String updateMaxMem(long numBytes)
+ public static void updateMaxMem(long numBytes)
+ {
+ // Utilize the system configFile and delegate.
+ updateMaxMem(numBytes, getConfigFile());
+ }
+
+ /**
+ * Utility method to update the specified max memory (-Xmx) value in the text file (aFile) to the specified maxMemVal.
+ *
+ * Note this method is very brittle, and assumes that there is a single value where the string, -Xmx, is specified in the script. It assumes this string will
+ * be surrounded by a single space character on each side.
+ *
+ * On failure this method will throw an exception of type ErrorDM.
+ */
+ public static void updateMaxMem(long numBytes, File aConfigFile)
{
- File configFile;
List inputList;
String strLine, updateStr;
boolean isProcessed;
- // Bail if we fail to locate the configFile.
- configFile = getConfigFile();
- if (configFile != null && configFile.isFile() == true)
- return "The config file could not be located.";
+ // Bail if the configFile is not writable
+ aConfigFile = getConfigFile();
+ if (aConfigFile.setWritable(true) == false)
+ throw new ErrorDM("The config file is not writeable: " + aConfigFile);
isProcessed = false;
inputList = new ArrayList<>();
// Process our input
- try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(configFile))))
+ try (BufferedReader br = MiscUtils.openFileAsBufferedReader(aConfigFile))
{
// Read the lines
while (true)
@@ -72,72 +154,11 @@ public class WindowsUtils
}
catch(Exception aExp)
{
- aExp.printStackTrace();
- return "Failed while processing the config file: " + configFile;
+ throw new ErrorDM(aExp, "Failed while processing the config file: " + aConfigFile);
}
// Update the script
- System.out.println("Updating contents of file: " + configFile);
- if (MiscUtils.writeDoc(configFile, inputList) == false)
- return "Failed to write the config file: " + configFile;
-
- // On success return null
- return null;
- }
-
- /**
- * Returns the l4j runtime configuration file. If one can not be determined then this method will return null.
- *
- * If the configuration file is determined but does not exist, then an empty configuration file will be created.
- *
- * Note this method looks for a file that ends in .l4j.cfg, or an exe file and creates the corresponding config file.
- *
- * If there are multiple .exe or .l4j.cfg files, then this method may grab the wrong file and fail.
- */
- public static File getConfigFile()
- {
- File[] fileArr;
- File installPath;
- File retFile;
-
- installPath = DistUtils.getAppPath().getParentFile();
- fileArr = installPath.listFiles();
-
- // Attempt to locate the .l4j.ini file
- retFile = null;
- for (File aFile : fileArr)
- {
- if (aFile.getName().endsWith(".l4j.ini") == true)
- retFile = aFile;
- }
-
- if (retFile == null)
- {
- for (File aFile : fileArr)
- {
- if (aFile.getName().endsWith(".exe") == true)
- retFile = new File(aFile.getParentFile(), aFile.getName().substring(0, aFile.getName().length() - 4) + ".l4j.ini");
- }
- }
-
- if (retFile == null)
- return null;
-
- if (retFile.isFile() == false)
- {
- try
- {
- retFile.createNewFile();
- }
- catch(IOException aExp)
- {
- aExp.printStackTrace();
- return null;
- }
- }
-
- System.out.println("Windows config file: " + retFile);
- return retFile;
+ MiscUtils.writeDoc(aConfigFile, inputList);
}
}
diff --git a/template/appLauncher.jar b/template/appLauncher.jar
index 0419b685c984bc842f284e3f0ecc1362a8a06a3c..d6ef2a801c85b97fd3464552f95c89148844a1b8 100644
GIT binary patch
delta 8650
zcmZ9SWl)?=*R65)L4pkK9wY<}?(P;OI0PRYa^nyP5(al2oZuc@g1fuBOCUh_o~O<^
z^?vW!{iAnRSO4g$uC7&UU9HjZ8#VCQ>Pm=6gm7qRXmFN!uo`SX6o4}gCJ*<&iu9k}
z+{H!N+{4Mj*2;}b#?8&yO56DQyfx**CPhi8`dw&xmM%Y%F?NTE{5c)5B9NxZBUK`b
z3}ez6eS8v#KoVm7{4JkbRXWxC)AwQ4T3q~NS0T}qFOs$~3#$tG!7hzzC151;_;z}V
z+bR*Utnx!>D(CLsyX=#s4!M$V`MA@vq$;R5KqH#dMNXwdM28n*I~M9YCjzn^>O|+6
z=u++CKQ&^WyEpB@#rq;$x(Pmbpyi!l@NsFMd|}OOFs)KBgQ_J(blU9J?nU_O>zc$y
z-Qy_kZ3+2jP42SwucFYQsGn;mBxf7h(xumXC*o>*yC9JT$0uZ2!;-C{~*eaWiW-5@mv`b0H>~m
zik6;plzapa2Um&+_s{zOy`6Z&qPl1lZ#+cvJQx&(gcT?!>iGZZqQ-7US-w5Z-O=0&n+W4UbkoMv=^GkJd=o&{
zlFB-|z77!cg5n@XLw}-ug-7d?W`Fha*+sO>RK%rSRsFd;6Rwa?FWqgo6-hT4+O4Ws
z?%6fSI;{5Jg=vPgYxnTgFpxDZ^N615y45XF)}=mke2pYs`NUXoeXi^~y*5QSGn-7S
z$Ay|CpIE0+J|mn&1bxa$O3qIo(($9sYC4
zh*38h*JO$4k{|^&rc)UhOVUmpJ4s$jWN-?PO8IBbDlJ$Ywot^lReJTzHyIhL#_d$b
zxVtD0q3!_A#)S|SYrbR!LyzvUHC=8CV8Y|m_%YgfeZthZZJ)9n=!*IqehXxl6Msz^
z7JMXKy@nx6Fk}f6_;_$Lg&|~GkW?VqYdx3yTZ0aYLd-k=M-_&Aq2QqSv0Mh(0S_ms
zpD$MnG*i}yw5aICHvh?B;3>aGU;Pn9`ZyZaHWw>hfe4)+x21&=NN6o$tna}9%)Z7k
z3uVJT^MUkR#Mv?pOs(9>0Nx|piVpttDC5u|3CH?%9Ip3}-RUs}UIa`XB9r7N$@$NUI{D|2aCj&Wpncw$+yB0_`$QojZW
z1ti)T1WiCPSl&ky;VzG^B=%A6IOb^y&m4EUcUqjX`$R12o8aXXf}Y1_9AKJ49=sf^
zYs=V*-SVS6JL(+Fp|eZl((K8HP*S!uW%`h>@<=3Jp&??+5^rNT!B9+Kez=Oi66H>w
z`^jus_pNve1I~Bu5`|ymfM6FsU8a97_3v(sP^~flYWpIS)Avc%QRdFur*+j%0>0S=OpSzk
z0{6pHSVgTMQY3qkDW+R9?LD4U6_~|~hnOW--BRNn3NLoK>YKB^?X6z~_lBj@X`=JK
zpUD;W!6cTfZG}Oy1$tobU{IQa*In$`NG!RTL2rFPG;YCNO)T)Nlrd-1nOJ`5%}G&&rcT?Tl*M764=iAMun!VmPjtE
zug5+Mhi2g5>kP2wFc|>(5US0n=ok|fFY}$O`dTm(W2kh3{1(|4FH9PPI0BW6-P7#p
zK`@Ilm`JlaT-~QHmusqkJKck{hn|0b{j+BG&p6&Wn=qw2L;T%$@3m;5sxrdyctRnP
z8o5k|rwLuY2m%Ie`F0wM@e;k7oj!Cm(iqFW1ig@HWe)%oCT2CcQ0SSCgrx5{0CIhB
zQC6mV@6wlBaEtwhG@6_P9+s7ShHgVtsc}tYjK9^1ZjeE-;~#C;$fGLq9Yt(05m~)<
zlDb0FVe)NIXoBsqwJlG;VD3d33wbK!k62W;2*py}FtRlI1YK#2>1fbIw3+1F#Pf@W
z>rrWfk5|CqUb5y6Ztj45K8fuu-PK=dM$#HL%6pznpO_>?xgF2hm7AV@H7sA58gx+M
zwD|c#sx?vexxD_phkeNEEw9}26QO8;lRKxj&f
zf(M{&$hXd-!vojZ%W`ox-ZvN9Ldwx^u6ao{By2Y`nS?;}JwaP&DYY**AFb7ll(sCu
zu(f!hLr#f-ZoGtU4
z4#4B=b%XHURc7gex@EzRWNKrcEI7J^?7$yyjI~pX4-;avr0h)tHNG%b6d%exbhi(yL!4-Aq#yM-auC(ajJ=NM#Sgfp
zvERu~%d2-`i{XBusHm*`Ncve5Et;^K4O=W{BjTAx;T>JWClWmAmaW&yxG_>&B1i(L
zNb38@PEkU!I$73&ypZ||#3_OL=p8_w%sKOj;QH!Rr~9*_jwG4WezK+CK%L9+@*sMf
zB~Vq8$Zk9=fgtn&h1pwfdfJCh#4;%@gg0vnHtK%5Qmp9q6)bTa3Fcj5@3XbAm(D(I
zXI0Q{Dqq%|&0+kqgZ>Oxe8Sh^O(;v;6=g1TWs6!nvG2jFPxN(Kfh8R7%@v@mmJuHu
zQ2=IloPaD(p%WE?QVd<$4|_6i)w@=ud6sp|8r-ydj)J{Du7j~$pGiU&_FOFapd$gR
zW5Rr~`TQeXKNRT;QRJw`Qic}-l`ya`*n6%;e<^9^j>r|uL1b%Mi?|sS5B6qPY)ZmA
zsy~M7u0UzQtXF1C1oB>uSwOAuk>}e
zTICzckPn<2xa|S8S_f*}{PMc!F-NLd&QzN8u@1U2o8?oJ`{QZ-e*n@$_LUM#+5{1u
z5IUP5-^rB6KRw$O8t{j!6%`&z{79pGK}X?8I=mL*uJk;9CT$}OmKM5zx*3HMb`s#*
z$=lo_b6hOVs%BVc7%8yxdS1b#@v@y|XZedYNkh%DtEbJsr;8i%j7if>r5_LP9jh{p
z8fOKR*clEOIkwRI&H;g!f&TZ}Xb>iZUZ<~JZa+ac9!DRq$Kr6wj5(>=6vtv({#LQ`
zu)@ylN58zy`~mZD6UdV2qD^>JVf*GVICW(}aAGOFq^Zt_lU+5VZBuihh`IP|Uu^zl9ZWCU}RO|0c1cRwn#e)9zn
zd)V%BVqLLkBoE?jZt8LvxIt#IMMNNFXBXpcTZ79bj2{9|)&gFQ^p9i`kz+o
zcmrUl1;&ipk%({Bx*U)%D}=Lntqz(f{OvBve?w48Y%3}-E!z?JE+}JcRnh3Z
zYKlvERDGbHI)wr-o7$wO-}!q-ohPhm_a{EX
zpsYuWZy@&r4w*NcfsH!)$8LUNBt)0%9`vh|#Z*7scyxi9zf#;omk>`Tq3?XzAn|nbt
zWLPOEifh>Brn$F~zwuNsAD&Gq@*{BL@tN=VLL0ij3nMPq@x9HZS*N0mhc2MjTkx@Y
zZ2=YpLcZNrMRFn$%y5#Xx}l8W2n-VmoTFd%H2kjk*vECEq1O(7TjG{Q_;z*Kqy|g+
zsO*ybBymCm%Y#$x!6H7ZVs}WYVBKO$?O|w)`UYMAzp*ZuSyR%fg9Y%X!
zve2?RTa@#+kE8JETocZ?neD<}+kQ(RWPseJEQVl?$!q_en1Ho~(^gmwp<-MjMgGp_
zlPTc_KsMOKyEUfzW(WCks}!je=Z!^U$8D44-z)JT1yk@yX%IGTIgKLA;)B+bEum&Y
zQzar(tDDi6?6$j<;I9Mvj~ju{ox0br$5h{6O*h(}f4p|?MpUwe<^6|x)J_*n`7eg3eS9p@iQ;mx#cxCo9G;L4)RW(VaqlU-`6|hxO=X%N%k$P=yDHw
zlnxM|`M9_g_dJOk)fS|vrwMlPd<$?x5UcCJ-nQ^eJL13i)&kSWgJPcSsMPOOTa(UZ
zotksRluV1^eY4MVNe-Jf4{=s~nSKUc89_*1(r{c^E*mq?cN8EW7A6sm<#G@SfFOa%
zkoK64Uu{R`>q@5ECr26`Ig|$xhv;8|J9;LjpwB`9F&nS~&a93tjLDg*7T}g+fy{Sc
zPI01@;1;Y$6|m5d({Ugqz2POQFwv3?JNWoxCc3KWD8ec0%P!7j+?x9l(>ecbR9ljX
z2h*X=x!9i;zYi}x0m3iSFFt>go?QPlJTdsLy)eEI8~({Ufc*J6EZbDzkMORXLfRfe
z(1M(lUz{$>?WWOUgTq=SItiTk{~j4Vm6SC&6dQ1=u-88Li^8*GVwu?dv~#*gUO9UA<~>AR1>nu48M`#5mm6DgNu3Z`c|#9)2V?w1Co<+8
zZIDAWR0z=y=weQsg8xXJhgCCSqr&SXC_YjPZ`!SdcQx~k#zeRK0E6`x9cK-@3KiRE?qR+TD>)dRh~DSLwb%1O^dXZx&SSIv^)ntURm%$8L269
zXQ$QWZtuOZyXQG#s&IT~BhkVW
z?0sBbmI?{Oa~Zo+FI*j}yg^|wSAqkj6f;GkMAt9fvMtjasv^{FsK4eP+J(;xr|i2^
zJIr2R*ZcqOpaD}L=2_Lv&|hEBtj%yj_ff_qufQ?cpDOQ3dz$KhpyGK^k-$y;Sg*wva@p?KoC$A$e14P=RK)
zBwO>xF0{*^DRc+|ldufo#o+Ml!j-HkDS6O>3zLeA!cBY#wTTL^>?Ftghg`VsZj=PW
z_i^N5Y`~x$36U`eFm8`7=AnHHAZw3QNU4Tj`APRh^agv2Jp*?F|sdi
zg431TP|S-W-5U_5ZPrk})C$VyyMXJ%=QHt1l|D~}Q(u)Y-dl#mrDmDBQ|(vHA7jMk
zEqun#dvAUFy=HKk^*z3B9{%OxoGY@ab;R1?5s>}~N|ed^?qi0-RBy>!?vb>sF|^+W
z_*-kizli+%@l07J$}F297u%`Wt#6A@dJ}REBW(|{zA35?wXkjSn^YXMBl$f|S--GwrK-MyKHR1bxkD`H
zl#^)4=or?U6nM0_B{NlUz8O?6JKfUEodPWXB6%-H`~BGG@W*yOP;*WA5YxGEEzuF%
z*|Tti`-t)-$ywWbwiM&nLM;P+=Rgy)$o2I|*_vmb@u8+;eTH>GV50RZVio7Ekxm*P
zoN`Od2TV)f80)JuO~xzYOzl(zij@LYZ=E1_gqzSi*UF)MDvwf$KfM(bhpVvnzLnQG1})FGG?aJ1Kf{x2F=8r@#h;)d~^7sEj8
z4JQ2+{Pd%0dXHDPK5Ei7YzgVXsi>JneUawQD&&z;aOC%2qJK@;#^j1uZX!6iDVDlT
z`nO=fS@*v!d#eA>viE@4edbcJbGOj>r}@9ez22iAxg6=2!Q}dc_A1aGguEEX4zyHT
zkXf{-U`VAmzHJL5^G#(J0K4N1`Y-O)KjOVYxDXHFI0(?oOhRbo>D@!~EHI#A=fRr(6<;SORW
zOSO>5>KZ`~YQF-V@N%hHOBaryC?$BJuz*hX9qC-{QfB3nEGPL~g5mnS3Y((nnyfA_
zQ|oFPbutZgMiQ%To`m?2Ei7qzTB1UtAh-dO*+osumCXrMQs;iPn(B0KOFRY`kX9im
zb^Y*YBSxFW@`8$JHG!oh=L%C8+|vBJ=lx56`=#36vKl=i@+ZqS~9{u<94X|~3ZIWR)oz8>4X*E(TQxp_)d
z+oe4Ul>j|*J5Ty2w=I5FA1Hp8Nf-k+%)qEy5v_Nxv{1RfYk0^yLi)1?1>MMopHe1wX!%O1Yd7G
zyhbRa_)oj6(Nxp?{m>J%-SNHWEtBvu>SvBC`J800;gDb&_=af|WEFM}1<}fpTW*7|
zZu`sO8Utyn~ZSyIO8+Uc2td!qZS&Tl%VKs}qz%JAs+X8WSK#RmDR#}RzO4Al+x
zjVUqZ$wwh(v?MMG;-{JAR(Hl6!*0b$1W7kp9&ZL&kflis+slCMG4E*cc#p37N*bwS
z15gzVwkJ_~SjoB`KG@t1!RA)|9yEVUo{Yd-y6tM9@ZvenA3I1Htb%}jx8NlDeUW;uN}Jn?c8Ly2
zC2wp18;SutaH>3>F4SOqN~qBDc&pIxxvovTD@KWcdBkdTo_CL|8A$}07vaR&3Bi3>
zac@9z{cM==cMN%GYfF?|xy)B8u4;LH0Rg4yA7#zq&cavOYQjOQMx`*OqBO)mUxUY=
zTHQ+@SvdjpO&Nc+ZZDD!TSj)C-0i1kRk+rQzVcz21H9j3N%kMUbe&Q#zo$$Idgb|2
zu_;GHq7hafmGkSH`n4Nt*4>WHZThZk0-eAsLZ{~Ch~{vNT;*GYZ|lIMLZy^pvfk;B
zEAr%h3{^jDjx39?#gE&PgWK;l`zqd&EM@o9)mL3F3MsS
zFNQDzq@+0T2enAX0Ch_%0IAUUJ=gv_o
zq@eDAIQIOGXBlShQn!4=HWn`QcJ%?Q<6CKYJ+~kM^DOTXq0?kgVD(A=xsy=md3F
zW90a~!;7N~WXx=XPb9YV%j%%bBqu~zANLxNn>M>-k|i$sXhh*~o_m^=xGw!{u2`?x
znk2lk=(P2>a&6Ece4Y?)9oHGP-VkFA*$F)30@n2)kXe+YMoZp(eHqF`uf$3q^%Wb
zH6W$3qG=(9cI=
z@WdUY!|`GzRi%@%rE$xViQSBSf3*v|2ijM!WPgTes2r67^*wh{h9{Wzqo?pXh
zQGue?!v@>a>r3;gM+tCDx$+D;;+c;J5#i$R^inwV@n{Cw@v5dgw~$_JM(Ux(`7w~M9!Hk!JH|5-31QXkW_g}6bJ!HdjjX@n>KMff(KCti19Uv_UqRCD(
zq%p%A58N=IWK$#7VnJjiNu>1jqaJjh1^ovlUJ{goqwgc!>ptv#E)-#-t!&_Z5Ci~W
z(`qLV=74X5BIB7tW8Z{(m0sA-_snlRiZOvr*32oYtX0qhv|wlA75QIvLw%m}9F7JD
zXO9O(mb6Cvhf9DuNhaX`%O(8p^qCZjF9jC-FB(Bz2_AtE?*HHn|3uRA!pJMmW9V>j
zeFSiD;Qxg*z`<3aL8+w_5C#aK4pNE;bA-@t0%quj6c+VA0k-^qX$p*7ON2wx=sX>}3a$^A1?LM>$og#XoL|G5nu+`qc)
zKh>}F-(bamskDF2b2FxD!@;STJK0%VxkET1-Vk*qWR!o;WB)Uj{t4hO|JjFw`yX-s
BV(kC`
delta 3778
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