All Downloads are FREE. Search and download functionalities are using the official Maven repository.

marytts.tools.install.ComponentDescription Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2009 DFKI GmbH.
 * All Rights Reserved.  Use is subject to license terms.
 *
 * This file is part of MARY TTS.
 *
 * MARY TTS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 */

package marytts.tools.install;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Observable;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import marytts.util.MaryUtils;
import marytts.util.dom.DomUtils;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.twmacinta.util.MD5;

/**
 * @author marc
 *
 */
public class ComponentDescription extends Observable implements Comparable {
	public enum Status {
		AVAILABLE, DOWNLOADING, PAUSED, VERIFYING, DOWNLOADED, INSTALLING, CANCELLED, ERROR, INSTALLED
	};

	public static final String installerNamespaceURI = "http://mary.dfki.de/installer";
	// Max size of download buffer.
	private static final int MAX_BUFFER_SIZE = 1024;

	private String name;
	private Locale locale;
	private String version;
	private String description;
	private URL license;
	private List locations;
	private String packageFilename;
	private int packageSize;
	private String packageMD5;
	private boolean isSelected = false;
	private Status status;
	private File archiveFile;
	private File infoFile;
	private int downloaded = 0;
	private int size = -1;
	private String installedFilesNames = null; // must be != null for installed components
	private Set sharedFileNames;

	/**
	 * An available update is non-null in the following circumstance: 1. the present component (this) has status INSTALLED; 2. a
	 * component is available that has the same name and type, but a higher version number. Only the newest update will be
	 * considered, i.e. if there are more than one newer version available, only the one with the highest version number will be
	 * remembered here.
	 */
	private ComponentDescription availableUpdate = null;

	/**
	 * Replace this component definition with its available update. The object will stay the same but all data will be overwritten
	 * with the content of the update. After this method returns, isUpdateAvailable() will return false.
	 */
	public void replaceWithUpdate() {
		if (availableUpdate == null) {
			return;
		}
		this.name = availableUpdate.name;
		this.locale = availableUpdate.locale;
		this.version = availableUpdate.version;
		this.description = availableUpdate.description;
		this.license = availableUpdate.license;
		this.locations = availableUpdate.locations;
		this.packageFilename = availableUpdate.packageFilename;
		this.packageSize = availableUpdate.packageSize;
		this.packageMD5 = availableUpdate.packageMD5;
		this.isSelected = availableUpdate.isSelected;
		this.status = availableUpdate.status;
		this.archiveFile = availableUpdate.archiveFile;
		this.infoFile = availableUpdate.infoFile;
		this.downloaded = availableUpdate.downloaded;
		this.size = availableUpdate.size;
		this.installedFilesNames = availableUpdate.installedFilesNames;
		this.availableUpdate = null;
		stateChanged();
	}

	protected ComponentDescription(String name, String version, String packageFilename) {
		this.name = name;
		this.version = version;
		this.packageFilename = packageFilename;
	}

	protected ComponentDescription(Element xmlDescription) throws NullPointerException {
		this.name = xmlDescription.getAttribute("name");
		this.locale = MaryUtils.string2locale(xmlDescription.getAttribute("locale"));
		this.version = xmlDescription.getAttribute("version");
		Element descriptionElement = (Element) xmlDescription.getElementsByTagName("description").item(0);
		this.description = descriptionElement.getTextContent().trim();
		Element licenseElement = (Element) xmlDescription.getElementsByTagName("license").item(0);
		try {
			this.license = new URL(licenseElement.getAttribute("href").trim().replaceAll(" ", "%20"));
		} catch (MalformedURLException mue) {
			new Exception("Invalid license URL -- ignoring", mue).printStackTrace();
			this.license = null;
		}
		Element packageElement = (Element) xmlDescription.getElementsByTagName("package").item(0);
		packageFilename = packageElement.getAttribute("filename").trim();
		packageSize = Integer.parseInt(packageElement.getAttribute("size"));
		packageMD5 = packageElement.getAttribute("md5sum");
		NodeList locationElements = packageElement.getElementsByTagName("location");
		locations = new ArrayList(locationElements.getLength());
		for (int i = 0, max = locationElements.getLength(); i < max; i++) {
			Element aLocationElement = (Element) locationElements.item(i);
			try {
				String urlString = aLocationElement.getAttribute("href").trim().replaceAll(" ", "%20");
				boolean isFolder = true;
				if (aLocationElement.hasAttribute("folder")) {
					isFolder = Boolean.valueOf(aLocationElement.getAttribute("folder"));
				}
				if (isFolder && !urlString.endsWith(packageFilename)) {
					if (!urlString.endsWith("/")) {
						urlString += "/";
					}
					urlString += packageFilename;
				}
				locations.add(new URL(urlString));
			} catch (MalformedURLException mue) {
				new Exception("Invalid location -- ignoring", mue).printStackTrace();
			}
		}
		archiveFile = new File(System.getProperty("mary.downloadDir"), packageFilename);
		String infoFilename = packageFilename.substring(0, packageFilename.lastIndexOf('.')) + "-component.xml";
		infoFile = new File(System.getProperty("mary.installedDir"), infoFilename);
		determineStatus();
		NodeList filesElements = xmlDescription.getElementsByTagName("files");
		if (filesElements.getLength() > 0) {
			Element filesElement = (Element) filesElements.item(0);
			installedFilesNames = filesElement.getTextContent();
		}
	}

