2016-01-14 45 views
0

我有兩個目錄結構,需要將它們同步,即將源文件夾中新增或更改的文件和文件夾複製到目標文件夾中,並刪除源中不存在的目標文件和文件夾。然而,這個過程需要有一個「取消」按鈕,這將停止執行並回滾所有的更改。我四處搜索,發現了這個代碼,但我對它的實際工作原理的理解充其量是朦朧的。如何在java中實現可停止和可取消的文件夾同步?

//$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,並將synchronized()與檢查相關聯,如果檢測結果爲false,則會停止執行。

  2. 我該如何實現回滾?我可以想到的最好的辦法是將更改/刪除/新文件複製到一邊,然後在取消後將它們複製回原來的位置。

都是我的猜測實際上做了正確的方式,還有一些注意事項給那些我不知道的,還是應該取消和回退可以在這種情況下完全不同實現的?

回答

1

如果你只是想保留位於源文件夾中的文件,我認爲,將它們複製到第三個文件夾(我們稱之爲tmp)會很簡單,如果用戶不取消操作,只需將其重命名爲目標,否則將其刪除。

1
  1. 該布爾是一種很好,但不是如果你曾考慮使用它多線程,尤其是因爲整個類是靜態的。在這種情況下,您應該使用具有同步布爾屬性的標記(只是某個對象)來避免任何問題。 Afaik沒有標準化的課程或方法。
  2. 你必須以某種方式存儲副本。對於少數和小文件,您可以簡單地使用內存,但是對於更大的數量或更大的大小,在同步之前,必須將文件存儲在文件系統中的某個位置。但是你只需要複製你想要同步的文件,所以它依賴於進程。 像hasnae建議你可以重新命名它們之前/之前的便利。 但最終總會有一個小小的時間,它可能仍然在運作,取消將不再是一個選項。

也許有點圖解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 

路3(hasnae):

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

幾年前,我已經實現了一些非常類似的東西,並且多年來它已被證明工作得非常好。

我們的同步是更復雜,因爲它涉及多個步驟(依賴管理,登記,暫停/恢復功能,與註銷登記和清潔回滾),但我相信同樣的解決方案適用於你的情況。

  1. 你說得對。讓多個線程工作人員將您的項目(文件)出列,並在每次迭代時檢查暫停標誌。睡眠/必要時喚醒它們。

  2. 對我們來說是最好用的文件擴展名,因爲它也允許版本。 因此,例如,你最終有:

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

      或:

    • destination.exe(新文件)

    • destination.exe.bak(舊文件)

完成後只需應用最終名稱(確認同步/回滾)。

1

要做到這一點,最好的辦法是做以下

  1. 確定不變的文件
  2. 名單確定刪除文件一覽
  3. 確定你必須複製
文件

一旦你有這些,在與你正在同步的目錄相同的驅動器捲上,創建一個新的目錄。

對於不變的文件列表,創建硬鏈接爲同一文件到新的目錄(使用相同的名稱,並在樹中的相對位置相同)。

對於被刪除的文件,你什麼都不做。

對於新文件,只需將它們複製到新的目錄結構。

全部完成後,將舊目錄重命名爲臨時。將新目錄重命名爲原始目錄名稱。然後,最後刪除舊的目錄。

如果你需要「回滾」,只需刪除新的目錄。

這樣做的好處是沒有改變的文件,沒有新的磁盤空間被消耗,不浪費時間複製你已經擁有的數據。硬鏈接基本上是指向相同數據的另一個指針。這就是爲什麼你必須在相同的驅動器上這樣做,這是一個目錄技巧。你永遠不會丟失被刪除的文件(你根本不會鏈接它們),然後你只需要在新文件夾中添加新的副本。

那麼到底,做到這一點的唯一文件複製是修改過的文件。

相關問題