package com.puttysoftware.updaterx; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import com.puttysoftware.commondialogs.CommonDialogs; public class UpdateChecker { /** * @author wrldwzrd89 * @version 8.0.0 */ // Fields and Constants private final ProductData prod; private boolean cachedDB; private final String cacheFile; private final String cacheTTLFile; private long cacheTTL; private final boolean prereleaseMode; private static final String MAC_CACHE_PREFIX = "HOME"; private static final String WIN_CACHE_PREFIX = "APPDATA"; private static final String UNIX_CACHE_PREFIX = "HOME"; private static final String MAC_CACHE_DIR = "/Library/Caches/"; private static final String WIN_CACHE_DIR = "\\Caches\\"; private static final String UNIX_CACHE_DIR = "/caches/"; private static final long CACHE_TTL_3_HOURS = 10800000L; private static final long CACHE_TTL_6_HOURS = 21600000L; private static final long CACHE_TTL_12_HOURS = 43200000L; private static final long CACHE_TTL_1_DAY = 86400000L; private static final long CACHE_TTL_2_DAYS = 172800000L; private static final long CACHE_TTL_4_DAYS = 345600000L; private static final long CACHE_TTL_1_WEEK = 604800000L; public static final long[] TTL_VALUES = new long[] { UpdateChecker.CACHE_TTL_3_HOURS, UpdateChecker.CACHE_TTL_6_HOURS, UpdateChecker.CACHE_TTL_12_HOURS, UpdateChecker.CACHE_TTL_1_DAY, UpdateChecker.CACHE_TTL_2_DAYS, UpdateChecker.CACHE_TTL_4_DAYS, UpdateChecker.CACHE_TTL_1_WEEK }; // Constructors public UpdateChecker(final ProductData data) { this.prod = data; this.cachedDB = false; final int code = this.prod.getCodeVersion(); this.prereleaseMode = code < ProductData.CODE_STABLE; String rt; if (code == ProductData.CODE_NIGHTLY) { rt = "nightly"; } else if (code == ProductData.CODE_ALPHA) { rt = "alpha"; } else if (code == ProductData.CODE_BETA) { rt = "beta"; } else if (code == ProductData.CODE_CANDIDATE) { rt = "rc"; } else if (code == ProductData.CODE_STABLE) { rt = "stable"; } else { rt = "mature"; } this.cacheFile = this.prod.getProductName() + "_" + rt + "updatecache"; this.cacheTTLFile = this.prod.getProductName() + "_" + rt + "updatettl"; this.cacheTTL = UpdateChecker.CACHE_TTL_1_DAY; } public UpdateChecker(final ProductData data, final long TTL) { this.prod = data; this.cachedDB = false; final int code = this.prod.getCodeVersion(); this.prereleaseMode = code < ProductData.CODE_STABLE; String rt; if (code == ProductData.CODE_NIGHTLY) { rt = "nightly"; } else if (code == ProductData.CODE_ALPHA) { rt = "alpha"; } else if (code == ProductData.CODE_BETA) { rt = "beta"; } else if (code == ProductData.CODE_CANDIDATE) { rt = "rc"; } else if (code == ProductData.CODE_STABLE) { rt = "stable"; } else { rt = "mature"; } this.cacheFile = this.prod.getProductName() + "_" + rt + "updatecache"; this.cacheTTLFile = this.prod.getProductName() + "_" + rt + "updatettl"; this.cacheTTL = TTL; } // Methods public void setTTL(final long TTL) { this.cacheTTL = TTL; } public void checkForUpdates() { this.checkForUpdatesInternal(); } public void checkForUpdatesAtStartup() { this.checkForUpdatesInternal(); } private void checkForUpdatesInternal() { if (this.prod.getUpdateURL() == null) { CommonDialogs.showErrorDialog( "An internal error occurred while checking for updates.", "Update Error"); } else { int newVersionMajor = this.prod.getMajorVersion(); int newVersionMinor = this.prod.getMinorVersion(); int newVersionBugfix = this.prod.getBugfixVersion(); final int newVersionCode = this.prod.getCodeVersion(); int newVersionPrerelease = this.prod.getPrereleaseVersion(); try { // Check the cache for validity this.isCacheGood(); // If the update DB isn't cached... if (!this.cachedDB) { // Generate the cache this.generateCache(); } // Read entries from the cached update DB String inputLine; try (BufferedReader in = new BufferedReader(new FileReader( this.getCacheFile()))) { inputLine = in.readLine(); newVersionMajor = Integer.parseInt(inputLine); inputLine = in.readLine(); newVersionMinor = Integer.parseInt(inputLine); inputLine = in.readLine(); newVersionBugfix = Integer.parseInt(inputLine); if (this.prereleaseMode) { inputLine = in.readLine(); newVersionPrerelease = Integer.parseInt(inputLine); } } catch (final NumberFormatException nf) { CommonDialogs .showErrorDialog( "An internal error occurred while checking for updates.", "Update Error"); } String blurb = ""; // Compare current version to most recent one if (newVersionMajor > this.prod.getMajorVersion()) { // Major update available blurb = this.readBlurb(); this.showUpdatesAvailableMessage(newVersionMajor, newVersionMinor, newVersionBugfix, blurb); } else if (newVersionMajor == this.prod.getMajorVersion() && newVersionMinor > this.prod.getMinorVersion()) { // Minor update available blurb = this.readBlurb(); this.showUpdatesAvailableMessage(newVersionMajor, newVersionMinor, newVersionBugfix, blurb); } else if (newVersionMajor == this.prod.getMajorVersion() && newVersionMinor == this.prod.getMinorVersion() && newVersionBugfix > this.prod.getBugfixVersion()) { // Bug fix update available blurb = this.readBlurb(); this.showUpdatesAvailableMessage(newVersionMajor, newVersionMinor, newVersionBugfix, blurb); } else if (newVersionMajor == this.prod.getMajorVersion() && newVersionMinor == this.prod.getMinorVersion() && newVersionBugfix == this.prod.getBugfixVersion() && newVersionCode > this.prod.getCodeVersion()) { // Release type update available blurb = this.readBlurb(); this.showUpdatesAvailableMessage(newVersionMajor, newVersionMinor, newVersionBugfix, newVersionCode, newVersionPrerelease, blurb); } else if (newVersionMajor == this.prod.getMajorVersion() && newVersionMinor == this.prod.getMinorVersion() && newVersionBugfix == this.prod.getBugfixVersion() && newVersionCode == this.prod.getCodeVersion() && newVersionPrerelease > this.prod .getPrereleaseVersion()) { // Pre-release update available blurb = this.readBlurb(); this.showUpdatesAvailableMessage(newVersionMajor, newVersionMinor, newVersionBugfix, newVersionCode, newVersionPrerelease, blurb); } } catch (final IOException ie) { CommonDialogs .showErrorDialog( "Unable to contact the update site.\n" + "Make sure you are connected to the Internet,\n" + "then try again.", "Update Error"); } } } private void showUpdatesAvailableMessage(final int major, final int minor, final int bugfix, final String blurb) { final String oldVersionString = Integer.toString(this.prod .getMajorVersion()) + "." + Integer.toString(this.prod.getMinorVersion()) + "." + Integer.toString(this.prod.getBugfixVersion()); final String newVersionString = Integer.toString(major) + "." + Integer.toString(minor) + "." + Integer.toString(bugfix); CommonDialogs.showTitledDialog("Version " + newVersionString + " is available.\nYou have version " + oldVersionString + ".", "Update Available"); if (this.prod.autoStart()) { // Apply update try { UpdateApplier.downloadAndApplyUpdate(this.prod.getUpdatePath(), this.prod.getProductName(), newVersionString, this.prod.getUpdateFile(), blurb, oldVersionString); } catch (final IOException e) { CommonDialogs .showErrorDialog( "Unable to automatically apply the update.\n" + "Make sure you are connected to the Internet,\n" + "then try again.", "Update Error"); } } } private void showUpdatesAvailableMessage(final int major, final int minor, final int bugfix, final int code, final int beta, final String blurb) { String rt, crt; if (code == ProductData.CODE_NIGHTLY) { rt = "-N"; crt = "Nightly "; } else if (code == ProductData.CODE_ALPHA) { rt = "-alpha"; crt = "Alpha "; } else if (code == ProductData.CODE_BETA) { rt = "-beta"; crt = "Beta "; } else if (code == ProductData.CODE_CANDIDATE) { rt = "-rc"; crt = "RC "; } else if (code == ProductData.CODE_STABLE) { rt = "."; crt = ""; } else { rt = "."; crt = ""; } final String oldVersionString = Integer.toString(this.prod .getMajorVersion()) + "." + Integer.toString(this.prod.getMinorVersion()) + "." + Integer.toString(this.prod.getBugfixVersion()) + rt + Integer.toString(this.prod.getPrereleaseVersion()); final String newVersionString = Integer.toString(major) + "." + Integer.toString(minor) + "." + Integer.toString(bugfix) + rt + Integer.toString(beta); CommonDialogs.showTitledDialog("Version " + newVersionString + " is available.\nYou have version " + oldVersionString + ".", crt + "Update Available"); if (this.prod.autoStart()) { // Apply update try { UpdateApplier.downloadAndApplyUpdate(this.prod.getUpdatePath(), this.prod.getProductName(), newVersionString, this.prod.getUpdateFile(), blurb, oldVersionString); } catch (final IOException e) { CommonDialogs .showErrorDialog( "Unable to automatically apply the update.\n" + "Make sure you are connected to the Internet,\n" + "then try again.", "Update Error"); } } } private String readBlurb() { // Attempt to read blurb final StringBuilder blurbBuilder = new StringBuilder(); String blurb = null; String line = ""; try (BufferedReader blurbReader = new BufferedReader( new InputStreamReader(this.prod.getBlurbURL().openStream()))) { line = blurbReader.readLine(); while (line != null) { blurbBuilder.append(line); blurbBuilder.append("\n"); line = blurbReader.readLine(); } blurb = blurbBuilder.toString(); blurbReader.close(); } catch (final IOException ie) { blurb = ""; } return blurb; } private static String getCachePrefix() { final String osName = System.getProperty("os.name"); if (osName.indexOf("Mac OS X") != -1) { // Mac OS X return System.getenv(UpdateChecker.MAC_CACHE_PREFIX); } else if (osName.indexOf("Windows") != -1) { // Windows return System.getenv(UpdateChecker.WIN_CACHE_PREFIX); } else { // Other - assume UNIX-like return System.getenv(UpdateChecker.UNIX_CACHE_PREFIX); } } private String getCacheDirectory() { final String osName = System.getProperty("os.name"); if (osName.indexOf("Mac OS X") != -1) { // Mac OS X return UpdateChecker.MAC_CACHE_DIR + "/" + this.prod.getrDNSCompanyName() + "/"; } else if (osName.indexOf("Windows") != -1) { // Windows return "\\" + this.prod.getCompanyName() + "\\" + this.prod.getProductName() + UpdateChecker.WIN_CACHE_DIR; } else { // Other - assume UNIX-like return "/." + this.prod.getProductName().toLowerCase() + UpdateChecker.UNIX_CACHE_DIR; } } private File getCacheFile() { final StringBuilder b = new StringBuilder(); b.append(UpdateChecker.getCachePrefix()); b.append(this.getCacheDirectory()); b.append(this.cacheFile); return new File(b.toString()); } private File getCacheTTLFile() { final StringBuilder b = new StringBuilder(); b.append(UpdateChecker.getCachePrefix()); b.append(this.getCacheDirectory()); b.append(this.cacheTTLFile); return new File(b.toString()); } private boolean generateCache() { final File cache = this.getCacheFile(); // Create the needed subdirectories, if they don't already exist final File cacheParent = new File(this.getCacheFile().getParent()); if (!cacheParent.exists()) { final boolean success = cacheParent.mkdirs(); if (!success) { return false; } } final String ttl = Long.toString(System.currentTimeMillis() + this.cacheTTL); final File ttlFile = this.getCacheTTLFile(); // Cache the update DB to a file try (BufferedReader in = new BufferedReader(new InputStreamReader( this.prod.getUpdateURL().openStream())); BufferedWriter out = new BufferedWriter(new FileWriter(cache)); BufferedWriter outTTL = new BufferedWriter(new FileWriter( ttlFile))) { String inputLine = ""; while (inputLine != null) { inputLine = in.readLine(); if (inputLine != null) { out.write(inputLine + "\n", 0, inputLine.length() + 1); } } in.close(); out.close(); outTTL.write(ttl, 0, ttl.length()); outTTL.close(); } catch (final IOException ioe) { return false; } this.cachedDB = true; return true; } private void isCacheGood() { final long curr = System.currentTimeMillis(); final File ttlFile = this.getCacheTTLFile(); try (BufferedReader inTTL = new BufferedReader(new FileReader(ttlFile))) { final String test = inTTL.readLine(); inTTL.close(); final long stored = Long.parseLong(test); this.cachedDB = curr <= stored; } catch (final IOException io) { this.cachedDB = false; } catch (final NumberFormatException nfe) { this.cachedDB = false; } } }