C#写差异文件备份工具的示例

 更新时间:2020年10月26日 13:52  点击:2141

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

以上就是C#写差异文件备份工具的示例的详细内容,更多关于c# 文件备份的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#中list用法实例

    这篇文章主要介绍了C#中list用法,结合实例形式分析了C#中list排序、运算、转换等常见操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25