	private void determineStatus() {
		File installedDir = new File(System.getProperty("mary.installedDir", "installed"));
		File downloadDir = new File(System.getProperty("mary.downloadDir", "download"));

		if (infoFile.exists()) {
			status = Status.INSTALLED;
		} else if (archiveFile.exists()) {
			if (archiveFile.length() == packageSize) {
				status = Status.DOWNLOADED;
			} else {
				status = Status.AVAILABLE;
			}
		} else if (locations.size() > 0) {
			status = Status.AVAILABLE;
		} else {
			status = Status.ERROR;
		}
	}

	public String getComponentTypeString() {
		return "component";
	}

	public String getName() {
		return name;
	}

	public Locale getLocale() {
		return locale;
	}

	public void setLocale(Locale aLocale) {
		this.locale = aLocale;
	}

	public String getVersion() {
		return version;
	}

	public void setVersion(String aVersion) {
		this.version = aVersion;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String aDescription) {
		this.description = aDescription;
	}

	public URL getLicenseURL() {
		return license;
	}

	public void setLicenseURL(URL aLicense) {
		this.license = aLicense;
	}

	public List getLocations() {
		return locations;
	}

	public void removeAllLocations() {
		locations.clear();
	}

	public void addLocation(URL aLocation) {
		if (this.locations == null) {
			this.locations = new ArrayList();
		}
		locations.add(aLocation);
	}

	public String getPackageFilename() {
		return packageFilename;
	}

	public void setPackageFilename(String aPackageFilename) {
		this.packageFilename = aPackageFilename;
	}

	public int getPackageSize() {
		return packageSize;
	}

	public void setPackageSize(int aSize) {
		this.packageSize = aSize;
	}

	public String getDisplayPackageSize() {
		return MaryUtils.toHumanReadableSize(packageSize);
	}

	public String getPackageMD5Sum() {
		return packageMD5;
	}

	public void setPackageMD5Sum(String aMD5) {
		this.packageMD5 = aMD5;
	}

	public boolean isSelected() {
		return isSelected;
	}

	public void setSelected(boolean value) {
		if (value != isSelected) {
			isSelected = value;
			stateChanged();
		}
	}

	public Status getStatus() {
		return status;
	}

	public LinkedList getInstalledFileNames() {
		LinkedList files = new LinkedList();
		if (installedFilesNames != null) {
			StringTokenizer st = new StringTokenizer(installedFilesNames, ",");
			while (st.hasMoreTokens()) {
				String next = st.nextToken().trim();
				if (!"".equals(next)) {
					files.addFirst(next); // i.e., reverse order
				}
			}
		}
		return files;
	}

	public void setSharedFiles(Set fileList) {
		sharedFileNames = fileList;
	}

