/* * JFileSync * Copyright (C) 2002-2007, Jens Heidrich * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301, USA */ package jfs.sync; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; import jfs.conf.JFSConfig; import jfs.conf.JFSSyncMode.SyncAction; import jfs.conf.JFSSyncModes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a single pair of corresponding files or directories on the source and target side within the directory * structures that have to be compared. * * @author Jens Heidrich * @version $Id: JFSElement.java,v 1.22 2007/03/29 14:11:35 heidrich Exp $ */ public class JFSElement implements Comparable<JFSElement> { private static final Logger LOG = LoggerFactory.getLogger(JFSElement.class); /** * The root element to which the element belongs to. */ protected JFSRootElement root; /** * Source file. */ protected JFSFile srcFile; /** * Target file. */ protected JFSFile tgtFile; /** * The parent of the current element. */ protected JFSElement parent = null; /** * The children of the current element. */ protected List<JFSElement> children = null; /** * Determines whether the File objects are directories. */ protected boolean directory; /** * Determines the state of the element. */ protected ElementState state = ElementState.NOT_DETERMINED; /** * Determines whether the action was manually set. */ protected boolean manuallySetAction = false; /** * The action that has to be performed for the JFS element. * * @see JFSSyncModes */ protected SyncAction action = SyncAction.NOP; /** * Determines whether the action is active or should be skipped. */ protected boolean active = true; /** * Determines whether the element is currently viewed. */ protected boolean viewed = false; /** * The states of the element. */ public enum ElementState { IS_ROOT, NOT_DETERMINED, EQUAL, SRC_IS_NULL, TGT_IS_NULL, SRC_GT_TGT, TGT_GT_SRC, LENGTH_INCONSISTENT } /** * Constructor for derived classes. */ protected JFSElement() { // just here to modify visibility. } /** * Constructs an element that compares two File objects and determines whether one is newer than the other or * whether both files are equal. At least one file has to be not equal to null. If both files are equal, there * relative paths have to match. The parent must be a directory and must not be null. * * @param srcFile Source file. * @param tgtFile Target file. * @param parent The parent of the current element. * @param isDirectory Determines whether the File objects are directories. */ public JFSElement(JFSFile srcFile, JFSFile tgtFile, JFSElement parent, boolean isDirectory) { assert (srcFile!=null||tgtFile!=null)&&parent!=null&&parent.isDirectory(); this.root = parent.getRoot(); this.srcFile = srcFile; this.tgtFile = tgtFile; this.parent = parent; this.directory = isDirectory; parent.addChild(this); revalidate(); } /** * Revalidates the comparison table element; that is checks which file is newer, etc. */ public final void revalidate() { if (state==ElementState.IS_ROOT) { return; } // Reset attributes: state = ElementState.NOT_DETERMINED; // Get last modified information: long srcLastModified = 0; long tgtLastModified = 0; if (!directory) { if (srcFile!=null) { srcLastModified = srcFile.getLastModified(); } if (tgtFile!=null) { tgtLastModified = tgtFile.getLastModified(); } } // Comparison: // Under the DOS and Windows FAT file system, the finest granularity on // time resolution is two seconds. So we have a maximum tolerance range // of 2000ms for each comparison: long diffTime = compareToTime(srcLastModified, tgtLastModified); LOG.debug("revalidate() diffTime={}", diffTime); if (srcFile==null) { state = ElementState.SRC_IS_NULL; } else if (tgtFile==null) { state = ElementState.TGT_IS_NULL; } else { if (!directory) { if (diffTime==0) { state = ElementState.EQUAL; } else { if (diffTime>0) { state = ElementState.SRC_GT_TGT; } else { state = ElementState.TGT_GT_SRC; } } } else { state = ElementState.EQUAL; } } // Check length: if (state==ElementState.EQUAL&&srcFile.getLength()!=tgtFile.getLength()) { state = ElementState.LENGTH_INCONSISTENT; } // Set action to NOP if isEqual is true: if (state==ElementState.EQUAL) { action = SyncAction.NOP; } } /** * @return Returns the root element to which the element belongs to. */ public final JFSRootElement getRoot() { return root; } /** * @return Returns the source file. */ public final JFSFile getSrcFile() { return srcFile; } /** * Sets the source file. Used when updating the synchronization table during the process. * * @param file The file to set as source. */ public void setSrcFile(JFSFile file) { srcFile = file; } /** * @return Returns the target file. */ public final JFSFile getTgtFile() { return tgtFile; } /** * Sets the traget file. Used when updating the synchronization table during the process. * * @param file The file to set as target. */ public void setTgtFile(JFSFile file) { tgtFile = file; } /** * @return Returns the parent element of the current element which reflects the file system structure. The parent * element can only be null, if the current element is the root element. */ public final JFSElement getParent() { return parent; } /** * @return Returns the children of the current element which reflects the file system structure. The children are * null, if the current element is not a directory or the directory it represents is empty. */ public final List<JFSElement> getChildren() { return children; } /** * Adds a child to the element. * * @param child * The child to add. */ public final void addChild(JFSElement child) { if (children==null) { children = new ArrayList<>(); } children.add(child); } /** * @return Computes whether source and target files are directories. */ public final boolean isDirectory() { return directory; } /** * @return Returns the state of the element. */ public final ElementState getState() { return state; } /** * @return Returns whether the action was manually set. */ public final boolean isManuallySetAction() { return manuallySetAction; } /** * Determines whether the action was manually set. * * @param manuallySetAction True, if it was manually set. */ public void setManuallySetAction(boolean manuallySetAction) { this.manuallySetAction = manuallySetAction; } /** * @return Returns the action that has to be performed for the JFS element. */ public final SyncAction getAction() { return action; } /** * Sets the action that has to be performed for the JFS element. * * @see JFSSyncModes * @param action The action to set. */ public void setAction(SyncAction action) { this.action = action; } /** * @return Returns whether the action of the JFS element is active. */ public final boolean isActive() { return active; } /** * Sets whether the action of the JFS element is active. * * @param isActive True, if the action is active. */ public void setActive(boolean isActive) { this.active = isActive; } /** * @return Returns whether the element is viewed. */ public final boolean isViewed() { return viewed; } /** * Sets whether the element is viewed. * * @param isViewed True, if the element is viewed. */ public final void setViewed(boolean isViewed) { this.viewed = isViewed; } /** * @return Returns whether the element is a root element. */ public final boolean isRoot() { return (parent==this); } /** * @return Returns the name of the element, which has to be the same for source and target if both files are equal. * If the source is equal to null the relative path of the target is returned, and vice versa. According to * the constructor of this element, it is not allowed that both files are equal to null. */ public String getName() { if (srcFile!=null) { return srcFile.getName(); } // if return tgtFile.getName(); } /** * @return Returns the relative path of the element, which has to be the same for source and target if both files * are equal. If the source is equal to null the relative path of the target is returned, and vice versa. * According to the constructor of this element, it is not allowed that both files are equal to null. */ public String getRelativePath() { if (srcFile!=null) { return srcFile.getRelativePath(); } return tgtFile.getRelativePath(); } /** * @see Comparable#compareTo(Object) */ @Override public int compareTo(JFSElement e) { return getRelativePath().compareTo(e.getRelativePath()); } /** * Compares two time stamps taking into account the specified granularity of the configuration object. * * @param time1 The first time stamp. * @param time2 The second time stamp. * @return Returns a positive number if the first is newer than the second time stamp, 0 if both are equal, and a * negative number if the second time stamp is newer than the first one. */ public static long compareToTime(long time1, long time2) { return (time1-time2)/JFSConfig.getInstance().getGranularity(); } /** * Returns the valid actions for the JFS element. * * @return A vector of valid synchronization actions. */ public Set<SyncAction> getValidActions() { Set<SyncAction> validActions = new TreeSet<>(); if (state==ElementState.IS_ROOT) { return validActions; } validActions.add(SyncAction.NOP); if (srcFile!=null) { validActions.add(SyncAction.COPY_SRC); } else { validActions.add(SyncAction.DELETE_TGT); } if (tgtFile!=null) { validActions.add(SyncAction.COPY_TGT); } else { validActions.add(SyncAction.DELETE_SRC); } if (srcFile!=null&&tgtFile!=null) { validActions.add(SyncAction.DELETE_SRC_AND_TGT); } return validActions; } }