用C#寫差別文件備份工具

你們是否是日常都有好多文件須要按期備份?如歌曲、視頻、文檔,代碼文件等等,若是常常增長刪除修改文件,就須要按期備份,最先以前文件都不大的時候我都是手工先所有刪除,而後再所有拷貝,感受比較保險。後來有了很大的電影文件和很瑣碎的代碼文件以後,這樣搞太折磨人,就學網上說的用Xcpoy組裝了一個批處理。學了C#後,感受仍是作一個GUI體驗更好用起來更方便。至於專業的工具,還真沒怎麼試過,有點不放心吧,有好用的卻是能夠試試。如今先本身作一個用着吧。windows

關鍵代碼以下:async

        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("備份完畢!", "提示", MessageBoxButtons.OK, 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}", target.FullName, 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, "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
        }

 注意事項:工具

// 文件和目錄的建立日期爲首次全新複製時的建立時間
// 文件複製後修改日期始終保持原先的不變,目錄的修改日期爲首次全新複製時的建立時間(由於本就是新建)
// 單純的覆蓋不會改變修改時間和建立時間
// 文件發生的屬性變化全新複製時能夠保留(沒法經過更新時間判斷文件的屬性變化)測試

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------spa

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

問題出在 if (await CopyAllAsync(diSource, diTarget)) 這個地方,備份開始後,lblWork.Text = "備份開始!";  結果發現標籤的設置並不生效,而後界面很卡,不能拖動窗口。在須要備份更新的文件特別多時感受更明顯。code

原來,設置控件的Enabled屬性是當即生效,但控件的Text屬性並非當即生效,就是UI界面不會當即更新,只是將設置信息加入了windows消息隊列,一般等所在的方法執行完畢後才生效,但若是方法中該語句後面還有同類的設置,就會感受不到它的生效,實際上是生效了,只是先設爲了一個值,而後又當即設爲了另外一個值,由於太快了,人眼看不出來。一樣的緣由,「正在複製文件XXX」也不即時顯示正在複製的文件信息。orm

而後,界面卡頓,是由於拷貝的時候執行緊密運算,可是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("備份完畢!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); } else lblWork.Text = "出現錯誤!";

 更新:添加了瀏覽選擇目錄功能,能夠不用手動輸入路徑了。點我下載源碼及可執行文件blog

相關文章
相關標籤/搜索