	public String toString() {
		return name;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof ComponentDescription)) {
			return false;
		}
		ComponentDescription o = (ComponentDescription) obj;
		return name.equals(o.name) && locale.equals(o.locale) && version.equals(o.version);

	}

	// Pause this download.
	public void pause() {
		status = Status.PAUSED;
		stateChanged();
	}

	// Resume this download.
	public void resume(boolean synchronous) {
		status = Status.DOWNLOADING;
		stateChanged();
		download(synchronous);
	}

	// Cancel this download.
	public void cancel() {
		status = Status.CANCELLED;
		stateChanged();
	}

	// Mark this download as having an error.
	private void error() {
		status = Status.ERROR;
		stateChanged();
	}

	// Start or resume downloading.
	public void download(boolean synchronous) {
		Downloader d = new Downloader();
		if (synchronous) {
			d.run();
		} else {
			new Thread(d).start();
		}
	}

	// Notify observers that this download's status has changed.
	private void stateChanged() {
		setChanged();
		notifyObservers();
	}

	/**
	 * Install this component, if the user accepts the license.
	 * 
	 * @param synchronous
	 *            synchronous
	 * @throws Exception
	 *             Exception
	 */
	public void install(boolean synchronous) throws Exception {
		status = Status.INSTALLING;
		stateChanged();
		Installer inst = new Installer();
		if (synchronous) {
			inst.run();
		} else {
			new Thread(inst).start();
		}
	}

	/**
	 * Uninstall this component.
	 * 
	 * @return true if component was successfully uninstalled, false otherwise.
	 */
	public boolean uninstall() {
		if (status != Status.INSTALLED) {
			throw new IllegalStateException("Can only uninstall installed components, but status is " + status.toString());
		}
		assert installedFilesNames != null; // when we have an installed component, we must also have this information
		/*
		 * int answer = JOptionPane.showConfirmDialog(null,
		 * "Completely remove "+getComponentTypeString()+" '"+toString()+"' from the file system?", "Confirm component uninstall",
		 * JOptionPane.YES_NO_OPTION); if (answer != JOptionPane.YES_OPTION) { return false; }
		 */
		try {
			String maryBase = System.getProperty("mary.base");
			System.out.println("Removing " + name + "-" + version + " from " + maryBase + "...");
			LinkedList files = getInstalledFileNames();
			for (String file : files) {
				if (file.trim().equals(""))
					continue; // skip empty lines
				if (sharedFileNames != null && sharedFileNames.contains(file)) {
					System.out.println("Keeping shared file: " + file);
					continue; // don't uninstall shared files!
				}
				File f = new File(maryBase + "/" + file);
				if (f.isDirectory()) {
					String[] kids = f.list();
					if (kids.length == 0) {
						System.err.println("Removing empty directory: " + file);
						f.delete();
					} else {
						System.err.println("Cannot delete non-empty directory: " + file);
					}
				} else if (f.exists()) { // not a directory
					System.err.println("Removing file: " + file);
					f.delete();
				} else { // else, file doesn't exist
					System.err.println("File doesn't exist -- cannot delete: " + file);
				}
			}
			infoFile.delete();
		} catch (Exception e) {
			System.err.println("Cannot uninstall:");
			e.printStackTrace();
			return false;
		}
		determineStatus();
		return true;
	}

	public int getProgress() {
		if (status == Status.DOWNLOADING) {
			return (int) (100L * downloaded / size);
		} else if (status == Status.INSTALLING) {
			return -1;
		}
		return 100;
	}

	private void writeDownloadedComponentXML() throws Exception {
		File archiveFolder = archiveFile.getParentFile();
		String archiveFilename = archiveFile.getName();
		String compdescFilename = archiveFilename.substring(0, archiveFilename.lastIndexOf('.')) + "-component.xml";
		File compdescFile = new File(archiveFolder, compdescFilename);
		Document doc = createComponentXML();

		DomUtils.document2File(doc, compdescFile);
	}

	/**
	 * Write a component xml file to the installed/ folder, containing the given list of installed files.
	 * 
	 * @param installedFilesList
	 *            installedFilesList
	 * @throws Exception
	 *             Exception
	 */
	private void writeInstalledComponentXML() throws Exception {
		assert installedFilesNames != null;
		File installedFolder = infoFile.getParentFile();
		String archiveFilename = archiveFile.getName();
		String compdescFilename = archiveFilename.substring(0, archiveFilename.lastIndexOf('.')) + "-component.xml";
		File compdescFile = new File(installedFolder, compdescFilename);
		Document doc = createComponentXML();
		DomUtils.document2File(doc, compdescFile);
	}

	public Document createComponentXML() throws ParserConfigurationException {
		DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
		fact.setNamespaceAware(true);
		Document doc = fact.newDocumentBuilder().newDocument();
		Element root = (Element) doc.appendChild(doc.createElementNS(installerNamespaceURI, "marytts-install"));
		Element desc = (Element) root.appendChild(doc.createElementNS(installerNamespaceURI, getComponentTypeString()));
		desc.setAttribute("locale", MaryUtils.locale2xmllang(locale));
		desc.setAttribute("name", name);
		desc.setAttribute("version", version);
		Element descriptionElt = (Element) desc.appendChild(doc.createElementNS(installerNamespaceURI, "description"));
		descriptionElt.setTextContent(description);
		Element licenseElt = (Element) desc.appendChild(doc.createElementNS(installerNamespaceURI, "license"));
		if (license != null) {
			licenseElt.setAttribute("href", license.toString());
		}
		Element packageElt = (Element) desc.appendChild(doc.createElementNS(installerNamespaceURI, "package"));
		packageElt.setAttribute("size", Integer.toString(packageSize));
		packageElt.setAttribute("md5sum", packageMD5);
		packageElt.setAttribute("filename", packageFilename);
		for (URL l : locations) {
			// Serialize the location without the filename:
			String urlString = l.toString();
			boolean isFolder = false;
			if (urlString.endsWith(packageFilename)) {
				urlString = urlString.substring(0, urlString.length() - packageFilename.length());
				isFolder = true;
			}
			Element lElt = (Element) packageElt.appendChild(doc.createElementNS(installerNamespaceURI, "location"));
			lElt.setAttribute("href", urlString);
			lElt.setAttribute("folder", String.valueOf(isFolder));
		}
		if (installedFilesNames != null) {
			Element filesElement = (Element) desc.appendChild(doc.createElementNS(installerNamespaceURI, "files"));
			filesElement.setTextContent(installedFilesNames);
		}
		return doc;
	}

	/**
	 * Inform whether an update is available for this component.
	 * 
	 * @return availableUpdate != null
	 */
	public boolean isUpdateAvailable() {
		return availableUpdate != null;
	}

	/**
	 * If this component has an available update, get that update.
	 * 
	 * @return null if no update is available.
	 */
	public ComponentDescription getAvailableUpdate() {
		return availableUpdate;
	}

	/**
	 * Set the given component description as the available update of this component. If we already have an available update, then
	 * aDesc is only retained as the available update if it has a higher version number than the previously set update.
	 * 
	 * @param aDesc
	 *            an available update, or null to erase any previously set available updates.
	 * @throws IllegalStateException
	 *             if aDesc is not null and our status is not "INSTALLED".
	 * @throws IllegalArgumentException
	 *             if aDesc is not null and our name is not the same as aDesc's name, or if our version number is not smaller than
	 *             aDesc's version number.
	 */
	public void setAvailableUpdate(ComponentDescription aDesc) {
		if (aDesc == null) {
			this.availableUpdate = null;
			stateChanged();
			return;
		}
		if (this.status != Status.INSTALLED) {
			throw new IllegalStateException("Can only set an available update if status is installed, but status is "
					+ status.toString());
		}
		if (!aDesc.getName().equals(name)) {
			throw new IllegalArgumentException(
					"Only a component with the same name can be an update of this component; but this has name " + name
							+ ", and argument has name " + aDesc.getName());
		}
		if (!(isVersionNewerThan(aDesc.getVersion(), version))) {
			throw new IllegalArgumentException("Version " + aDesc.getVersion() + " is not higher than installed version "
					+ version);
		}
		if (availableUpdate != null) { // already have an available update: we will replace it with aDesc only if aDesc has a
										// higher version number
			if (!(isVersionNewerThan(aDesc.getVersion(), availableUpdate.getVersion()))) {
				return;
			}
		}
		this.availableUpdate = aDesc;
		stateChanged();
	}

	/**
	 * This is an update of other if and only if the following is true:
	 * 
    *
  1. Both components have the same type (as identified by the class) and name;
  2. *
  3. other has status INSTALLED;
  4. *
  5. our version number is higher than other's version number.
  6. *
* * @param other * other * @return false if other == null or !this.getClass.equals(other.getClass) or !name.equals(other.getName) or other.getStatus * or != Status.INSTALLED or !(isVersionNewerThan(version, other.getVersion), otherwise true */ public boolean isUpdateOf(ComponentDescription other) { if (other == null || !this.getClass().equals(other.getClass()) || !name.equals(other.getName()) || other.getStatus() != Status.INSTALLED || !(isVersionNewerThan(version, other.getVersion()))) { return false; } return true; } /** * Determine whether oneVersion is newer than otherVersion. This performs a lexicographic comparison with the exception that a * version with suffix "-SNAPSHOT" is treated as older than the version without that suffix. A version is not newer than * itself. * * @param oneVersion * oneVersion * @param otherVersion * otherVersion * @return true if oneVersion is newer than otherVersion, false if they are equal or otherVersion is newer. */ public static boolean isVersionNewerThan(String oneVersion, String otherVersion) { if (oneVersion == null || otherVersion == null) { return false; } // The only special cases we need to consider is when otherVersion is oneVersion suffixed with "-SNAPSHOT", // or the other way round: in this case the one with the suffix is newer. // All other cases are covered by lexicographic comparison. if (otherVersion.equals(oneVersion + "-SNAPSHOT")) { return true; } if (oneVersion.equals(otherVersion + "-SNAPSHOT")) { return false; } return oneVersion.compareTo(otherVersion) > 0; } /** * Define a natural ordering for component descriptions. Languages first, in alphabetic order, then voices, in alphabetic * order. * * @param o * o */ public int compareTo(ComponentDescription o) { int myPos = 0; int oPos = 0; if (this instanceof LanguageComponentDescription) { myPos = 5; } else if (this instanceof VoiceComponentDescription) { myPos = 10; } if (o instanceof LanguageComponentDescription) { oPos = 5; } else if (o instanceof VoiceComponentDescription) { oPos = 10; } if (oPos - myPos != 0) { return (oPos - myPos); } // Same type, sort by name return name.compareTo(o.name); } public static final void copyInputStream(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) >= 0) out.write(buffer, 0, len); in.close(); out.close(); } class Downloader implements Runnable { private HttpURLConnection openAndRedirectIfRequired(URL url) throws IOException { int maxRedirects = 5; for (int i = 0; i < maxRedirects; i++) { HttpURLConnection c = (HttpURLConnection) url.openConnection(); c.setInstanceFollowRedirects(false); // Specify what portion of file to download. c.setRequestProperty("Range", "bytes=" + downloaded + "-"); // Connect to server. c.connect(); // Check for redirects int stat = c.getResponseCode(); if (stat >= 300 && stat <= 307 && stat != 306 && stat != HttpURLConnection.HTTP_NOT_MODIFIED) { URL base = c.getURL(); String location = c.getHeaderField("Location"); c.disconnect(); if (location == null) { throw new SecurityException("No redirect location given."); } URL target = new URL(base, location); String protocol = target.getProtocol(); if (!(protocol.equals("http") || protocol.equals("https"))) { throw new SecurityException("Redirect supported to http and https protocols only, but found '" + protocol + "'"); } url = target; } else { return c; } } throw new SecurityException("More than five redirects, aborting"); } public void run() { status = Status.DOWNLOADING; stateChanged(); RandomAccessFile file = null; InputStream stream = null; HttpURLConnection connection = null; for (URL u : locations) { try { System.out.println("Trying location " + u + "..."); // Open connection to URL. connection = openAndRedirectIfRequired(u); // Make sure response code is in the 200 range. if (connection.getResponseCode() / 100 != 2) { throw new IOException("Non-OK response code: " + connection.getResponseCode() + " (" + connection.getResponseMessage() + ")"); } System.out.println("...connected"); // Check for valid content length. int contentLength = connection.getContentLength(); if (contentLength > -1) { if (contentLength != packageSize) { throw new IOException("Expected package size " + packageSize + ", but web server reports " + contentLength); } } /* * Set the size for this download if it hasn't been already set. */ if (size == -1) { size = packageSize; stateChanged(); } System.out.println("...downloading" + (downloaded > 0 ? " from byte " + downloaded : "")); boolean success = tryToDownloadFromLocation(file, stream, connection); if (success) { break; // current location seems OK, leave loop } } catch (Exception exc) { exc.printStackTrace(); } } } private boolean tryToDownloadFromLocation(RandomAccessFile file, InputStream stream, HttpURLConnection connection) { boolean success = false; try { // Open file and seek to the end of it. file = new RandomAccessFile(archiveFile, "rw"); file.seek(downloaded); stream = connection.getInputStream(); if (status == Status.ERROR) { downloaded = 0; } status = Status.DOWNLOADING; byte[] buffer = new byte[MAX_BUFFER_SIZE]; while (status == Status.DOWNLOADING) { /* * target number of bytes to download depends on how much of the file is left to download. */ int len = Math.min(buffer.length, size - downloaded); // Read from server into buffer. int read = stream.read(buffer, 0, len); if (read == -1) break; // Write buffer to file. file.write(buffer, 0, read); downloaded += read; stateChanged(); } /* * Change status to complete if this point was reached because downloading has finished. */ if (status == Status.DOWNLOADING) { System.err.println("Download of " + packageFilename + " has finished."); System.err.print("Computing checksum..."); status = Status.VERIFYING; stateChanged(); String hash = MD5.asHex(MD5.getHash(archiveFile)); if (hash.equals(packageMD5)) { System.err.println("ok!"); writeDownloadedComponentXML(); status = Status.DOWNLOADED; success = true; } else { System.err.println("failed!"); System.out.println("MD5 according to component description: " + packageMD5); System.out.println("MD5 computed: " + hash); status = Status.ERROR; downloaded = 0; } stateChanged(); } } catch (Exception e) { e.printStackTrace(); error(); } finally { // Close file. if (file != null) { try { file.close(); } catch (Exception e) { } } // Close connection to server. if (stream != null) { try { stream.close(); } catch (Exception e) { } } } return success; } } class Installer implements Runnable { public void run() { String maryBase = System.getProperty("mary.base"); System.out.println("Installing " + name + "-" + version + " in " + maryBase + "..."); ArrayList files = new ArrayList(); try { ZipFile zipfile = new ZipFile(archiveFile); Enumeration entries = zipfile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); files.add(entry.getName()); // add to installed filelist; rely on uninstaller retaining shared files File newFile = new File(maryBase + "/" + entry.getName()); if (entry.isDirectory()) { System.err.println("Extracting directory: " + entry.getName()); newFile.mkdir(); } else { if (newFile.exists()) { // is existing file newer? boolean existingIsNewer = false; try { // existing JAR: JarFile existingJar = new JarFile(newFile); Manifest existingManifest = existingJar.getManifest(); String existingVersion = existingManifest.getMainAttributes().getValue("Specification-Version"); // packaged JAR: JarInputStream packagedJar = new JarInputStream(zipfile.getInputStream(entry)); Manifest packagedManifest = packagedJar.getManifest(); String packagedVersion = packagedManifest.getMainAttributes().getValue("Specification-Version"); // compare the version strings: existingIsNewer = isVersionNewerThan(existingVersion, packagedVersion); if (existingIsNewer) { // if we don't overwrite a newer existing JAR, then never log it as installed, otherwise we // lose it during uninstall! files.remove(entry.getName()); } } catch (Exception e) { // we're not dealing with a JAR file // TODO disabled this block on short notice because installed files are touched and will therefore // always be newer: // long existingDate; // // fall back to last modification time: // try { // existingDate = newFile.lastModified(); // } catch (SecurityException f) { // // WTF, we can't get the date from the existing file!? // e.printStackTrace(); // // assume it's outdated, to trigger reinstall (which may well fail): // existingDate = Long.MIN_VALUE; // } // long packagedDate = entry.getTime(); // if (existingDate > packagedDate) { // existingIsNewer = true; // } } // do not overwrite existing files if they are newer: if (existingIsNewer) { System.err.println("NOT overwriting existing newer file: " + entry.getName()); continue; } } if (!newFile.getParentFile().isDirectory()) { System.err.println("Creating directory tree: " + newFile.getParentFile().getAbsolutePath()); newFile.getParentFile().mkdirs(); } System.err.println("Extracting file: " + entry.getName()); copyInputStream(zipfile.getInputStream(entry), new BufferedOutputStream(new FileOutputStream(newFile))); // better hack: try to set executable bit on files in bin/ if (entry.getName().startsWith("bin/")) { try { if (newFile.setExecutable(true, false)) { System.err.println("Setting executable bit on file: " + entry.getName()); } } catch (SecurityException e) { e.printStackTrace(); // but ignore } } } } zipfile.close(); installedFilesNames = StringUtils.join(files, ", "); writeInstalledComponentXML(); } catch (Exception e) { System.err.println("... installation failed:"); e.printStackTrace(); status = Status.ERROR; stateChanged(); } System.err.println("...done"); status = Status.INSTALLED; stateChanged(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy