/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander;
import java.io.InputStream;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.mucommander.commons.file.FileFactory;
/**
* Retrieves information about the latest release of muCommander.
* <p>
* The {@link com.mucommander.RuntimeConstants#VERSION_URL} URL contains information
* about the latest release of muCommander:<br>
* - date of latest release.<br>
* - latest official version.<br>
* - where to download the latest version from.<br>
* This class is used to access those informations and compare them with what is known
* of the current one, making it possible to notify users of new releases.
* </p>
* <p>
* Checking for new releases is a fairly straightforward process, and can be done
* with a few lines of code:
* <pre>
* VersionChecker version;
*
* try {
* version = VersionChecker.getInstance();
* if(version.isNewVersionAvailable())
* System.out.println("A new version of muCommander is available");
* else
* System.out.println("You've got the latest muCommander version");
* }
* catch(Exception e) {System.err.println("An error occured.");}
* </pre>
* </p>
* <p>
* muCommander is considered up to date if:<br>
* - the {@link com.mucommander.RuntimeConstants#VERSION local version} is
* not smaller than the remote one.<br>
* - the {@link com.mucommander.RuntimeConstants#BUILD_DATE local release date} is
* not smaller than the remote one.<br>
* While comparing release dates seems a bit odd - after all, if a new version is release,
* a new version number should be created. However, it's possible to download development
* versions of the current release, and those might be updated almost daily. Comparing dates
* makes it possible to automate the whole process without having to worry about out version
* numbers growing silly.
* </p>
* @author Maxence Bernard, Nicolas Rinaudo
*/
public class VersionChecker extends DefaultHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(VersionChecker.class);
// - XML structure ----------------------------------------------------------
// --------------------------------------------------------------------------
/** Root XML element. */
public static final String ROOT_ELEMENT = "mucommander";
/** Version XML element. */
public static final String VERSION_ELEMENT = "latest_version";
/** Download URL XML element. */
public static final String DOWNLOAD_URL_ELEMENT = "download_url";
/** JAR URL XML element. */
public static final String JAR_URL_ELEMENT = "jar_url";
/** Date XML element. */
public static final String DATE_ELEMENT = "release_date";
// - XML parsing states -----------------------------------------------------
// --------------------------------------------------------------------------
/** Currently parsing the version tag. */
public static final int STATE_VERSION = 1;
/** Currently parsing the download URL tag. */
public static final int STATE_DOWNLOAD_URL = 2;
/** Currently parsing the download URL tag. */
public static final int STATE_JAR_URL = 3;
/** Currently parsing the date tag. */
public static final int STATE_DATE = 4;
/** We're not quite sure what we're parsing. */
public static final int STATE_UNKNOWN = 5;
// - Instance fields --------------------------------------------------------
// --------------------------------------------------------------------------
/** Remote version number. */
private String latestVersion;
/** Where to download the latest version. */
private String downloadURL;
/** URL to the latest JAR file. */
private String jarURL;
/** Remote release date. */
private String releaseDate;
/** Current state the parser is in. */
private int state;
// - Initialisation ---------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Creates a new version checker instance.
*/
private VersionChecker() {}
/**
* Retrieves a description of the latest released version of muCommander.
* @return a description of the latest released version of muCommander.
* @exception Exception thrown if any error happens while retrieving the remote version.
*/
public static VersionChecker getInstance() throws Exception {
VersionChecker instance;
InputStream in; // Input stream on the remote XML file.
LOGGER.debug("Opening connection to " + RuntimeConstants.VERSION_URL);
// Parses the remote XML file using UTF-8 encoding.
in = FileFactory.getFile(RuntimeConstants.VERSION_URL).getInputStream();
try {
SAXParserFactory.newInstance().newSAXParser().parse(in, instance = new VersionChecker());
}
catch(Exception e) {
LOGGER.debug("Failed to read version XML file at "+RuntimeConstants.VERSION_URL, e);
throw e;
}
finally {
in.close();
}
// Makes sure we retrieved the information we were looking for.
// We're not checking the release date as older version of muCommander
// didn't use it.
if(instance.latestVersion == null || instance.latestVersion.equals("") ||
instance.downloadURL == null || instance.downloadURL.equals(""))
throw new Exception();
return instance;
}
// - Remote version information ---------------------------------------------
// --------------------------------------------------------------------------
/**
* Checks whether the remote version is newer than the current one.
* @return <code>true</code> if the remote version is newer than the current one,
* <code>false</code> otherwise.
*/
public boolean isNewVersionAvailable() {
// If the local and remote versions are the same, compares release dates.
if(latestVersion.equals(RuntimeConstants.VERSION.trim().toLowerCase())) {
// This ensures backward compatiblity - if the remote version file does not contain
// release date information, ignore it.
if(releaseDate.equals(""))
return true;
// Checks whether the remote release date is later than the current release date.
return releaseDate.compareTo(RuntimeConstants.BUILD_DATE) > 0;
}
return true;
}
/**
* Returns the version number of the latest muCommander release.
* @return the version number of the latest muCommander release.
*/
public String getLatestVersion() {return latestVersion;}
/**
* Returns the URL at which the latest version of muCommander can be downloaded.
* @return the URL at which the latest version of muCommander can be downloaded.
*/
public String getDownloadURL() {return downloadURL;}
/**
* Returns the URL to the latest JAR file, <code>null</code> if not available.
* @return the URL to the latest JAR file.
*/
public String getJarURL() {return jarURL;}
/**
* Returns the date at which the latest version of muCommander has been released.
* <p>
* The date format is YYYYMMDD.
* </p>
* @return the date at which the latest version of muCommander has been released.
*/
public String getReleaseDate() {return releaseDate;}
// - XML parsing ------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Called when the XML document parsing has started.
*/
@Override
public void startDocument() {
latestVersion = "";
downloadURL = "";
jarURL = "";
releaseDate = "";
}
/**
* Notifies the parser of CDATA.
*/
@Override
public void characters(char[] ch, int offset, int length) {
switch(state) {
case STATE_VERSION:
latestVersion += new String(ch, offset, length);
break;
case STATE_DOWNLOAD_URL:
downloadURL += new String(ch, offset, length);
break;
case STATE_JAR_URL:
jarURL += new String(ch, offset, length);
break;
case STATE_DATE:
releaseDate += new String(ch, offset, length);
break;
}
}
/**
* Notifies the parser that a new tag is starting.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// Checks whether we know the tag and updates the current state.
if(qName.equals(VERSION_ELEMENT))
state = STATE_VERSION;
else if(qName.equals(DOWNLOAD_URL_ELEMENT))
state = STATE_DOWNLOAD_URL;
else if(qName.equals(JAR_URL_ELEMENT))
state = STATE_JAR_URL;
else if(qName.equals(DATE_ELEMENT))
state = STATE_DATE;
else
state = STATE_UNKNOWN;
}
/**
* Notifies the parser that the current element is finished.
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {state = STATE_UNKNOWN;}
/**
* Notifies the parser that XML parsing is finished.
*/
@Override
public void endDocument() {
// Make sure we're not keep meaningless whitecase characters in the data.
latestVersion = latestVersion.toLowerCase().trim();
downloadURL = downloadURL.trim();
jarURL = jarURL.trim();
if("".equals(jarURL))
jarURL = null;
releaseDate = releaseDate.trim();
// Logs the data if in debug mode.
LOGGER.debug("download URL: " + downloadURL);
LOGGER.debug("jar URL: " + jarURL);
LOGGER.debug("latestVersion: " + latestVersion);
LOGGER.debug("releaseDate: " + releaseDate);
}
}