Updating Progress Bar With File Copy Status

madaxe2021

Member
Joined
Jun 25, 2021
Messages
6
Programming Experience
3-5
I'm having a mental block, I have written a small file copying application using WPF and MVVM I want to update the progress bar based on the file copying status, I found the bits of code I want but, I don't know how to wire up the directory sync and the status methods back to my percentage complete property.

I know I have to replace my CopyTo methods but some of them overwrite and some don't so that's also confusing

Can somebody Help please, is there and easy way to connect these together to report progress

Very much appreciated.

Madaxe

WPF MV - Progressabr Property:
public void Download(object message)
        {
            DataTransfer.Copy(Path, @"C:\\DES",new Action<int>);
        }
        public bool CanDownload(object message)
        {
            if (PercentageCompleted == 0 || PercentageCompleted == 99)
            { return true; }
            return false;
        }

        int _PercentageCompleted;
        public int PercentageCompleted
        {
            get => _PercentageCompleted;
            set { _PercentageCompleted = value; NotifyPropertyChanged(); }
        }


DataTransfer:
using System;
using System;
using System.IO;
using System.Linq;

namespace Infrastructure_Project.FileSystem
{
    public static class DataTransfer
    {
        public static void Copy(string sourceDirectory, string targetDirectory)
        {
            DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);
            DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);

            CopyAll(diSource, diTarget);
        }

        private static void CopyAll(DirectoryInfo source, DirectoryInfo target)
        {
            Directory.CreateDirectory(target.FullName);

            foreach (FileInfo fi in source.GetFiles())
            {
                DateTime created = fi.CreationTime;
                DateTime lastmodified = fi.LastWriteTime;

                if (File.Exists(Path.Combine(target.FullName, fi.Name)))
                {
                    string tFileName = Path.Combine(target.FullName, fi.Name);
                    FileInfo f2 = new FileInfo(tFileName);
                    DateTime lm = f2.LastWriteTime;
                    Console.WriteLine(@"File {0}\{1} Already Exist {2} last modified {3}", target.FullName, fi.Name, tFileName, lm);

                    try
                    {
                        if (lastmodified > lm)
                        {
                            Console.WriteLine(@"Source file {0}\{1} last modified {2} is newer than the target file {3}\{4} last modified {5}",
                            fi.DirectoryName, fi.Name, lastmodified.ToString(), target.FullName, fi.Name, lm.ToString());
                            fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
                        }
                        else
                        {
                            Console.WriteLine(@"Destination File {0}\{1} Skipped", target.FullName, fi.Name);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
                else
                {
                    Console.WriteLine(@"Copying {0}\{1}", target.FullName, fi.Name);
                    fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
                }
            }

            SyncFiles(source, target);
            SyncFolders(source, target);

            foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
            {
                DirectoryInfo nextTargetSubDir =
                target.CreateSubdirectory(diSourceSubDir.Name);
                CopyAll(diSourceSubDir, nextTargetSubDir);
            }
        }

        private static void SyncFiles(DirectoryInfo source, DirectoryInfo target)
        {
            var targetFilesToDelete = target.GetFiles().Select(fi => fi.Name).ToHashSet();
            foreach (FileInfo sourceFile in source.GetFiles())
            {
                if (targetFilesToDelete.Contains(sourceFile.Name)) targetFilesToDelete.Remove(sourceFile.Name);

                var targetFile = new FileInfo(Path.Combine(target.FullName, sourceFile.Name));
                if (!targetFile.Exists)
                {
                    sourceFile.CopyTo(targetFile.FullName);
                }
                else if (sourceFile.LastWriteTime > targetFile.LastWriteTime)
                {
                    sourceFile.CopyTo(targetFile.FullName, true);
                }
            }
            foreach (var file in targetFilesToDelete)
            {
                File.Delete(Path.Combine(target.FullName, file));
            }
        }
        private static void SyncFolders(DirectoryInfo source, DirectoryInfo target)
        {
            var targetSubDirsToDelete = target.GetDirectories().Select(fi => fi.Name).ToHashSet();
            foreach (DirectoryInfo sourceSubDir in source.GetDirectories())
            {
                if (targetSubDirsToDelete.Contains(sourceSubDir.Name)) targetSubDirsToDelete.Remove(sourceSubDir.Name);
                DirectoryInfo targetSubDir = new DirectoryInfo(Path.Combine(target.FullName, sourceSubDir.Name));
                DataTransfer.SynchronizeDirectory(sourceSubDir, targetSubDir);
            }
            foreach (var subdir in targetSubDirsToDelete)
            {
                Directory.Delete(Path.Combine(target.FullName, subdir), true);
            }
        }
        private static void SynchronizeDirectory(DirectoryInfo source, DirectoryInfo target)
        {
            if (source.FullName == target.FullName || !source.Exists)
            {
                return;
            }
            if (!target.Exists)
            {
                Directory.CreateDirectory(target.FullName);
            }
        }
    }
}


Copy Method With Status Action:
using System;
using System.IO;
using System.Threading.Tasks;

namespace Infrastructure_Project.FileSystem
{
    public static class FileInfoExtension
    {
        public static void CopyTo(this FileInfo file, FileInfo destination, Action<int> progressCallback)
        {
            const int bufferSize = 1024 * 1024;
            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();
                    writer = dest.WriteAsync(swap ? buffer : buffer2, 0, read);
                    swap = !swap;
                }
                writer?.Wait();
            }
        }
    }
}
 
