我有两个目录结构,需要同步它们,即将源文件夹中新的或更改的文件和文件夹复制到目标文件夹中,并删除目标中不存在于源中的文件和文件夹。但是,这个过程需要有一个"取消"按钮,它将阻止它执行并回滚所有更改。我四处搜索并找到了这段代码,但我对其实际工作原理的理解充其量是朦胧的。
//$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 );
}
}
}
}
代码的功能与宣传的相同,但我不确定的是:
我如何实施取消?我能想到的最好的方法是引入一个synchronizationCancelled布尔变量,该变量在按下取消按钮时将设置为true,并将synchronize()与检查结合,如果为false则将其停止执行。
我如何实现回滚?我能提出的最好的想法是将更改/删除/新文件复制到一边,然后在取消后将它们复制回原来的位置。
我的猜测是否真的是正确的方法,是否有一些我不知道的警告,或者在这种情况下取消和回滚是否完全不同?
答案 0 :(得分:1)
如果您只想保留源文件夹中的文件,我认为,将它们复制到第三个文件夹(让我们称之为tmp)很简单,如果用户没有取消操作,只需重命名它要定位,否则删除它。
答案 1 :(得分:1)
也许有点插图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)
几年前我实施了一些非常相似的东西,多年来它已经证明非常有效。
我们的同步要复杂得多,因为它涉及更多步骤(依赖关系管理,注册,暂停/恢复功能,使用取消注册和清理回滚),但我相信同样的解决方案适用于您的情况。
你做得对。让多个线程工作者将项目(文件)出列,并在每次迭代时检查暂停标志。睡觉/必要时唤醒他们。
对我们来说,最好使用文件扩展名,因为它也允许版本控制。 例如,你最终得到了:
destination.exe.v2(新文件)
或:
destination.exe(新文件)
完成后,只需应用最终名称(确认同步/回滚)。
答案 3 :(得分:1)
执行此操作的最佳方法是执行以下操作
获得这些文件后,在与您同步的目录相同的驱动器卷上创建一个新目录。
对于未更改的文件列表,请为同一文件创建一个到新目录的硬链接(名称相同,树中的相对位置相同)。
对于已删除的文件,您什么都不做。
对于新文件,只需将它们复制到新目录结构即可。
完成所有操作后,将旧目录重命名为临时目录。将新目录重命名为原始目录名称。然后,最后,删除旧目录。
如果您需要&#34;回滚&#34;,只需删除新目录。
这样做的好处是对于没有更改的文件,不消耗新的磁盘空间,并且没有时间丢失复制您已有的数据。硬链接基本上是指向相同数据的另一个指针。这就是为什么你必须在同一驱动器上执行此操作,这是一个目录技巧。你永远不会丢失已删除的文件(你只是不要链接它们),然后你就可以在新文件夹中找到新的副本。
因此,最后,唯一复制的文件是更改的文件。