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 icy.common.Version; 022import icy.file.FileUtil; 023import icy.file.xml.XMLPersistent; 024import icy.util.StringUtil; 025import icy.util.XMLUtil; 026 027import java.io.File; 028import java.util.ArrayList; 029 030import org.w3c.dom.Element; 031import org.w3c.dom.Node; 032 033/** 034 * @author stephane 035 */ 036public class ElementDescriptor implements XMLPersistent 037{ 038 private static final String ID_NAME = "name"; 039 private static final String ID_VERSION = "version"; 040 private static final String ID_FILES = "files"; 041 private static final String ID_FILE = "file"; 042 private static final String ID_LINK = "link"; 043 private static final String ID_EXECUTE = "execute"; 044 private static final String ID_WRITE = "write"; 045 private static final String ID_DIRECTORY = "directory"; 046 private static final String ID_FILENUMBER = "fileNumber"; 047 private static final String ID_DATEMODIF = "datemodif"; 048 private static final String ID_LOCALPATH = "localpath"; 049 private static final String ID_ONLINEPATH = "onlinepath"; 050 private static final String ID_CHANGESLOG = "changeslog"; 051 052 public class ElementFile implements XMLPersistent 053 { 054 private String localPath; 055 private String onlinePath; 056 057 /** 058 * symbolic link element, onlinePath define the target of the link file 059 */ 060 private boolean link; 061 062 /** 063 * need execute permission 064 */ 065 private boolean executable; 066 067 /** 068 * need write permission 069 */ 070 private boolean writable; 071 072 /** 073 * directory file. 074 */ 075 private boolean directory; 076 077 /** 078 * date of modification 079 */ 080 private long dateModif; 081 082 /** 083 * number of file (for directory only, -1 = don't check file number) 084 */ 085 private int fileNumber; 086 087 /** 088 * 089 */ 090 public ElementFile(Node node) 091 { 092 super(); 093 094 loadFromXML(node); 095 } 096 097 /** 098 * Create a new element file using specified element informations 099 */ 100 public ElementFile(ElementFile elementFile) 101 { 102 super(); 103 104 localPath = elementFile.localPath; 105 onlinePath = elementFile.onlinePath; 106 dateModif = elementFile.dateModif; 107 link = elementFile.link; 108 executable = elementFile.executable; 109 writable = elementFile.writable; 110 directory = elementFile.directory; 111 fileNumber = elementFile.fileNumber; 112 } 113 114 @Override 115 public boolean loadFromXML(Node node) 116 { 117 if (node == null) 118 return false; 119 120 localPath = XMLUtil.getElementValue(node, ID_LOCALPATH, ""); 121 onlinePath = XMLUtil.getElementValue(node, ID_ONLINEPATH, ""); 122 dateModif = XMLUtil.getElementLongValue(node, ID_DATEMODIF, 0L); 123 link = XMLUtil.getElementBooleanValue(node, ID_LINK, false); 124 executable = XMLUtil.getElementBooleanValue(node, ID_EXECUTE, false); 125 writable = XMLUtil.getElementBooleanValue(node, ID_WRITE, false); 126 directory = XMLUtil.getElementBooleanValue(node, ID_DIRECTORY, false); 127 fileNumber = XMLUtil.getElementIntValue(node, ID_FILENUMBER, 1); 128 129 return true; 130 } 131 132 @Override 133 public boolean saveToXML(Node node) 134 { 135 return saveToNode(node, true); 136 } 137 138 boolean saveToNode(Node node, boolean onlineSave) 139 { 140 if (node == null) 141 return false; 142 143 XMLUtil.addElement(node, ID_LOCALPATH, localPath); 144 145 if (onlineSave) 146 { 147 XMLUtil.addElement(node, ID_ONLINEPATH, onlinePath); 148 XMLUtil.addElement(node, ID_DATEMODIF, Long.toString(dateModif)); 149 if (link) 150 XMLUtil.addElement(node, ID_LINK, Boolean.toString(link)); 151 if (executable) 152 XMLUtil.addElement(node, ID_EXECUTE, Boolean.toString(executable)); 153 if (writable) 154 XMLUtil.addElement(node, ID_WRITE, Boolean.toString(writable)); 155 if (directory) 156 { 157 XMLUtil.addElement(node, ID_DIRECTORY, Boolean.toString(directory)); 158 XMLUtil.addElement(node, ID_FILENUMBER, Integer.toString(fileNumber)); 159 } 160 } 161 162 return true; 163 } 164 165 public boolean isEmpty() 166 { 167 return StringUtil.isEmpty(localPath) && StringUtil.isEmpty(onlinePath); 168 } 169 170 public boolean exists() 171 { 172 return FileUtil.exists(localPath); 173 } 174 175 /** 176 * @return the localPath 177 */ 178 public String getLocalPath() 179 { 180 return localPath; 181 } 182 183 /** 184 * @return the onlinePath 185 */ 186 public String getOnlinePath() 187 { 188 return onlinePath; 189 } 190 191 /** 192 * @return the dateModif 193 */ 194 public long getDateModif() 195 { 196 return dateModif; 197 } 198 199 /** 200 * @return the link 201 */ 202 public boolean isLink() 203 { 204 return link; 205 } 206 207 /** 208 * @return the executable 209 */ 210 public boolean isExecutable() 211 { 212 return executable; 213 } 214 215 /** 216 * @return the writable 217 */ 218 public boolean isWritable() 219 { 220 return writable; 221 } 222 223 /** 224 * @return the directory 225 */ 226 public boolean isDirectory() 227 { 228 return directory; 229 } 230 231 /** 232 * @return the fileNumber 233 */ 234 public int getFileNumber() 235 { 236 return fileNumber; 237 } 238 239 /** 240 * @param dateModif 241 * the dateModif to set 242 */ 243 public void setDateModif(long dateModif) 244 { 245 this.dateModif = dateModif; 246 } 247 248 /** 249 * @param link 250 * the link to set 251 */ 252 public void setLink(boolean link) 253 { 254 this.link = link; 255 } 256 257 /** 258 * @param executable 259 * the executable to set 260 */ 261 public void setExecutable(boolean executable) 262 { 263 this.executable = executable; 264 } 265 266 /** 267 * @param writable 268 * the writable to set 269 */ 270 public void setWritable(boolean writable) 271 { 272 this.writable = writable; 273 } 274 275 /** 276 * @param directory 277 * the directory to set 278 */ 279 public void setDirectory(boolean directory) 280 { 281 this.directory = directory; 282 } 283 284 /** 285 * @param fileNumber 286 * the fileNumber to set 287 */ 288 public void setFileNumber(int fileNumber) 289 { 290 this.fileNumber = fileNumber; 291 } 292 293 /** 294 * Return true if the specified ElementFile is the same than current one.<br> 295 * 296 * @param elementFile 297 * the element file to compare 298 * @param compareOnlinePath 299 * specify if we compare online path information 300 * @param compareValidDateOnly 301 * true if we do compare only valid date (!= 0) 302 */ 303 public boolean isSame(ElementFile elementFile, boolean compareOnlinePath, boolean compareValidDateOnly) 304 { 305 if (elementFile == null) 306 return false; 307 308 if (!StringUtil.equals(elementFile.localPath, localPath)) 309 return false; 310 if (compareOnlinePath && (!StringUtil.equals(elementFile.onlinePath, onlinePath))) 311 return false; 312 // -1 means we don't check file number 313 if ((elementFile.fileNumber != -1) && (fileNumber != -1)) 314 { 315 if (elementFile.fileNumber != fileNumber) 316 return false; 317 } 318 319 if ((elementFile.dateModif == 0) || (dateModif == 0)) 320 { 321 // don't compare dates if one is invalid 322 if (compareValidDateOnly) 323 return true; 324 325 // one of the date is not valid --> can't compare 326 return false; 327 } 328 329 return (elementFile.dateModif == dateModif); 330 } 331 332 @Override 333 public String toString() 334 { 335 return FileUtil.getFileName(localPath); 336 } 337 338 } 339 340 private String name; 341 private Version version; 342 private final ArrayList<ElementFile> files; 343 private String changelog; 344 345 /** 346 * 347 */ 348 public ElementDescriptor(Node node) 349 { 350 super(); 351 352 files = new ArrayList<ElementFile>(); 353 354 loadFromXML(node); 355 } 356 357 /** 358 * Create a new element descriptor using specified element informations 359 */ 360 public ElementDescriptor(ElementDescriptor element) 361 { 362 super(); 363 364 name = element.name; 365 version = new Version(element.version.toString()); 366 changelog = element.changelog; 367 368 files = new ArrayList<ElementFile>(); 369 370 for (ElementFile f : element.files) 371 files.add(new ElementFile(f)); 372 } 373 374 @Override 375 public boolean loadFromXML(Node node) 376 { 377 if (node == null) 378 return false; 379 380 name = XMLUtil.getElementValue(node, ID_NAME, ""); 381 version = new Version(XMLUtil.getElementValue(node, ID_VERSION, "")); 382 changelog = XMLUtil.getElementValue(node, ID_CHANGESLOG, ""); 383 384 final ArrayList<Node> nodesFile = XMLUtil.getChildren(XMLUtil.getElement(node, ID_FILES), ID_FILE); 385 if (nodesFile != null) 386 { 387 for (Node n : nodesFile) 388 { 389 final ElementFile elementFile = new ElementFile(n); 390 391 if (!elementFile.isEmpty()) 392 files.add(elementFile); 393 } 394 } 395 396 return true; 397 } 398 399 @Override 400 public boolean saveToXML(Node node) 401 { 402 return saveToNode(node, true); 403 } 404 405 public boolean saveToNode(Node node, boolean onlineSave) 406 { 407 if (node == null) 408 return false; 409 410 XMLUtil.addElement(node, ID_NAME, name); 411 XMLUtil.addElement(node, ID_VERSION, version.toString()); 412 413 // some informations aren't needed for local version 414 if (onlineSave) 415 XMLUtil.addElement(node, ID_CHANGESLOG, changelog); 416 417 final Element filesNode = XMLUtil.addElement(node, ID_FILES); 418 for (ElementFile elementFile : files) 419 elementFile.saveToNode(XMLUtil.addElement(filesNode, ID_FILE), onlineSave); 420 421 return true; 422 } 423 424 /** 425 * return ElementFile containing specified local path 426 */ 427 public ElementFile getElementFile(String localPath) 428 { 429 for (ElementFile file : files) 430 if (file.getLocalPath().compareToIgnoreCase(localPath) == 0) 431 return file; 432 433 return null; 434 } 435 436 /** 437 * return true if element contains the specified local path 438 */ 439 public boolean hasLocalPath(String localPath) 440 { 441 return getElementFile(localPath) != null; 442 } 443 444 public boolean addElementFile(ElementFile file) 445 { 446 return files.add(file); 447 } 448 449 public boolean removeElementFile(ElementFile file) 450 { 451 return files.remove(file); 452 } 453 454 public void removeElementFile(String localPath) 455 { 456 removeElementFile(getElementFile(localPath)); 457 } 458 459 /** 460 * Validate the current element descriptor.<br> 461 * It actually remove missing files from the element.<br> 462 * Return true if all files are valid. 463 */ 464 public boolean validate() 465 { 466 boolean result = true; 467 468 for (int i = files.size() - 1; i >= 0; i--) 469 { 470 final ElementFile elementFile = files.get(i); 471 final File file = new File(elementFile.getLocalPath()); 472 473 if (file.exists()) 474 { 475 // update modification date 476 elementFile.setDateModif(file.lastModified()); 477 478 // directory file ? 479 if (file.isDirectory()) 480 { 481 // update directory informations 482 elementFile.setDirectory(true); 483 elementFile.setFileNumber(FileUtil.getFiles(file, null, true, false, false).length); 484 } 485 } 486 else 487 { 488 // remove missing file 489 files.remove(i); 490 result = false; 491 } 492 } 493 494 return result; 495 } 496 497 public boolean isValid() 498 { 499 for (ElementFile file : files) 500 if (!file.exists()) 501 return false; 502 503 return true; 504 } 505 506 /** 507 * @return the name 508 */ 509 public String getName() 510 { 511 return name; 512 } 513 514 /** 515 * @return the version 516 */ 517 public Version getVersion() 518 { 519 return version; 520 } 521 522 /** 523 * @return the number of files 524 */ 525 public int getFilesNumber() 526 { 527 return files.size(); 528 } 529 530 /** 531 * @return the files 532 */ 533 public ArrayList<ElementFile> getFiles() 534 { 535 return files; 536 } 537 538 /** 539 * @return the specified file 540 */ 541 public ElementFile getFile(int index) 542 { 543 return files.get(index); 544 } 545 546 /** 547 * @return the changelog 548 */ 549 public String getChangelog() 550 { 551 return changelog; 552 } 553 554 /** 555 * @param version 556 * the version to set 557 */ 558 public void setVersion(Version version) 559 { 560 this.version = version; 561 } 562 563 /** 564 * Return true if the specified ElementDescriptor is the same than current one.<br> 565 * 566 * @param element 567 * the element descriptor to compare 568 * @param compareFileOnlinePath 569 * specify if we compare file online path information 570 */ 571 public boolean isSame(ElementDescriptor element, boolean compareFileOnlinePath) 572 { 573 if (element == null) 574 return false; 575 576 // different name 577 if (!name.equals(element.name)) 578 return false; 579 // different version 580 if (!version.equals(element.version)) 581 return false; 582 // different number of files 583 if (files.size() != element.files.size()) 584 return false; 585 586 // compare files 587 for (ElementFile file : files) 588 { 589 final ElementFile elementFile = element.getElementFile(file.getLocalPath()); 590 591 // file missing --> different 592 if (elementFile == null) 593 return false; 594 595 // file different (compare date only if they are valid) --> different 596 if (!elementFile.isSame(file, compareFileOnlinePath, true)) 597 return false; 598 } 599 600 // same element 601 return true; 602 } 603 604 /** 605 * Process and return the update the element which contain differences<br> 606 * from the specified local and online elements.<br> 607 * If local element refers the same item, only missing or different files will remains.<br> 608 * If local element refers a different element, online element is returned unchanged. 609 * 610 * @return the update element (null if local and online elements are the same) 611 */ 612 public static ElementDescriptor getUpdateElement(ElementDescriptor localElement, ElementDescriptor onlineElement) 613 { 614 if (onlineElement == null) 615 return null; 616 617 // use a copy 618 final ElementDescriptor result = new ElementDescriptor(onlineElement); 619 620 if (localElement == null) 621 return result; 622 // different name 623 if (!StringUtil.equals(result.name, localElement.name)) 624 return result; 625 626 // if same version, compare files on valid date only 627 final boolean compareValidDateOnly = result.version.equals(localElement.version); 628 629 // compare files 630 for (int i = result.files.size() - 1; i >= 0; i--) 631 { 632 final ElementFile onlineFile = result.files.get(i); 633 final ElementFile localFile = localElement.getElementFile(onlineFile.getLocalPath()); 634 635 // same file ? --> remove it (no need to be updated) 636 if ((localFile != null) && onlineFile.isSame(localFile, false, compareValidDateOnly)) 637 result.files.remove(i); 638 } 639 640 // no files to update ? --> return null 641 if (result.files.isEmpty()) 642 return null; 643 644 return result; 645 } 646 647 /** 648 * Update current element with informations from specified element 649 */ 650 public void update(ElementDescriptor updateElement) 651 { 652 // update version info 653 version = updateElement.version; 654 655 // updateElement contains only new or modified files (do not contain unmodified ones) 656 // so we have to add or update files but not remove old ones. 657 for (ElementFile updateFile : updateElement.files) 658 { 659 // get corresponding file 660 final ElementFile localFile = getElementFile(updateFile.getLocalPath()); 661 662 // file missing ? --> add it 663 if (localFile == null) 664 files.add(updateFile); 665 else 666 { 667 // update file (we don't care about online information) 668 localFile.setDateModif(updateFile.getDateModif()); 669 localFile.setExecutable(updateFile.isExecutable()); 670 localFile.setLink(updateFile.isLink()); 671 localFile.setWritable(updateFile.isWritable()); 672 localFile.setDirectory(updateFile.isDirectory()); 673 } 674 } 675 } 676 677 @Override 678 public String toString() 679 { 680 return name + " " + version; 681 } 682 683}