/* * 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 icy.common.Version; import icy.file.FileUtil; import icy.file.xml.XMLPersistent; import icy.util.StringUtil; import icy.util.XMLUtil; import java.io.File; import java.util.ArrayList; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * @author stephane */ public class ElementDescriptor implements XMLPersistent { private static final String ID_NAME = "name"; private static final String ID_VERSION = "version"; private static final String ID_FILES = "files"; private static final String ID_FILE = "file"; private static final String ID_LINK = "link"; private static final String ID_EXECUTE = "execute"; private static final String ID_WRITE = "write"; private static final String ID_DIRECTORY = "directory"; private static final String ID_FILENUMBER = "fileNumber"; private static final String ID_DATEMODIF = "datemodif"; private static final String ID_LOCALPATH = "localpath"; private static final String ID_ONLINEPATH = "onlinepath"; private static final String ID_CHANGESLOG = "changeslog"; public class ElementFile implements XMLPersistent { private String localPath; private String onlinePath; /** * symbolic link element, onlinePath define the target of the link file */ private boolean link; /** * need execute permission */ private boolean executable; /** * need write permission */ private boolean writable; /** * directory file. */ private boolean directory; /** * date of modification */ private long dateModif; /** * number of file (for directory only, -1 = don't check file number) */ private int fileNumber; /** * */ public ElementFile(Node node) { super(); loadFromXML(node); } /** * Create a new element file using specified element informations */ public ElementFile(ElementFile elementFile) { super(); localPath = elementFile.localPath; onlinePath = elementFile.onlinePath; dateModif = elementFile.dateModif; link = elementFile.link; executable = elementFile.executable; writable = elementFile.writable; directory = elementFile.directory; fileNumber = elementFile.fileNumber; } @Override public boolean loadFromXML(Node node) { if (node == null) return false; localPath = XMLUtil.getElementValue(node, ID_LOCALPATH, ""); onlinePath = XMLUtil.getElementValue(node, ID_ONLINEPATH, ""); dateModif = XMLUtil.getElementLongValue(node, ID_DATEMODIF, 0L); link = XMLUtil.getElementBooleanValue(node, ID_LINK, false); executable = XMLUtil.getElementBooleanValue(node, ID_EXECUTE, false); writable = XMLUtil.getElementBooleanValue(node, ID_WRITE, false); directory = XMLUtil.getElementBooleanValue(node, ID_DIRECTORY, false); fileNumber = XMLUtil.getElementIntValue(node, ID_FILENUMBER, 1); return true; } @Override public boolean saveToXML(Node node) { return saveToNode(node, true); } boolean saveToNode(Node node, boolean onlineSave) { if (node == null) return false; XMLUtil.addElement(node, ID_LOCALPATH, localPath); if (onlineSave) { XMLUtil.addElement(node, ID_ONLINEPATH, onlinePath); XMLUtil.addElement(node, ID_DATEMODIF, Long.toString(dateModif)); if (link) XMLUtil.addElement(node, ID_LINK, Boolean.toString(link)); if (executable) XMLUtil.addElement(node, ID_EXECUTE, Boolean.toString(executable)); if (writable) XMLUtil.addElement(node, ID_WRITE, Boolean.toString(writable)); if (directory) { XMLUtil.addElement(node, ID_DIRECTORY, Boolean.toString(directory)); XMLUtil.addElement(node, ID_FILENUMBER, Integer.toString(fileNumber)); } } return true; } public boolean isEmpty() { return StringUtil.isEmpty(localPath) && StringUtil.isEmpty(onlinePath); } public boolean exists() { return FileUtil.exists(localPath); } /** * @return the localPath */ public String getLocalPath() { return localPath; } /** * @return the onlinePath */ public String getOnlinePath() { return onlinePath; } /** * @return the dateModif */ public long getDateModif() { return dateModif; } /** * @return the link */ public boolean isLink() { return link; } /** * @return the executable */ public boolean isExecutable() { return executable; } /** * @return the writable */ public boolean isWritable() { return writable; } /** * @return the directory */ public boolean isDirectory() { return directory; } /** * @return the fileNumber */ public int getFileNumber() { return fileNumber; } /** * @param dateModif * the dateModif to set */ public void setDateModif(long dateModif) { this.dateModif = dateModif; } /** * @param link * the link to set */ public void setLink(boolean link) { this.link = link; } /** * @param executable * the executable to set */ public void setExecutable(boolean executable) { this.executable = executable; } /** * @param writable * the writable to set */ public void setWritable(boolean writable) { this.writable = writable; } /** * @param directory * the directory to set */ public void setDirectory(boolean directory) { this.directory = directory; } /** * @param fileNumber * the fileNumber to set */ public void setFileNumber(int fileNumber) { this.fileNumber = fileNumber; } /** * Return true if the specified ElementFile is the same than current one.<br> * * @param elementFile * the element file to compare * @param compareOnlinePath * specify if we compare online path information * @param compareValidDateOnly * true if we do compare only valid date (!= 0) */ public boolean isSame(ElementFile elementFile, boolean compareOnlinePath, boolean compareValidDateOnly) { if (elementFile == null) return false; if (!StringUtil.equals(elementFile.localPath, localPath)) return false; if (compareOnlinePath && (!StringUtil.equals(elementFile.onlinePath, onlinePath))) return false; // -1 means we don't check file number if ((elementFile.fileNumber != -1) && (fileNumber != -1)) { if (elementFile.fileNumber != fileNumber) return false; } if ((elementFile.dateModif == 0) || (dateModif == 0)) { // don't compare dates if one is invalid if (compareValidDateOnly) return true; // one of the date is not valid --> can't compare return false; } return (elementFile.dateModif == dateModif); } @Override public String toString() { return FileUtil.getFileName(localPath); } } private String name; private Version version; private final ArrayList<ElementFile> files; private String changelog; /** * */ public ElementDescriptor(Node node) { super(); files = new ArrayList<ElementFile>(); loadFromXML(node); } /** * Create a new element descriptor using specified element informations */ public ElementDescriptor(ElementDescriptor element) { super(); name = element.name; version = new Version(element.version.toString()); changelog = element.changelog; files = new ArrayList<ElementFile>(); for (ElementFile f : element.files) files.add(new ElementFile(f)); } @Override public boolean loadFromXML(Node node) { if (node == null) return false; name = XMLUtil.getElementValue(node, ID_NAME, ""); version = new Version(XMLUtil.getElementValue(node, ID_VERSION, "")); changelog = XMLUtil.getElementValue(node, ID_CHANGESLOG, ""); final ArrayList<Node> nodesFile = XMLUtil.getChildren(XMLUtil.getElement(node, ID_FILES), ID_FILE); if (nodesFile != null) { for (Node n : nodesFile) { final ElementFile elementFile = new ElementFile(n); if (!elementFile.isEmpty()) files.add(elementFile); } } return true; } @Override public boolean saveToXML(Node node) { return saveToNode(node, true); } public boolean saveToNode(Node node, boolean onlineSave) { if (node == null) return false; XMLUtil.addElement(node, ID_NAME, name); XMLUtil.addElement(node, ID_VERSION, version.toString()); // some informations aren't needed for local version if (onlineSave) XMLUtil.addElement(node, ID_CHANGESLOG, changelog); final Element filesNode = XMLUtil.addElement(node, ID_FILES); for (ElementFile elementFile : files) elementFile.saveToNode(XMLUtil.addElement(filesNode, ID_FILE), onlineSave); return true; } /** * return ElementFile containing specified local path */ public ElementFile getElementFile(String localPath) { for (ElementFile file : files) if (file.getLocalPath().compareToIgnoreCase(localPath) == 0) return file; return null; } /** * return true if element contains the specified local path */ public boolean hasLocalPath(String localPath) { return getElementFile(localPath) != null; } public boolean addElementFile(ElementFile file) { return files.add(file); } public boolean removeElementFile(ElementFile file) { return files.remove(file); } public void removeElementFile(String localPath) { removeElementFile(getElementFile(localPath)); } /** * Validate the current element descriptor.<br> * It actually remove missing files from the element.<br> * Return true if all files are valid. */ public boolean validate() { boolean result = true; for (int i = files.size() - 1; i >= 0; i--) { final ElementFile elementFile = files.get(i); final File file = new File(elementFile.getLocalPath()); if (file.exists()) { // update modification date elementFile.setDateModif(file.lastModified()); // directory file ? if (file.isDirectory()) { // update directory informations elementFile.setDirectory(true); elementFile.setFileNumber(FileUtil.getFiles(file, null, true, false, false).length); } } else { // remove missing file files.remove(i); result = false; } } return result; } public boolean isValid() { for (ElementFile file : files) if (!file.exists()) return false; return true; } /** * @return the name */ public String getName() { return name; } /** * @return the version */ public Version getVersion() { return version; } /** * @return the number of files */ public int getFilesNumber() { return files.size(); } /** * @return the files */ public ArrayList<ElementFile> getFiles() { return files; } /** * @return the specified file */ public ElementFile getFile(int index) { return files.get(index); } /** * @return the changelog */ public String getChangelog() { return changelog; } /** * @param version * the version to set */ public void setVersion(Version version) { this.version = version; } /** * Return true if the specified ElementDescriptor is the same than current one.<br> * * @param element * the element descriptor to compare * @param compareFileOnlinePath * specify if we compare file online path information */ public boolean isSame(ElementDescriptor element, boolean compareFileOnlinePath) { if (element == null) return false; // different name if (!name.equals(element.name)) return false; // different version if (!version.equals(element.version)) return false; // different number of files if (files.size() != element.files.size()) return false; // compare files for (ElementFile file : files) { final ElementFile elementFile = element.getElementFile(file.getLocalPath()); // file missing --> different if (elementFile == null) return false; // file different (compare date only if they are valid) --> different if (!elementFile.isSame(file, compareFileOnlinePath, true)) return false; } // same element return true; } /** * Process and return the update the element which contain differences<br> * from the specified local and online elements.<br> * If local element refers the same item, only missing or different files will remains.<br> * If local element refers a different element, online element is returned unchanged. * * @return the update element (null if local and online elements are the same) */ public static ElementDescriptor getUpdateElement(ElementDescriptor localElement, ElementDescriptor onlineElement) { if (onlineElement == null) return null; // use a copy final ElementDescriptor result = new ElementDescriptor(onlineElement); if (localElement == null) return result; // different name if (!StringUtil.equals(result.name, localElement.name)) return result; // if same version, compare files on valid date only final boolean compareValidDateOnly = result.version.equals(localElement.version); // compare files for (int i = result.files.size() - 1; i >= 0; i--) { final ElementFile onlineFile = result.files.get(i); final ElementFile localFile = localElement.getElementFile(onlineFile.getLocalPath()); // same file ? --> remove it (no need to be updated) if ((localFile != null) && onlineFile.isSame(localFile, false, compareValidDateOnly)) result.files.remove(i); } // no files to update ? --> return null if (result.files.isEmpty()) return null; return result; } /** * Update current element with informations from specified element */ public void update(ElementDescriptor updateElement) { // update version info version = updateElement.version; // updateElement contains only new or modified files (do not contain unmodified ones) // so we have to add or update files but not remove old ones. for (ElementFile updateFile : updateElement.files) { // get corresponding file final ElementFile localFile = getElementFile(updateFile.getLocalPath()); // file missing ? --> add it if (localFile == null) files.add(updateFile); else { // update file (we don't care about online information) localFile.setDateModif(updateFile.getDateModif()); localFile.setExecutable(updateFile.isExecutable()); localFile.setLink(updateFile.isLink()); localFile.setWritable(updateFile.isWritable()); localFile.setDirectory(updateFile.isDirectory()); } } } @Override public String toString() { return name + " " + version; } }