/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy 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.
*
* Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.update;
import java.io.File;
import java.util.ArrayList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import icy.file.FileUtil;
import icy.update.ElementDescriptor.ElementFile;
import icy.util.StringUtil;
import icy.util.XMLUtil;
import icy.util.ZipUtil;
public class Updater
{
public static final String ICYKERNEL_NAME = "ICY Kernel";
public static final String ICYUPDATER_NAME = "ICY Updater";
public static final String UPDATE_DIRECTORY = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + "update";
public static final String BACKUP_DIRECTORY = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + "backup";
public static final String UPDATE_BASE_NAME = "update";
public static final String UPDATE_EXT_NAME = ".xml";
public static final String UPDATE_NAME = UPDATE_BASE_NAME + UPDATE_EXT_NAME;
public static final String VERSION_NAME = "version.xml";
public static final String UPDATER_NAME = "updater.jar";
public static final String ARG_NOSTART = "-nostart";
public static final String ARG_UPDATE = "-update";
private static final String ID_ELEMENTS = "elements";
private static final String ID_ELEMENT = "element";
private static final String ID_OBSOLETES = "obsoletes";
private static final String ID_LOCALPATH = "localpath";
// /**
// * Get update elements.<br>
// * Compare local elements with online element and return a list of element<br>
// * which need to be updated.
// */
// public static ArrayList<ElementDescriptor> getUpdateElements()
// {
// return getUpdateElements(getLocalElements());
// }
// /**
// * Update the local version.xml file so it contains only present elements with correct
// * modification date.
// */
// public static boolean validateLocalElementsXML()
// {
// // get local elements
// final ArrayList<ElementDescriptor> localElements = getLocalElements();
// // validate them
// validateLocalElements(localElements);
// // and save to local XML file
// return saveElementsToXML(localElements, VERSION_NAME, false);
// }
// public static boolean updateXML()
// {
// final ArrayList<ElementDescriptor> localElements = getLocalElements();
// final ArrayList<ElementDescriptor> onlineElements = getOnlineElements();
//
// // update local list
// for (ElementDescriptor onlineElement : onlineElements)
// {
// final ElementDescriptor localElement = findElement(onlineElement.getName(), localElements);
// // local element absent or outdated ?
// if (localElement == null)
// localElements.add(onlineElement);
// else if (onlineElement.getVersion().isGreater(localElement.getVersion()))
// // set new version
// localElement.setVersion(onlineElement.getVersion());
// }
//
// // save new version XML file return
// return saveElementsToXML(localElements, VERSION_NAME, false);
// }
/**
* Validate the specified list of elements against local files.<br>
* This actually remove missing files and update the file modification date.
*/
public static void validateElements(ArrayList<ElementDescriptor> elements)
{
// validate elements against local files
for (int i = elements.size() - 1; i >= 0; i--)
{
final ElementDescriptor element = elements.get(i);
// validate element
element.validate();
// no more valid file ? --> remove element
if (element.getFilesNumber() == 0)
elements.remove(i);
}
}
/**
* Get the list of local elements.<br>
* Elements are fetched from local version.xml file then validated with local files.
*/
public static ArrayList<ElementDescriptor> getLocalElements()
{
// get local elements from XML file
final ArrayList<ElementDescriptor> result = loadElementsFromXML(
FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + VERSION_NAME);
// validate elements
validateElements(result);
return result;
}
/**
* Get the list of online elements (online update.xml file)
*/
public static ArrayList<ElementDescriptor> getOnlineElements()
{
return loadElementsFromXML(UPDATE_DIRECTORY + FileUtil.separator + UPDATE_NAME);
}
/**
* Get update elements.<br>
* Compare specified local elements with online element and return a list of element<br>
* which need to be updated.
*/
public static ArrayList<ElementDescriptor> getUpdateElements(ArrayList<ElementDescriptor> localElements)
{
final ArrayList<ElementDescriptor> result = new ArrayList<ElementDescriptor>();
final ArrayList<ElementDescriptor> onlineElements = getOnlineElements();
// build update list
for (ElementDescriptor onlineElement : onlineElements)
{
final ElementDescriptor localElement = findElement(onlineElement.getName(), localElements);
// get update element (differences between online and local element)
final ElementDescriptor updateElement = ElementDescriptor.getUpdateElement(localElement, onlineElement);
if (updateElement != null)
// add the element to update list
result.add(updateElement);
}
return result;
}
/**
* Get the list of obsoletes files
*/
public static ArrayList<String> getObsoletes()
{
final ArrayList<String> result = new ArrayList<String>();
final Document document = XMLUtil.loadDocument(UPDATE_DIRECTORY + FileUtil.separator + UPDATE_NAME, false);
if (document != null)
{
// TODO: check if we can really remove that
// document.normalizeDocument();
// get obsoletes node
final Node obsoletes = XMLUtil.getElement(document.getDocumentElement(), ID_OBSOLETES);
// get all local path
final ArrayList<Node> nodesLocalpath = XMLUtil.getChildren(obsoletes, ID_LOCALPATH);
if (nodesLocalpath != null)
{
for (Node n : nodesLocalpath)
{
final String value = XMLUtil.getValue((Element) n, "");
if (!StringUtil.isEmpty(value, true))
result.add(value);
}
}
}
return result;
}
public static ArrayList<ElementDescriptor> loadElementsFromXML(String path)
{
final ArrayList<ElementDescriptor> result = new ArrayList<ElementDescriptor>();
final Document document = XMLUtil.loadDocument(path, true);
if (document != null)
{
// TODO: check if we can really remove that
// document.normalizeDocument();
// get elements node
final Node elements = XMLUtil.getElement(document.getDocumentElement(), ID_ELEMENTS);
// get elements
final ArrayList<Node> nodesElement = XMLUtil.getChildren(elements, ID_ELEMENT);
if (nodesElement != null)
{
for (Node n : nodesElement)
result.add(new ElementDescriptor(n));
}
}
return result;
}
/**
* Save the specified elements to the specified filename
*/
public static boolean saveElementsToXML(ArrayList<ElementDescriptor> elements, String path, boolean onlineSave)
{
final Document document = XMLUtil.createDocument(true);
final Element elementsNode = XMLUtil.addElement(document.getDocumentElement(), ID_ELEMENTS);
// set elements
for (ElementDescriptor element : elements)
element.saveToNode(XMLUtil.addElement(elementsNode, ID_ELEMENT), onlineSave);
return XMLUtil.saveDocument(document, path);
}
/**
* Find an element in the specified list
*/
public static ElementDescriptor findElement(String name, ArrayList<ElementDescriptor> list)
{
for (ElementDescriptor element : list)
if (name.equals(element.getName()))
return element;
return null;
}
/**
* Find an element from his local path in the specified list
*/
// private static ElementDescriptor findElementFromLocalPath(String path,
// ArrayList<ElementDescriptor> list)
// {
// for (ElementDescriptor element : list)
// if (element.hasLocalPath(path))
// return element;
//
// return null;
// }
/**
* Return true if some update files are present in the update directory
*/
public static boolean hasUpdateFiles()
{
final String[] paths = FileUtil.getFiles(UPDATE_DIRECTORY, null, true, false, false);
for (String path : paths)
{
final String filename = FileUtil.getFileName(path);
// check if we have others files other than updater and XML definitions
if ((!filename.equals(UPDATER_NAME)) && (!filename.equals(UPDATE_NAME)))
return true;
}
return false;
}
/**
* Update the specified "update" element (move files from update to application directory)<br>
* then modify local elements list according to changes made.
*
* @return true if update succeed, false otherwise
*/
public static boolean udpateElement(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements)
{
// update all element files
if (Updater.updateFiles(updateElement.getFiles()))
{
// then modify local elements list
updateElementInfos(updateElement, localElements);
return true;
}
return false;
}
/**
* Update local elements according to changes presents in updateElement
*/
public static void clearElementInfos(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements)
{
// find corresponding current local element
final ElementDescriptor localElement = Updater.findElement(updateElement.getName(), localElements);
// remove it
localElements.remove(localElement);
}
/**
* Update local elements according to changes presents in updateElement
*/
public static void updateElementInfos(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements)
{
// find corresponding current local element
final ElementDescriptor localElement = Updater.findElement(updateElement.getName(), localElements);
// local element doesn't exist
if (localElement == null)
// add it
localElements.add(updateElement);
else
// just update local element with update element info
localElement.update(updateElement);
}
/**
* Update the specified files
*/
public static boolean updateFiles(ArrayList<ElementFile> files)
{
for (ElementFile file : files)
// if update fails --> exit
if (!updateFile(file))
return false;
return true;
}
/**
* Update the specified local file
*/
public static boolean updateFile(ElementFile file)
{
final String localPath = file.getLocalPath();
// directory type file --> extract it
if (file.isDirectory())
{
final String dirName = UPDATE_DIRECTORY + FileUtil.separator + localPath;
final String zipName = dirName + ".zip";
// rename directory type file (no extension) to zip file
if (!FileUtil.rename(dirName, zipName, true))
return false;
// extract zip file
if (!ZipUtil.extract(zipName))
return false;
}
if (updateFile(localPath, file.getDateModif()))
{
final File dest = new File(FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath);
// there is no reason the file doesn't exists but anyway...
if (dest.exists())
{
if (file.isExecutable())
dest.setExecutable(true, false);
if (file.isWritable())
if (!dest.setWritable(true, false))
dest.setWritable(true, true);
return true;
}
}
return false;
}
/**
* Backup the specified local file
*/
public static boolean backup(String localPath)
{
final String src = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath;
// file exist ? backup it
if (FileUtil.exists(src))
{
final String dest = BACKUP_DIRECTORY + FileUtil.separator + localPath;
if (!FileUtil.copy(src, dest, true, true))
return false;
// verify that backup file exist
return FileUtil.exists(dest);
}
return true;
}
/**
* Update the specified local file
*/
public static boolean updateFile(String localPath, long dateModif)
{
// no update needed
if (!needUpdate(localPath, dateModif))
return true;
// backup file
if (!backup(localPath))
{
// backup failed
System.err.println("Updater.udpateFile(" + localPath + ") failed :");
// System.err.println("Cannot backup file to '" + BACKUP_DIRECTORY + FileUtil.separator
// + localPath);
return false;
}
// move file
if (!FileUtil.rename(UPDATE_DIRECTORY + FileUtil.separator + localPath,
FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath, true))
{
// move failed
System.err.println("Updater.udpateFile('" + localPath + "') failed !");
// System.err.println("Cannot rename file from '" + UPDATE_DIRECTORY +
// FileUtil.separator + localPath
// + "' to '" + localPath + "'");
return false;
}
return true;
}
/**
* Return true if specified file is different from the update file (in Update directory)
*/
public static boolean needUpdate(String localPath, long dateModif)
{
final File localFile = new File(FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath);
return (!localFile.exists()) || (dateModif == 0L) || (localFile.lastModified() != dateModif);
}
/**
* Process to restoration (in case the update failed)
*/
public static boolean restore()
{
final int len = BACKUP_DIRECTORY.length();
// get files only (no directory)
final String[] paths = FileUtil.getFiles(BACKUP_DIRECTORY, null, true, false, false);
boolean result = true;
for (String backupPath : paths)
{
final String finalPath = backupPath.substring(len + 1);
// don't restore updater
if (finalPath.equals(UPDATER_NAME))
continue;
if (!FileUtil.rename(backupPath, finalPath, true))
{
// rename failed (FileUtil.rename is already displaying error messages if needed)
System.err.println("Updater.restore() cannot restore '" + finalPath + "', you should do it manually.");
result = false;
}
}
return result;
}
/**
* Delete obsoletes files
*/
public static void deleteObsoletes()
{
// delete obsolete files
for (String obsolete : getObsoletes())
FileUtil.delete(obsolete, false);
}
}