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}