By patlimosnero


2011-05-18 12:25:44 8 Comments

I used this code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;

namespace WindowsApplication1 {
  public partial class Form1 : Form {
    // Class to report progress
    private class UIProgress {
      public UIProgress(string name_, long bytes_, long maxbytes_) {
        name = name_; bytes = bytes_; maxbytes = maxbytes_;
      }
      public string name;
      public long bytes;
      public long maxbytes;
    }
    // Class to report exception {
    private class UIError {
      public UIError(Exception ex, string path_) {
        msg = ex.Message; path = path_; result = DialogResult.Cancel;
      }
      public string msg;
      public string path;
      public DialogResult result;
    }
    private BackgroundWorker mCopier;
    private delegate void ProgressChanged(UIProgress info);
    private delegate void CopyError(UIError err);
    private ProgressChanged OnChange;
    private CopyError OnError;

    public Form1() {
      InitializeComponent();
      mCopier = new BackgroundWorker();
      mCopier.DoWork += Copier_DoWork;
      mCopier.RunWorkerCompleted += Copier_RunWorkerCompleted;
      mCopier.WorkerSupportsCancellation = true;
      OnChange += Copier_ProgressChanged;
      OnError += Copier_Error;
      button1.Click += button1_Click;
      ChangeUI(false);
    }

    private void Copier_DoWork(object sender, DoWorkEventArgs e) {
      // Create list of files to copy
      string[] theExtensions = { "*.jpg", "*.jpeg", "*.bmp", "*.png", "*.gif" };
      List<FileInfo> files = new List<FileInfo>();
      string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
      DirectoryInfo dir = new DirectoryInfo(path);
      long maxbytes = 0;
      foreach (string ext in theExtensions) {
        FileInfo[] folder = dir.GetFiles(ext, SearchOption.AllDirectories);
        foreach (FileInfo file in folder) {
          if ((file.Attributes & FileAttributes.Directory) != 0) continue;
          files.Add(file);
          maxbytes += file.Length;
        }
      }
      // Copy files
      long bytes = 0;
      foreach (FileInfo file in files) {
        try {
          this.BeginInvoke(OnChange, new object[] { new UIProgress(file.Name, bytes, maxbytes) });
          File.Copy(file.FullName, @"c:\temp\" + file.Name, true);
        }
        catch (Exception ex) {
          UIError err = new UIError(ex, file.FullName); 
          this.Invoke(OnError, new object[] { err });
          if (err.result == DialogResult.Cancel) break;
        }
        bytes += file.Length;
      }
    }
    private void Copier_ProgressChanged(UIProgress info) {
      // Update progress
      progressBar1.Value = (int)(100.0 * info.bytes / info.maxbytes);
      label1.Text = "Copying " + info.name;
    }
    private void Copier_Error(UIError err) {
      // Error handler
      string msg = string.Format("Error copying file {0}\n{1}\nClick OK to continue copying files", err.path, err.msg);
      err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
    }
    private void Copier_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
      // Operation completed, update UI
      ChangeUI(false);
    }
    private void ChangeUI(bool docopy) {
      label1.Visible = docopy;
      progressBar1.Visible = docopy;
      button1.Text = docopy ? "Cancel" : "Copy";
      label1.Text = "Starting copy...";
      progressBar1.Value = 0;
    }
    private void button1_Click(object sender, EventArgs e) {
      bool docopy = button1.Text == "Copy";
      ChangeUI(docopy);
      if (docopy) mCopier.RunWorkerAsync();
      else mCopier.CancelAsync();
    }
  }
}

posted here (the one that nobugz posted) in copying files and displaying the status in progress bar.

I wanted to continuously increment the value of the progress bar while copying, especially large files. What happens in this sample code is that the value in progress bar stops on every file copied and after one file has been copied it will then increment to the size of the next file to be copied. I wanted it to work like CopyFileEx in Windows that progress bar continuously increment when copying (I cant use CopyFileEx because I wanted to have my own implementation).

6 comments

@Anton Semenov 2011-05-19 07:42:56

