Package org.hive2hive.core.file

Source Code of org.hive2hive.core.file.FileSynchronizer

package org.hive2hive.core.file;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hive2hive.core.model.FileIndex;
import org.hive2hive.core.model.FolderIndex;
import org.hive2hive.core.model.Index;
import org.hive2hive.core.model.UserProfile;
import org.hive2hive.core.security.H2HEncryptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Helps to synchronize when a client comes online. It compares the meta data from last logout with the
* current situation on disc and in the user profile.
*
* @author Nico
*
*/
public class FileSynchronizer {

  private static final Logger logger = LoggerFactory.getLogger(FileSynchronizer.class);

  private final Path root;
  private final UserProfile userProfile;

  private final FolderIndex profileRootNode;
  private final Map<String, byte[]> before;
  private Map<String, byte[]> now;

  public FileSynchronizer(Path rootDirectory, UserProfile userProfile) throws ClassNotFoundException, IOException {
    this.root = rootDirectory;
    this.userProfile = userProfile;
    this.profileRootNode = userProfile.getRoot();

    // load the two file trees
    before = FileUtil.readPersistentMetaData(root).getFileTree();

    PersistenceFileVisitor visitor = new PersistenceFileVisitor(root);
    try {
      Files.walkFileTree(root, visitor);
      now = visitor.getFileTree();
    } catch (IOException e) {
      logger.error("Cannot walk the current tree.", e);
      now = new HashMap<String, byte[]>(0);
    }
  }

  /**
   * Returns a list of files that have been deleted from the disc during this client was offline
   *
   * @return
   */
  public List<Index> getDeletedLocally() {
    List<Index> deletedLocally = new ArrayList<Index>();

    for (String path : before.keySet()) {
      if (now.containsKey(path)) {
        // skip, this file is still here
        continue;
      } else {
        // test whether it is in the user profile
        Index node = userProfile.getFileByPath(Paths.get(path));
        if (node != null) {
          // file is still in user profile
          if (node.isFolder()) {
            deletedLocally.add(node);
          } else {
            // check the MD5 value to not delete a modified file
            FileIndex fileNode = (FileIndex) node;
            if (H2HEncryptionUtil.compareMD5(fileNode.getMD5(), before.get(path))) {
              // file has not been modified remotely, delete it
              logger.debug("File '{}' has been deleted locally during absence.", path);
              deletedLocally.add(node);
            }
          }
        }
      }
    }

    // delete from behind
    sortNodesPreorder(deletedLocally);
    Collections.reverseOrder();

    logger.info("Found {} files/folders that have been deleted locally during absence.", deletedLocally.size());
    return deletedLocally;
  }

  /**
   * Returns a list of files that have been deleted by another client during the absence of this client.
   *
   * @return
   */
  public List<Path> getDeletedRemotely() {
    List<Path> deletedRemotely = new ArrayList<Path>();

    for (String p : now.keySet()) {
      Path path = Paths.get(p);
      if (before.containsKey(p) && userProfile.getFileByPath(path) == null) {
        // is on disk but deleted in the user profile
        if (H2HEncryptionUtil.compareMD5(before.get(p), now.get(p))) {
          // only delete the file, if it was not modified locally
          deletedRemotely.add(Paths.get(root.toString(), path.toString()));
        }
      }
    }

    logger.debug("Found {} files/folders that have been deleted remotely during absence.", deletedRemotely.size());
    return deletedRemotely;
  }

  /**
   * Returns the missing files that exist on disk but not in the file tree in the user profile. The list is
   * in pre-order
   *
   * @return
   */
  public List<Path> getAddedLocally() {
    List<Path> addedLocally = new ArrayList<Path>();

    for (String p : now.keySet()) {
      Path path = Paths.get(p);
      // test whether it is in the user profile
      Index node = userProfile.getFileByPath(path);
      if (node == null) {
        // not in profile --> it has been added locally
        logger.debug("File '{}' has been added locally during absence.", p);
        addedLocally.add(Paths.get(root.toString(), path.toString()));
      }
    }

    sortFilesPreorder(addedLocally);
    logger.info("Found {} files/folders that have been added locally during absence.", addedLocally.size());
    return addedLocally;
  }

