diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 7341ab1..a698e59 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,11 +1,12 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/lib/commons-compress-1.10-sources.jar b/lib/commons-compress-1.10-sources.jar deleted file mode 100644 index fd755c5..0000000 Binary files a/lib/commons-compress-1.10-sources.jar and /dev/null differ diff --git a/lib/commons-compress-1.10.jar b/lib/commons-compress-1.10.jar deleted file mode 100644 index 75ced20..0000000 Binary files a/lib/commons-compress-1.10.jar and /dev/null differ diff --git a/lib/commons-compress-1.15-sources.jar b/lib/commons-compress-1.15-sources.jar new file mode 100644 index 0000000..e52e768 Binary files /dev/null and b/lib/commons-compress-1.15-sources.jar differ diff --git a/lib/commons-compress-1.15.jar b/lib/commons-compress-1.15.jar new file mode 100644 index 0000000..72b5a1c Binary files /dev/null and b/lib/commons-compress-1.15.jar differ diff --git a/lib/glum-src.jar b/lib/glum-src.jar index 2849c44..8c17295 100644 Binary files a/lib/glum-src.jar and b/lib/glum-src.jar differ diff --git a/lib/glum.jar b/lib/glum.jar index 3655548..afc00be 100644 Binary files a/lib/glum.jar and b/lib/glum.jar differ diff --git a/lib/miglayout-3.7.2.jar b/lib/miglayout-3.7.2.jar deleted file mode 100644 index f57a3df..0000000 Binary files a/lib/miglayout-3.7.2.jar and /dev/null differ diff --git a/src/distMaker/DistApp.java b/src/distMaker/DistApp.java new file mode 100644 index 0000000..5e36544 --- /dev/null +++ b/src/distMaker/DistApp.java @@ -0,0 +1,25 @@ +package distMaker; + +import distMaker.utils.PlainVersion; +import distMaker.utils.Version; + +/** + * Provides main entry point. + *

+ * Currently this will just print the library name and the version. This is used during the build process for making + * Distmaker releases. + */ +public class DistApp +{ + /** The DistMaker version is defined here. */ + public static final Version version = new PlainVersion(0, 48, 0); + + /** + * Main entry point that will print out the version of DistMaker to stdout. + */ + public static void main(String[] aArgArr) + { + System.out.println("DistMaker " + DistApp.version); + } + +} diff --git a/src/distMaker/DistMakerEngine.java b/src/distMaker/DistMakerEngine.java index 7aaec1d..72d91fa 100644 --- a/src/distMaker/DistMakerEngine.java +++ b/src/distMaker/DistMakerEngine.java @@ -1,21 +1,12 @@ package distMaker; -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.reflect.FunctionRunnable; -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.*; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.util.*; @@ -30,12 +21,19 @@ 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."; + 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; @@ -100,7 +98,8 @@ public class DistMakerEngine taskPanel.setVisible(true); // Launch the actual checking of updates in a separate worker thread - ThreadUtil.launchRunnable(new FunctionRunnable(this, "checkForUpdatesWorker", taskPanel, listener), "thread-checkForUpdates"); + Runnable tmpRunnable = () -> checkForUpdatesWorker(taskPanel, listener); + ThreadUtil.launchRunnable(tmpRunnable, "thread-checkForUpdates"); } /** @@ -150,7 +149,8 @@ public class DistMakerEngine } /** - * Sets in the credentials used to access the update site. If either argument is null, then the credentials will be cleared out. + * 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) { @@ -243,7 +243,7 @@ public class DistMakerEngine // Form the PickReleasePanel pickVersionPanel = new PickReleasePanel(parentFrame, currRelease); - pickVersionPanel.setSize(550, 500); // 320, 350); + pickVersionPanel.setSize(550, 500); // Notify the user of (any) update results showUpdateResults(); @@ -254,7 +254,6 @@ public class DistMakerEngine *