You need something like this:

    public delegate void ProgressChangeDelegate(double Persentage, ref bool Cancel);
    public delegate void Completedelegate();

    class CustomFileCopier
    {
        public CustomFileCopier(string Source, string Dest)
        {
            this.SourceFilePath = Source;
            this.DestFilePath = Dest;

            OnProgressChanged += delegate { };
            OnComplete += delegate { };
        }

        public void Copy()
        {
            byte[] buffer = new byte[1024 * 1024]; // 1MB buffer
            bool cancelFlag = false;

            using (FileStream source = new FileStream(SourceFilePath, FileMode.Open, FileAccess.Read))
            {
                long fileLength = source.Length;
                using (FileStream dest = new FileStream(DestFilePath, FileMode.CreateNew, FileAccess.Write))
                {
                    long totalBytes = 0;
                    int currentBlockSize = 0;

                    while ((currentBlockSize = source.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        totalBytes += currentBlockSize;
                        double persentage = (double)totalBytes * 100.0 / fileLength;

                        dest.Write(buffer, 0, currentBlockSize);

                        cancelFlag = false;
                        OnProgressChanged(persentage, ref cancelFlag);

                        if (cancelFlag == true)
                        {
                            // Delete dest file here
                            break;
                        }
                    }
                }
            }

            OnComplete();
        }

        public string SourceFilePath { get; set; }
        public string DestFilePath { get; set; }

        public event ProgressChangeDelegate OnProgressChanged;
        public event Completedelegate OnComplete;
    }

Just run it in separate thread and subscribe for OnProgressChanged event.

@patlimosnero 2011-05-20 02:27:06

Thanks man! this is exactly what I was looking for.. Thanks a lot bro!

@MemphiZ 2013-03-14 13:44:41

Did someone adapt this to copy folders as well?

@Offler 2013-06-21 06:26:29

can you show how to use the delegate? The "OnComplete += delegate { };" looks a bit disturbing

@Anton Semenov 2013-06-24 07:13:53

This is an empty delegate stub, it do nothing. I introduce it only for simplify code. otherwise code OnProgressChanged(persentage, ref cancelFlag); should be writen as if ( OnProgressChanged != null) OnProgressChanged(persentage, ref cancelFlag); You can read about delegates here msdn.microsoft.com/en-us/library/900fyy8e(v=vs.71).aspx

@Grisgram 2019-02-04 14:09:16

Well, tbh you should obey a bit more to the event pattern by creating CancelEventArgs for the progress event and not use ref in any way for this. Besides that, thanks for the code snippet. Helped a lot!

@toddmo 2013-11-03 16:45:51

I like this solution, because

The copy engine is in the framework

public delegate void IntDelegate(int Int);

public static event IntDelegate FileCopyProgress;
public static void CopyFileWithProgress(string source, string destination)
{
    var webClient = new WebClient();
    webClient.DownloadProgressChanged += DownloadProgress;
    webClient.DownloadFileAsync(new Uri(source), destination);
}

private static void DownloadProgress(object sender, DownloadProgressChangedEventArgs e)
{
    if(FileCopyProgress != null)
        FileCopyProgress(e.ProgressPercentage);
}

UNC Paths

This should work with UNC paths as long as the permissions are set up. If not, you will get this error, in which case, I vote for the authenticated request user route.

System.UnauthorizedAccessException: Access to the path '\testws01\c$\foo' is denied.

ASP.NET is not authorized to access the requested resource. Consider granting access rights to the resource to the ASP.NET request identity. ASP.NET has a base process identity (typically {MACHINE}\ASPNET on IIS 5 or Network Service on IIS 6 and IIS 7, and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating. If the application is impersonating via <identity impersonate="true"/>, the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user.

@Muis 2014-10-13 13:04:03

I totally agree, this is far the easiest solution.

@TEK 2016-03-21 10:33:38

This is very nice (and quick), but my only concern is the inability to capture proper IO Exceptions. DownloadFileAsync throws ArgumentNullException, WebException and InvalidOperationException none of which are much use for when file handling goes wrong. Still, if all you need is a quick way to copy files with progress and you know all will be well, this is a great way.

@toddmo 2016-03-21 14:52:04

@TEK, I would be surprised if the IOException was not in the inner exception property of the Exception thrown. If you can point me to a url for the mono code for this class, I'll see what it is actually doing.

@TEK 2016-03-21 20:38:52

@toddmo I can do one better, I can link you directly to Microsoft's repo: referencesource.microsoft.com/#System/net/System/Net/… Thank you for taking a look.

@toddmo 2016-03-21 21:01:24

@TEK, ok yes, it wraps every other exception inside of a WebException but keeps the inner exception. So I'd simulate an IO Error (lock the file, whatever) to verify it, but I bet the IO exception is there. I would run the experiment in .net fiddle but it requires disk access.

@toddmo 2016-10-23 17:37:05

@codea, Yes, if allowed: System.UnauthorizedAccessException: Access to the path '\\test\c$\blah' is denied. Consider granting access rights to the resource to the ASP.NET request identity. ASP.NET has a base process identity (typically {MACHINE}\ASPNET on IIS 5 or Network Service on IIS 6 and IIS 7, and the configured application pool identity on IIS 7.5) that is used if the application is not impersonating. If the application is impersonating via <identity impersonate="true"/>, the identity will be the anonymous user (typically IUSR_MACHINENAME) or the authenticated request user.

@codea 2016-10-23 18:35:31

Thanks a lot for chiming in :), I just tested and it works perfectly.

@Beauty 2017-01-25 13:51:39

Great solution. Also supported by Windows 10. Don't forget to add a try-catch for an OperationCancelledException. It's thrown in the case that the user clicks to abort.

@mafu 2020-04-05 18:01:44

Note that this downloads in background, i.e. it returns before the download completes. Consider this when comparing to other methods. Replace with DownloadFileTaskAsync to get a Task.

@Robear 2014-10-24 21:00:33

Here's an optimized solution that utilizes .NET extensions and a double-buffer for better performance. A new overload of CopyTo is added to FileInfo with an Action that indicates progress only when it has changed.

This sample implementation in WPF with a progress bar named progressBar1 that performs the copy operation in the background.

private FileInfo _source = new FileInfo(@"C:\file.bin");
private FileInfo _destination = new FileInfo(@"C:\file2.bin");

private void CopyFile()
{
  if(_destination.Exists)
    _destination.Delete();

  Task.Run(()=>{
    _source.CopyTo(_destination, x=>Dispatcher.Invoke(()=>progressBar1.Value = x));
  }).GetAwaiter().OnCompleted(() => MessageBox.Show("File Copied!"));
}

Here's an example for a Console Application

class Program
{
  static void Main(string[] args)
  {
    var _source = new FileInfo(@"C:\Temp\bigfile.rar");
    var _destination = new FileInfo(@"C:\Temp\bigfile2.rar");

    if (_destination.Exists) _destination.Delete();

    _source.CopyTo(_destination, x => Console.WriteLine($"{x}% Complete"));
    Console.WriteLine("File Copied.");
  }
}

To use, create a new file, such as FileInfoExtensions.cs and add this code:

public static class FileInfoExtensions
{
  public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback)
  {
    const int bufferSize = 1024 * 1024;  //1MB
    byte[] buffer = new byte[bufferSize], buffer2 = new byte[bufferSize];
    bool swap = false;
    int progress = 0, reportedProgress = 0, read = 0;
    long len = file.Length;
    float flen = len;
    Task writer = null;

    using (var source = file.OpenRead())
    using (var dest = destination.OpenWrite())
    {
      dest.SetLength(source.Length);
      for (long size = 0; size < len; size += read)
      {
        if ((progress = ((int)((size / flen) * 100))) != reportedProgress)
          progressCallback(reportedProgress = progress);
        read = source.Read(swap ? buffer : buffer2, 0, bufferSize);
        writer?.Wait();  // if < .NET4 // if (writer != null) writer.Wait(); 
        writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read);
        swap = !swap;
      }
      writer?.Wait();  //Fixed - Thanks @sam-hocevar
    }
  }
}

