/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package loci.formats;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that allows checking for new versions of Bio-Formats, as well as
* updating to the latest stable, daily, or trunk version.
*/
public class UpgradeChecker {
// -- Constants - OMERO registry IDs --
/** Registry ID identifying usage of Bio-Formats in ImageJ. */
public static final String REGISTRY_IMAGEJ = "OMERO.imagej";
/** Registry ID identifying usage of Bio-Formats as a library in general. */
public static final String REGISTRY_LIBRARY = "OMERO.bioformats";
// -- Constants --
/** Version number of the latest stable release. */
public static final String STABLE_VERSION = "5.1.4";
/** Location of the OME continuous integration server. */
public static final String CI_SERVER = "http://ci.openmicroscopy.org";
/**
* Location of the JAR artifacts for Bio-Formats' trunk build.
*/
public static final String TRUNK_BUILD =
CI_SERVER + "/job/BIOFORMATS-5.1-latest/lastSuccessfulBuild/artifact/artifacts/";
/**
* Location of the JAR artifacts for Bio-Formats' daily build.
*/
public static final String DAILY_BUILD =
CI_SERVER + "/job/BIOFORMATS-5.1-daily/lastSuccessfulBuild/artifact/artifacts/";
/**
* Location of the JAR artifacts for the stable releases.
*/
public static final String STABLE_BUILD =
"http://downloads.openmicroscopy.org/bio-formats/" + STABLE_VERSION + "/artifacts/";
/** Name of the ueber tools JAR. */
public static final String TOOLS = "bioformats_package.jar";
/** Name of the previous versions' tools JAR. */
public static final String OLD_TOOLS = "loci_tools.jar";
/** Name of the OME tools JAR. */
public static final String OME_TOOLS = "ome_tools.jar";
/** Names of the individual JARs. */
public static final String[] INDIVIDUAL_JARS = new String[] {
"formats-api.jar", "formats-bsd.jar", "formats-gpl.jar",
"jai_imageio.jar", "common.jar", "mdbtools-java.jar", "metakit.jar",
"ome-xml.jar", "ome-poi.jar"
};
/** Location of the OME registry. */
public static final String REGISTRY = "http://upgrade.openmicroscopy.org.uk";
/** Value of "bioformats.caller" for Bio-Formats utilities. */
public static final String DEFAULT_CALLER = "Bio-Formats utilities";
/** Properties that are sent to the OME registry. */
private static final String[] REGISTRY_PROPERTIES = new String[] {
"version", "os.name", "os.version", "os.arch", "java.runtime.version",
"java.vm.vendor", "bioformats.caller"
};
/** System property to set once the upgrade check is performed. */
private static final String UPGRADE_CHECK_PROPERTY =
"bioformats_upgrade_check";
/** System property indicating whether the upgrade check is ever allowed. */
private static final String UPGRADE_CHECK_ALLOWED_PROPERTY =
"bioformats_can_do_upgrade_check";
/** Number of bytes to read from the CI server at a time. */
private static final int CHUNK_SIZE = 8192;
private static final Logger LOGGER =
LoggerFactory.getLogger(UpgradeChecker.class);
// -- UpgradeChecker API methods --
/**
* Return true if an upgrade check has already been performed in this
* JVM session.
*/
public boolean alreadyChecked() {
String checked = System.getProperty(UPGRADE_CHECK_PROPERTY);
if (checked == null) {
return false;
}
return Boolean.parseBoolean(checked);
}
/**
* Return whether or not we are ever allowed to perform an upgrade check.
*/
public boolean canDoUpgradeCheck() {
String checked = System.getProperty(UPGRADE_CHECK_ALLOWED_PROPERTY);
if (checked == null) {
return true;
}
return Boolean.parseBoolean(checked);
}
/**
* Set whether or not we are ever allowed to perform an upgrade check.
*/
public void setCanDoUpgradeCheck(boolean canDo) {
System.setProperty(UPGRADE_CHECK_ALLOWED_PROPERTY, String.valueOf(canDo));
}
/**
* Contact the OME registry and return true if a new version is available.
* OMERO.registry will identify this as a generic library usage of
* Bio-Formats (i.e. not associated with a specific client application).
*
* @param caller name of the calling application, e.g. "MATLAB"
*/
public boolean newVersionAvailable(String caller) {
return newVersionAvailable(REGISTRY_LIBRARY, caller);
}
/**
* Contact the OME registry and return true if a new version is available.
*
* @param registryID how the application identifies itself to OMERO.registry
* @see #REGISTRY_IMAGEJ
* @see #REGISTRY_LIBRARY
* @param caller name of the calling application, e.g. "MATLAB"
*/
public boolean newVersionAvailable(String registryID, String caller) {
if (!canDoUpgradeCheck()) {
return false;
}
// build the registry query
System.setProperty("bioformats.caller", caller);
StringBuffer query = new StringBuffer(REGISTRY);
for (int i=0; i<REGISTRY_PROPERTIES.length; i++) {
if (i == 0) {
query.append("?");
}
else {
query.append(";");
}
query.append(REGISTRY_PROPERTIES[i]);
query.append("=");
if (i == 0) {
query.append(FormatTools.VERSION);
}
else {
try {
query.append(URLEncoder.encode(
System.getProperty(REGISTRY_PROPERTIES[i]), "UTF-8"));
}
catch (UnsupportedEncodingException e) {
LOGGER.warn("Failed to append query argument: " +
REGISTRY_PROPERTIES[i], e);
}
}
}
System.setProperty(UPGRADE_CHECK_PROPERTY, "true");
try {
// connect to the registry
URLConnection conn = new URL(query.toString()).openConnection();
conn.setConnectTimeout(5000);
conn.setUseCaches(false);
conn.addRequestProperty("User-Agent", registryID);
conn.connect();
// retrieve the string from the registry
InputStream in = conn.getInputStream();
StringBuffer sb = new StringBuffer();
while (true) {
int data = in.read();
if (data == -1) {
break;
}
sb.append((char) data);
}
in.close();
// check if the string is not empty (upgrade available)
String result = sb.toString();
if (sb.length() == 0) {
LOGGER.debug("No update needed");
return false;
} else {
LOGGER.debug("UPGRADE AVAILABLE:" + result);
return true;
}
}
catch (IOException e) {
LOGGER.warn("Failed to compare version numbers", e);
}
return false;
}
/**
* Download and install all of the individual JAR files into the given
* directory.
*
* @param urlDir the location from which to download the JAR files
* @param downloadDir the directory into which to save the JAR files
* @return true if installation was successfull
*
* @see #install(String, String)
*/
public boolean installIndividualJars(String urlDir, String downloadDir) {
boolean overallSuccess = true;
for (String jar : INDIVIDUAL_JARS) {
boolean success = install(urlDir + File.separator + jar,
downloadDir + File.separator + jar);
if (overallSuccess) {
success = overallSuccess;
}
}
return overallSuccess;
}
/**
* Download and install a JAR file from the given URL.
*
* @param urlPath the location from which to download the JAR
* @param downloadPath the location in which to write the JAR;
* if this location already exists, it will be overwritten
* @return true if installation was successful
*/
public boolean install(String urlPath, String downloadPath) {
// if an old version exists, then remove it
File jar = new File(downloadPath + ".tmp");
if (jar.exists()) {
LOGGER.debug("Removing {}", jar.getAbsolutePath());
if (!jar.delete()) {
LOGGER.warn("Failed to delete '{}'", jar.getAbsolutePath());
return false;
}
}
// download new version
try {
LOGGER.debug("Attempting to download {}", urlPath);
URL url = new URL(urlPath);
URLConnection urlConn = url.openConnection();
int total = urlConn.getContentLength();
byte[] buf = new byte[total];
LOGGER.debug("File length: {} bytes", total);
DataInputStream in = new DataInputStream(
new BufferedInputStream(urlConn.getInputStream()));
int off = 0;
while (off < total) {
int len = CHUNK_SIZE;
if (off + len > total) {
len = total - off;
}
int r = in.read(buf, off, len);
if (r <= 0) {
LOGGER.warn("Truncated JAR file");
return false;
}
off += r;
}
in.close();
// write the downloaded JAR to a file on disk
LOGGER.debug("Writing downloaded bytes to {}", jar.getAbsolutePath());
FileOutputStream out = new FileOutputStream(jar);
out.write(buf);
out.close();
// remove the old bundle jar if the new bundle jar was downloaded
File downloadFile = new File(downloadPath);
File oldFile = new File(downloadFile.getParent(), OLD_TOOLS);
if (oldFile.exists() && downloadFile.getName().equals(TOOLS)) {
LOGGER.debug("Deleting {}", oldFile.getAbsolutePath());
oldFile.delete();
}
LOGGER.debug("Renaming {} to {}", jar.getAbsolutePath(), downloadPath);
boolean success = jar.renameTo(downloadFile);
if (!success) {
LOGGER.warn("Failed to rename '{}' to '{}'", jar.getAbsolutePath(),
downloadPath);
}
return success;
}
catch (IOException e) {
LOGGER.warn("Failed to download from " + urlPath, e);
}
return false;
}
}