需求概要
由于拷贝的文件比较大,有几G、十几G、几十G单个的文件,并且需要整个目录拷贝,直接用复制粘贴的操作导致内存开销不够,需要建立任务性的拷贝任务,并且把单个文件分段读写。中间有人工中断操作或者被意外中断的可能,下次续传需要接上一次的拷贝进度继续拷贝。
分析功能概要
1.可建立源目录到目标目录的复制任务
2.任务明细包括目录的文件以及子目录递归的所有子文件,并且在目标目录建立相同的子目录结构存放文件。
3.支持操作中断传送任务,以及支持意外中断后续传
4.任务进行中有可视化的任务进度反馈
实现方案
1.基于硬盘之间的拷贝,使用winform桌面应用程序实现功能需求。
界面效果:
2.既然需要记录任务,需要使用数据库记录信息,使用免安装的文件型数据库Sqlite。
3.建立任务表,用于记录拷贝任务;
文件复制任务表:
HT_CopyJob
字段名称 |
类型 |
备注说明 |
HT_ID |
Integer |
标识 |
FromDirectoryPath |
Varchar |
来源目录 |
ToDirectoryPath |
Varchar |
目标目录 |
CreateTime |
Datetime |
创建时间 |
FileCount |
Integer |
总文件数 |
CopyCount |
Integer |
当前已复制文件数 |
LeaveCount |
Integer |
剩下复制文件数 |
4.建立任务明细表,使用FileStream缓冲读取和写入,先将流放入内存,再写入文件,每次读取流的位置,要根据上一次最后读取的位置继续读取;记录文件拷贝的信息,以及上一次拷贝的进度,拷贝的情况。
文件复制明细表:
HT_CopyFile
字段名称 |
类型 |
备注说明 |
HT_ID |
Integer |
标识 |
CopyJob_ID |
Integet |
复制任务ID,ht_copyjob.HT_ID |
FromPath |
Varchar |
来源路径 |
ToPath |
Varchar |
目标路径 |
LenCount |
Integer |
文件总大小 |
CopyLenCount |
Integer |
已经复制大小 |
Position |
Integer |
记录当前流的位置 |
IsStart |
Integer |
是否开始复制0:否,1:开始 |
IsFinish |
Integer |
是否完成,0否,1:完成 |
Msg |
Varchar |
复制消息 |
5.创建复制任务主要代码
if (txt_CopyPath.Text.Trim() == "" || txt_targetPath.Text.Trim() == "") { MessageBox.Show("请完整选择复制目录和目标目录"); return; } List<HT.Model.HT_CopyJobModel> list_cjModel = cjBLL.GetModelList(string.Format(" and FromDirectoryPath='{0}' and ToDirectoryPath='{1}' ", txt_CopyPath.Text.Trim(), txt_targetPath.Text.Trim())); if (null != list_cjModel && list_cjModel.Count > 0) { DialogResult result = MessageBox.Show("该相同目录已经存在" + list_cjModel.Count + "个任务,是否还要继续创建?", "警告信息", MessageBoxButtons.YesNoCancel); if (result == DialogResult.No || result == DialogResult.Cancel) { return; } } HT.Model.HT_CopyJobModel cjModel = new HT.Model.HT_CopyJobModel(); cjModel.CreateTime = DateTime.Now; cjModel.FromDirectoryPath = txt_CopyPath.Text.Trim(); cjModel.ToDirectoryPath = txt_targetPath.Text.Trim(); int cjid = cjBLL.Add(cjModel); if (cjid > 0) { int filecount = 0; GetAllDirList(cjid, txt_CopyPath.Text.Trim(), txt_CopyPath.Text.Trim(), txt_targetPath.Text.Trim(), ref filecount); cjModel = cjBLL.GetModel(cjid); cjModel.FileCount = filecount; cjModel.LeaveCount = filecount; cjModel.CopyCount = 0; cjBLL.Update(cjModel); // FreeConsole(); MessageBox.Show("任务创建成功,该任务有" + filecount + "个文件需要复制,请到任务列表开始任务"); } else { MessageBox.Show("创建任务错误,请重新操作"); }
递归读取子目录的文件,并且保存到数据库
/// <summary> /// 获取子目录以及文件路径保存 /// </summary> /// <param name="cjid">任务ID</param> /// <param name="startDir">源路径</param> /// <param name="strBaseDir">上一级路径</param> /// <param name="targetPath">目标路径</param> private void GetAllDirList(int cjid, string startDir, string strBaseDir, string targetPath, ref int fileCount) { //AllocConsole(); DirectoryInfo dit = new DirectoryInfo(strBaseDir); SaveFilePath(cjid, startDir, dit, targetPath, ref fileCount); DirectoryInfo[] list_dit = dit.GetDirectories(); for (int i = 0; i < list_dit.Length; i++) { GetAllDirList(cjid, startDir, list_dit[i].FullName, targetPath, ref fileCount); } } /// <summary> /// 找到文件夹里的文件,并保存文件路径 /// </summary> /// <param name="cjid">任务ID</param> /// <param name="startDir">源路径</param> /// <param name="dit">文件夹</param> /// <param name="targetPath">目标路径</param> private void SaveFilePath(int cjid, string startDir, DirectoryInfo dit, string targetPath, ref int fileCount) { HT.BLL.HT_CopyFileBLL cfBLL = new HT.BLL.HT_CopyFileBLL(); HT.Model.HT_CopyFileModel cfModel = null; FileInfo[] files = dit.GetFiles(); if (null != files && files.Count() > 0) { foreach (var fl in files) { try { cfModel = new HT.Model.HT_CopyFileModel(); cfModel.CopyJob_ID = cjid; cfModel.FromPath = fl.FullName; cfModel.IsFinish = 0; cfModel.IsStart = 0; cfModel.LenCount = fl.Length + ""; cfModel.Msg = ""; cfModel.Position = "0"; cfModel.ToPath = fl.FullName.Replace(startDir, targetPath); cfModel.CopyLenCount = "0"; int cfid = cfBLL.Add(cfModel); if (cfid > 0) { fileCount++; //Console.WriteLine(fl.FullName + " 添加记录成功"); richTextBox_Log.Text += fl.FullName + " 添加记录成功 "; } else { //写入错误日志 } } catch { } } } }
6.开始执行复制任务主要代码
文件分段读写主要代码:
//文件复制方法 private bool CopyFileGo(HT.Model.HT_CopyFileModel cfModel, string fromPath, string toPath, int eachReadLength) { //将源文件 读取成文件流 FileStream fromFile = new FileStream(fromPath, FileMode.Open, FileAccess.Read); FileInfo fi = new FileInfo(toPath); var di = fi.Directory; if (!di.Exists) { di.Create(); } //已追加的方式 写入文件流 FileStream toFile = new FileStream(toPath, FileMode.Append, FileAccess.Write); if (toFile.Length == fromFile.Length) { cfModel.IsFinish = 1;//已拷贝完成 cfModel.CopyLenCount = fromFile.Length + ""; cfBLL.Update(cfModel); fromFile.Close(); toFile.Close(); return false; } if (toFile.Length > fromFile.Length) { cfModel.IsFinish = 2;//拷贝出错了 cfModel.CopyLenCount = toFile.Length + ""; cfModel.Msg = "目标文件比源文件大了"; cfBLL.Update(cfModel); fromFile.Close(); toFile.Close(); return false; } //实际读取的文件长度 int toCopyLength = 0; //如果每次读取的长度小于 源文件的长度 分段读取 if (eachReadLength < fromFile.Length) { byte[] buffer = new byte[eachReadLength]; long copied = toFile.Length; while (copied <= fromFile.Length - eachReadLength && updateWorker.CancellationPending == false) { fromFile.Position = toFile.Length; ;//读之前指定读取的位置 toCopyLength = fromFile.Read(buffer, 0, eachReadLength); fromFile.Flush(); //toFile.Position = fromFile.Position-eachReadLength;//写之前指定写的位置,等于对完文件后的位置减去读的大小 toFile.Write(buffer, 0, eachReadLength); toFile.Flush(); copied += toCopyLength; cfModel.CopyLenCount = copied + ""; cfModel.Position = fromFile.Position + ""; cfBLL.Update(cfModel); progressBarFile.Value += progressBarFile.Step; label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum; //Console.WriteLine(fromPath + "在分割复制,总共大小为:" + fromFile.Length + "目前进度:" + copied); } if (updateWorker.CancellationPending)//已取消后台操作 { fromFile.Close(); toFile.Close(); return false; } int left = (int)(fromFile.Length - copied); fromFile.Position = toFile.Length; toCopyLength = fromFile.Read(buffer, 0, left); fromFile.Flush(); //toFile.Position = fromFile.Position-eachReadLength; toFile.Write(buffer, 0, left); toFile.Flush(); cfModel.CopyLenCount = (copied + left) + ""; cfModel.Position = fromFile.Position + ""; cfModel.IsFinish = 1; cfBLL.Update(cfModel); progressBarFile.Value = progressBarFile.Maximum; label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum; //Console.WriteLine(fromPath + "总共大小为:" + fromFile.Length + "目前进度:" + copied); } else { if (updateWorker.CancellationPending)//已取消后台操作 { fromFile.Close(); toFile.Close(); return false; } //如果每次拷贝的文件长度大于源文件的长度 则将实际文件长度直接拷贝 byte[] buffer = new byte[fromFile.Length]; fromFile.Read(buffer, 0, buffer.Length); fromFile.Flush(); toFile.Write(buffer, 0, buffer.Length); toFile.Flush(); cfModel.CopyLenCount = fromFile.Length + ""; cfModel.Position = fromFile.Position + ""; cfModel.IsFinish = 1; cfBLL.Update(cfModel); progressBarFile.Value = progressBarFile.Maximum; label_File.Text = "文件(" + cfModel.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum; } fromFile.Close(); toFile.Close(); return true; }
开始执行复制任务主要代码:
void GoCopyJob(object sender, DoWorkEventArgs e) { if (cjid == 0) { MessageBox.Show("请选择执行任务编号"); return; } HT.Model.HT_CopyJobModel cjModel = cjBLL.GetModel(cjid); if (null != cjModel) { progressBarJob.Maximum = cjModel.FileCount; progressBarJob.Value = cjModel.CopyCount; progressBarJob.Step = 1; label_Job.Text = "任务(" + cjid + ")进度:" + cjModel.CopyCount + @"/" + cjModel.FileCount; List<HT.Model.HT_CopyFileModel> list_cfModel = cfBLL.GetModelList(string.Format(" and CopyJob_ID={0} and (IsStart=0 or IsFinish=0) order by IsStart DESC", cjid)); if (null != list_cfModel && list_cfModel.Count > 0) { foreach (var item in list_cfModel) { if (updateWorker.CancellationPending) { return; }//已取消后台操作 progressBarFile.Maximum = int.Parse((long.Parse(item.LenCount) / 1024 / 1024) + "");//转化单位为M progressBarFile.Value = int.Parse((long.Parse(item.CopyLenCount) / 1024 / 1024) + ""); progressBarFile.Step = eachReadLength / 1024 / 1024; label_File.Text = "文件(" + item.FromPath + ")进度:" + progressBarFile.Value + "/" + progressBarFile.Maximum; item.IsStart = 1; if (CopyFileGo(item, item.FromPath, item.ToPath, eachReadLength)) { //单个文件复制完成,更新界面显示进度 cjModel.CopyCount++; cjModel.LeaveCount--; cjBLL.Update(cjModel); if (progressBarJob.Value < progressBarJob.Maximum) { progressBarJob.Value += progressBarJob.Step; label_Job.Text = "任务(" + cjid + ")进度:" + cjModel.CopyCount + @"/" + cjModel.FileCount; } } else { //有文件复制不成功了 } } if (progressBarJob.Maximum == progressBarJob.Value) { MessageBox.Show("任务执行完毕"); } DataBind(); } else { cjModel.CopyCount = cjModel.FileCount; cjModel.LeaveCount = 0; if (cjBLL.Update(cjModel)) { MessageBox.Show("已经拷贝完了"); DataBind(); } } } else { MessageBox.Show("该任务不存在"); } }
以上就是主要的实现设计与主要的代码了,还有很多未完善和待完善的地方。很少写文章,语言组织上很难讲的明白,还需要以后多写了。欢迎大家指教。
需要源码的欢迎留下联系哦。