如何在java中实现可停止和可取消的文件夹同步?

时间:2016-01-14 16:07:17

标签: java synchronization rollback cancellation

我有两个目录结构,需要同步它们,即将源文件夹中新的或更改的文件和文件夹复制到目标文件夹中,并删除目标中不存在于源中的文件和文件夹。但是,这个过程需要有一个"取消"按钮,它将阻止它执行并回滚所有更改。我四处搜索并找到了这段代码,但我对其实际工作原理的理解充其量是朦胧的。

//$Id: FileHelper.java 15522 2008-11-05 20:06:43Z hardy.ferentschik $

//Revised from hibernate search util
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;


/**
 * Utility class for synchronizing files/directories.
 *
 * @author Emmanuel Bernard
 * @author Sanne Grinovero
 * @author Hardy Ferentschik
 */
public abstract class FileHelper {

  private static final int FAT_PRECISION = 2000;
  public static final long DEFAULT_COPY_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB


  public static boolean areInSync(File source, File destination) throws IOException {
    if ( source.isDirectory() ) {
      if ( !destination.exists() ) {
        return false;
      }
      else if ( !destination.isDirectory() ) {
        throw new IOException(
            "Source and Destination not of the same type:"
                + source.getCanonicalPath() + " , " + destination.getCanonicalPath()
        );
      }
      String[] sources = source.list();
      Set<String> srcNames = new HashSet<String>( Arrays.asList( sources ) );
      String[] dests = destination.list();

      // check for files in destination and not in source
      for ( String fileName : dests ) {
        if ( !srcNames.contains( fileName ) ) {
          return false;
        }
      }

      boolean inSync = true;
      for ( String fileName : sources ) {
        File srcFile = new File( source, fileName );
        File destFile = new File( destination, fileName );
        if ( !areInSync( srcFile, destFile ) ) {
          inSync = false;
          break;
        }
      }
      return inSync;
    }
    else {
      if ( destination.exists() && destination.isFile() ) {
        long sts = source.lastModified() / FAT_PRECISION;
        long dts = destination.lastModified() / FAT_PRECISION;
        return sts == dts;
      }
      else {
        return false;
      }
    }
  }

  public static void synchronize(File source, File destination, boolean smart) throws IOException {
    synchronize( source, destination, smart, DEFAULT_COPY_BUFFER_SIZE );
  }

  public static void synchronize(File source, File destination, boolean smart, long chunkSize) throws IOException {
    if ( chunkSize <= 0 ) {
      System.out.println("Chunk size must be positive: using default value." );
      chunkSize = DEFAULT_COPY_BUFFER_SIZE;
    }
    if ( source.isDirectory() ) {
      if ( !destination.exists() ) {
        if ( !destination.mkdirs() ) {
          throw new IOException( "Could not create path " + destination );
        }
      }
      else if ( !destination.isDirectory() ) {
        throw new IOException(
            "Source and Destination not of the same type:"
                + source.getCanonicalPath() + " , " + destination.getCanonicalPath()
        );
      }
      String[] sources = source.list();
      Set<String> srcNames = new HashSet<String>( Arrays.asList( sources ) );
      String[] dests = destination.list();

      //delete files not present in source
      for ( String fileName : dests ) {
        if ( !srcNames.contains( fileName ) ) {
          delete( new File( destination, fileName ) );
        }
      }
      //copy each file from source
      for ( String fileName : sources ) {
        File srcFile = new File( source, fileName );
        File destFile = new File( destination, fileName );
        synchronize( srcFile, destFile, smart, chunkSize );
      }
    }
    else {
      if ( destination.exists() && destination.isDirectory() ) {
        delete( destination );
      }
      if ( destination.exists() ) {
        long sts = source.lastModified() / FAT_PRECISION;
        long dts = destination.lastModified() / FAT_PRECISION;
        //do not copy if smart and same timestamp and same length
        if ( !smart || sts == 0 || sts != dts || source.length() != destination.length() ) {
          copyFile( source, destination, chunkSize );
        }
      }
      else {
        copyFile( source, destination, chunkSize );
      }
    }
  }

  private static void copyFile(File srcFile, File destFile, long chunkSize) throws IOException {
    FileInputStream is = null;
    FileOutputStream os = null;
    try {
      is = new FileInputStream( srcFile );
      FileChannel iChannel = is.getChannel();
      os = new FileOutputStream( destFile, false );
      FileChannel oChannel = os.getChannel();
      long doneBytes = 0L;
      long todoBytes = srcFile.length();
      while ( todoBytes != 0L ) {
        long iterationBytes = Math.min( todoBytes, chunkSize );
        long transferredLength = oChannel.transferFrom( iChannel, doneBytes, iterationBytes );
        if ( iterationBytes != transferredLength ) {
          throw new IOException(
              "Error during file transfer: expected "
                  + iterationBytes + " bytes, only " + transferredLength + " bytes copied."
          );
        }
        doneBytes += transferredLength;
        todoBytes -= transferredLength;
      }
    }
    finally {
      if ( is != null ) {
        is.close();
      }
      if ( os != null ) {
        os.close();
      }
    }
    boolean successTimestampOp = destFile.setLastModified( srcFile.lastModified() );
    if ( !successTimestampOp ) {
      System.out.println("Could not change timestamp for {}. Index synchronization may be slow. " + destFile );
    }
  }

