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

  • A+
所属分类:.NET技术
摘要

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

大家是不是平常都有好多文件需要定期备份?如歌曲、视频、文档,代码文件等等,如果经常增加删除修改文件,就需要定期备份,最早之前文件都不大的时候我都是手工先全部删除,然后再全部拷贝,感觉比较保险。后来有了很大的电影文件和很琐碎的代码文件之后,这样搞太折磨人,就学网上说的用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("备份完毕!", "提示", 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;             }         }

 注意事项:

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