2020-10-25

用C#写差异文件备份工具

大家是不是平常都有好多文件需要定期备份?如歌曲、视频、文档,代码文件等等,如果经常增加删除修改文件,就需要定期备份,最早之前文件都不大的时候我都是手工先全部删除,然后再全部拷贝,感觉比较保险。后来有了很大的电影文件和很琐碎的代码文件之后,这样搞太折磨人,就学网上说的用Xcpoy组装了一个批处理。学了C#后,感觉还是做一个GUI体验更好用起来更方便。至于专业的工具,还真没怎么试过,有点不放心吧,有好用的倒是可以试试。现在先自己做一个用着吧。

关键代码如下:

  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;   }  }

 注意事项:

// 文件和目录的创建日期为首次全新复制时的创建时间
// 文件复制后修改日期始终保持原先的不变,目录的修改日期为首次全新复制时的创建时间(因为本就是新建)
// 单纯的覆盖不会改变修改时间和创建时间
// 文件发生的属性变化全新复制时可以保留(无法通过更新时间判断文件的属性变化)

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

今天测试,又发现一个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("备份完毕!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);}else lblWork.Text = "出现错误!";

 

原文转载:http://www.shaoqun.com/a/482792.html

oklink:https://www.ikjzd.com/w/1362

跨境通:https://www.ikjzd.com/w/1329

damai:https://www.ikjzd.com/w/1391


大家是不是平常都有好多文件需要定期备份?如歌曲、视频、文档,代码文件等等,如果经常增加删除修改文件,就需要定期备份,最早之前文件都不大的时候我都是手工先全部删除,然后再全部拷贝,感觉比较保险。后来有了很大的电影文件和很琐碎的代码文件之后,这样搞太折磨人,就学网上说的用Xcpoy组装了一个批处理。学了C#后,感觉还是做一个GUI体验更好用起来更方便。至于专业的工具,还真没怎么试过,有点不放心吧,有好
折扣网站:https://www.ikjzd.com/w/74
gtc:https://www.ikjzd.com/w/974
广州塔门票是多少?有什么优惠?:http://tour.shaoqun.com/a/39086.html
三亚有什么好玩的?:http://tour.shaoqun.com/a/43982.html
跨境电商数据:广东省大涨47.8%,厦门增长14.5%:https://www.ikjzd.com/home/17332

No comments:

Post a Comment