Introduction

The last article, DirectoryInfoEx – Asynchronous Enumeration, explained how to enumerate FileSystemInfoEx entries asynchronously, using EnumerateFiles() method, this article will explain how to Copy, Move and Delete files asynchronously using DirectoryInfoEx.

FileStreamEx

FileStreamEx is the DirectoryInfoEx version of FileStream.  As it is inherited from Stream, StreamReader/BinaryWriter can be used.

Using FileStreamEx instead of using File.Copy() method to Copy a file allows progress to be updated, and user can abort the operation any time.

The following is a simple method that copy a file.

public static void CopyFile(string source, string dest, FileCancelDelegate cancel)
{
  using (FileStreamEx srcStream = FileEx.OpenRead(source))
  {
    byte[] buffer = new byte[Math.Min(1024 * 1024 * 32, srcStream.Length)];  //32MB
    int readCount;
    ushort completePercent = 0;
    long completeCount = 0;
    using (FileStreamEx destStream = FileEx.Create(dest))
    {
      while ((readCount = srcStream.Read(buffer, 0, buffer.Length)) > 0
        && !IsCancelTriggered(cancel, completePercent))
        {
           completeCount += readCount;
           destStream.Write(buffer, 0, readCount);
           completePercent = srcStream.Length == 0 ?
              (ushort)100 : (ushort)((float)completeCount / (float)srcStream.Length * 100.0);
        }
      destStream.Flush();
      destStream.Close();
    }
  srcStream.Close();
  }
}
public delegate bool FileCancelDelegate(ushort percentageCompleted);

FileCancelDelegate is a function that return a boolean, it allow user to cancel when Copying a file.

Work

To copy files in dotNet framework, you can use Directory.Copy() method, the method may take several seconds to several hours, because this method does not report it’s progress, user have no way to know whats going on.

Most experienced developer will implement their own way to handle the Copy, which does the following :

  • Discover the files/directories to copy.
  • Transfer the file/directories
    - for directory, create empty one.
    - for file, copy the file using FileEx.Copy() method or FileStreamEx
  • Handle if the file already exists
    - for move, ask whether overwrite or not.
    - for copy, ignore the file if the files are same (size, crc), otherwise ask.
  • Run the operation in a separate thread to make it smoother.

This is the basic idea of what DirectoryInfoEx’s Work does.  DirectoryInfoEx includes 4 types of Work, List, Copy, Move and Delete.

All works are inherited from ExWorkBase, which

  • start work (in the same thread or a new thread)
  • report progress
  • pause/abort a work
  • display shell or custom progress dialog when working

Starting a Work :

DirectoryInfoEx srcFolder = new DirectoryInfoEx(@"C:\Dir1");
DirectoryInfoEx destFolder = new DirectoryInfoEx(@"C:\Dir2");
CopyWork cw = new CopyWork(-1, srcFolder.GetFileSystemInfos(), destFolder);
cw.Start(true);

The above one demonstrate the most used one : CopyWork, it provides a constructor which takes id (related to WorkSpawner mentioned below), source entries and destination directory.

The Start() method start the work, the parameter specify whether to run in a new thread or not.

Reporting Progress

cw.WorkProgress += (WorkProgressEventHandler)delegate(object sender, WorkProgressEventArgs e)
{
   Console.WriteLine(String.Format("{0}/{1} - {2}", e.Completed, e.Total, e.File));
   //if (e.Completed == 80) //Cancel the operation when 80 entries is transferred.
   //    e.Cancel = true;
};

Whever CopyWork completes something, it will call it’s internal ReportWork() method, which will trigger a number of events (WorkProgress, WorkMessage, WorkStarted, WorkFinished, WorkPaused, WorkOverwrite and WorkList)

Pause/Abort a work

To Abort an ongoing work, you can set e.Cancel in the OnProgress event mentioned above, or call the Abort() method.
To Pause an ongoing work, you can block the OnProgress event, or set the Paused property to true.

CancelDelegate cancel = delegate { return Aborted; };
IOTools.CopyFile(item.FullName, PathEx.Combine(destDir.FullName, item.Name), cancel);

CopyWork (not MoveWork) uses FileStreamEx to do the copy, so you can abort when it’s copying, however, as you see it cannot report progress of copy of individual file.

Progress Dialog

cw.IsProgressDialogEnabled = true;

The default IsProgressDialogEnabled is false, if enabled, will display WinProgressDialog, which show progress and allow user to cancel the operation.  You can also implement your own dialog by setting cw.ProgressDialog property to your dialog (which implement the ICustomProgressDialog interface).

The default Progress dialog do not support pause, you may need to implement a new dialog if you want pause support.

ProgressDialog requires DirectoryInfoEx 0.18+

WorkSpawner

int ID = WorkSpawner.SpawnCopyWork(srcFolder.GetFileSystemInfos(), destFolder, true);
WorkSpawner.WorkProgress += (WorkProgressEventHandler)delegate(object sender, WorkProgressEventArgs e)
{
  Console.WriteLine(String.Format("{0} - {1}/{2} - {3}", e.ID, e.Completed, e.Total, e.File));
};
WorkSpawner.Start(ID);

WorkSpawner, as it’s name implies, spawn Works, it contains SpawnCopyWork() and other methods to spawn new Works, and instead of return you a Work, it return the ID of the Work, you can use WorkSpawner.Works[ID] property to access the Work.

The reason for storing the Work inside a class is to allow developer to manage all Work in a central place, and to run Spawned Work sequentially.

private static void startID(int[] workIDs, int currentIdx)
{
  if (currentIdx < workIDs.Length - 1)
  Works[workIDs[currentIdx]].WorkFinished +=
    delegate { startID(workIDs, currentIdx + 1); };
  Works[workIDs[currentIdx]].Start(true);
}

As you can see, when one work is completed (WorkFinished) it will trigger the next Work.