  /**
   * Returns a list of files that are in the user profile but not on the local disk yet.
   *
   * @return
   */
  public List<Index> getAddedRemotely() {
    List<Index> addedRemotely = new ArrayList<Index>();

    // visit all files in the tree and compare to disk
    List<Index> indexList = Index.getIndexList(profileRootNode);
    indexList.remove(profileRootNode);

    for (Index index : indexList) {
      if (now.containsKey(index.getFullPath().toString())) {
        // was here before and is still here --> nothing to add
        logger.trace("File '{}' was already here.", index.getFullPath());
      } else {
        logger.debug("File '{}' has been added remotely during absence.", index.getFullPath());
        addedRemotely.add(index);
      }
    }

    sortNodesPreorder(addedRemotely);
    logger.info("Found {} files/folders that have been added remotely during absence.", addedRemotely.size());
    return addedRemotely;
  }

  /**
   * Returns a list of files that already existed but have been modified by the client while he was offline.
   *
   * @return
   */
  public List<Path> getUpdatedLocally() {
    List<Path> updatedLocally = new ArrayList<Path>();

    for (String path : now.keySet()) {
      if (!before.containsKey(path)) {
        // was not here before --> skip
        continue;
      }

      if (H2HEncryptionUtil.compareMD5(before.get(path), now.get(path))) {
        // md5 before and after match --> nothing changed
        continue;
      }

      Index index = userProfile.getFileByPath(Paths.get(path));
      if (index == null || index.isFolder()) {
        // file not found --> skip, this is not the task of this method
        // file node is a folder --> cannot compare the modification
        continue;
      }

      FileIndex fileNode = (FileIndex) index;

      // has been modified --> check if profile has same md5 as 'before'. If not, there are three
      // different versions. Thus, the profile wins.
      if (H2HEncryptionUtil.compareMD5(fileNode.getMD5(), before.get(path))
          && !H2HEncryptionUtil.compareMD5(fileNode.getMD5(), now.get(path))) {
        logger.debug("File '{}' has been updated locally during absence.", path);
        updatedLocally.add(FileUtil.getPath(root, fileNode));
      }
    }

    sortFilesPreorder(updatedLocally);
    logger.info("Found {} files/folders that have been updated locally during absence.", updatedLocally.size());
    return updatedLocally;
  }

  /**
   * Returns files that have been remotely modified while the client was offline
   *
   * @return
   */
  public List<FileIndex> getUpdatedRemotely() {
    List<FileIndex> updatedRemotely = new ArrayList<FileIndex>();

    // visit all files in the tree and compare to disk
    List<Index> indexList = Index.getIndexList(profileRootNode);
    for (Index index : indexList) {
      if (index.isFolder()) {
        // folder cannot be modified
        continue;
      }

      FileIndex fileIndex = (FileIndex) index;
      if (before.containsKey(fileIndex.getFullPath().toString())
          && now.containsKey(fileIndex.getFullPath().toString())) {
        if (!H2HEncryptionUtil.compareMD5(fileIndex.getMD5(), now.get(fileIndex.getFullPath().toString()))
            && !H2HEncryptionUtil.compareMD5(fileIndex.getMD5(), before.get(fileIndex.getFullPath().toString()))) {
          // different md5 hashes than 'before' and 'now'
          logger.debug("File '{}' has been updated remotely during absence.", fileIndex.getFullPath());
          updatedRemotely.add(fileIndex);
        }
      }
    }

    logger.info("Found {} files/folders that have been updated remotely during absence.", updatedRemotely.size());
    return updatedRemotely;
  }

  /**
   * Sorts a list of {@link FolderIndex} in pre-order style
   *
   * @param deletedLocally
   */
  private void sortNodesPreorder(List<Index> fileList) {
    Collections.sort(fileList, new Comparator<Index>() {

      @Override
      public int compare(Index node1, Index node2) {
        return node1.getFullPath().toString().compareTo(node2.getFullPath().toString());
      }
    });
  }

  /**
   * Sorts a list of files in pre-order style
   *
   * @param deletedLocally
   */
  private void sortFilesPreorder(List<Path> fileList) {
    Collections.sort(fileList, new Comparator<Path>() {

      @Override
      public int compare(Path path1, Path path2) {
        return path1.compareTo(path2);
      }
    });
  }
}
TOP

Related Classes of org.hive2hive.core.file.FileSynchronizer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.