Last edited:

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,762
Location
Chesapeake, VA
Programming Experience
10+
From the looks of this call:
C#:
DataTransfer.Copy(Path, @"C:\\DES",new Action<int>);
you just wasted an opportunity to get notifications of the copying progress. I'm going to assume that last parameter (new Action <int> was meant to be a callback delegate so that the current progress value could be given to you, and you could update the UI appropriately. Right now, it looks like you are just passing in a do nothing delegate.
 

madaxe2021

Member
Joined
Jun 25, 2021
Messages
6
Programming Experience
3-5
From the looks of this call:
C#:
DataTransfer.Copy(Path, @"C:\\DES",new Action<int>);
you just wasted an opportunity to get notifications of the copying progress. I'm going to assume that last parameter (new Action <int> was meant to be a callback delegate so that the current progress value could be given to you, and you could update the UI appropriately. Right now, it looks like you are just passing in a do nothing delegate.

OK, so I created a new action with a delegate that updates the progress bar value property, so how do I run this on a background thread as it currently locks up the main UI thread.

WPF MV:
 public void Download(object message)
        {
                Action<int> actionUpdateProgressBar = UpdateProgressBar;
                DataTransfer.Copy(Path, @"C:\\DES", actionUpdateProgressBar);
        }

        private void UpdateProgressBar(int index)
        {
            PercentageCompleted = index;
        }

        int _PercentageCompleted;
        public int PercentageCompleted
        {
            get => _PercentageCompleted;
            set { _PercentageCompleted = value; NotifyPropertyChanged(); }
        }
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,762
Location
Chesapeake, VA
Programming Experience
10+

madaxe2021

Member
Joined
Jun 25, 2021
Messages
6
Programming Experience
3-5
So i have it all working, but i dont like what i did here is there a cleaner way to make this work?


working but not the best solution I'm sure:
public void Download(object message)
        {
            BackgroundWorker worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.DoWork += worker_DoWork;
            worker.ProgressChanged += worker_ProgressChanged;

            worker.RunWorkerAsync();         
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            Action<int> actionUpdateProgressBar = UpdateProgressBar;
            DataTransfer.Copy(Path, @"C:\\DES", actionUpdateProgressBar);
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            PercentageCompleted = e.ProgressPercentage;
        }

        private void UpdateProgressBar(int index)
        {
            PercentageCompleted = index;
        }
 

madaxe2021

Member
Joined
Jun 25, 2021
Messages
6
Programming Experience
3-5
I'm a bit green when it comes to the delegates and tasks

public static System.Threading.Tasks.Task Run (Action action);

I can see that I can run an action on a separate thread but how do I update the progress bar property in the mode view?

thanks

Madaxe
 

Skydiver

Staff member
Joined
Apr 6, 2019
Messages
3,762
Location
Chesapeake, VA
Programming Experience
10+
Inside your progress callback delegate, you would update the view model, just like you currently are doing. But since you are running in a different thread, you'll need to have the update happen in a UI thread. Use Dispatcher.Invoke() to do so.

 
Top Bottom