Files
DistMaker/src/distMaker/DistMakerEngine.java

1045 lines
33 KiB
Java

package distMaker;
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.StandardCopyOption;
import java.security.MessageDigest;
import java.util.*;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.google.common.base.Joiner;
import distMaker.digest.Digest;
import distMaker.digest.DigestUtils;
import distMaker.gui.PickReleasePanel;
import distMaker.jre.*;
import distMaker.node.*;
import distMaker.platform.PlatformUtils;
import glum.gui.panel.generic.MessagePanel;
import glum.gui.panel.generic.PromptPanel;
import glum.gui.panel.task.FullTaskPanel;
import glum.io.IoUtil;
import glum.net.Credential;
import glum.task.*;
import glum.unit.DateUnit;
import glum.util.ThreadUtil;
public class DistMakerEngine
{
// Constants
private final String NonDistmakerAppMsg = "This application does not appear to be a properly configured DistMaker application.\n\n" + "Please check installation configuration.";
// State vars
private URL updateSiteUrl;
private AppRelease currRelease;
private Credential refCredential;
// Gui vars
private JFrame parentFrame;
private MessagePanel msgPanel;
private PromptPanel promptPanel;
private PickReleasePanel pickVersionPanel;
public DistMakerEngine(JFrame aParentFrame, URL aUpdateSiteUrl)
{
updateSiteUrl = aUpdateSiteUrl;
currRelease = null;
refCredential = null;
parentFrame = aParentFrame;
msgPanel = new MessagePanel(parentFrame, "Untitled", 700, 400);
promptPanel = new PromptPanel(parentFrame, "Untitled", 500, 300);
initialize();
}
/**
* Method that will notify the user that updates are being checked for
*/
public void checkForUpdates(UpdateCheckListener listener)
{
FullTaskPanel taskPanel;
File installPath;
String appName, infoMsg;
// Bail if we do not have a valid release
if (currRelease == null)
{
if (DistUtils.isDevelopersEnvironment() == true)
displayNotice("Updates are not possible in a developer environment.");
else
displayNotice(NonDistmakerAppMsg);
return;
}
appName = currRelease.getName();
// Determine the installation path and ensure the entire tree is writable
installPath = DistUtils.getAppPath().getParentFile();
if (DistUtils.isFullyWriteable(installPath) == false)
{
infoMsg = "The install tree, " + installPath + ", is not completely writable.\n";
infoMsg += "Please run as the proper user or ensure you are running via writeable media.";
displayNotice(infoMsg);
return;
}
// Setup our TaskPanel
taskPanel = new FullTaskPanel(parentFrame, true, false);
taskPanel.setTitle(appName + ": Checking for updates...");
// taskPanel.setSize(680, taskPanel.getPreferredSize().height);
taskPanel.setSize(680, 400);
taskPanel.setTabSize(2);
taskPanel.setVisible(true);
// Launch the actual checking of updates in a separate worker thread
Runnable tmpRunnable = () -> checkForUpdatesWorker(taskPanel, listener);
ThreadUtil.launchRunnable(tmpRunnable, "thread-checkForUpdates");
}
/**
* Returns the currently running release of this software package.
* <P>
* Note this method may return null if we are:
* <LI>running from a developers environment (Ex: Eclipse IDE)
* <LI>If the software application was not properly packaged (or has become corrupt) with DistMaker.
*/
public AppRelease getCurrentRelease()
{
return currRelease;
}
/**
* Returns the URL where software updates for this application are retrieved from.
*/
public URL getUpdateSite()
{
return updateSiteUrl;
}
/**
* returns
*
* @return
*/
public UpdateStatus isUpToDate()
{
LoggingTask task = new LoggingTask();
String appName = currRelease.getName();
List<AppRelease> unsortedReleaseList = DistUtils.getAvailableAppReleases(task, updateSiteUrl, appName, refCredential);
if (unsortedReleaseList == null)
{
// The update check failed, so return a status of false with a message about the problem
String msg = Joiner.on("; ").join(task.getMessages());
return new UpdateStatus(msg);
}
// Sort the items, and isolate the newest item
LinkedList<AppRelease> fullList = new LinkedList<>(unsortedReleaseList);
Collections.sort(fullList);
AppRelease newestRelease = fullList.removeLast();
// The check succeeded, so return whether or not the app is up to date.
return new UpdateStatus(newestRelease.equals(currRelease));
}
/**
* Sets in the credentials used to access the update site. If either argument is null, then the credentials will be
* cleared out.
*/
public void setCredentials(String aUsername, char[] aPassword)
{
refCredential = null;
if (aUsername == null || aPassword == null)
return;
refCredential = new Credential(aUsername, aPassword);
}
/**
* Helper method to fully set up this object
*/
private void initialize()
{
File appPath, cfgFile;
DateUnit dateUnit;
String currInstr, strLine;
String appName, verName, buildStr;
long buildTime;
currRelease = null;
appName = null;
verName = null;
buildStr = null;
// Locate the official DistMaker configuration file associated with this release
appPath = DistUtils.getAppPath();
cfgFile = new File(appPath, "app.cfg");
// Bail if there is no configuration file
if (cfgFile.isFile() == false)
{
// Alert the user to the incongruence if this is not a developer's build
if (DistUtils.isDevelopersEnvironment() == false)
displayNotice(NonDistmakerAppMsg);
return;
}
// Read in the configuration file
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfgFile))))
{
// Read the lines
currInstr = "None";
while (true)
{
strLine = br.readLine();
// Bail once we get to the end of the file
if (strLine == null)
break;
// Skip empty lines / comments
if (strLine.isEmpty() == true || strLine.startsWith("#") == true)
; // Nothing to do
// Record the (current) instruction
else if (strLine.startsWith("-") == true)
currInstr = strLine;
// Process the instruction
else if (currInstr.equals("-name") == true)
appName = strLine;
else if (currInstr.equals("-version") == true)
verName = strLine;
else if (currInstr.equals("-buildDate") == true)
buildStr = strLine;
}
}
catch(IOException aExp)
{
aExp.printStackTrace();
}
if (appName == null || verName == null)
{
displayNotice(NonDistmakerAppMsg);
System.err.println("Failed to properly parse DistMaker config file: " + cfgFile);
return;
}
// Form the installed Release
dateUnit = new DateUnit("", "yyyyMMMdd HH:mm:ss");
buildTime = 0;
if (buildStr != null)
buildTime = dateUnit.parseString(buildStr, 0);
currRelease = new AppRelease(appName, verName, buildTime);
// Form the PickReleasePanel
pickVersionPanel = new PickReleasePanel(parentFrame, currRelease);
pickVersionPanel.setSize(550, 500);
// Notify the user of (any) update results
showUpdateResults();
}
/**
* Helper method that does the heavy lifting of the checking for updates.
* <P>
* This method will be called via reflection.
*/
private void checkForUpdatesWorker(FullTaskPanel aTask, UpdateCheckListener listener)
{
List<AppRelease> fullList;
AppRelease chosenItem;
File installPath, deltaPath;
String appName;
boolean isPass;
// Determine the path to download updates
installPath = DistUtils.getAppPath().getParentFile();
deltaPath = new File(installPath, "delta");
// Status info
appName = currRelease.getName();
aTask.infoAppendln("Application: " + appName + " - " + currRelease.getVersion());
// Retrieve the list of available releases
aTask.infoAppendln("Checking for updates...\n");
fullList = DistUtils.getAvailableAppReleases(aTask, updateSiteUrl, appName, refCredential);
if (fullList == null)
{
aTask.abort();
return;
}
// a successful test has been done, so notify the listener
listener.checkForNewVersionsPerformed();
// In case there is only the current version, don't show the update selection panel.
// Just show a short message that everything is up to date, and abort.
// (This check used to be in the getAvailableReleases() call above, but I needed
// that to not throw an error for the case of only one release, so I moved that
// check here.)
if (fullList.size() == 1)
{
if (fullList.get(0).equals(currRelease))
{
// There is only one release out there, and its the same
// as the one being run, so there is nothing to update.
String msg = "There are no updates of " + appName + ". Only one release has been made.";
aTask.infoAppendln(msg);
aTask.abort();
return;
}
}
// Hide the taskPanel
aTask.setVisible(false);
// Prompt the user for the Release
aTask.infoAppendln("Please select the release to install...");
try
{
Runnable tmpRunnable = () -> queryUserForInput(aTask, deltaPath, fullList);
SwingUtilities.invokeAndWait(tmpRunnable);
}
catch(Exception aExp)
{
aExp.printStackTrace();
}
// Bail if the task has been aborted
if (aTask.isActive() == false)
return;
// Retrieve the chosen item
chosenItem = pickVersionPanel.getChosenItem();
if (chosenItem == null)
return;
// Reshow the taskPanel
aTask.setVisible(true);
// Log the user chosen action
aTask.infoAppendln("\tRelease chosen: " + chosenItem.getVersion());
if (currRelease.getBuildTime() < chosenItem.getBuildTime())
aTask.infoAppendln("\t" + appName + " will be updated...");
else
aTask.infoAppendln("\t" + appName + " will be reverted...");
// Form the destination path
isPass = deltaPath.mkdirs();
if (isPass == false || aTask.isActive() == false)
{
aTask.infoAppendln("Failed to create delta path: " + deltaPath);
aTask.infoAppendln("Application update aborted.");
aTask.abort();
return;
}
// Download the release
try
{
isPass = downloadAppRelease(aTask, chosenItem, deltaPath);
}
catch(Throwable aThrowable)
{
IoUtil.deleteDirectory(deltaPath);
aTask.infoAppendln("An error occurred while trying to perform an update.");
aTask.infoAppendln("Application update aborted.");
aTask.infoAppendln("\nStackTrace:\n" + ThreadUtil.getStackTraceClassic(aThrowable));
aTask.abort();
return;
}
if (isPass == false || aTask.isActive() == false)
{
IoUtil.deleteDirectory(deltaPath);
aTask.infoAppendln("Application update aborted.");
aTask.abort();
return;
}
// Notify the user of success
aTask.infoAppendln(appName + " has been updated to version: " + chosenItem.getVersion() + ".");
aTask.infoAppendln("These updates will become active when " + appName + " is restarted.");
aTask.setProgress(1.0);
}
/**
* Helper method to show an informative message on msgPanel.
*/
private void displayNotice(String aMsg)
{
Runnable silentRunnable = () ->
{
; // Nothing to do
};
// Delegate to displayNoticeAndExecute
displayNoticeAndExecute(aMsg, silentRunnable, false);
}
/**
* Helper method to show an informative message on msgPanel and execute the specified runnable.
* <P>
* If isModal == true then aRunnable will only be executed after the msgPanel has been accepted.
* <P>
* The runnable will not be run until the parentFrame is visible.
*/
private void displayNoticeAndExecute(final String aMsg, final Runnable aRunnable, final boolean isModal)
{
// Transform tabs to 3 spaces
final String infoMsg = aMsg.replace("\t", " ");
// If the parentFrame is not visible then execute the code once it is made visible
if (parentFrame.isVisible() == false)
{
parentFrame.addComponentListener(new ComponentAdapter()
{
@Override
public void componentShown(ComponentEvent aEvent)
{
// Show the message panel and wait for user to close panel
msgPanel.setTitle("Application Updater");
msgPanel.setInfo(infoMsg);
if (isModal == true)
msgPanel.setVisibleAsModal();
else
msgPanel.setVisible(true);
// Execute aRunnable's logic
aRunnable.run();
// Deregister for events after the parentFrame is made visible
parentFrame.removeComponentListener(this);
}
});
return;
}
// Show the message panel and wait for user to close panel
msgPanel.setTitle("Application Updater");
msgPanel.setInfo(infoMsg);
if (isModal == true)
msgPanel.setVisibleAsModal();
else
msgPanel.setVisible(true);
// Execute aRunnable's logic
aRunnable.run();
}
/**
* Helper method to download the specified release.
* <P>
* Returns true if the release was downloaded properly.
*/
private boolean downloadAppRelease(Task aTask, AppRelease aRelease, File destPath)
{
AppCatalog staleCat, updateCat;
Node staleNode, updateNode;
URL catUrl, staleUrl, updateUrl;
File catalogFile;
Task mainTask, tmpTask;
double progressVal;
long tmpFileLen;
try
{
staleUrl = DistUtils.getAppPath().toURI().toURL();
updateUrl = IoUtil.createURL(updateSiteUrl.toString() + "/" + aRelease.getName() + "/" + aRelease.getVersion() + "/delta");
}
catch(MalformedURLException aExp)
{
aTask.infoAppendln(ThreadUtil.getStackTrace(aExp));
aExp.printStackTrace();
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
updateCat = DistUtils.readAppCatalog(aTask, catalogFile, updateUrl);
if (updateCat == null)
return false;
// Determine the total number of bytes to be transferred and set up the mainTask
long releaseSizeFull = 0L, releaseSizeCurr = 0L;
for (Node aNode : updateCat.getAllNodesList())
{
if (aNode instanceof FileNode)
releaseSizeFull += ((FileNode)aNode).getFileLen();
}
// 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 compatible for this release
JreRelease targJre = null;
JreVersion currJreVer = DistUtils.getJreVersion();
if (updateCat.isJreVersionCompatible(currJreVer) == false)
{
// Bail if we failed to download a compatible JRE
targJre = downloadJreUpdate(mainTask, updateCat, destPath, releaseSizeFull);
if (targJre == null)
return false;
// Update the progress to reflect the downloaded / updated JRE
releaseSizeCurr += targJre.getFileLen();
releaseSizeFull += targJre.getFileLen();
progressVal = releaseSizeCurr / (releaseSizeFull + 0.00);
mainTask.setProgress(progressVal);
}
// 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;
updateNode = aNode;
staleNode = staleCat.getNode(updateNode.getFileName());
tmpFileLen = 0L;
if (updateNode instanceof FileNode)
tmpFileLen = ((FileNode)updateNode).getFileLen();
tmpTask = new PartialTask(mainTask, mainTask.getProgress(), tmpFileLen / (releaseSizeFull + 0.00));
// Attempt to use the local copy
isPass = false;
if (staleNode != null && updateNode.areContentsEqual(staleNode) == true)
{
// 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 failure is recoverable and this serves just as an optimization)
// isPass = staleNode.transferContentTo(tmpTask, refCredential, destPath);
isPass = staleNode.transferContentTo(new SilentTask(), refCredential, appNewPath);
if (isPass == true)
mainTask.infoAppendln("\t(L) " + staleNode.getFileName());
}
// 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, appNewPath);
if (isPass == true)
mainTask.infoAppendln("\t(R) " + updateNode.getFileName());
}
// Log the failure and bail
if (isPass == false && mainTask.isActive() == true)
{
mainTask.infoAppendln("Failed to download from update site.");
mainTask.infoAppendln("\tSite: " + updateUrl);
mainTask.infoAppendln("\tFile: " + updateNode.getFileName());
mainTask.infoAppendln("\tDest: " + appNewPath);
return false;
}
// Update the progress
releaseSizeCurr += tmpFileLen;
progressVal = releaseSizeCurr / (releaseSizeFull + 0.00);
mainTask.setProgress(progressVal);
}
mainTask.infoAppendln("Finished downloading release.\n");
// Update the platform configuration files
try
{
PlatformUtils.updateAppRelease(aRelease);
}
catch(ErrorDM aExp)
{
aTask.infoAppendln("Failed updating application configuration.");
MiscUtils.printErrorDM(aTask, aExp, 1);
return false;
}
// 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("reboot,trash," + JreUtils.getExpandJrePath(targJreVer) + "\n");
tmpFW.write("exit\n\n");
tmpFW.write("# Define the pass section (clean up for success)\n");
tmpFW.write("sect,pass\n");
tmpFW.write("trash," + JreUtils.getExpandJrePath(currJreVer) + "\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, JreUtils.getExpandJrePath(targJre.getVersion()));
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());
MiscUtils.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.
* <P>
* On success the JreVersion that was downloaded is returned.
*/
private JreRelease downloadJreUpdate(Task aTask, AppCatalog aUpdateCat, File aDestPath, long releaseSizeFull)
{
List<JreRelease> 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("\tMinimum JRE: " + aUpdateCat.getMinJreVersion().getLabel());
JreVersion tmpJreVer = aUpdateCat.getMaxJreVersion();
if (tmpJreVer != null)
aTask.infoAppendln("\tMaximum 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 AppLauncher if required
AppLauncherRelease pickAppLauncher = null;
if (AppLauncherUtils.isAppLauncherUpdateNeeded(aTask, pickJre) == true)
{
pickAppLauncher = AppLauncherUtils.updateAppLauncher(aTask, pickJre, aDestPath, updateSiteUrl, refCredential);
if (pickAppLauncher == null)
return null;
}
// 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\tReceived " + 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, JreUtils.getExpandJrePath(pickJreVer));
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. Ensure that the unpacked JRE results in 1 top level 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);
String errMsg = ThreadUtil.getStackTrace(aExp);
aTask.infoAppend("\nStack Trace:\n" + errMsg);
return null;
}
return pickJre;
}
/**
* 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:
* <UL>
* <LI>Removal of any downloaded and installed JRE
* <LI>Removing the delta directory
* <LI>Removing the delta.cfg file
* </UL>
* <P>
* 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 void revertUpdate(Task aTask)
{
// Revert our application's configuration (which will be loaded when it is restarted) to reflect the proper JRE
try
{
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.");
MiscUtils.printErrorDM(aTask, aExp, 1);
}
// 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.");
MiscUtils.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 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);
// Bail if we reach the exit command
if (strArr.length == 1 && cmdStr.equals("exit") == true)
break;
// We are interested only in trash (or reboot,trash) commands. Execute the individual trash (or
// reboot,trash) commands. It is safe to execute reboot,trash commands now since the actual update is not
// running yet.
String delTargStr = null;
if (inputStr.startsWith("trash,") == true)
delTargStr = inputStr.substring(6);
else if (inputStr.startsWith("reboot,trash,") == true)
delTargStr = inputStr.substring(13);
else
continue;
// Ensure we are not looking at a "hollow" trash command
if (delTargStr.isEmpty() == true)
throw new ErrorDM("File (" + deltaCmdFile + ") has invalid input: " + inputStr);
// Resolve the argument to the corresponding file and ensure it is relative to our rootPath
File trashFile = new File(rootPath, delTargStr).getCanonicalFile();
if (MiscUtils.getRelativePath(rootPath, trashFile) == null)
throw new ErrorDM("File (" + trashFile + ") is not relative to folder: " + rootPath);
if (trashFile.isFile() == true)
{
if (trashFile.delete() == false)
throw new ErrorDM("Failed to delete file: " + trashFile);
}
else if (trashFile.isDirectory() == true)
{
if (IoUtil.deleteDirectory(trashFile) == false)
throw new ErrorDM("Failed to delete folder: " + trashFile);
}
else
{
throw new ErrorDM("File type is not recognized: " + trashFile);
}
}
}
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);
}
/**
* Helper method that prompts the user for forms of input depending on the state of the App
* <P>
* This method will be called via reflection.
*/
private void queryUserForInput(Task aTask, File aDeltaPath, List<AppRelease> aFullList)
{
AppRelease chosenItem;
// Query the user, if the wish to destroy the old update
if (aDeltaPath.isDirectory() == true)
{
promptPanel.setTitle("Overwrite recent update?");
promptPanel.setInfo("An update has already been downloaded... If you proceed this update will be removed. Proceed?");
promptPanel.setVisibleAsModal();
if (promptPanel.isAccepted() == false)
{
aTask.infoAppendln("Previous update will not be overwritten.");
aTask.abort();
return;
}
// Revert the update
revertUpdate(aTask);
}
// Query the user of the version to update to
pickVersionPanel.setConfiguration(aFullList);
pickVersionPanel.setVisibleAsModal();
chosenItem = pickVersionPanel.getChosenItem();
if (chosenItem == null)
{
aTask.infoAppendln("No release specified. Update has been aborted.");
aTask.abort();
return;
}
}
/**
* Notification that the corresponding application has been fully initialized. This helper method will notify the
* user on the status of any update.
*/
private void showUpdateResults()
{
String appName, infoMsg;
int updateCode;
updateCode = DistUtils.getUpdateCode();
appName = currRelease.getName();
// No update
if (updateCode == 0)
{
return;
}
// Update passed
else if (updateCode == 1)
{
infoMsg = "The application, " + currRelease.getName() + ", has been updated to ";
infoMsg += "version: " + currRelease.getVersion();
}
// Update failed
else
{
infoMsg = "There was an issue while updating the " + appName + " application.\n";
infoMsg += "The application, " + appName + ", is currently at version: " + currRelease.getVersion() + "\n\n";
infoMsg += DistUtils.getUpdateMsg();
}
// Setup the runnable that will clean up our delta folder
Runnable cleanDeltaRunnable = new Runnable()
{
@Override
public void run()
{
// Remove the delta folder (if it exists)
File deltaPath = new File(DistUtils.getAppPath().getParentFile(), "delta");
if (deltaPath.isDirectory() == false)
return;
if (IoUtil.deleteDirectory(deltaPath) == false)
System.err.println("Failed to remove delta path. Cleanup after update was not fully completed.");
}
};
// Show the message panel and execute cleanDeltaRunnable
displayNoticeAndExecute(infoMsg, cleanDeltaRunnable, true);
}
}