  public static void delete(File file) {
    if ( file.isDirectory() ) {
      for ( File subFile : file.listFiles() ) {
        delete( subFile );
      }
    }
    if ( file.exists() ) {
      if ( !file.delete() ) {
        System.out.println( "Could not delete {}" + file );
      }
    }
  }
}

代码的功能与宣传的相同,但我不确定的是:

  1. 我如何实施取消?我能想到的最好的方法是引入一个synchronizationCancelled布尔变量,该变量在按下取消按钮时将设置为true,并将synchronize()与检查结合,如果为false则将其停止执行。

  2. 我如何实现回滚?我能提出的最好的想法是将更改/删除/新文件复制到一边,然后在取消后将它们复制回原来的位置。

  3. 我的猜测是否真的是正确的方法,是否有一些我不知道的警告,或者在这种情况下取​​消和回滚是否完全不同?

4 个答案:

答案 0 :(得分:1)

如果您只想保留源文件夹中的文件,我认为,将它们复制到第三个文件夹(让我们称之为tmp)很简单,如果用户没有取消操作,只需重命名它要定位,否则删除它。

答案 1 :(得分:1)

  1. 布尔值很好但是如果您考虑使用多线程则不会,因为整个类都是静态的。在这种情况下,您应该使用具有synchronized布尔属性的标记(只是某个对象)来避免任何问题。 Afaik没有标准化的课程或方法。
  2. 你必须以某种方式存储副本。对于少量和小文件,您可以简单地使用内存,但是对于更大的数量或大小,您必须在同步之前将文件存储在文件系统中的某个位置。但是您只需要复制要同步的文件,因此它取决于进程。 就像hasnae提出的那样,你可以事先/事先重新命名它们以获得方便。 但最终总是有一个时间尽可能小,它仍在运行,取消不再是一个选项。
  3. 也许有点插图2: 文件A到文件B

    方式一:

    Store B in memory -> copy A to B -> Point of save return -> Delete B in memory
    Rollback memory B to file B
    

    方式二:

    Store B in C > copy A to B -> Point of save return -> Delete C
    Rollback copy C to B
    

    方式三(hasnae):

    Copy A in C > rename B to D -> rename/move C to B -> Point of save return -> Delete D
    Rollback delete C
    

答案 2 :(得分:1)

几年前我实施了一些非常相似的东西,多年来它已经证明非常有效。

我们的同步要复杂得多,因为它涉及更多步骤(依赖关系管理,注册,暂停/恢复功能,使用取消注册和清理回滚),但我相信同样的解决方案适用于您的情况。

  1. 你做得对。让多个线程工作者将项目(文件)出列,并在每次迭代时检查暂停标志。睡觉/必要时唤醒他们。

  2. 对我们来说,最好使用文件扩展名,因为它也允许版本控制。 例如,你最终得到了:

    • destination.exe(旧文件)
    • destination.exe.v2(新文件)

      或:

    • destination.exe(新文件)

    • destination.exe.bak(旧文件)
  3. 完成后,只需应用最终名称(确认同步/回滚)。

答案 3 :(得分:1)

执行此操作的最佳方法是执行以下操作

  1. 确定未更改文件列表
  2. 确定已删除文件的列表
  3. 确定您必须复制的文件
  4. 获得这些文件后,在与您同步的目录相同的驱动器卷上创建一个新目录。

    对于未更改的文件列表,请为同一文件创建一个到新目录的硬链接(名称相同,树中的相对位置相同)。

    对于已删除的文件,您什么都不做。

    对于新文件,只需将它们复制到新目录结构即可。

    完成所有操作后,将旧目录重命名为临时目录。将新目录重命名为原始目录名称。然后,最后,删除旧目录。

    如果您需要&#34;回滚&#34;,只需删除新目录。

    这样做的好处是对于没有更改的文件,不消耗新的磁盘空间,并且没有时间丢失复制您已有的数据。硬链接基本上是指向相同数据的另一个指针。这就是为什么你必须在同一驱动器上执行此操作,这是一个目录技巧。你永远不会丢失已删除的文件(你只是不要链接它们),然后你就可以在新文件夹中找到新的副本。

    因此,最后,唯一复制的文件是更改的文件。