Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CopyFileGeneric

0.00/5 (No votes)
29 Apr 2010 1  
Copy files with additional functionalities

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.

CopyFile1.png

CopyFile2.png

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:

/// <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;
            }
        }
    }
}

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here