The double buffer works by using one thread to read and one thread to write, so the max speed is dictated only by the slower of the two. Two buffers are used (a double buffer), ensuring that the read and write threads are never using the same buffer at the same time.

Example: the code reads into buffer 1, then when the read completes, a write operation starts writing the contents of buffer 1. Without waiting finish writing, the buffer is swapped to buffer 2 and data is read into buffer 2 while buffer 1 is still being written. Once the read completes in buffer 2, it waits for write to complete on buffer 1, starts writing buffer 2, and the process repeats. Essentially, 1 thread is always reading, and one is always writing.

WriteAsync uses overlapped I/O, which utilizes I/O completion ports, which rely on hardware to perform asynchronous operations rather than threads, making this very efficient. TLDR: I lied about there being 2 threads, but the concept is the same.

@halloweenlv 2016-02-24 10:27:15

I did use Your code for quite a while and randomly started having problems, when I got DAMAGED FILES due to this implementation - one of the writers did continue to write to file one more time - it did write the beginning of the source file to the end of destination file thus corrupting it. Can not recommend this, sorry.

@sam hocevar 2016-03-02 12:47:33

@halloweenlv this code is not very well written, but it almost works. You can fix it by replacing the dest.Write(…) at the end with if (writer != null) writer.Wait();

@Robear 2016-03-07 16:20:16

@sam-hocevar I updated the sample code and tested it. Thanks for catching that bug. I also added code for testing it in a console app. Could you please elaborate on "this code is not very well written"? Any suggestions are welcome.

@sam hocevar 2016-03-07 19:27:30

