package esmska.update; import esmska.data.Config; import esmska.data.DeprecatedGateway; import esmska.data.Gateway; import esmska.data.Gateways; import esmska.data.Links; import esmska.data.event.ActionEventSupport; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingWorker; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathFactory; import org.apache.commons.io.IOUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** Checks for newer program or gateway version on program's website * * First of all you must run the {@link #checkForUpdates()} method, only after it * has finished the other methods will give you correct answer about updates * availability. * * @author ripper */ public class UpdateChecker { private static UpdateChecker instance; /** new program version is available */ public static final int ACTION_PROGRAM_UPDATE_AVAILABLE = 0; /** new or updated gateway script is available */ public static final int ACTION_GATEWAY_UPDATE_AVAILABLE = 1; /** new program version and gateway script is available */ public static final int ACTION_PROGRAM_AND_GATEWAY_UPDATE_AVAILABLE = 2; /** no updates found */ public static final int ACTION_NO_UPDATE_AVAILABLE = 3; /** could not check updates - network error? */ public static final int ACTION_CHECK_FAILED = 4; /** The interval in seconds how often to check for updates automatically */ public static final int AUTO_CHECK_INTERVAL = 2 * 60 * 60; // 2 hours private static final Logger logger = Logger.getLogger(UpdateChecker.class.getName()); private static final Config config = Config.getInstance(); private String onlineVersion = "0.0.0"; private String onlineUnstableVersion = "0.0.0"; private HashSet<GatewayUpdateInfo> gatewayUpdates = new HashSet<GatewayUpdateInfo>(); private AtomicBoolean running = new AtomicBoolean(); // <editor-fold defaultstate="collapsed" desc="ActionEvent support"> private ActionEventSupport actionSupport = new ActionEventSupport(this); public void addActionListener(ActionListener actionListener) { actionSupport.addActionListener(actionListener); } public void removeActionListener(ActionListener actionListener) { actionSupport.removeActionListener(actionListener); } // </editor-fold> /** Disabled constructor */ private UpdateChecker() { } /** Get program instance */ public static UpdateChecker getInstance() { if (instance == null) { instance = new UpdateChecker(); } return instance; } /** Checks for updates asynchronously and notify all added listeners after being finished. * Does nothing if already running. */ public void checkForUpdates() { if (running.get()) { //do nothing if already running return; } running.set(true); logger.fine("Checking for program updates..."); final HttpDownloader downloader = new HttpDownloader(Links.CHECK_UPDATE, false); downloader.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("state") && evt.getNewValue() == SwingWorker.StateValue.DONE) { try { if (!downloader.isFinishedOk()) { throw new IOException("Could not download version file"); } //version file is downloaded ok parseVersionFile(downloader.getTextContent()); boolean updateAvailable = isProgramUpdateAvailable(); logger.fine("Found program update: " + (updateAvailable ? getLatestProgramVersion() : "false")); boolean gatewayUpdateAvailable = isGatewayUpdateAvailable(true); logger.fine("Found gateway update: " + gatewayUpdateAvailable); //send events if (updateAvailable) { if (gatewayUpdateAvailable) { actionSupport.fireActionPerformed(ACTION_PROGRAM_AND_GATEWAY_UPDATE_AVAILABLE, null); } else { actionSupport.fireActionPerformed(ACTION_PROGRAM_UPDATE_AVAILABLE, null); } } else if (gatewayUpdateAvailable) { actionSupport.fireActionPerformed(ACTION_GATEWAY_UPDATE_AVAILABLE, null); } else { actionSupport.fireActionPerformed(ACTION_NO_UPDATE_AVAILABLE, null); } } catch (Exception e) { logger.log(Level.WARNING, "Could not check for updates", e); actionSupport.fireActionPerformed(ACTION_CHECK_FAILED, null); } finally { running.set(false); } } } }); downloader.execute(); } /** Check whether downloaded file text indicates newer version available. * @param text contents of the version file */ private synchronized void parseVersionFile(String text) throws Exception { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); XPathFactory xpf = XPathFactory.newInstance(); XPath xpath = xpf.newXPath(); Document doc = db.parse(IOUtils.toInputStream(text, "UTF-8")); onlineVersion = doc.getElementsByTagName(VersionFile.TAG_LAST_VERSION). item(0).getTextContent(); onlineUnstableVersion = doc.getElementsByTagName(VersionFile.TAG_LAST_UNSTABLE_VERSION). item(0).getTextContent(); gatewayUpdates.clear(); NodeList gateways = doc.getElementsByTagName(VersionFile.TAG_GATEWAY); for (int i = 0; i < gateways.getLength(); i++) { Node gateway = gateways.item(i); String name = xpath.evaluate(VersionFile.TAG_NAME + "/text()", gateway); String fileName = xpath.evaluate(VersionFile.TAG_FILENAME + "/text()", gateway); String version = xpath.evaluate(VersionFile.TAG_VERSION + "/text()", gateway); String minVersion = xpath.evaluate(VersionFile.TAG_MIN_VERSION + "/text()", gateway); String url = xpath.evaluate(VersionFile.TAG_DOWNLOAD + "/text()", gateway); String iconUrl = xpath.evaluate(VersionFile.TAG_ICON + "/text()", gateway); GatewayUpdateInfo info = new GatewayUpdateInfo(name, fileName, version, minVersion, url, iconUrl); gatewayUpdates.add(info); } //only add new or updated gateways refreshUpdatedGateways(); } /** Go through all downloaded update information and only leave those gateways * which are new or updated compared to current ones. This can be used to reload * update info after partial update. Also removes gateways requiring more recent * program version than the currently running one and all deprecated gateways. */ private void refreshUpdatedGateways() { for (Iterator<GatewayUpdateInfo> it = gatewayUpdates.iterator(); it.hasNext(); ) { GatewayUpdateInfo info = it.next(); Gateway gw = Gateways.getInstance().get(info.getName()); if (gw != null && info.getVersion().compareTo(gw.getVersion()) <= 0) { //gateway is same or older, remove it it.remove(); continue; } if (Config.compareProgramVersions(info.getMinProgramVersion(), Config.getLatestVersion()) > 0) { //required program version is newer than the one currently running, remove it it.remove(); continue; } //check for deprecated gateways for (DeprecatedGateway depr : Gateways.getInstance().getDeprecatedGateways()) { if (info.getName().equals(depr.getName()) && info.getVersion().compareTo(depr.getVersion()) <= 0) { //gateway has been deprecated it.remove(); continue; } } } } /** Return only visible gateways updates from all of the available gateway updates. */ private Set<GatewayUpdateInfo> filterVisibleGateways() { HashSet<GatewayUpdateInfo> visible = new HashSet<GatewayUpdateInfo>(); for (Iterator<GatewayUpdateInfo> it = gatewayUpdates.iterator(); it.hasNext(); ) { GatewayUpdateInfo info = it.next(); Gateway gw = Gateways.getInstance().get(info.getName()); //add just visible gateways if (gw != null && !gw.isHidden()) { visible.add(info); } } return visible; } /** Whether checking for updates is (still) running */ public boolean isRunning() { return running.get(); } /** Whether a new program update is available. According to user preference it checks against latest stable or unstable program version. */ public synchronized boolean isProgramUpdateAvailable() { if (config.isAnnounceUnstableUpdates()) { return Config.compareProgramVersions(onlineUnstableVersion, Config.getLatestVersion()) > 0; } else { return Config.compareProgramVersions(onlineVersion, Config.getLatestVersion()) > 0; } } /** Get latest program version available online */ public synchronized String getLatestProgramVersion() { return config.isAnnounceUnstableUpdates() ? onlineUnstableVersion : onlineVersion; } /** Whether an gateway update is available */ public synchronized boolean isGatewayUpdateAvailable(boolean includeHidden) { refreshUpdatedGateways(); if (includeHidden) { return !gatewayUpdates.isEmpty(); } else { return !filterVisibleGateways().isEmpty(); } } /** Get info about gateway updates * @return unmodifiable set of gateway updates info */ public synchronized Set<GatewayUpdateInfo> getGatewayUpdates(boolean includeHidden) { refreshUpdatedGateways(); if (includeHidden) { return Collections.unmodifiableSet(gatewayUpdates); } else { return filterVisibleGateways(); } } }