CopyFileGeneric






4.76/5 (14投票s)
复制文件并增加其他功能
引言
Windows 复制只是复制文件,而它唯一提供的功能是取消复制。然而,我经常需要一些额外的功能;其中包括暂停复制过程,将未复制的文件列在最后而不是烦人的中间。

此应用程序包含以下功能
- 暂停/恢复复制过程
- 跳过单个文件
- 在复制时抑制警告和错误
- 在最后显示未复制的文件列表,并可以选择重试复制它们
- 如果文件存在,则可以选择禁止覆盖警告。
我将代码分为两个不同的部分
- 复制:处理复制文件(项目
Backup2
) - 用户界面:处理用户界面以及与复制的同步(项目
CopyFilesGeneric
)
Using the Code
项目 Backup2
这是一个库项目。我使用了 Shell 函数 CopyFileEx()
来复制文件。此函数的定义是
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern unsafe bool CopyFileEx(string ExistingFileName, string NewFileName,
CopyProgressRoutine ProgressRoutine, IntPtr Data, ref Boolean Cancel,
CopyFileFlags CopyFlags);
另一个函数 HResultToString()
将 HRESULT
值转换为相应的消息。为此,我使用了一个文件 XPSP2CodesEn.csv,它存储了 HRESULT
及其对应的字符串消息。
public static string HResultToString(uint result)
{
System.IO.StreamReader str =
new StreamReader(Properties.Resources.ErrorCodeSource);
LumenWorks.Framework.IO.Csv.CsvReader csv = new CsvReader(str, true);
csv.SkipEmptyLines = true;
csv.SupportsMultiline = true;
foreach (var item in csv)
{
if (uint.Parse(item[0],
System.Globalization.CultureInfo.InvariantCulture) == (uint)result)
{
return item[2];
}
}
return "INVALID HRESULT";
}
为了读取 CSV 文件,我使用了 Sebastien Lorion 的 CsvReader
(https://codeproject.org.cn/KB/database/CsvReader.aspx)。
主函数 CopyFiles()
执行复制操作。需要注意的是,在未对文件进行计数之前,开始复制是无用的。这是因为在计数文件时,CountFiles()
函数会标记要复制的文件(通过将其添加到文件中)。
private void CopyFiles()
{
if (this.CurrentStatus == CopyStatus.Counting)
{
restEvent.Reset();
}
resetEvent.WaitOne();
this.CurrentStatus = CopyStatus.Copying;
RaiseCopyStartedEvent();
RaiseStatusChangedEvent();
foreach (KeyValuePair<string, double> item in files)
{
Retry:
bool success = false;
Boolean b = false;
previousCopiedSize = 0;
skip = false;
string destinationDirectory = Destination +
item.Key.Substring(0, item.Key.LastIndexOf(
Path.DirectorySeparatorChar)).Substring(sourceDirectory.Length - 1);
if (Directory.Exists(item.Key))
{
if ((Directory.CreateDirectory(Destination +
item.Key.Substring(sourceDirectory.Length - 1)) == null))
unCopiedFiles.Add(new UncopiedFilesStructure(item.Key,
item.Value, UnsafeNativeMethods.GetHResult(
(uint)Marshal.GetLastWin32Error()).ToString()));
continue;
}
if (cancelCopy)
{
RaiseCopyCanceledEvent();
copyThread.Abort();
}
if (destinationDirectory[destinationDirectory.Length - 1] !=
Path.DirectorySeparatorChar)
destinationDirectory += Path.DirectorySeparatorChar;
this.CurrentFile = item.Key;
RaiseFileCopyStartEvent(item.Key, Path.Combine
(destinationDirectory, Path.GetFileName(item.Key)), item.Value);
unsafe
{
success = UnsafeNativeMethods.CopyFileEx(item.Key,
destinationDirectory + Path.GetFileName(item.Key),
new CopyProgressRoutine(CopyProgressRoutineHandler), IntPtr.Zero,
ref b, this.CopyFlags);
}
if (!success)
{
string hres = UnsafeNativeMethods.HResultToString
(UnsafeNativeMethods.GetHResult((uint)Marshal.GetLastWin32Error()));
if (skip) { unCopiedFiles.Add(new UncopiedFilesStructure
(item.Key, item.Value, "Skipped: " + hres)); continue; }
switch (RaiseFileCopyUnsuccessfulEvent
(item.Key, destinationDirectory + Path.GetFileName(item.Key),
item.Value, hres))
{
case FileHandleProcedure.Skip:
case FileHandleProcedure.Cancel:
unCopiedFiles.Add(new UncopiedFilesStructure
(item.Key, item.Value, hres)); break;
case FileHandleProcedure.Retry: goto Retry;
case FileHandleProcedure.CancelAll: this.cancelCopy = true; break;
default: break;
}
}
else
{
RaiseFileCopyCompletedEvent(item.Key,
destinationDirectory + Path.GetFileName(item.Key), item.Value);
}
}
this.CurrentStatus = CopyStatus.CopyCompleted;
RaiseStatusChangedEvent();
RaiseCopyCompletedEvent();
}
此外,必须通知用户复制失败并进行处理。此任务由事件 FileCopyUnsuccessfull
执行。
项目 CopyFile
该项目引用了 Backup2
项目,并处理用户界面和同步过程。
最值得注意的需要稍作解释的函数是 UpdateUI()
。此函数的定义是
/// <summary>
/// Synchronize UI with the copying status
/// </summary>
/// <param name="sync">Synchronize Operation To perform</param>
/// <param name="objs">Objects and their values. It must have the following structure:
/// index representation
/// 0. Text; Control whose caption(text) is to be changed
/// 1. the text for control at index 0
/// 2. Status; Text for CopyStatusLabel
/// 3. Status; Text for CurrentFileLabel
/// 4. Progress; the progressbar whose value is to be updated
/// 5. the value of the progressbar specified at index 4
/// 6. UpdateList; Listbox that has to be updated
/// 7. Operation that has to be applied on Listbox at index 6; i.e., ADD, DELETE or CLEAR
/// 8+. objects at index 8 or more are added or deleted from
/// the listbox at index 6, depending on the operation specified at index 7
/// </param>
private void UpdateUI(SyncronizationOperations sync, object[] objs)
{
if ((sync & SyncronizationOperations.CheckBox) == SyncronizationOperations.CheckBox)
{
CheckBoxStatus();
}
if ((sync & SyncronizationOperations.SetText) == SyncronizationOperations.SetText)
{
if (objs.Length > 1 && objs[0] != null && objs[1] != null)
{
Control content = objs[0] as Control;
content.Text = objs[1] as string;
}
}
if ((sync & SyncronizationOperations.SetStatus) == SyncronizationOperations.SetStatus)
{
if (objs.Length > 2 && objs[2] != null)
{
CopyStatusLabel.Text = objs[2] as string;
this.Text = CopyStatusLabel.Text;
}
if (objs.Length > 3 && objs[3] != null)
{
CurrentFileLabel.Text = objs[3] as string;
}
}
if ((sync & SyncronizationOperations.SetProgress) ==
SyncronizationOperations.SetProgress)
{
if (objs.Length > 5 && objs[4] != null && objs[5] != null)
{
ProgressBar p = objs[4] as ProgressBar;
p.Value = Convert.ToInt32(objs[5], CultureInfo.InvariantCulture);
}
}
if ((sync & SyncronizationOperations.UpdateList) ==
SyncronizationOperations.UpdateList)
{
if (objs.Length > 7 && objs[6] != null && objs[7] != null)
{
ListView list = objs[6] as ListView;
switch ((ListboxOperations)objs[7])
{
case ListboxOperations.ADD:
for (int i = 8; i < objs.Length; i++)
{
list.Items.Add(new ListViewItem
((objs[i] as string).Split('\t')));
}
break;
case ListboxOperations.DELETE:
for (int i = 8; i < objs.Length; i++)
{
list.Items.Remove(new ListViewItem
((objs[i] as string).Split('\t')));
}
break;
case ListboxOperations.CLEAR:
list.Items.Clear();
break;
default:
break;
}
}
}
}
这段代码真的很令人困惑,需要编辑。问题是我对线程的概念很陌生。这似乎是我能想到的最好的方法。在这个方法中,有 2 个参数:第一个是 SyncronizationOperations
,它定义了要执行的操作;第二个是对象数组。索引 0
处的对象具有一个控件,其文本(标题)需要更改;索引 1
处的对象具有需要分配给索引 0
处对象的 string
。在索引 2
处,我们有用于更新拷贝器整体进度的 string
。类似地,索引 3
处的 string
用于更新当前文件的名称。索引 4
处的对象具有一个 progressbar
对象,而索引 5
处的对象具有索引 4
处 progressbar 的新值。在索引 6
处,我们有一个需要更新的 listview
。listview
的更新操作由对象 (ListboxOperations
) 提供,后续元素是要操作的 listview
的值。
历史
- 2010 年 4 月 23 日 - 初始上传
- 2010 年 4 月 30 日 - 修订
- 添加了支持 UI 以选择要复制的文件
- 其他一些小的改动