@Robear apart from the using directives, the code is very “C-ish” (not that there’s anything wrong with C, but in C# things are often done very differently), has a few redundancies (the swap ? buffer : buffer2 part), also buffer2 is used when swap is false, not when it’s true, the last Read call will ask for too many bytes, the variable name progress2 makes it look like it has something to do with buffer2 but it’s just the previous value of progress… of course there’s nothing really wrong with all that, it just doesn’t look very C#.

@Robear 2016-03-08 01:10:35

Ah okay. Well if the main objection is that it doesn't "look" like C#, I'm fine with that. The code utilizes a double buffer, allowing a read and a write to occur simultaneously, so there's no redundancy there - it's an optimization (absence of double buffers in stream copies always irks me). I've renamed progress2 to reportedProgress for clarity. If you have any other suggestions, though, specifically with regards to making it "look more C#ish", please feel free to pastebin me something and link it here.

@Robear 2016-03-08 01:17:47

Also note that the count parameter for the Read method specified the maximum size to be read, not the desired size. It's really just to specify the upper bounds for the buffer. There's no benefit to adding logic to reducing the read request size. MSDN - Read Method

@Richie86 2019-01-25 06:46:27

great snippet. should add a progressCallback(100); before writer?.Wait(); otherwise the progress will end at 99 only.

@Polity 2011-05-18 12:59:45

Making your own file copy logic by using 2 streams as presented by Gal is a viable option but its not recommended solely because there is a deeply intergrated windows operation which is optimized in reliability, security and performance named CopyFileEx.

that said, in the following article: http://msdn.microsoft.com/en-us/magazine/cc163851.aspx they do exactly what you want, but ofcourse you have to use CopyFileEx

Good luck

** EDIT ** (fixed my answer, badly witten)

@Jim Mischel 2011-05-18 14:29:22

CopyFileEx is very broken when copying large files across the network. See blog.mischel.com/2008/10/14/copying-large-files-on-windows for details. Also, it's pretty easy to improve on the speed of CopyFileEx using two streams and a little bit of asynchronous coding.

@mafu 2020-04-04 18:45:33

@JimMischel Don't make such a broad statement. Streams do not support DMA. Async is not a magic solution.

@Jim Mischel 2020-04-04 19:40:42

@mafu Don't be too quick to judge that which you don't fully understand. The blog post (which is, unfortunately unavailable currently) documents very well how broken CopyFileEx is, or was at the time I wrote that comment. And at the time I was using a custom CopyFile method that far outperformed CopyFileEx simply by using two different threads: one for reading and one for writing. No, async isn't magic. But properly used, it can improve performance.

@mafu 2020-04-04 22:47:59

I should have picked a friendlier tone, sorry about that. I also don't know enough about this topic to argue in detail. My main point was that anything using streams afaik requires moving bytes through the CPU, which should be possible to be avoided. When, however, using streams, your idea is likely very good.

@Akrem 2011-05-18 12:33:46

you can use Dispatcher to update your ProgressBar .

UpdateProgressBarDelegate updatePbDelegate = new UpdateProgressBarDelegate(ProgressBar1.SetValue);

Dispatcher.Invoke(updatePbDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { ProgressBar.ValueProperty, value });

@mafu 2020-04-04 18:47:22

This does not interact with copying a file, which is the crux of the question...

@Gal 2011-05-18 12:34:26

You can copy parts of the file stream from each file, and update after each "chunk" you update. Thus it will be more continuous - you can also easily calculate the relative size of the current "chunk" you are copying relative to the total stream size in order to show the correct percentage done.

Related Questions

Sponsored Content

33 Answered Questions

[SOLVED] How do I create a file and write to it in Java?

  • 2010-05-21 19:58:55
  • Drew Johnson
  • 2883830 View
  • 1394 Score
  • 33 Answer
  • Tags:   java file-io

13 Answered Questions

[SOLVED] How to delete a file or folder?

32 Answered Questions

[SOLVED] How do I create a Java string from the contents of a file?

44 Answered Questions

[SOLVED] How do I create an Excel (.XLS and .XLSX) file in C# without installing Microsoft Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 1114255 View
  • 1912 Score
  • 44 Answer
  • Tags:   c# .net excel file-io

20 Answered Questions

[SOLVED] How do I tell if a regular file does not exist in Bash?

  • 2009-03-12 14:48:43
  • Bill the Lizard
  • 2549932 View
  • 3306 Score
  • 20 Answer
  • Tags:   bash file-io scripting

18 Answered Questions

[SOLVED] Copying files from Docker container to host

26 Answered Questions

[SOLVED] Find all files in a directory with extension .txt in Python

  • 2010-10-19 01:09:13
  • usertest
  • 1873239 View
  • 1043 Score
  • 26 Answer
  • Tags:   python file-io

16 Answered Questions

[SOLVED] How do I copy a file in Python?

27 Answered Questions

[SOLVED] Find and restore a deleted file in a Git repository

13 Answered Questions

[SOLVED] Correct way to write line to file?

  • 2011-05-28 05:44:53
  • Yaroslav Bulatov
  • 2134376 View
  • 1088 Score
  • 13 Answer
  • Tags:   python file-io

Sponsored Content