1. 程式人生 > 程式設計 >C#寫差異檔案備份工具的示例

C#寫差異檔案備份工具的示例

大家是不是平常都有好多檔案需要定期備份?如歌曲、視訊、文件,程式碼檔案等等,如果經常增加刪除修改檔案,就需要定期備份,最早之前檔案都不大的時候我都是手工先全部刪除,然後再全部拷貝,感覺比較保險。後來有了很大的電影檔案和很瑣碎的程式碼檔案之後,這樣搞太折磨人,就學網上說的用Xcpoy組裝了一個批處理。學了C#後,感覺還是做一個GUI體驗更好用起來更方便。至於專業的工具,還真沒怎麼試過,有點不放心吧,有好用的倒是可以試試。現在先自己做一個用著吧。

C#寫差異檔案備份工具的示例

關鍵程式碼如下:

private async void btnBackUp_Click(object sender,EventArgs e)
    {
      string sourceDirectory = txtSource.Text;
      string targetDirectory = txtTarget.Text;
      if (sourceDirectory.ToLower() == targetDirectory.ToLower())
      {
        Console.WriteLine("源目錄和備份目錄不能是同一目錄!");
        MessageBox.Show("源目錄和備份目錄不能是同一目錄!","提示",MessageBoxButtons.OK,MessageBoxIcon.Warning);
        return;
      }
      DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);  // 源目錄
      DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);  // 備份目錄
      if (diTarget.Name != diSource.Name)
        diTarget = new DirectoryInfo(Path.Combine(diTarget.FullName,diSource.Name));  // 建立同名目錄
      if (!diTarget.Exists) diTarget.Create();  // 如果該目錄已存在,則此方法不執行任何操作
      btnBackUp.Enabled = false;
      txtSource.Enabled = false;
      txtTarget.Enabled = false;
      lblWork.Text = "備份開始!";
      if (await CopyAllAsync(diSource,diTarget))
      {
        lblWork.Text = "備份完成!";
        MessageBox.Show("備份完畢!",MessageBoxIcon.Information);
      }
      else lblWork.Text = "出現錯誤!";
      btnBackUp.Enabled = true;
      txtSource.Enabled = true;
      txtTarget.Enabled = true;
      btnBackUp.Focus();
    }

    public async Task<bool> CopyAllAsync(DirectoryInfo source,DirectoryInfo target)
    {
      try
      {
        foreach (FileInfo fi in source.GetFiles())  // 複製最新檔案
        {
          Console.WriteLine(@"準備複製檔案 {0}\{1}",target.FullName,fi.Name);  // Name不含路徑,僅檔名
          FileInfo newfi = new FileInfo(Path.Combine(target.FullName,fi.Name));
          if (!newfi.Exists || (newfi.Exists && fi.LastWriteTime > newfi.LastWriteTime))
          {
            Console.WriteLine("正在複製檔案 {0}",newfi.FullName);
            lblWork.Text = string.Format("正在複製檔案 {0}",newfi.FullName);
            if (newfi.Exists && newfi.IsReadOnly) newfi.IsReadOnly = false;
            // 覆蓋或刪除只讀檔案會產生異常:對路徑“XXX”的訪問被拒絕
            fi.CopyTo(newfi.FullName,true);  // Copy each file into it's new directory
          }
        }

        foreach (FileInfo fi2 in target.GetFiles())  // 刪除源目錄沒有而目標目錄中有的檔案
        {
          FileInfo newfi2 = new FileInfo(Path.Combine(source.FullName,fi2.Name));
          if (!newfi2.Exists)
          {
            Console.WriteLine("正在刪除檔案 {0}",fi2.FullName);
            lblWork.Text = string.Format("正在刪除檔案 {0}",fi2.FullName);
            if (fi2.IsReadOnly) fi2.IsReadOnly = false;
            fi2.Delete();  // 沒有許可權(如系統盤需管理員許可權)會產生異常,檔案不存在不會產生異常
          }
        }

        foreach (DirectoryInfo di in source.GetDirectories())  // 複製目錄(實際上是建立同名目錄,和源目錄的屬性不同步)
        {
          Console.WriteLine(" {0} {1}",di.FullName,di.Name);  // Name不含路徑,僅本級目錄名
          Console.WriteLine(@"準備建立目錄 {0}\{1}",di.Name);
          DirectoryInfo newdi = new DirectoryInfo(Path.Combine(target.FullName,di.Name));
          if (!newdi.Exists)  // 如果CopyAllAsync放在if裡的bug: 只要存在同名目錄,則不會進行子目錄和子檔案的檢查和更新
          {
            Console.WriteLine("正在建立目錄 {0}",newdi.FullName);
            lblWork.Text = string.Format("正在複製目錄 {0}",newdi.FullName);
            DirectoryInfo diTargetSubDir = target.CreateSubdirectory(di.Name);  // 建立目錄
            Console.WriteLine("完成建立目錄 {0}",diTargetSubDir.FullName);
          }
          if (await CopyAllAsync(di,newdi) == false) return false; ;  // Copy each subdirectory using recursion
        }

        foreach (DirectoryInfo di2 in target.GetDirectories())  // 刪除源目錄沒有而目標目錄中有的目錄(及其子目錄和檔案)
        {
          DirectoryInfo newdi2 = new DirectoryInfo(Path.Combine(source.FullName,di2.Name));
          if (!newdi2.Exists)
          {
            Console.WriteLine("正在刪除目錄 {0}",di2.FullName);
            lblWork.Text = string.Format("正在刪除目錄 {0}",di2.FullName);
            di2.Delete(true);  // 只讀的目錄和檔案也能刪除,如不使用引數則異常"目錄不是空的"
          }
        }
        return true;
      }
      catch (Exception e)
      {
        Console.WriteLine(e.Message);
        MessageBox.Show(e.Message,MessageBoxIcon.Error);
        return false;
      }
    }