* This method will be called via reflection. */ - @SuppressWarnings("unused") private void checkForUpdatesWorker(FullTaskPanel aTask, UpdateCheckListener listener) { List fullList; @@ -308,10 +307,8 @@ public class DistMakerEngine aTask.infoAppendln("Please select the release to install..."); try { - FunctionRunnable aFuncRunnable; - - aFuncRunnable = new FunctionRunnable(this, "queryUserForInput", aTask, deltaPath, fullList); - SwingUtilities.invokeAndWait(aFuncRunnable); + Runnable tmpRunnable = () -> queryUserForInput(aTask, deltaPath, fullList); + SwingUtilities.invokeAndWait(tmpRunnable); } catch(Exception aExp) { @@ -348,7 +345,19 @@ public class DistMakerEngine } // Download the release - isPass = downloadAppRelease(aTask, chosenItem, deltaPath); + 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); @@ -368,12 +377,9 @@ public class DistMakerEngine */ private void displayNotice(String aMsg) { - Runnable silentRunnable = new Runnable() { - @Override - public void run() - { - ; // Nothing to do - } + Runnable silentRunnable = () -> + { + ; // Nothing to do }; // Delegate to displayNoticeAndExecute @@ -395,7 +401,8 @@ public class DistMakerEngine // If the parentFrame is not visible then execute the code once it is made visible if (parentFrame.isVisible() == false) { - parentFrame.addComponentListener(new ComponentAdapter() { + parentFrame.addComponentListener(new ComponentAdapter() + { @Override public void componentShown(ComponentEvent aEvent) { @@ -574,7 +581,8 @@ public class DistMakerEngine // 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) + // 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)) { @@ -586,12 +594,12 @@ public class DistMakerEngine 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,jre" + targJreVer.getLabel() + "\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,jre" + currJreVer.getLabel() + "\n"); + tmpFW.write("trash," + JreUtils.getExpandJrePath(currJreVer) + "\n"); tmpFW.write("exit\n\n"); } else @@ -626,7 +634,7 @@ public class DistMakerEngine // 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 jreDropPath = new File(destPath, JreUtils.getExpandJrePath(targJre.getVersion())); File jreTargPath = PlatformUtils.getJreLocation(targJre); jreTargPath.getParentFile().setWritable(true); if (jreDropPath.renameTo(jreTargPath) == false) @@ -688,10 +696,10 @@ public class DistMakerEngine 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()); + aTask.infoAppendln("\tMinimum JRE: " + aUpdateCat.getMinJreVersion().getLabel()); JreVersion tmpJreVer = aUpdateCat.getMaxJreVersion(); if (tmpJreVer != null) - aTask.infoAppendln("\tMaximun JRE: " + tmpJreVer.getLabel()); + aTask.infoAppendln("\tMaximum JRE: " + tmpJreVer.getLabel()); aTask.infoAppendln(""); // Bail if we are running a bundled JRE @@ -738,6 +746,15 @@ public class DistMakerEngine } 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; @@ -761,21 +778,21 @@ public class DistMakerEngine 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"); + 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, "jre" + pickJreVer.getLabel()); + 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 and ensure that the unpacked JRE results in a 1 top level root folder. + // 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(); @@ -793,6 +810,9 @@ public class DistMakerEngine 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; } @@ -800,16 +820,17 @@ public class DistMakerEngine } /** - * 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: + * 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: *

*

- * 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. + * 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) { @@ -883,8 +904,9 @@ public class DistMakerEngine 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. + // 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); @@ -935,13 +957,12 @@ public class DistMakerEngine *

* This method will be called via reflection. */ - @SuppressWarnings("unused") - private void queryUserForInput(Task aTask, File deltaPath, List fullList) + private void queryUserForInput(Task aTask, File aDeltaPath, List aFullList) { AppRelease chosenItem; // Query the user, if the wish to destroy the old update - if (deltaPath.isDirectory() == true) + 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?"); @@ -958,7 +979,7 @@ public class DistMakerEngine } // Query the user of the version to update to - pickVersionPanel.setConfiguration(fullList); + pickVersionPanel.setConfiguration(aFullList); pickVersionPanel.setVisibleAsModal(); chosenItem = pickVersionPanel.getChosenItem(); if (chosenItem == null) @@ -970,7 +991,8 @@ public class DistMakerEngine } /** - * Notification that the corresponding application has been fully initialized. This helper method will notify the user on the status of any update. + * 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() { @@ -1000,7 +1022,8 @@ public class DistMakerEngine } // Setup the runnable that will clean up our delta folder - Runnable cleanDeltaRunnable = new Runnable() { + Runnable cleanDeltaRunnable = new Runnable() + { @Override public void run() { diff --git a/src/distMaker/DistUtils.java b/src/distMaker/DistUtils.java index a3f6c7b..30f4831 100644 --- a/src/distMaker/DistUtils.java +++ b/src/distMaker/DistUtils.java @@ -22,18 +22,56 @@ import distMaker.digest.Digest; import distMaker.digest.DigestType; import distMaker.jre.JreVersion; import distMaker.node.*; +import distMaker.utils.ParseUtils; +import distMaker.utils.PlainVersion; +import distMaker.utils.Version; public class DistUtils { - // Static members that may be automatically updated by the AppLauncher class loader. - // Do not rename or change these variables, without changing the AppLauncher class loader. + // ----------------------------------------------------------------------------------------------------------------- + // Start of AppLauncher class loader related vars. + // Static members that will be automatically configured by the AppLauncher class loader. + // Do not rename or change the variables below, without changing the AppLauncher class loader. + // ----------------------------------------------------------------------------------------------------------------- + private static String appLauncherVersion = null; private static boolean isDevelopersEnvironment = true; private static int updateCode = 0; // Static member to declare the update status, 0: None, 1: Pass, 2: Fail private static String updateMsg = null; + // ----------------------------------------------------------------------------------------------------------------- + // Do not rename or change the variables above, without changing the AppLauncher class loader. + // End of AppLauncher class loader related vars. + // ----------------------------------------------------------------------------------------------------------------- - // Static field used only when developing DistMaker. Otherwise this field should always be null. + /** Cached value of the AppLauncherVersion. Note the cached value is of type Version rather than String */ + private static Version cAppLauncherVersion; + + /* Static field used only when developing DistMaker. Otherwise this field should always be null. */ private static File developAppPath = null; + /** + * Utility method to return the version of the AppLauncher that started this process. + *

+ * If we are running in a developers environment then this value will be null. + */ + public static Version getAppLauncherVersion() + { + // Return the cached value + if (cAppLauncherVersion != null) + return cAppLauncherVersion; + + // Return null if we are in a developers environment + if (isDevelopersEnvironment == true) + return null; + + // Legacy AppLaunchers do not configure this field. All legacy AppLaunchers will be defined as version: 0.0 + if (appLauncherVersion == null && isDevelopersEnvironment == false) + cAppLauncherVersion = PlainVersion.Zero; + else + cAppLauncherVersion = PlainVersion.parse(appLauncherVersion); + + return cAppLauncherVersion; + } + /** * Utility method to determine the path where the application is installed. *

@@ -66,6 +104,14 @@ public class DistUtils return jarPath.getParentFile().getParentFile(); } + /** + * Returns the version of DistMaker which is running. + */ + public static Version getDistMakerVersion() + { + return DistApp.version; + } + /** * Returns the JreVersion for the JRE which we are running on. */ @@ -124,10 +170,10 @@ public class DistUtils /** * Downloads the specified file from srcUrl to destFile. Returns true on success *

- * Note the passed in task's progress will be updated from 0% to 100% at file download completion, If the specified file size is invalid (aFileSize <= 0) or - * the download turns out to be bigger than the specified size then there will be no progress update while the file is being downloaded - only at completion. + * Note the passed in task's progress will be updated from 0% to 100% at file download completion, If the specified + * file size is invalid (aFileSize <= 0) or the download turns out to be bigger than the specified size then there + * will be no progress update while the file is being downloaded - only at completion. */ - @SuppressWarnings("resource") public static boolean downloadFile(Task aTask, URL aUrl, File aFile, Credential aCredential, long aFileSize, MessageDigest aDigest) { URLConnection connection; @@ -222,26 +268,23 @@ public class DistUtils public static List getAvailableAppReleases(Task aTask, URL aUpdateUrl, String appName, Credential aCredential) { List fullList; + AppRelease workAR; URL catUrl; URLConnection connection; InputStream inStream; BufferedReader bufReader; - DateUnit dateUnit; String errMsg; errMsg = null; fullList = new ArrayList<>(); - catUrl = IoUtil.createURL(aUpdateUrl.toString() + "/" + appName + "/" + "releaseInfo.txt"); + catUrl = IoUtil.createURL(aUpdateUrl.toString() + "/" + appName + "/" + "appCatalog.txt"); + workAR = null; connection = null; inStream = null; bufReader = null; try { - String[] tokens; - String strLine, verName; - long buildTime; - // Read the contents of the file connection = catUrl.openConnection(); inStream = NetUtil.getInputStream(connection, aCredential); @@ -250,30 +293,84 @@ public class DistUtils // Read the lines while (true) { + String strLine; strLine = bufReader.readLine(); // Bail once we are done if (strLine == null) break; - tokens = strLine.split(","); + // Ignore comments and empty lines if (strLine.isEmpty() == true || strLine.startsWith("#") == true) + continue; + + String[] tokenArr; + tokenArr = strLine.split(","); + if (tokenArr.length == 2 && tokenArr[0].equals("name") == true) ; // Nothing to do - else if (tokens.length == 2 && tokens[0].equals("name") == true) - ; // Nothing to do - else if (tokens.length != 2) - aTask.infoAppendln("Unreconized line: " + strLine); - else - // if (tokens.length == 2) + // Logic to handle the 'exit' command + else if (tokenArr.length >= 1 && tokenArr[0].equals("exit") == true) { - verName = tokens[0]; + // We support exit commands with 3 tokens. All others + // we will just exit. + if (tokenArr.length != 3) + break; + + String targName = tokenArr[1]; + String needVer = tokenArr[2]; + if (ParseUtils.shouldExitLogic(targName, needVer) == true) + break; + } + // Logic to handle a distribution release + else if (tokenArr.length == 3 && tokenArr[0].equals("R") == true) + { + DateUnit dateUnit; + String verName; + long buildTime; + + verName = tokenArr[1]; dateUnit = new DateUnit("", "yyyyMMMdd HH:mm:ss"); - buildTime = dateUnit.parseString(tokens[1], 0); + buildTime = dateUnit.parseString(tokenArr[2], 0); - fullList.add(new AppRelease(appName, verName, buildTime)); + // Record the prior AppRelease + if (workAR != null) + fullList.add(workAR); + + workAR = new AppRelease(appName, verName, buildTime); + } + // Record any comments and associate with the current AppRelease + else if (tokenArr.length >= 2 && tokenArr[0].equals("info") == true) + { + // We support the 'info,msg' instruction. Otherwise we just ignore info instructions silently + if (tokenArr[1].equals("msg") == true && workAR != null) + { + // Retokenize to ensure at most we get only 3 tokens + tokenArr = strLine.split(",", 3); + + String infoMsg; + infoMsg = workAR.getInfoMsg(); + if (infoMsg != null) + infoMsg += "\n"; + else + infoMsg = ""; + if (tokenArr.length > 2) + infoMsg += tokenArr[2]; + + // Form an updated AppRelease with the updated infoMsg + workAR = new AppRelease(workAR.getName(), workAR.getVersion(), workAR.getBuildTime(), infoMsg); + } + + } + else + { + aTask.infoAppendln("Unreconized line: " + strLine); } } + + // Add the last AppRelease + if (workAR != null) + fullList.add(workAR); } catch(IOException aExp) { @@ -310,40 +407,39 @@ public class DistUtils */ private static String getErrorCodeMessage(URL aUpdateUrl, URLConnection aConnection, IOException aExp) { - URL fetchUrl; - Result result; - String errMsg; - // Form a user friendly exception + String errMsg; errMsg = "The update site, " + aUpdateUrl + ", is not available.\n\t"; + Result result; result = NetUtil.getResult(aExp, aConnection); switch (result) { case BadCredentials: - errMsg += "The update site is password protected and bad credentials were provided.\n"; - break; + errMsg += "The update site is password protected and bad credentials were provided.\n"; + break; case ConnectFailure: case UnreachableHost: case UnsupportedConnection: - errMsg += "The update site appears to be unreachable.\n"; - break; + errMsg += "The update site appears to be unreachable.\n"; + break; case Interrupted: - errMsg += "The retrival of the remote file has been interrupted.\n"; - break; + errMsg += "The retrival of the remote file has been interrupted.\n"; + break; case InvalidResource: - errMsg += "The remote file does not appear to be valid.\n"; - break; + errMsg += "The remote file does not appear to be valid.\n"; + break; default: - errMsg += "An undefined error occurred while retrieving the remote file.\n"; - break; + errMsg += "An undefined error occurred while retrieving the remote file.\n"; + break; } // Log the URL which we failed on + URL fetchUrl; fetchUrl = aConnection.getURL(); errMsg += "\tURL: " + fetchUrl + "\n"; @@ -351,8 +447,9 @@ public class DistUtils } /** - * Utility method to determine if the specified path is fully writable by this process. This is done by making sure that all folders and child folders are - * writable by the current process. Note after this method is called, all folders will have the write permission bit set. + * Utility method to determine if the specified path is fully writable by this process. This is done by making sure + * that all folders and child folders are writable by the current process. Note after this method is called, all + * folders will have the write permission bit set. */ public static boolean isFullyWriteable(File aPath) { @@ -388,7 +485,6 @@ public class DistUtils { List nodeList; JreVersion minJreVersion, maxJreVersion; - DigestType digestType; String errMsg, strLine; errMsg = null; @@ -397,16 +493,17 @@ public class DistUtils maxJreVersion = null; // Default to DigestType of MD5 + DigestType digestType; digestType = DigestType.MD5; - try (BufferedReader bufReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(aCatalogFile))));) + try (BufferedReader tmpBR = new BufferedReader(new InputStreamReader(new BufferedInputStream(new FileInputStream(aCatalogFile))));) { String[] tokens; // Read the contents of the file while (true) { - strLine = bufReader.readLine(); + strLine = tmpBR.readLine(); // Bail once we are done if (strLine == null) @@ -450,8 +547,7 @@ public class DistUtils { if (minJreVersion != null) { - aTask.infoAppendln("JRE version has already been specified. Current ver: " + minJreVersion.getLabel() + " Requested ver: " + tokens[1] - + ". Skipping..."); + aTask.infoAppendln("JRE version has already been specified. Current ver: " + minJreVersion.getLabel() + " Requested ver: " + tokens[1] + ". Skipping..."); continue; } @@ -487,7 +583,8 @@ public class DistUtils } /** - * Utility method to switch the DistMaker library into debug mode. You should never call this method unless you are modifying the DistMaker library. + * Utility method to switch the DistMaker library into debug mode. You should never call this method unless you are + * modifying the DistMaker library. *

* This functionality only exists to allow rapid development of DistMaker */ diff --git a/src/distMaker/LoggingTask.java b/src/distMaker/LoggingTask.java index ce9ac0a..51468f7 100644 --- a/src/distMaker/LoggingTask.java +++ b/src/distMaker/LoggingTask.java @@ -7,31 +7,31 @@ import java.util.List; public class LoggingTask extends SilentTask { - private final List messages = new ArrayList<>(); + private final List messages = new ArrayList<>(); - @Override - public void infoAppend(String aMsg) - { - messages.add(aMsg); - super.infoAppend(aMsg); - } + @Override + public void infoAppend(String aMsg) + { + messages.add(aMsg); + super.infoAppend(aMsg); + } - @Override - public void infoAppendln(String aMsg) - { - messages.add(aMsg); - super.infoAppendln(aMsg); - } + @Override + public void infoAppendln(String aMsg) + { + messages.add(aMsg); + super.infoAppendln(aMsg); + } - @Override - public void infoUpdate(String aMsg) - { - messages.add(aMsg); - super.infoUpdate(aMsg); - } + @Override + public void infoUpdate(String aMsg) + { + messages.add(aMsg); + super.infoUpdate(aMsg); + } - List getMessages() - { - return messages; - } + List getMessages() + { + return messages; + } } diff --git a/src/distMaker/MiscUtils.java b/src/distMaker/MiscUtils.java index abaf042..50cfb26 100644 --- a/src/distMaker/MiscUtils.java +++ b/src/distMaker/MiscUtils.java @@ -34,7 +34,7 @@ public class MiscUtils */ 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--) { @@ -87,7 +87,8 @@ public class MiscUtils /** * 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. + * 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. */ public static void printErrorDM(Task aTask, ErrorDM aErrorDM, int numTabs) { @@ -123,16 +124,15 @@ public class MiscUtils * 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. + * 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 + * the input .tar file * @param aDestPath - * The destination folder where the content will be dumped. + * 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 */ @@ -176,6 +176,9 @@ public class MiscUtils } else if (entry.isSymbolicLink() == true) { + // Ensure the parent folders exist + outputFile.getParentFile().mkdirs(); + File tmpFile = new File(entry.getLinkName()); Files.createSymbolicLink(outputFile.toPath(), tmpFile.toPath()); @@ -194,7 +197,11 @@ public class MiscUtils } else if (entry.isFile() == true) { - final OutputStream outputFileStream = new FileOutputStream(outputFile); + // Ensure the parent folders exist + outputFile.getParentFile().mkdirs(); + + // Copy over the file + OutputStream outputFileStream = new FileOutputStream(outputFile); IOUtils.copy(debInputStream, outputFileStream); outputFileStream.close(); diff --git a/src/distMaker/digest/Digest.java b/src/distMaker/digest/Digest.java index 4231b25..a987533 100644 --- a/src/distMaker/digest/Digest.java +++ b/src/distMaker/digest/Digest.java @@ -80,4 +80,4 @@ public class Digest return true; } -} \ No newline at end of file +} diff --git a/src/distMaker/digest/DigestUtils.java b/src/distMaker/digest/DigestUtils.java index b9ceafe..f3f0b83 100644 --- a/src/distMaker/digest/DigestUtils.java +++ b/src/distMaker/digest/DigestUtils.java @@ -3,7 +3,7 @@ package distMaker.digest; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import javax.xml.bind.annotation.adapters.HexBinaryAdapter; +import com.google.common.io.BaseEncoding; /** * Collection of utility methods to ease working with the MessageDigest and associated classes. @@ -44,24 +44,24 @@ public class DigestUtils } /** - * Utility method that returns the hex string corresponding to the byte array. + * Utility method that returns the (lower case) hex string corresponding to the byte array. + *

+ * Delegates to {@link BaseEncoding.base16().lowerCase().encode(CharSequence)} */ public static String byteArr2HexStr(byte[] aByteArr) { - String retStr; - - retStr = (new HexBinaryAdapter()).marshal(aByteArr).toLowerCase(); + String retStr = BaseEncoding.base16().lowerCase().encode(aByteArr); return retStr; } /** * Utility method that returns a byte array corresponding to the hex string. + *

+ * Delegates to {@link BaseEncoding.base16().lowerCase().decode(CharSequence)} */ public static byte[] hexStr2ByteArr(String aHexStr) { - byte[] retArr; - - retArr = (new HexBinaryAdapter()).unmarshal(aHexStr); + byte[] retArr = BaseEncoding.base16().lowerCase().decode(aHexStr); return retArr; } diff --git a/src/distMaker/gui/PickReleasePanel.java b/src/distMaker/gui/PickReleasePanel.java index 6987bf1..afa3e02 100644 --- a/src/distMaker/gui/PickReleasePanel.java +++ b/src/distMaker/gui/PickReleasePanel.java @@ -28,15 +28,17 @@ import distMaker.node.AppRelease; public class PickReleasePanel extends GlassPanel implements ActionListener, ListSelectionListener { - private static final long serialVersionUID = 1L; + // Constants + private static final long serialVersionUID = 1L; + private static final Color ColorFail = Color.red.darker().darker(); - // GUI vars + // GUI vars private JLabel titleL; private JRadioButton newestRB, olderRB; private ItemListPanel listPanel; private QueryTableCellRenderer col0Renderer, col1Renderer; private JButton abortB, proceedB; - private JTextArea infoTA, warnTA; + private JTextArea headTA, infoTA; private Font smallFont; // State vars @@ -57,7 +59,7 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List // Build the actual GUI smallFont = (new JTextField()).getFont(); buildGuiArea(); - setPreferredSize(new Dimension(250, getPreferredSize().height)); + setPreferredSize(new Dimension(350, getPreferredSize().height)); // Set up some keyboard shortcuts FocusUtil.addAncestorKeyBinding(this, "ESCAPE", new ClickAction(abortB)); @@ -74,17 +76,17 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List /** * Sets in the configuration of available versions */ - public void setConfiguration(List itemList) + public void setConfiguration(List aItemList) { DateUnit dateUnit; // String currBuildStr; String lastBuildStr; String currVerStr, lastVerStr; - String appName, infoMsg; + String appName, headMsg; // Sort the items, and isolate the newest item LinkedList linkedList; - linkedList = new LinkedList<>(itemList); + linkedList = new LinkedList<>(aItemList); Collections.sort(linkedList); Collections.reverse(linkedList); // reverse the list to show most recent versions on top newestItem = linkedList.removeFirst(); @@ -104,20 +106,27 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List myItemProcessor.setItems(linkedList); // Update the infoTA - if (newestItem.equals(installedItem) == true) { - titleL.setText(appName + " is up to date."); - infoMsg = "You are running the latest release " - + " (" + lastVerStr + ") that was built on " + lastBuildStr + ". "; - infoMsg += "You may switch to an older release by choosing one of the versions below."; - } else { - titleL.setText(appName + " needs to be updated."); - infoMsg = "You are running version " + currVerStr + ". "; - infoMsg += "You may update to the latest release. You may also switch to an " - + "older release by choosing another version below. "; + if (newestItem.equals(installedItem) == true) + { + titleL.setText(appName + " is up to date."); + headMsg = "You are running the latest release " + " (" + lastVerStr + ") that was built on " + lastBuildStr + ". "; + headMsg += "You may switch to an older release by choosing one of the versions below."; } - infoMsg += "\n"; + else if (installedItem.getBuildTime() > newestItem.getBuildTime()) + { + titleL.setText(appName + " (Out-Of-Band Release)"); + headMsg = "You are running version " + currVerStr + ". This version has never been released! "; + headMsg += "You may update to the latest release or switch to an older release by choosing another version below. "; + } + else + { + titleL.setText(appName + " needs to be updated."); + headMsg = "You are running version " + currVerStr + ". "; + headMsg += "You may update to the latest release. You may also switch to an " + "older release by choosing another version below. "; + } + headMsg += "\n"; - infoTA.setText(infoMsg); + headTA.setText(headMsg); updateGui(); } @@ -161,38 +170,50 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List */ private void buildGuiArea() { - JPanel tmpPanel; + JPanel listPanel, mainPanel; // Form the layout - setLayout(new MigLayout("", "[left][grow][]", "[]25[][][]3[grow]10[]")); + setLayout(new MigLayout("", "[left][grow][]", "[]")); - // Title Area - titleL = new JLabel("Please select an update", JLabel.CENTER); // this text gets replaced once the curent version status is known + // Title Area: Note that the default text gets replaced once the current version status is known + titleL = new JLabel("Please select an update", JLabel.CENTER); add(titleL, "growx,span 2,wrap"); - // Info area - infoTA = GuiUtil.createUneditableTextArea(2, 0); - add(infoTA, "w 0::,growx,span,wrap"); + // Header area + headTA = GuiUtil.createUneditableTextArea(2, 0); + add(headTA, "w 0::,growx,span,wrap"); // Latest version area newestRB = GuiUtil.createJRadioButton("Unspecified", this, smallFont); newestRB.setSelected(true); - add(newestRB, "span,wrap"); // Older version area olderRB = GuiUtil.createJRadioButton("Select an older release:", this, smallFont); - add(olderRB, "span,wrap"); - tmpPanel = buildItemListTablePanel(); - tmpPanel.setBorder(new EmptyBorder(0, 15, 0, 0)); - add(tmpPanel, "growx,growy,span,wrap"); + listPanel = buildItemListTablePanel(); + listPanel.setBorder(new EmptyBorder(0, 15, 0, 0)); + + mainPanel = new JPanel(new MigLayout("", "0[left,grow]", "0[][]3[grow]0")); + mainPanel.add(newestRB, "wrap"); + mainPanel.add(olderRB, "wrap"); + mainPanel.add(listPanel, "growx,growy"); // Link the radio buttons GuiUtil.linkRadioButtons(newestRB, olderRB); - // Warn Area - warnTA = GuiUtil.createUneditableTextArea(0, 0); - add(warnTA, "w 0::,growx,span,wrap"); + // Info Area + JScrollPane tmpSP; + infoTA = GuiUtil.createUneditableTextArea(0, 0); + infoTA.setTabSize(3); + tmpSP = new JScrollPane(infoTA); + tmpSP.setBorder(new EmptyBorder(0, 5, 0, 5)); + + JSplitPane tmpPane; + tmpPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, mainPanel, tmpSP); + tmpPane.setBorder(null); + tmpPane.setResizeWeight(0.15); +// tmpPane.setDividerLocation(0.50); + add(tmpPane, "growx,growy,pushy,span,wrap"); // Action area abortB = GuiUtil.createJButton("Abort", this, smallFont); @@ -208,26 +229,26 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List */ private JPanel buildItemListTablePanel() { - QueryComposer aComposer; - QueryItemHandler aItemHandler; + QueryComposer tmpComposer; + QueryItemHandler tmpIH; DateUnit dateUnit; dateUnit = new DateUnit("", "yyyyMMMdd HH:mm"); - aComposer = new QueryComposer(); - aComposer.addAttribute(LookUp.Version, String.class, "Version", null); - aComposer.addAttribute(LookUp.BuildTime, new ConstUnitProvider(dateUnit), "Build Date", null); + tmpComposer = new QueryComposer(); + tmpComposer.addAttribute(LookUp.Version, String.class, "Version", null); + tmpComposer.addAttribute(LookUp.BuildTime, new ConstUnitProvider(dateUnit), "Build Date", null); col0Renderer = new QueryTableCellRenderer(); col1Renderer = new QueryTableCellRenderer(); col1Renderer.setUnit(dateUnit); - aComposer.setRenderer(LookUp.Version, col0Renderer); - aComposer.setRenderer(LookUp.BuildTime, col1Renderer); + tmpComposer.setRenderer(LookUp.Version, col0Renderer); + tmpComposer.setRenderer(LookUp.BuildTime, col1Renderer); - aItemHandler = new QueryItemHandler(aComposer); - myItemProcessor = new StaticItemProcessor(); + tmpIH = new QueryItemHandler(tmpComposer); + myItemProcessor = new StaticItemProcessor<>(); - listPanel = new ItemListPanel(aItemHandler, myItemProcessor, false, false); + listPanel = new ItemListPanel<>(tmpIH, myItemProcessor, false, false); listPanel.setSortingEnabled(false); listPanel.addListSelectionListener(this); return listPanel; @@ -239,7 +260,7 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List private void updateGui() { AppRelease pickItem; - String warnMsg; + String failMsg, infoMsg; boolean isEnabled; // Determine the selected version @@ -258,15 +279,39 @@ public class PickReleasePanel extends GlassPanel implements ActionListener, List col1Renderer.setEnabled(isEnabled); // Determine the warnMsg - warnMsg = null; + failMsg = null; + infoMsg = null; if (pickItem == null) - warnMsg = "Select the version you want to switch to."; + failMsg = "Select the version you want to switch to."; else if (pickItem.equals(installedItem) == true) - warnMsg = "You cannot update to the same version you are running. You can switch to a different version."; + failMsg = "You cannot update to the same version you are running. You can switch to a different version."; else if (pickItem.getBuildTime() < installedItem.getBuildTime()) - warnMsg = "Please note that your current selection will revert back to an older version than the currently installed version."; + infoMsg = "Please note that your current selection will revert back to an older version than the currently installed version."; - warnTA.setText(warnMsg); + // Add the app release notes + if (pickItem != null) + { + String noteMsg; + noteMsg = pickItem.getInfoMsg(); + if (noteMsg != null) + { + if (infoMsg != null) + infoMsg += "\n\n" + noteMsg; + else + infoMsg = noteMsg; + } + } + + Color tmpColor = Color.BLACK; + if (failMsg != null) + tmpColor = ColorFail; + + String tmpMsg = failMsg; + if (tmpMsg == null) + tmpMsg = infoMsg; + + infoTA.setText(tmpMsg); + infoTA.setForeground(tmpColor); } } diff --git a/src/distMaker/jre/AppLauncherRelease.java b/src/distMaker/jre/AppLauncherRelease.java new file mode 100644 index 0000000..8abda3f --- /dev/null +++ b/src/distMaker/jre/AppLauncherRelease.java @@ -0,0 +1,80 @@ +package distMaker.jre; + +import distMaker.digest.Digest; +import distMaker.utils.PlainVersion; +import distMaker.utils.Version; +import distMaker.utils.VersionUtils; + +/** + * Immutable class that describes an AppLauncher release. + *

+ * The reference fileName should be a jar file. + */ +public class AppLauncherRelease implements Comparable +{ + private final Version version; + private final Digest digest; + private final String fileName; + private final long fileLen; + + public AppLauncherRelease(String aVersion, String aFileName, Digest aDigest, long aFileLen) + { + version = PlainVersion.parse(aVersion); + fileName = aFileName; + digest = aDigest; + fileLen = aFileLen; + } + + /** + * Returns the Digest associated with the AppLauncher (jar) file. + */ + public Digest getDigest() + { + return digest; + } + + /** + * Returns the version of the AppLauncher corresponding to this release. + */ + public Version getVersion() + { + return version; + } + + /** + * Returns the length of the associated file + */ + public long getFileLen() + { + return fileLen; + } + + /** + * Returns the filename of this AppLauncher release. + */ + public String getFileName() + { + return fileName; + } + + @Override + public int compareTo(AppLauncherRelease aItem) + { + int cmpVal; + + cmpVal = VersionUtils.compare(version, aItem.version); + if (cmpVal != 0) + return cmpVal; + + cmpVal = fileName.compareTo(aItem.fileName); + if (cmpVal != 0) + return cmpVal; + + cmpVal = Long.compare(fileLen, aItem.fileLen); + if (cmpVal != 0) + return cmpVal; + + return 0; + } + +} diff --git a/src/distMaker/jre/AppLauncherUtils.java b/src/distMaker/jre/AppLauncherUtils.java new file mode 100644 index 0000000..51fc55d --- /dev/null +++ b/src/distMaker/jre/AppLauncherUtils.java @@ -0,0 +1,339 @@ +package distMaker.jre; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; + +import distMaker.DistUtils; +import distMaker.digest.Digest; +import distMaker.digest.DigestType; +import distMaker.digest.DigestUtils; +import distMaker.platform.PlatformUtils; +import distMaker.utils.ParseUtils; +import distMaker.utils.PlainVersion; +import distMaker.utils.Version; +import distMaker.utils.VersionUtils; +import glum.gui.GuiUtil; +import glum.io.IoUtil; +import glum.net.Credential; +import glum.net.NetUtil; +import glum.task.PartialTask; +import glum.task.Task; +import glum.util.ThreadUtil; + +public class AppLauncherUtils +{ + /** + * Returns a list of all the available AppLauncher releases specified at:
+ * {@literal /launcher/appCatalog.txt} + */ + public static List getAvailableAppLauncherReleases(Task aTask, URL aUpdateSiteUrl, Credential aCredential) + { + List retList; + URL catUrl; + URLConnection connection; + InputStream inStream; + BufferedReader bufReader; + DigestType digestType; + String errMsg, strLine; + + errMsg = null; + retList = new ArrayList<>(); + catUrl = IoUtil.createURL(aUpdateSiteUrl.toString() + "/launcher/appCatalog.txt"); + + // Default to DigestType of MD5 + digestType = DigestType.MD5; + + inStream = null; + bufReader = null; + try + { + // Read the contents of the file + connection = catUrl.openConnection(); + inStream = NetUtil.getInputStream(connection, aCredential); + bufReader = new BufferedReader(new InputStreamReader(new BufferedInputStream(inStream))); + + // Read the lines + while (true) + { + strLine = bufReader.readLine(); + + // Bail once we are done + if (strLine == null) + break; + + // Ignore comments and empty lines + if (strLine.isEmpty() == true || strLine.startsWith("#") == true) + continue; + + String[] tokens; + tokens = strLine.split(",", 5); + if (tokens.length == 2 && tokens[0].equals("name") == true && tokens[1].equals("AppLauncher") == true) + ; // Nothing to do - we just entered the "JRE" section + // Logic to handle the 'exit' command + else if (tokens.length >= 1 && tokens[0].equals("exit") == true) + { + // We support exit commands with 3 tokens. All others + // we will just exit. + if (tokens.length != 3) + break; + + String targName = tokens[1]; + String needVer = tokens[2]; + if (ParseUtils.shouldExitLogic(targName, needVer) == true) + break; + } + // Logic to handle the 'digest' command + else if (tokens.length == 2 && tokens[0].equals("digest") == true) + { + DigestType tmpDigestType; + + tmpDigestType = DigestType.parse(tokens[1]); + if (tmpDigestType == null) + aTask.infoAppendln("Failed to locate DigestType for: " + tokens[1]); + else + digestType = tmpDigestType; + } + // Logic to handle the 'F' command: AppLauncher File + else if (tokens.length == 5 && tokens[0].equals("F") == true) + { + String filename, digestStr, version; + long fileLen; + + // Form the JreRelease + digestStr = tokens[1]; + fileLen = GuiUtil.readLong(tokens[2], -1); + filename = tokens[3]; + version = tokens[4]; + + Digest tmpDigest = new Digest(digestType, digestStr); + retList.add(new AppLauncherRelease(version, filename, tmpDigest, fileLen)); + } + else + { + aTask.infoAppendln("Unreconized line: " + strLine); + } + } + } + catch(FileNotFoundException aExp) + { + errMsg = "Failed to locate resource: " + catUrl; + } + catch(IOException aExp) + { + errMsg = ThreadUtil.getStackTrace(aExp); + } + finally + { + IoUtil.forceClose(inStream); + IoUtil.forceClose(bufReader); + } + + // See if we are in a valid state + if (errMsg != null) + ; // Nothing to do, as an earlier error has occurred + else if (retList.size() == 0) + errMsg = "The catalog appears to be invalid."; + + // Bail if there were issues + if (errMsg != null) + { + aTask.infoAppendln(errMsg); + return null; + } + + return retList; + } + + /** + * Utility method that checks to see if the AppLauncher will need be updated in order to support the specified JRE. + *

+ * Returns false if the AppLauncher does NOT need be updated. Otherwise true will be returned and the version info + * will be logged to the specified aTask. + */ + public static boolean isAppLauncherUpdateNeeded(Task aTask, JreRelease aJreRelease) + { + Version currVer, nMinVer, nMaxVer; + + // Bail if the installed version of AppLauncher is equal to or later than the required version + currVer = DistUtils.getAppLauncherVersion(); + nMinVer = aJreRelease.getAppLauncherMinVersion(); + nMaxVer = aJreRelease.getAppLauncherMaxVersion(); + if (VersionUtils.isInRange(currVer, nMinVer, nMaxVer) == true) + return false; + + // Log the fact that we need to update our AppLauncher + aTask.infoAppendln("Your current AppLauncher is not compatible with this release. It will need to be upgraded!"); + aTask.infoAppendln("\tCurrent ver: " + currVer); + if (nMinVer != PlainVersion.AbsMin) + aTask.infoAppendln("\tMinimum ver: " + nMinVer); + if (nMaxVer != PlainVersion.AbsMax) + aTask.infoAppendln("\tMaximum ver: " + nMaxVer); + aTask.infoAppendln(""); + + return true; + } + + /** + * Utility method that will return the AppLauncherRelease that satisfies the requirements as specified by the + * JreRelease. + *

+ * Returns null if no AppLauncherRelease is located that can satisfy the specified JreRelease. + *

+ * Any issues that cropped up while searching for a valid AppLauncherRelease will be logged to the specified task, + * aTask. + *

+ * TODO: Remove the comments below + *

+ * corresponding to the AppLauncher that we to the specified version required by this JRE. + *

+ * Returns false on any failure. + */ + public static AppLauncherRelease updateAppLauncher(Task aTask, JreRelease aJreRelease, File aDestPath, URL updateSiteUrl, Credential aCredential) + { + // Locate the list of available AppLaunchers + List availList; + availList = AppLauncherUtils.getAvailableAppLauncherReleases(aTask, updateSiteUrl, aCredential); + if (availList == null) + { + aTask.infoAppendln("The update site does not have any deployed AppLaunchers."); + aTask.infoAppendln("Please contact the update site adminstartor."); + return null; + } + + if (availList.size() == 0) + { + aTask.infoAppendln("No AppLauncher releases found!"); + aTask.infoAppendln("Please contact the update site adminstartor."); + return null; + } + + // Retrieve the AppLauncher that is compatible from the list + Version nMinVer = aJreRelease.getAppLauncherMinVersion(); + Version nMaxVer = aJreRelease.getAppLauncherMaxVersion(); + + AppLauncherRelease pickRelease = null; + for (AppLauncherRelease aRelease : availList) + { + Version evalVer = aRelease.getVersion(); + if (VersionUtils.isInRange(evalVer, nMinVer, nMaxVer) == false) + continue; + + pickRelease = aRelease; + break; + } + + // Bail if no compatible release could be found + if (pickRelease == null) + { + aTask.infoAppendln("No compatible AppLauncher releases have been deployed!"); + aTask.infoAppendln("Please contact the update site adminstartor."); + return null; + } + + // Get stats on the release + Version pickVer = pickRelease.getVersion(); + long fileLen = pickRelease.getFileLen(); + + // Define the path to the launcher + File launcherPath; + if (PlatformUtils.getPlatform().equals("Apple") == true) + launcherPath = new File(aDestPath.getParentFile(), "Java"); + else + launcherPath = new File(aDestPath.getParentFile(), "launcher"); + + // Download the AppLauncher + Digest targDigest, testDigest; + MessageDigest msgDigest; + targDigest = pickRelease.getDigest(); + msgDigest = DigestUtils.getDigest(targDigest.getType()); + aTask.infoAppendln("Downloading AppLauncher... Version: " + pickVer); + URL srcUrl = IoUtil.createURL(updateSiteUrl.toString() + "/launcher/appLauncher-" + pickVer + ".jar"); + File dstFile = new File(launcherPath, pickRelease.getFileName()); + int zios_2018Feb15; // TODO: Consider refactoring this code and making it cleaner... + Task tmpTask = new PartialTask(aTask, aTask.getProgress(), 0.01); +// Task tmpTask = new PartialTask(aTask, aTask.getProgress(), (tmpFileLen * 0.75) / (releaseSizeFull + 0.00)); +// Task tmpTask = new SilentTask(); + if (DistUtils.downloadFile(tmpTask, srcUrl, dstFile, aCredential, fileLen, msgDigest) == false) + { + aTask.infoAppendln("Failed to download updated AppLauncher."); + aTask.infoAppendln("\tSource: " + srcUrl); + aTask.infoAppendln("\tFile: " + dstFile + "\n"); + return null; + } + + // Validate that the AppLauncher was downloaded successfully + testDigest = new Digest(targDigest.getType(), msgDigest.digest()); + if (targDigest.equals(testDigest) == false) + { + aTask.infoAppendln("The download of the AppLauncher appears to be corrupted."); + aTask.infoAppendln("\tFile: " + dstFile); + aTask.infoAppendln("\t\tExpected " + targDigest.getDescr()); + aTask.infoAppendln("\t\tReceived " + testDigest.getDescr() + "\n"); + return null; + } + + // Update the appLauncher.jar file + File targFile = new File(launcherPath, "appLauncher.jar"); + boolean isPass; + + // Back up the "original" targFile (only if a backup has not already already been made). In case + // there are multiple updates we do not want to overwrite the "real" original + boolean isOriginalBackedUp = false; + File origFile = new File(launcherPath, "appLauncher.jar.orig"); + if (origFile.exists() == false) + { + isPass = targFile.renameTo(origFile); + if (isPass == false) + { + aTask.infoAppendln("Failed to rename: " + targFile + " to " + origFile); + } + else + { + isOriginalBackedUp = true; + aTask.infoAppendln("Original file has been backed up."); + aTask.infoAppendln("\t" + targFile + " ---> " + origFile); + } + } + + // Update the targFile with the newly downloaded file + isPass = dstFile.renameTo(targFile); +// targFile.renameTo(origFile); + if (isPass == false) + { + aTask.infoAppendln("Failed to rename: " + dstFile + " to " + targFile); + aTask.infoAppendln("Update is being aborted"); + + // We need to revert to the original backup + if (isOriginalBackedUp == true) + { + isPass = origFile.renameTo(targFile); + if (isPass == false) + { + aTask.infoAppendln("Failed to revert to the orginal AppLauncher."); + aTask.infoAppendln("\t" + targFile); + aTask.infoAppendln("Update has FAILED and left application in an invalid state.\n"); + } + } + + return null; + } + else + { + aTask.infoAppendln("Success updating AppLauncher... "); + aTask.infoAppendln("\t" + dstFile + " ---> " + targFile + "\n"); + } + + return pickRelease; + } + +} diff --git a/src/distMaker/jre/JreRelease.java b/src/distMaker/jre/JreRelease.java index 8a9a405..327ea86 100644 --- a/src/distMaker/jre/JreRelease.java +++ b/src/distMaker/jre/JreRelease.java @@ -1,6 +1,7 @@ package distMaker.jre; import distMaker.digest.Digest; +import distMaker.utils.Version; /** * Immutable class that describes a JRE Release. @@ -9,19 +10,39 @@ import distMaker.digest.Digest; */ public class JreRelease implements Comparable { - private String platform; - private JreVersion version; - private Digest digest; - private String fileName; - private long fileLen; + private final Version alMinVer; + private final Version alMaxVer; + private final String platform; + private final JreVersion version; + private final Digest digest; + private final String fileName; + private final long fileLen; - public JreRelease(String aPlatform, String aVersion, String aFileName, Digest aDigest, long aFileLen) + public JreRelease(String aPlatform, String aVersion, String aFileName, Digest aDigest, long aFileLen, Version aAlMinVer, Version aAlMaxVer) { platform = aPlatform; version = new JreVersion(aVersion); fileName = aFileName; digest = aDigest; fileLen = aFileLen; + alMinVer = aAlMinVer; + alMaxVer = aAlMaxVer; + } + + /** + * Returns the minimum AppLauncher version compatible with this JRE release. + */ + public Version getAppLauncherMinVersion() + { + return alMinVer; + } + + /** + * Returns the maximum AppLauncher version compatible with this JRE release. + */ + public Version getAppLauncherMaxVersion() + { + return alMaxVer; } /** diff --git a/src/distMaker/jre/JreUtils.java b/src/distMaker/jre/JreUtils.java index 07ee1b3..24c5ccb 100644 --- a/src/distMaker/jre/JreUtils.java +++ b/src/distMaker/jre/JreUtils.java @@ -1,5 +1,21 @@ package distMaker.jre; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import distMaker.digest.Digest; +import distMaker.digest.DigestType; +import distMaker.utils.ParseUtils; +import distMaker.utils.PlainVersion; import glum.gui.GuiUtil; import glum.io.IoUtil; import glum.net.Credential; @@ -7,19 +23,30 @@ import glum.net.NetUtil; import glum.task.Task; import glum.util.ThreadUtil; -import java.io.*; -import java.net.URL; -import java.net.URLConnection; -import java.util.*; - -import distMaker.digest.Digest; -import distMaker.digest.DigestType; -import distMaker.platform.PlatformUtils; - public class JreUtils { /** - * Returns a list of all the available JRE releases specified in <aUpdateSiteUrl>/jre/jreCatalog.txt + * Returns the relative path a JRE should be expanded to. + *

+ * Namely legacy JRE versions (versions prior to Java 9) will be expanded to:
+ * {@code }
+ * while non legacy versions will be expanded to something like:
+ * {@code -} + */ + public static String getExpandJrePath(JreVersion aJreVersion) + { + String version; + + version = aJreVersion.getLabel(); + if (version.startsWith("1.") == true) + return "jre" + version; + else + return "jre-" + version; + } + + /** + * Returns a list of all the available JRE releases specified at:
+ * {@literal /jre/jreCatalog.txt} */ public static List getAvailableJreReleases(Task aTask, URL aUpdateSiteUrl, Credential aCredential) { @@ -28,24 +55,22 @@ public class JreUtils URLConnection connection; InputStream inStream; BufferedReader bufReader; - DigestType digestType; String errMsg, strLine; - String version; errMsg = null; retList = new ArrayList<>(); catUrl = IoUtil.createURL(aUpdateSiteUrl.toString() + "/jre/jreCatalog.txt"); // Default to DigestType of MD5 - digestType = DigestType.MD5; - version = null; + PlainVersion alMinVer = PlainVersion.Zero; + PlainVersion alMaxVer = PlainVersion.AbsMax; + DigestType digestType = DigestType.MD5; + String version = null; inStream = null; bufReader = null; try { - String[] tokens; - // Read the contents of the file connection = catUrl.openConnection(); inStream = NetUtil.getInputStream(connection, aCredential); @@ -60,13 +85,28 @@ public class JreUtils if (strLine == null) break; - tokens = strLine.split(",", 4); + // Ignore comments and empty lines if (strLine.isEmpty() == true || strLine.startsWith("#") == true) - ; // 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) + continue; + + String[] tokens; + tokens = strLine.split(",", 5); + if (tokens.length == 2 && tokens[0].equals("name") == true && tokens[1].equals("JRE") == true) ; // Nothing to do - we just entered the "JRE" section + // Logic to handle the 'exit' command + else if (tokens.length >= 1 && tokens[0].equals("exit") == true) + { + // We support exit commands with 3 tokens. All others + // we will just exit. + if (tokens.length != 3) + break; + + String targName = tokens[1]; + String needVer = tokens[2]; + if (ParseUtils.shouldExitLogic(targName, needVer) == true) + break; + } + // Logic to handle the 'digest' command else if (tokens.length == 2 && tokens[0].equals("digest") == true) { DigestType tmpDigestType; @@ -77,10 +117,57 @@ public class JreUtils else digestType = tmpDigestType; } + // Logic to handle the 'jre' command else if (tokens.length == 2 && tokens[0].equals("jre") == true) { version = tokens[1]; + + // On any new JRE version reset the default required AppLauncher versions + alMinVer = PlainVersion.Zero; + alMaxVer = new PlainVersion(0, 99, 0); } + // Logic to handle the 'require' command: JRE File + else if (tokens.length >= 3 && tokens[0].equals("require") == true) + { + String target; + + // Process the require,AppLauncher instruction + target = tokens[1]; + if (target.equals("AppLauncher") == true && (tokens.length == 3 || tokens.length == 4)) + { + alMinVer = PlainVersion.parse(tokens[2]); + alMaxVer = new PlainVersion(0, 99, 0); + if (tokens.length == 4) + alMaxVer = PlainVersion.parse(tokens[3]); + + continue; + } + + aTask.infoAppendln("Unreconized line: " + strLine); + } + // Logic to handle the 'F' command: JRE File + else if (tokens.length == 5 && 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]; + fileLen = GuiUtil.readLong(tokens[2], -1); + platform = tokens[3]; + filename = tokens[4]; + + Digest tmpDigest = new Digest(digestType, digestStr); + retList.add(new JreRelease(platform, version, filename, tmpDigest, fileLen, alMinVer, alMaxVer)); + } + // Legacy Logic to handle the 'F' command: JRE File (pre DistMaker 0.50) else if (tokens.length == 4 && tokens[0].equals("F") == true) { String platform, filename, digestStr; @@ -98,7 +185,7 @@ public class JreUtils fileLen = GuiUtil.readLong(tokens[2], -1); filename = tokens[3]; - platform = PlatformUtils.getPlatformOfJreTarGz(filename); + platform = JreUtils.getPlatformOfJreTarGz(filename); if (platform == null) { aTask.infoAppendln("Skipping input: " + strLine); @@ -106,7 +193,7 @@ public class JreUtils continue; } - retList.add(new JreRelease(platform, version, filename, new Digest(digestType, digestStr), fileLen)); + retList.add(new JreRelease(platform, version, filename, new Digest(digestType, digestStr), fileLen, alMinVer, alMaxVer)); } else { @@ -145,8 +232,8 @@ public class JreUtils } /** - * Utility method that returns a list of matching JREs. The list will be sorted in order from newest to oldest. All returned JREs will have a platform that - * matches aPlatform. + * Utility method that returns a list of matching JREs. The list will be sorted in order from newest to oldest. All + * returned JREs will have a platform that matches aPlatform. */ public static List getMatchingPlatforms(List aJreList, String aPlatform) { @@ -169,4 +256,25 @@ public class JreUtils return retList; } + /** + * Utility method that returns the platform of the JRE file. + *

+ * This only examines the filename to determine the platform. + *

+ * This method should be considered deprecated as of DistMaker 0.48 + */ + @Deprecated + private 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; + } + } diff --git a/src/distMaker/jre/JreVersion.java b/src/distMaker/jre/JreVersion.java index a443425..9d63303 100644 --- a/src/distMaker/jre/JreVersion.java +++ b/src/distMaker/jre/JreVersion.java @@ -1,14 +1,46 @@ package distMaker.jre; +import java.util.ArrayList; + +import com.google.common.collect.ImmutableList; + +import distMaker.utils.Version; import glum.gui.GuiUtil; -public class JreVersion implements Comparable +/** + * Immutable class which defines a Java version. + */ +public class JreVersion implements Comparable, Version { - private String version; + /** String used to construct this JreVersion */ + private final String label; - public JreVersion(String aVersion) + /** List of integers corresponding to the various components of the JRE version. */ + private final ImmutableList compL; + + /** Flag for legacy JRE versions. JRE versions prior to 9.0 are considered legacy. */ + private final boolean isLegacy; + + public JreVersion(String aLabel) { - version = aVersion; + label = aLabel; + + String[] tokenArr = label.split("[._]"); + ArrayList workL = new ArrayList<>(); + for (String aStr : tokenArr) + { + int tmpVal = GuiUtil.readInt(aStr, Integer.MIN_VALUE); + if (tmpVal == Integer.MIN_VALUE) + break; + + workL.add(tmpVal); + } + compL = ImmutableList.copyOf(workL); + + if (compL.size() >= 2 && compL.get(0) == 1) + isLegacy = true; + else + isLegacy = false; } /** @@ -16,7 +48,7 @@ public class JreVersion implements Comparable */ public String getLabel() { - return version; + return label; } /** @@ -29,43 +61,78 @@ public class JreVersion implements Comparable public static JreVersion getBetterVersion(JreVersion verA, JreVersion verB) { JreVersion defaultVer; - String[] tokenA, tokenB; int valA, valB, idxCnt; - tokenA = verA.getLabel().split("[._]"); - tokenB = verB.getLabel().split("[._]"); - // Default JreVersion is the version that is more specific defaultVer = null; - if (tokenA.length < tokenB.length) + if (verA.compL.size() < verB.compL.size()) defaultVer = verB; - else if (tokenB.length < tokenA.length) + else if (verB.compL.size() < verA.compL.size()) defaultVer = verA; // Set the idxCnt to the less specific JreVersion - idxCnt = tokenA.length; - if (tokenB.length < tokenA.length) - idxCnt = tokenB.length; + idxCnt = Math.min(verA.compL.size(), verB.compL.size()); - // Compare each component of the version string. Each component should be separated by '.' - // Assume each component is an integer where larger values correspond to later versions + // Compare each integral component (which originated from the label) + // Assume higher values correspond to later versions for (int c1 = 0; c1 < idxCnt; c1++) { - valA = GuiUtil.readInt(tokenA[c1], -1); - valB = GuiUtil.readInt(tokenB[c1], -1); - if (valA == -1 && valB == -1) - return null; - - if (valB == -1 || valA > valB) + valA = verA.compL.get(c1); + valB = verB.compL.get(c1); + if (valA > valB) return verA; - if (valA == -1 || valB > valA) + if (valB > valA) return verB; } - // Defaults to verA + // Defaults to the defaultVer return defaultVer; } + @Override + public int getMajorVersion() + { + if (isLegacy == true) + return compL.get(1); + + if (compL.size() >= 1) + return compL.get(0); + + return 0; + } + + @Override + public int getMinorVersion() + { + if (isLegacy == true) + { + if (compL.size() >= 3) + return compL.get(2); + return 0; + } + + if (compL.size() >= 2) + return compL.get(1); + + return 0; + } + + @Override + public int getPatchVersion() + { + if (isLegacy == true) + { + if (compL.size() >= 4) + return compL.get(3); + return 0; + } + + if (compL.size() >= 3) + return compL.get(2); + + return 0; + } + @Override public int compareTo(JreVersion aItem) { diff --git a/src/distMaker/node/AppCatalog.java b/src/distMaker/node/AppCatalog.java index b0c7f77..00c9171 100644 --- a/src/distMaker/node/AppCatalog.java +++ b/src/distMaker/node/AppCatalog.java @@ -88,7 +88,7 @@ public class AppCatalog */ public boolean isJreVersionTooNew(JreVersion aJreVer) { - // Check to make sure aJreVer is not too old + // Check to make sure aJreVer is not too new if (maxJreVer != null && JreVersion.getBetterVersion(maxJreVer, aJreVer) == aJreVer) return true; diff --git a/src/distMaker/node/AppRelease.java b/src/distMaker/node/AppRelease.java index 30aa535..0b3c68a 100644 --- a/src/distMaker/node/AppRelease.java +++ b/src/distMaker/node/AppRelease.java @@ -11,12 +11,19 @@ public class AppRelease implements Comparable, QueryItem private final String appName; private final String version; private final long buildTime; + private final String infoMsg; - public AppRelease(String aAppName, String aVersion, long aBuildTime) + public AppRelease(String aAppName, String aVersion, long aBuildTime, String aInfoMsg) { appName = aAppName; version = aVersion; buildTime = aBuildTime; + infoMsg = aInfoMsg; + } + + public AppRelease(String aAppName, String aVersion, long aBuildTime) + { + this(aAppName, aVersion, aBuildTime, null); } /** @@ -42,6 +49,16 @@ public class AppRelease implements Comparable, QueryItem { return buildTime; } + + /** + * Returns the info message associated with this release. + *

+ * Returns null if no infoMsg has been specified. + */ + public String getInfoMsg() + { + return infoMsg; + } @Override public int compareTo(AppRelease o) diff --git a/src/distMaker/platform/AppleUtils.java b/src/distMaker/platform/AppleUtils.java index 1315f55..53c8f44 100644 --- a/src/distMaker/platform/AppleUtils.java +++ b/src/distMaker/platform/AppleUtils.java @@ -13,6 +13,7 @@ import javax.xml.transform.stream.StreamResult; import org.w3c.dom.*; import distMaker.*; +import distMaker.jre.JreUtils; import distMaker.jre.JreVersion; /** @@ -54,8 +55,8 @@ public class AppleUtils /** * Utility method to update the specified version in the plist file (pFile) 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. TODO: Consider reducing brittleness. + * 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. */ @@ -68,8 +69,8 @@ public class AppleUtils /** * Utility method to update the specified version in the plist file (pFile) 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. TODO: Consider reducing brittleness. + * 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. */ @@ -170,7 +171,7 @@ public class AppleUtils // Update the pFile String regex = "(.*?)"; - String repStr = "jre" + aJreVersion.getLabel() + ""; + String repStr = "" + JreUtils.getExpandJrePath(aJreVersion) + ""; if (targLineNum == -1) throw new ErrorDM("[" + pFile + "] The pFile does not specify a 'JVMRuntime' section."); else if (targLineNum >= inputList.size()) @@ -183,10 +184,11 @@ public class AppleUtils } /** - * Utility method to update the specified max memory (-Xmx) value in the system plist file to the specified maxMemVal. + * 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. + * 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. */ @@ -197,10 +199,11 @@ public class AppleUtils } /** - * Utility method to update the specified max memory (-Xmx) value in the plist file (pFile) to the specified maxMemVal. + * 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. + * 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. */ diff --git a/src/distMaker/platform/LinuxUtils.java b/src/distMaker/platform/LinuxUtils.java index ab0dcf8..6b22a59 100644 --- a/src/distMaker/platform/LinuxUtils.java +++ b/src/distMaker/platform/LinuxUtils.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import distMaker.*; +import distMaker.jre.JreUtils; import distMaker.jre.JreVersion; public class LinuxUtils @@ -15,8 +16,8 @@ public class LinuxUtils *

* 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. + * 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. */ @@ -45,7 +46,7 @@ public class LinuxUtils 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! + // Ensure the file is executable. If this is 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."); @@ -107,7 +108,7 @@ public class LinuxUtils // Update the script if (targLineNum != -1) - inputList.set(targLineNum, "javaExe=../jre" + aJreVersion.getLabel() + "/bin/java"); + inputList.set(targLineNum, "javaExe=../" + JreUtils.getExpandJrePath(aJreVersion) + "/bin/java"); else throw new ErrorDM("[" + aScriptFile + "] The script does not specify 'javaExe'."); @@ -118,11 +119,11 @@ public class LinuxUtils /** * 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. + * 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. + * 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. */ @@ -135,11 +136,11 @@ public class LinuxUtils /** * 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. + * 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. + * 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. */ diff --git a/src/distMaker/platform/MemUtils.java b/src/distMaker/platform/MemUtils.java index e00094d..ac6673c 100644 --- a/src/distMaker/platform/MemUtils.java +++ b/src/distMaker/platform/MemUtils.java @@ -15,8 +15,8 @@ public class MemUtils public static final long GB_SIZE = 1024 * 1024 * 1024; /** - * Utility method that attempts to compute the installed system memory (ram). If the installed system ram can not be computed, then the system is assumed to - * have 4 GB. + * Utility method that attempts to compute the installed system memory (ram). If the installed system ram can not be + * computed, then the system is assumed to have 4 GB. */ public static long getInstalledSystemMemory() { @@ -57,7 +57,8 @@ public class MemUtils } /** - * Utility method that takes an inputStr, locates the fragment -Xmx*, and replaces the fragment with the appropriate -Xmx with respect to numBytes. + * Utility method that takes an inputStr, locates the fragment -Xmx*, and replaces the fragment with the appropriate + * -Xmx with respect to numBytes. *

* This method is a bit brittle in that it assumes the -Xmx string is surrounded with 1 white space character. *

diff --git a/src/distMaker/platform/PlatformUtils.java b/src/distMaker/platform/PlatformUtils.java index ac9e6ab..96c137a 100644 --- a/src/distMaker/platform/PlatformUtils.java +++ b/src/distMaker/platform/PlatformUtils.java @@ -4,8 +4,7 @@ import java.io.File; import distMaker.DistUtils; import distMaker.ErrorDM; -import distMaker.jre.JreRelease; -import distMaker.jre.JreVersion; +import distMaker.jre.*; import distMaker.node.AppRelease; public class PlatformUtils @@ -45,22 +44,22 @@ public class PlatformUtils */ public static File getJreLocation(JreVersion aJreVersion) { - String platform, versionStr; - File jrePath, installPath; + String platform, relJrePathStr; + File installPath, retJrePath; - jrePath = null; + retJrePath = null; installPath = DistUtils.getAppPath(); - versionStr = aJreVersion.getLabel(); + relJrePathStr = JreUtils.getExpandJrePath(aJreVersion); platform = PlatformUtils.getPlatform().toUpperCase(); if (platform.equals("APPLE") == true) - jrePath = new File(installPath.getParentFile(), "PlugIns/jre" + versionStr); + retJrePath = new File(installPath.getParentFile(), "PlugIns/" + relJrePathStr); else if (platform.equals("LINUX") == true) - jrePath = new File(installPath.getParentFile(), "jre" + versionStr); + retJrePath = new File(installPath.getParentFile(), relJrePathStr); else if (platform.equals("WINDOWS") == true) - jrePath = new File(installPath.getParentFile(), "jre" + versionStr); + retJrePath = new File(installPath.getParentFile(), relJrePathStr); - return jrePath; + return retJrePath; } /** @@ -81,24 +80,6 @@ public class PlatformUtils 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. *

@@ -107,7 +88,7 @@ public class PlatformUtils * On failure this method will throw an exception of type ErrorDM. * * @param aJrePath - * Path to top of the JRE. + * Path to top of the JRE. */ public static void setJreVersion(JreVersion aJreVersion) { @@ -133,7 +114,7 @@ public class PlatformUtils * On failure this method will throw an exception of type ErrorDM. * * @param maxMemSize - * Maximum heap memory in bytes. + * Maximum heap memory in bytes. */ public static void setMaxHeapMem(long maxMemSize) { diff --git a/src/distMaker/utils/DeployUtils.java b/src/distMaker/utils/DeployUtils.java index 0e83b81..acc1416 100644 --- a/src/distMaker/utils/DeployUtils.java +++ b/src/distMaker/utils/DeployUtils.java @@ -84,7 +84,7 @@ public final class DeployUtils { updateButton.addActionListener(new ActionListener() { @Override - public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) + public void actionPerformed(ActionEvent e) { UpdateCheckListener listener = new UpdateCheckListener() { @Override @@ -105,7 +105,7 @@ public final class DeployUtils { configureMemoryButton.addActionListener(new ActionListener() { @Override - public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) + public void actionPerformed(ActionEvent e) { if(DistUtils.isDevelopersEnvironment()) { JOptionPane.showMessageDialog(parentFrame, "Cannot configure memory in a developer environment."); diff --git a/src/distMaker/utils/ParseUtils.java b/src/distMaker/utils/ParseUtils.java new file mode 100644 index 0000000..e805322 --- /dev/null +++ b/src/distMaker/utils/ParseUtils.java @@ -0,0 +1,64 @@ +package distMaker.utils; + +import distMaker.DistUtils; + +public class ParseUtils +{ + + /** + * Utility method that processes the 'exit' instruction. + *

+ * Returns true if the processing of the configuration file should exit. + *

+ * Processing of the configuration file should exit if the specified needed version is not met or the version string + * could not be parsed into major minor components. + * + * @param aTargName + * The target component whose version will be evaluated. Current supported values are one of the following: + * [AppLauncher, DistMaker] + * @param aNeededVer + * A string describing the minimum version that is required in order for this exit instruction to be ignored. + * @return Returns true if the needed version requirements are not met. + */ + public static boolean shouldExitLogic(String aTargName, String aNeededVer) + { + // We handle logic for the following targets: [AppLauncher, DistMaker] + // If not one of the specified targets then further parsing should stop + Version evalVer; + if (aTargName.equals("DistMaker") == true) + evalVer = DistUtils.getDistMakerVersion(); + else if (aTargName.equals("AppLauncher") == true) + evalVer = DistUtils.getAppLauncherVersion(); + else + return true; + + // Determine the needed version + int needMajorVer = Integer.MAX_VALUE; + int needMinorVer = Integer.MAX_VALUE; + try + { + String[] versionArr; + + versionArr = aNeededVer.split("\\."); + if (versionArr.length >= 1) + needMajorVer = Integer.parseInt(versionArr[0]); + if (versionArr.length >= 2) + needMinorVer = Integer.parseInt(versionArr[1]); + } + catch(Throwable aExp) + { + // Ignore just assume version components are whatever we managed to parse + } + Version needVer; + needVer = new PlainVersion(needMajorVer, needMinorVer, 0); + + // Exit the logic if the needVer > evalVer + if (needVer.getMajorVersion() > evalVer.getMajorVersion()) + return true; + if (needVer.getMajorVersion() == needVer.getMajorVersion() && needVer.getMinorVersion() > evalVer.getMinorVersion()) + return true; + + return false; + } + +} diff --git a/src/distMaker/utils/PlainVersion.java b/src/distMaker/utils/PlainVersion.java new file mode 100644 index 0000000..555a69d --- /dev/null +++ b/src/distMaker/utils/PlainVersion.java @@ -0,0 +1,72 @@ +package distMaker.utils; + +/** + * Provides the standard implementation of the Version interface. + */ +public class PlainVersion implements Version +{ + // Constants + public static PlainVersion AbsMin = new PlainVersion(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE); + public static PlainVersion AbsMax = new PlainVersion(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + public static PlainVersion Zero = new PlainVersion(0, 0, 0); + + // Attributes + private final int major; + private final int minor; + private final int patch; + + public PlainVersion(int aMajor, int aMinor, int aPatch) + { + major = aMajor; + minor = aMinor; + patch = aPatch; + } + + /** + * Forms a PlainVersion from the specified string. The version should have have at most 3 integer components + * separated by the char: '.'. Any extra components after the first 3 will be ignored. A NumberFormatException will + * be thrown if the type of any of the first 3 are not integers. + */ + public static PlainVersion parse(String aStr) + { + String[] tokenArr = aStr.split("\\."); + + int major = 0, minor = 0, patch = 0; + major = Integer.parseInt(tokenArr[0]); + if (tokenArr.length >= 2) + minor = Integer.parseInt(tokenArr[1]); + if (tokenArr.length >= 3) + patch = Integer.parseInt(tokenArr[2]); + + return new PlainVersion(major, minor, patch); + } + + @Override + public int getMajorVersion() + { + return major; + } + + @Override + public int getMinorVersion() + { + return minor; + } + + @Override + public int getPatchVersion() + { + return patch; + } + + @Override + public String toString() + { + String retStr = "" + major + "." + minor; + if (patch != 0) + retStr += "." + patch; + + return retStr; + } + +} diff --git a/src/distMaker/utils/Version.java b/src/distMaker/utils/Version.java new file mode 100644 index 0000000..8bf3ed0 --- /dev/null +++ b/src/distMaker/utils/Version.java @@ -0,0 +1,29 @@ +package distMaker.utils; + +/** + * Interface which provides access to version components (major, minor, patch). + *

+ * Each component is modeled as an integer and it is assumed that higher values correspond to more developed software. + *

+ * Reference: https://semver.org/ + *

+ * Implementors of this interface should be immutable. + */ +public interface Version +{ + /** + * Returns the major version component. + */ + public int getMajorVersion(); + + /** + * Returns the minor version component. + */ + public int getMinorVersion(); + + /** + * Returns the patch version component. + */ + public int getPatchVersion(); + +} diff --git a/src/distMaker/utils/VersionUtils.java b/src/distMaker/utils/VersionUtils.java new file mode 100644 index 0000000..1aadef2 --- /dev/null +++ b/src/distMaker/utils/VersionUtils.java @@ -0,0 +1,94 @@ +package distMaker.utils; + +/** + * Utility class that allows for comparing Versions. + *

+ * Eventually when Java allows operator overloading then this class can go away since the standard mathematical + * comparison symbols would be much clearer. + */ +public class VersionUtils +{ + /** + * Utility method that returns true if aVerA occurs after aVerB + */ + public static boolean isAfter(Version aVerA, Version aVerB) + { + int majorA = aVerA.getMajorVersion(); + int minorA = aVerA.getMinorVersion(); + int patchA = aVerA.getPatchVersion(); + int majorB = aVerB.getMajorVersion(); + int minorB = aVerB.getMinorVersion(); + int patchB = aVerB.getPatchVersion(); + + if (majorA > majorB) + return true; + if (majorA == majorB && minorA > minorB) + return true; + if (majorA == majorB && minorA == minorB && patchA > patchB) + return true; + + return false; + } + + /** + * Utility method that returns true if aVerA occurs after aVerB + */ + public static boolean isAfterOrEquar(Version aVerA, Version aVerB) + { + // Delegate to isAfter + return isAfter(aVerB, aVerA) == false; + } + + /** + * Utility method that returns true if the following statement is true: + *

+ * aVerEval >= aVerMin && aVerEval <= aVerMax + *

+ * A LogicError will be thrown if the aVerMin and aVerMax are inverted (aVerMin > aVerMax) + */ + public static boolean isInRange(Version aVerEval, Version aVerMin, Version aVerMax) + { + // Ensure the endpoints are not inverted + if (isAfter(aVerMin, aVerMax) == true) + throw new RuntimeException("Min/Max versions appear to be swapped. min: " + aVerMin + " max: " + aVerMax); + + // Decompose and delegate + if (isAfter(aVerMin, aVerEval) == true) + return false; + if (isAfter(aVerEval, aVerMax) == true) + return false; + + return true; + } + + /** + * Utility method to allow the comparison of two versions. + * + * @param aVerA + * @param aVerB + * @return + */ + public static int compare(Version aVerA, Version aVerB) + { + + int majorA = aVerA.getMajorVersion(); + int minorA = aVerA.getMinorVersion(); + int patchA = aVerA.getPatchVersion(); + int majorB = aVerB.getMajorVersion(); + int minorB = aVerB.getMinorVersion(); + int patchB = aVerB.getPatchVersion(); + + int cmpVal; + cmpVal = majorA - majorB; + if (cmpVal != 0) + return cmpVal; + cmpVal = minorA - minorB; + if (cmpVal != 0) + return cmpVal; + cmpVal = patchA - patchB; + if (cmpVal != 0) + return cmpVal; + + return 0; + } +} diff --git a/template/appLauncher.jar b/template/appLauncher.jar index a1ad704..256bda4 100644 Binary files a/template/appLauncher.jar and b/template/appLauncher.jar differ diff --git a/tools/buildRelease b/tools/buildRelease index 6af0543..46a9428 100755 --- a/tools/buildRelease +++ b/tools/buildRelease @@ -1,30 +1,14 @@ #! /usr/bin/env python -import fnmatch +import argparse import glob import os -import subprocess import shutil import signal +import subprocess import sys -import time -# Globals -# The default version of DistMaker -version = '0.47' - - -def logAndPrint(message="", indent=0, showTime=False): - while indent > 0: - indent -= 1 - message = ' ' + message - if showTime == True: - message = '[' + getCurrTimeStr() + '] ' + message; -# logging.info(message) - print(message) - - -def buildRelease(version, doNotClean=False): +def getDistMakerVersion(): # Retrieve the install path installPath = getInstallRoot() installPath = os.path.dirname(installPath) @@ -32,15 +16,38 @@ def buildRelease(version, doNotClean=False): # Check for distMaker.jar library prerequisite testPath = os.path.join(installPath, 'lib', 'distMaker.jar') if os.path.exists(testPath) == False: - logAndPrint('Aborting DistMaker release build. The file ' + testPath + ' does not exist.', indent=1) - logAndPrint('Please run the buildDistMakerBin.jardesc from your workspace.', indent=1) + errPrintln('Aborting DistMaker release build. The file ' + testPath + ' does not exist.', indent=1) + errPrintln('Please run the buildDistMakerBin.jardesc from your workspace.', indent=1) exit(-1) + try: + exeCmd = ['java', '-cp', 'lib/distMaker.jar', 'distMaker.DistApp', '--version'] + output = subprocess.check_output(exeCmd).decode('utf-8') + version = output.split()[1] + return version + except: + errPrintln('Please run the buildDistMakerBin.jardesc from your workspace.', indent=1) + exit(-1) + + +def errPrintln(message="", indent=0): + """Print the specified string with a trailing newline to stderr.""" + while indent > 0: + indent -= 1 + message = ' ' + message + sys.stderr.write(message + '\n') + + +def buildRelease(version, doNotClean=False): + # Retrieve the install path + installPath = getInstallRoot() + installPath = os.path.dirname(installPath) + # Check for static jre prerequisites isPass = os.path.exists(os.path.join(installPath, 'jre')) if isPass == False: - logAndPrint('Aborting DistMaker release build. The jre path is not properly configured.', indent=1) - logAndPrint('Please setup the jre path properly. A quick fix is to copy the jre tree from a previous release of DistMaker.', indent=1) + errPrintln('Aborting DistMaker release build. The jre path is not properly configured.', indent=1) + errPrintln('Please setup the jre path properly. A quick fix is to copy the jre tree from a previous release of DistMaker.', indent=1) exit(-1) # Determine the workPath @@ -49,12 +56,12 @@ def buildRelease(version, doNotClean=False): # Bail if the work folder for which we compose the release already exists if os.path.exists(workPath) == True: - logAndPrint('Aborting DistMaker release build. Release folder already exists: ' + workPath, indent=1) + errPrintln('Aborting DistMaker release build. Release folder already exists: ' + workPath, indent=1) exit(-1) # Bail if the release already exists if os.path.exists(destFileGZ) == True: - logAndPrint('Aborting DistMaker release build. Release already exists. File: ' + destFileGZ, indent=1) + errPrintln('Aborting DistMaker release build. Release already exists. File: ' + destFileGZ, indent=1) exit(-1) # Laydown the structure, and let the user know of the version we are building @@ -113,16 +120,6 @@ def getInstallRoot(): return installRoot -def printUsage(): - scriptName = os.path.split(sys.argv[0])[1] - print(scriptName + ' [-help,-doNotClean,-full],[-version ]') - print(' -help: Show this help message and exit.') - print(' -version: Version to build. Default is: ' + version) - print(' -doNotClean: Do NOT remove temporary work folder created while generating release.') - print(' -full: Force a full build of the main jar file. (Unsupported action)') - exit(-1) - - def handleSignal(signal, frame): """Signal handler, typically used to capture ctrl-c.""" print('User aborted processing!') @@ -130,43 +127,31 @@ def handleSignal(signal, frame): if __name__ == "__main__": - argv = sys.argv - argc = len(argv) - - # Intercept any request for a help message and bail - for aArg in argv: - if aArg == '-h' or aArg == '-help': - printUsage() - exit() - - doFullBuild = False - doNotClean = False - skipNext = False; - for currIdx in xrange(1, len(argv)): - aArg = argv[currIdx] - if skipNext == True: - skipNext = False - continue - if aArg == '-full': - doFullBuild = True - elif aArg == '-doNotClean': - doNotClean = True - elif aArg == '-version': - if currIdx == argc - 1: - print('\tPlease specify an actual version when using -version') - exit(-1) - else: - version = argv[currIdx + 1] - skipNext = True - else: - print(' Unrecognized argument: ' + aArg) - printUsage() - # Logic to capture Ctrl-C and bail signal.signal(signal.SIGINT, handleSignal) - # TODO: Finish this functionality - if doFullBuild == True: - print("Unsupported action: [-full]. Skipping...") + tmpDescr = 'Utility to build a DistMaker release\n' + parser = argparse.ArgumentParser(prefix_chars='-', description=tmpDescr, add_help=False, fromfile_prefix_chars='@') + parser.add_argument('-help', '-h', help='Show this help message and exit.', action='help') + parser.add_argument('-doNotClean', default=False, action='store_true', help='Do NOT remove temporary work folder created while generating release.') + parser.add_argument('-doFullBuild', default=False, action='store_true', help='Force a full build of the main jar file. (Unsupported action)') - buildRelease(version, doNotClean) + # Intercept any request for a help message and bail + argv = sys.argv; + if '-h' in argv or '-help' in argv: + parser.print_help() + exit() + + # Parse the args + parser.formatter_class.max_help_position = 50 + args = parser.parse_args() + + # Get the version of DistMaker we are building + version = getDistMakerVersion() + print('DistMaker version: ' + version) + + # TODO: Finish this functionality + if args.doFullBuild == True: + print("Unsupported action: [-doFullBuild]. Skipping...") + + buildRelease(version, args.doNotClean)