Introduction
Windows Copy just copies the files while the only functionality it provides is cancelling the copy. However there were a few additional functionalities that I required often; they include pausing a copying process, have the files that are not copied list at the end rather than annoying in between.
This application includes the following features:
- Pause/Resume a copy process
- Skip a single file
- Suppress warnings and errors while copying
- Show list of un-copied files at the end and option to retry copying them
- Option to suppress overwrite warnings if a file exists.
I have divided the code in two different segments:
- Copy: deals with copying the files (Project
Backup2
)
- User Interface: deals with the User Interface and synchronization with Copy (Project
CopyFilesGeneric
)
Using the Code
Project Backup2
This is a Library Project. I have used the Shell Function CopyFileEx()
to copy the files. This function is defined as:
[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);
Another function HResultToString()
converts an HRESULT
value to the corresponding message. For this, I have used a file XPSP2CodesEn.csv which stores the HRESULT
and their corresponding string message.
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";
}
For reading the CSV file, I have used CsvReader
by Sebastien Lorion (http://www.codeproject.com/KB/database/CsvReader.aspx).
The main function CopyFiles()
performs the copying operation. It is to be noted that until the files are not counted, it will be useless to start copying. This is because while counting files, the CountFiles()
function marks the files to be copied (by adding it to files).
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();
}
Also the user must be notified of a copy failure and dealt with. This task is performed by event FileCopyUnsuccessfull
.
Project CopyFile
This project references the Backup2
project and deals with the User Interface and synchronization process.
The most notable function that needs a little explaining is UpdateUI()
. This function is defined as:
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;
}
}
}
}
This code is a really confusing one and needs editing. The thing is I'm new to the concept of threading. This seemed to be the best method I could have came up with. In this method, there are 2 parameters: the first is SyncronizationOperations
that defines the operations to be performed; and the second one is an array of objects. The object at index 0
has a control whose text (caption) is to be changed; object at index 1
has the string
that has to be assigned to object at index 0
. At index 2
, we have string
that is used to update the overall progress of the copier. Similarly string
at index 3
is used to update the name of the current file. Object at index 4
has a progressbar
object while that at index 5
has the new value progressbar at index 4
. At index 6
, we have a listview
which is to be updated. The update operations of listview
is provide by object (ListboxOperations
) and the subsequent elements are the values of listview
that are to be operated upon.
History
- 23 April 2010 - Initial upload
- 30 April 2010 - Revision
- Added Support UI to select files to copy
- Some other minor changes