001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.update; 020 021import java.io.File; 022import java.util.ArrayList; 023 024import org.w3c.dom.Document; 025import org.w3c.dom.Element; 026import org.w3c.dom.Node; 027 028import icy.file.FileUtil; 029import icy.update.ElementDescriptor.ElementFile; 030import icy.util.StringUtil; 031import icy.util.XMLUtil; 032import icy.util.ZipUtil; 033 034public class Updater 035{ 036 public static final String ICYKERNEL_NAME = "ICY Kernel"; 037 public static final String ICYUPDATER_NAME = "ICY Updater"; 038 039 public static final String UPDATE_DIRECTORY = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + "update"; 040 public static final String BACKUP_DIRECTORY = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + "backup"; 041 public static final String UPDATE_BASE_NAME = "update"; 042 public static final String UPDATE_EXT_NAME = ".xml"; 043 public static final String UPDATE_NAME = UPDATE_BASE_NAME + UPDATE_EXT_NAME; 044 public static final String VERSION_NAME = "version.xml"; 045 public static final String UPDATER_NAME = "updater.jar"; 046 047 public static final String ARG_NOSTART = "-nostart"; 048 public static final String ARG_UPDATE = "-update"; 049 050 private static final String ID_ELEMENTS = "elements"; 051 private static final String ID_ELEMENT = "element"; 052 private static final String ID_OBSOLETES = "obsoletes"; 053 private static final String ID_LOCALPATH = "localpath"; 054 055 // /** 056 // * Get update elements.<br> 057 // * Compare local elements with online element and return a list of element<br> 058 // * which need to be updated. 059 // */ 060 // public static ArrayList<ElementDescriptor> getUpdateElements() 061 // { 062 // return getUpdateElements(getLocalElements()); 063 // } 064 065 // /** 066 // * Update the local version.xml file so it contains only present elements with correct 067 // * modification date. 068 // */ 069 // public static boolean validateLocalElementsXML() 070 // { 071 // // get local elements 072 // final ArrayList<ElementDescriptor> localElements = getLocalElements(); 073 // // validate them 074 // validateLocalElements(localElements); 075 // // and save to local XML file 076 // return saveElementsToXML(localElements, VERSION_NAME, false); 077 // } 078 079 // public static boolean updateXML() 080 // { 081 // final ArrayList<ElementDescriptor> localElements = getLocalElements(); 082 // final ArrayList<ElementDescriptor> onlineElements = getOnlineElements(); 083 // 084 // // update local list 085 // for (ElementDescriptor onlineElement : onlineElements) 086 // { 087 // final ElementDescriptor localElement = findElement(onlineElement.getName(), localElements); 088 // // local element absent or outdated ? 089 // if (localElement == null) 090 // localElements.add(onlineElement); 091 // else if (onlineElement.getVersion().isGreater(localElement.getVersion())) 092 // // set new version 093 // localElement.setVersion(onlineElement.getVersion()); 094 // } 095 // 096 // // save new version XML file return 097 // return saveElementsToXML(localElements, VERSION_NAME, false); 098 // } 099 100 /** 101 * Validate the specified list of elements against local files.<br> 102 * This actually remove missing files and update the file modification date. 103 */ 104 public static void validateElements(ArrayList<ElementDescriptor> elements) 105 { 106 // validate elements against local files 107 for (int i = elements.size() - 1; i >= 0; i--) 108 { 109 final ElementDescriptor element = elements.get(i); 110 111 // validate element 112 element.validate(); 113 114 // no more valid file ? --> remove element 115 if (element.getFilesNumber() == 0) 116 elements.remove(i); 117 } 118 } 119 120 /** 121 * Get the list of local elements.<br> 122 * Elements are fetched from local version.xml file then validated with local files. 123 */ 124 public static ArrayList<ElementDescriptor> getLocalElements() 125 { 126 // get local elements from XML file 127 final ArrayList<ElementDescriptor> result = loadElementsFromXML( 128 FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + VERSION_NAME); 129 130 // validate elements 131 validateElements(result); 132 133 return result; 134 } 135 136 /** 137 * Get the list of online elements (online update.xml file) 138 */ 139 public static ArrayList<ElementDescriptor> getOnlineElements() 140 { 141 return loadElementsFromXML(UPDATE_DIRECTORY + FileUtil.separator + UPDATE_NAME); 142 } 143 144 /** 145 * Get update elements.<br> 146 * Compare specified local elements with online element and return a list of element<br> 147 * which need to be updated. 148 */ 149 public static ArrayList<ElementDescriptor> getUpdateElements(ArrayList<ElementDescriptor> localElements) 150 { 151 final ArrayList<ElementDescriptor> result = new ArrayList<ElementDescriptor>(); 152 final ArrayList<ElementDescriptor> onlineElements = getOnlineElements(); 153 154 // build update list 155 for (ElementDescriptor onlineElement : onlineElements) 156 { 157 final ElementDescriptor localElement = findElement(onlineElement.getName(), localElements); 158 // get update element (differences between online and local element) 159 final ElementDescriptor updateElement = ElementDescriptor.getUpdateElement(localElement, onlineElement); 160 161 if (updateElement != null) 162 // add the element to update list 163 result.add(updateElement); 164 } 165 166 return result; 167 } 168 169 /** 170 * Get the list of obsoletes files 171 */ 172 public static ArrayList<String> getObsoletes() 173 { 174 final ArrayList<String> result = new ArrayList<String>(); 175 176 final Document document = XMLUtil.loadDocument(UPDATE_DIRECTORY + FileUtil.separator + UPDATE_NAME, false); 177 178 if (document != null) 179 { 180 // TODO: check if we can really remove that 181 // document.normalizeDocument(); 182 183 // get obsoletes node 184 final Node obsoletes = XMLUtil.getElement(document.getDocumentElement(), ID_OBSOLETES); 185 186 // get all local path 187 final ArrayList<Node> nodesLocalpath = XMLUtil.getChildren(obsoletes, ID_LOCALPATH); 188 if (nodesLocalpath != null) 189 { 190 for (Node n : nodesLocalpath) 191 { 192 final String value = XMLUtil.getValue((Element) n, ""); 193 if (!StringUtil.isEmpty(value, true)) 194 result.add(value); 195 } 196 } 197 } 198 199 return result; 200 } 201 202 public static ArrayList<ElementDescriptor> loadElementsFromXML(String path) 203 { 204 final ArrayList<ElementDescriptor> result = new ArrayList<ElementDescriptor>(); 205 206 final Document document = XMLUtil.loadDocument(path, true); 207 208 if (document != null) 209 { 210 // TODO: check if we can really remove that 211 // document.normalizeDocument(); 212 213 // get elements node 214 final Node elements = XMLUtil.getElement(document.getDocumentElement(), ID_ELEMENTS); 215 216 // get elements 217 final ArrayList<Node> nodesElement = XMLUtil.getChildren(elements, ID_ELEMENT); 218 if (nodesElement != null) 219 { 220 for (Node n : nodesElement) 221 result.add(new ElementDescriptor(n)); 222 } 223 } 224 225 return result; 226 } 227 228 /** 229 * Save the specified elements to the specified filename 230 */ 231 public static boolean saveElementsToXML(ArrayList<ElementDescriptor> elements, String path, boolean onlineSave) 232 { 233 final Document document = XMLUtil.createDocument(true); 234 235 final Element elementsNode = XMLUtil.addElement(document.getDocumentElement(), ID_ELEMENTS); 236 237 // set elements 238 for (ElementDescriptor element : elements) 239 element.saveToNode(XMLUtil.addElement(elementsNode, ID_ELEMENT), onlineSave); 240 241 return XMLUtil.saveDocument(document, path); 242 } 243 244 /** 245 * Find an element in the specified list 246 */ 247 public static ElementDescriptor findElement(String name, ArrayList<ElementDescriptor> list) 248 { 249 for (ElementDescriptor element : list) 250 if (name.equals(element.getName())) 251 return element; 252 253 return null; 254 } 255 256 /** 257 * Find an element from his local path in the specified list 258 */ 259 // private static ElementDescriptor findElementFromLocalPath(String path, 260 // ArrayList<ElementDescriptor> list) 261 // { 262 // for (ElementDescriptor element : list) 263 // if (element.hasLocalPath(path)) 264 // return element; 265 // 266 // return null; 267 // } 268 269 /** 270 * Return true if some update files are present in the update directory 271 */ 272 public static boolean hasUpdateFiles() 273 { 274 final String[] paths = FileUtil.getFiles(UPDATE_DIRECTORY, null, true, false, false); 275 276 for (String path : paths) 277 { 278 final String filename = FileUtil.getFileName(path); 279 280 // check if we have others files other than updater and XML definitions 281 if ((!filename.equals(UPDATER_NAME)) && (!filename.equals(UPDATE_NAME))) 282 return true; 283 } 284 285 return false; 286 } 287 288 /** 289 * Update the specified "update" element (move files from update to application directory)<br> 290 * then modify local elements list according to changes made. 291 * 292 * @return true if update succeed, false otherwise 293 */ 294 public static boolean udpateElement(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements) 295 { 296 // update all element files 297 if (Updater.updateFiles(updateElement.getFiles())) 298 { 299 // then modify local elements list 300 updateElementInfos(updateElement, localElements); 301 return true; 302 } 303 304 return false; 305 } 306 307 /** 308 * Update local elements according to changes presents in updateElement 309 */ 310 public static void clearElementInfos(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements) 311 { 312 // find corresponding current local element 313 final ElementDescriptor localElement = Updater.findElement(updateElement.getName(), localElements); 314 315 // remove it 316 localElements.remove(localElement); 317 } 318 319 /** 320 * Update local elements according to changes presents in updateElement 321 */ 322 public static void updateElementInfos(ElementDescriptor updateElement, ArrayList<ElementDescriptor> localElements) 323 { 324 // find corresponding current local element 325 final ElementDescriptor localElement = Updater.findElement(updateElement.getName(), localElements); 326 327 // local element doesn't exist 328 if (localElement == null) 329 // add it 330 localElements.add(updateElement); 331 else 332 // just update local element with update element info 333 localElement.update(updateElement); 334 } 335 336 /** 337 * Update the specified files 338 */ 339 public static boolean updateFiles(ArrayList<ElementFile> files) 340 { 341 for (ElementFile file : files) 342 // if update fails --> exit 343 if (!updateFile(file)) 344 return false; 345 346 return true; 347 } 348 349 /** 350 * Update the specified local file 351 */ 352 public static boolean updateFile(ElementFile file) 353 { 354 final String localPath = file.getLocalPath(); 355 356 // directory type file --> extract it 357 if (file.isDirectory()) 358 { 359 final String dirName = UPDATE_DIRECTORY + FileUtil.separator + localPath; 360 final String zipName = dirName + ".zip"; 361 362 // rename directory type file (no extension) to zip file 363 if (!FileUtil.rename(dirName, zipName, true)) 364 return false; 365 // extract zip file 366 if (!ZipUtil.extract(zipName)) 367 return false; 368 } 369 370 if (updateFile(localPath, file.getDateModif())) 371 { 372 final File dest = new File(FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath); 373 374 // there is no reason the file doesn't exists but anyway... 375 if (dest.exists()) 376 { 377 if (file.isExecutable()) 378 dest.setExecutable(true, false); 379 if (file.isWritable()) 380 if (!dest.setWritable(true, false)) 381 dest.setWritable(true, true); 382 383 return true; 384 } 385 } 386 387 return false; 388 } 389 390 /** 391 * Backup the specified local file 392 */ 393 public static boolean backup(String localPath) 394 { 395 final String src = FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath; 396 // file exist ? backup it 397 if (FileUtil.exists(src)) 398 { 399 final String dest = BACKUP_DIRECTORY + FileUtil.separator + localPath; 400 401 if (!FileUtil.copy(src, dest, true, true)) 402 return false; 403 404 // verify that backup file exist 405 return FileUtil.exists(dest); 406 } 407 408 return true; 409 } 410 411 /** 412 * Update the specified local file 413 */ 414 public static boolean updateFile(String localPath, long dateModif) 415 { 416 // no update needed 417 if (!needUpdate(localPath, dateModif)) 418 return true; 419 420 // backup file 421 if (!backup(localPath)) 422 { 423 // backup failed 424 System.err.println("Updater.udpateFile(" + localPath + ") failed :"); 425 // System.err.println("Cannot backup file to '" + BACKUP_DIRECTORY + FileUtil.separator 426 // + localPath); 427 return false; 428 } 429 430 // move file 431 if (!FileUtil.rename(UPDATE_DIRECTORY + FileUtil.separator + localPath, 432 FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath, true)) 433 { 434 // move failed 435 System.err.println("Updater.udpateFile('" + localPath + "') failed !"); 436 // System.err.println("Cannot rename file from '" + UPDATE_DIRECTORY + 437 // FileUtil.separator + localPath 438 // + "' to '" + localPath + "'"); 439 return false; 440 } 441 442 return true; 443 } 444 445 /** 446 * Return true if specified file is different from the update file (in Update directory) 447 */ 448 public static boolean needUpdate(String localPath, long dateModif) 449 { 450 final File localFile = new File(FileUtil.APPLICATION_DIRECTORY + FileUtil.separator + localPath); 451 452 return (!localFile.exists()) || (dateModif == 0L) || (localFile.lastModified() != dateModif); 453 } 454 455 /** 456 * Process to restoration (in case the update failed) 457 */ 458 public static boolean restore() 459 { 460 final int len = BACKUP_DIRECTORY.length(); 461 // get files only (no directory) 462 final String[] paths = FileUtil.getFiles(BACKUP_DIRECTORY, null, true, false, false); 463 boolean result = true; 464 465 for (String backupPath : paths) 466 { 467 final String finalPath = backupPath.substring(len + 1); 468 469 // don't restore updater 470 if (finalPath.equals(UPDATER_NAME)) 471 continue; 472 473 if (!FileUtil.rename(backupPath, finalPath, true)) 474 { 475 // rename failed (FileUtil.rename is already displaying error messages if needed) 476 System.err.println("Updater.restore() cannot restore '" + finalPath + "', you should do it manually."); 477 result = false; 478 } 479 } 480 481 return result; 482 } 483 484 /** 485 * Delete obsoletes files 486 */ 487 public static void deleteObsoletes() 488 { 489 // delete obsolete files 490 for (String obsolete : getObsoletes()) 491 FileUtil.delete(obsolete, true); 492 } 493}