注意事項:

// 檔案和目錄的建立日期為首次全新複製時的建立時間
// 檔案複製後修改日期始終保持原先的不變,目錄的修改日期為首次全新複製時的建立時間(因為本就是新建)
// 單純的覆蓋不會改變修改時間和建立時間
// 檔案發生的屬性變化全新複製時可以保留(無法通過更新時間判斷檔案的屬性變化)

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

今天測試,又發現一個bug,真是防不勝防,好在終於找到病根並解決了。

問題出在 if (await CopyAllAsync(diSource,diTarget)) 這個地方,備份開始後,lblWork.Text = "備份開始!"; 結果發現標籤的設定並不生效,然後介面很卡,不能拖動視窗。在需要備份更新的檔案特別多時感覺更明顯。

原來,設定控制元件的Enabled屬性是立即生效,但控制元件的Text屬性並不是立即生效,就是UI介面不會立即更新,只是將設定資訊加入了windows訊息佇列,通常等所在的方法執行完畢後才生效,但如果方法中該語句後面還有同類的設定,就會感覺不到它的生效,其實是生效了,只是先設為了一個值,然後又立即設為了另一個值,因為太快了,人眼看不出來。同樣的原因,“正在複製檔案XXX”也不即時顯示正在複製的檔案資訊。

然後,介面卡頓,是因為拷貝的時候執行緊密運算,但是CopyAllAsync(diSource,diTarget)方法並沒有在單獨的執行緒執行,佔用了UI執行緒,導致介面卡頓,改成下面這樣,完美解決:

lblWork.Text = "備份開始!"; bool result = await Task.Run(() => CopyAllAsync(diSource,diTarget));   // 這兒是關鍵
if (result)  // if (await CopyAllAsync(diSource,diTarget)) 開始後介面會卡{
  lblWork.Text = "備份完成!";
  MessageBox.Show("備份完畢!",MessageBoxIcon.Information);
}
else lblWork.Text = "出現錯誤!";

以上就是C#寫差異檔案備份工具的示例的詳細內容,更多關於c# 檔案備份的資料請關注我們其它相關文章!