Background thread to delete multiple types files


Story: 

We work for an online image storage service who gives opportunity to store different types of images. The requirements are so simple indeed. We need to delete different types of image files from our storage which is 8 weeks old so that we make enough space for our new users in each month. The deletion will take long time and we need to do the job as a background thread. Also we make an option for simulate the scenario to understand whether the right thing is going on or not. One thing needs to take in and that is another option to include or exclude sub directories of the target storage.

Expected output
Solution:

1. First, we need a class which depicts the arguments involved in this process:

     /// <summary>
    /// Class for Storage Cleanup Arguments
    /// </summary>
    public class StorageCleanArgs
    {
        /// <summary>
        ///  Gets or sets the directory path
        /// </summary>
        public string Directory { get; set; }

        /// <summary>
        /// Gets or sets the types name
        /// </summary>
        public string Types { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the SubDirectory is available
        /// </summary>
        public bool SubDirectory { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether simulate or not
        /// </summary>
        public bool Simulate { get; set; }

        /// <summary>
        /// Gets or sets the date from which deletion will be made
        /// </summary>
        public DateTime DateBefore { get; set; }
    }


2. Now we required a utility/helper class which checks whether time limit is okay or not and also the validation of directories:

/// <summary>
    /// Class to provide support to StorageCleanUC
    /// </summary>
    public class StorageCleanUtility
    {
        /// <summary>
        /// Days that just greater than 8 weeks
        /// </summary>
        private const int MoreThanEightWeeks = 59;

        /// <summary>
        /// File list to delete
        /// </summary>
        private IEnumerable<string> fileList = null;        
       
        /// <summary>
        /// Method to determine the storage path whether null or invalid.
        /// </summary>
        /// <param name=" storagePath">The path where our taget files are exist</param>
        /// <returns>Status of checking</returns>
        public string CheckDir(string storagePath)
        {
            if (string.IsNullOrEmpty(storagePath))
            {
                return "Storage Path is empty!!";
            }
            else
                if (!this.IsValidFilePath(ref storagePath))
                {
                    return " Storage Path is invalid!!";
                }

            return "OK";
        }

        /// <summary>
        /// Method to determine Before date whether less than 8 weeks or future one
        /// </summary>
        /// <param name="dateBefore">The date before which we want to delete</param>
        /// <returns>Status of checking</returns>
        public string CheckBeforeDate(DateTime dateBefore)
        {
            if (((DateTime.Today - dateBefore).TotalDays > 1) &&
            ((DateTime.Today - dateBefore).TotalDays < MoreThanFourWeeks))
            {
                return "Delete Before Date is too close i.e., less than 4 weeks!!";
            }
            else
                if ((DateTime.Today - dateBefore).TotalDays <= 1)
                {
                    return "Delete Before Date cannot be current or future date!!";
                }

            return "OK";
        }

        /// <summary>
        /// Method to get the all types image files i.e, .jpg, .png and .gif
        /// </summary>
        /// <param name="directory">The directory where we search</param>
        /// <param name="availibilityOfSubDir">Whether Sub Directory included or not</param>
        /// <returns>Name of files found under the selected option</returns>
        public IEnumerable<string> GetImageFiles(string directory, bool availibilityOfSubDir)
        {
            if (availibilityOfSubDir)
            {
                this.fileList = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories)
                    .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
                        s.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
                        s.EndsWith(".gif", StringComparison.OrdinalIgnoreCase));
            }
            else
            {
                this.fileList = Directory.GetFiles(directory, "*.*", SearchOption.TopDirectoryOnly)
                    .Where(s => s.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
                        s.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
                        s.EndsWith(".gif", StringComparison.OrdinalIgnoreCase));
            }

            return this.fileList;
        }

        /// <summary>
        /// Method to determine whether file can be delete or not
        /// </summary>
        /// <param name="fileName">Whose creation date need to find</param>
        /// <param name="dateBefore">Before which date we need to delete</param>
        /// <returns>True if can otherwise false</returns>
        public bool FileCanBeDel(string fileName, DateTime dateBefore)
        {
            DateTime dt = Directory.GetCreationTime(fileName);
            if (dt <= dateBefore)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Method to determine is the Storage path is a valid path
        /// </summary>
        /// <param name="path">The path we want to check</param>
        /// <returns>True if exists, otherwise false</returns>
        private bool IsValidFilePath(ref string path)
        {
            string standardPattern = @"^(([a-zA-Z]\:)|(\\))(\\{1}|((\\{1})[^\\]([^/:*?<>""|]*))+)$";
            Regex reg = new Regex(standardPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
            return reg.IsMatch(path);
        }
    }

3. Next we write a UserControl class which conduct the thread in background and update the status list with progress bar:

    /// <summary>
    /// Class for Storage Cleanup process at background thread
    /// </summary>
    public partial class StorageCleanUC : UserControl
    {    
        /// <summary>
        /// To check whether first time execution 
        /// </summary>
        private bool firstTimeExe = true;
       
        /// <summary>
        /// To calculate percentage of completion
        /// </summary>
        private int completePercentage = 0; 
       
        /// <summary>
        /// To store the file path of all Image files of given directory and optional sub directory
        /// </summary>
        private IEnumerable<string> allIamgeFiles = null;
       
        /// <summary>
        /// To store the file path of all files to delete
        /// </summary>
        private List<string> fileToDel = null;
       
        /// <summary>
        /// To store the path of the parent directory to choose for deletion
        /// </summary>
        private string parentDirectory = null;

        /// <summary>
        /// To keep the input validity status
        /// </summary>
        private string status = null;

        /// <summary>
        /// To keep a common message box title 
        /// </summary>
        private string msgBoxTitle = "Storage Cleanup";
       
        /// <summary>
        /// To use the functionality of StorageCleanUtility class
        /// </summary>
        private StorageCleanUtility utilityObj = null;
      
        /// <summary>
        ///  Initializes a new instance of the StorageCleanUC class.
        /// </summary>
        public StorageCleanUC()
        {
            InitializeComponent();
            dateBeforeDateTimePicker.Format = DateTimePickerFormat.Custom;
            dateBeforeDateTimePicker.CustomFormat = "dd-MMM-yyyy";
        }

        /// <summary>
        /// Method that handle the click event of Start button
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void StartButton_Click(object sender, EventArgs e)
        {
            imgDelRichTextBox.Clear(); // Clear previous logs
           
            ////Check the given Directory and Before Date
            this.utilityObj = new StorageCleanUtility();
            this.status = this.utilityObj.CheckDir(storagePathTextBox.Text);
            if (this.status != "OK")
            {
                MessageBox.Show(
                    this.status,
                    this.msgBoxTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
                return;
            }

            this.status = this.utilityObj.CheckBeforeDate(dateBeforeDateTimePicker.Value);
            if (this.status != "OK")
            {
                if (MessageBox.Show(
                    this.status,
                    this.msgBoxTitle,
                    MessageBoxButtons.OKCancel,
                    MessageBoxIcon.Warning) == DialogResult.Cancel)
                {
                    return;
                }
            }

            ////Enable or disable some UI controls
            this.ChangeAbilityOfUIControls();

            ////Wrap-up the required data for process
            StorageCleanArgs argumentsObj = new StorageCleanArgs();
            argumentsObj.Directory = storagePathTextBox.Text;
            if (subdirCheckBox.Checked)
            {
                argumentsObj.SubDirectory = true;
            }
            else
            {
                argumentsObj.SubDirectory = false;
            }

            if (simulateCheckBox.Checked)
            {
                argumentsObj.Simulate = true;
            }
            else
            {
                argumentsObj.Simulate = false;
            }
                           
            argumentsObj.DateBefore = dateBeforeDateTimePicker.Value;

            ////Start the background
            if (!imgDelBackgroundWorker.IsBusy)
            {
                imgDelBackgroundWorker.RunWorkerAsync(argumentsObj);
            }
               
            ////To report progress from the background worker we need to set this property
            imgDelBackgroundWorker.WorkerReportsProgress = true;
        }

        /// <summary>
        /// Make some UI control enable and disable
        /// </summary>
        private void ChangeAbilityOfUIControls()
        {
            storagePathTextBox.Enabled = false;
            subdirCheckBox.Enabled = false;
            simulateCheckBox.Enabled = false;
            dateBeforeDateTimePicker.Enabled = false;
            browseButton.Enabled = false;
            if (startButton.Enabled)
            {
                startButton.Enabled = false;
            }
               
            if (!cancelButton.Enabled)
            {
                cancelButton.Enabled = true;
            }
        }

        /// <summary>
        /// Method to handle the DoWork event of the imgDelBackgroundWorker
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void ImgDelBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            this.utilityObj = new StorageCleanUtility();
            StorageCleanArgs argumentsObj = (StorageCleanArgs)e.Argument;
            this.parentDirectory = argumentsObj.Directory;

            ////Get all Image files with given directory and optional sub directory
            try
            {
                this.allImgFiles = this.utilityObj.GetIMGFiles(this.parentDirectory, argumentsObj.SubDirectory);
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    "Error Loading Data - \n" + ex,
                    this.msgBoxTitle,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
                return;
            }

            this.fileToDel = new List<string>();
            if (this.allImgFiles != null)
            {
                ////Listed out files to delete with given Before Date
                foreach (string fp in this.allImgFiles)
                {
                    try
                    {
                        if (this.utilityObj.FileCanBeDel(fp, argumentsObj.DateBefore))
                        {
                            this.fileToDel.Add(fp);
                        }                           
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(
                            "Error Getting Data - \n" + ex,
                            this.msgBoxTitle,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error);
                        return;
                    }                                      
                }

                if (this.fileToDel.Count > 0)
                {
                    int total = this.fileToDel.Count;
                   
                    ////Delete or not all files those are listed to do so                   
                    for (int i = 0; i < total; ++i)
                    {
                        if (imgDelBackgroundWorker.CancellationPending)
                        {
                            break;
                        }

                        this.completePercentage = (int)(((float)(i + 1) / total) * 100);

                        ////Delete files if not simulate
                        if (!argumentsObj.Simulate)
                        {
                            try
                            {
                                File.Delete(this.fileToDel[i]);
                            }
                            catch (Exception ex)
                            {
                                MessageBox.Show(
                                    "Error Deleting Files - \n" + ex,
                                    this.msgBoxTitle,
                                    MessageBoxButtons.OK,
                                    MessageBoxIcon.Error);
                                return;
                            }                                      
                        }

                        ////Report progress to 'UI' thread
                        imgDelBackgroundWorker.ReportProgress(this.completePercentage, "Deleting File :" + this.fileToDel[i]);
                                             
#if DEBUG
                        ////Simulate long task
                        Thread.Sleep(300);
#endif
                    }
                   
                    if (!imgDelBackgroundWorker.CancellationPending)
                    {
                        e.Result = "Task Complete";
                    }
                    else if (imgDelBackgroundWorker.CancellationPending)
                    {
                        e.Result = "Task Cancelled";
                    }
                }
                else
                {
                    e.Result = "Task Complete";
                    this.completePercentage = 100;
                    imgDelBackgroundWorker.ReportProgress(this.completePercentage, "No files found to delete");
                }
            }
            else
            {
                e.Result = "Task Complete";
                this.completePercentage = 100;
                imgDelBackgroundWorker.ReportProgress(this.completePercentage, "No files found to delete");              
            }
        }

        /// <summary>
        /// Method to handle the Click event of Cancel button
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void CancelButton_Click(object sender, EventArgs e)
        {
            if (imgDelBackgroundWorker.IsBusy)
            {
                ////Request for cancellation of background process
                imgDelBackgroundWorker.CancelAsync();
                cancelButton.Text = "Stopping..";
                if (cancelButton.Enabled)
                {
                    cancelButton.Enabled = false;               
                }                   
            }
        }

        /// <summary>
        /// Method to handle the ProgressChanged event of imgDelBackgroundWorker 
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void ImgDelBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            imgDelProgressBar.Value = e.ProgressPercentage;
            if (this.firstTimeExe)
            {
                imgDelRichTextBox.Text = "Cleaning Up Folder: " + this.parentDirectory;
                imgDelRichTextBox.Text += "\nDelete Old Files... \n";
                this.firstTimeExe = false;
            }

            imgDelRichTextBox.Text += "\n" + e.UserState;           
        }

        /// <summary>
        /// Method to handle the RunWorkerCompleted event of imgDelBackgroundWorker 
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void ImgDelBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Result.ToString() == "Task Cancelled")
            {
                imgDelRichTextBox.Text += "\nYou canceled!";
            }               
            else if (e.Error != null)
            {
                imgDelRichTextBox.Text += "Worker exception: " + e.Error;               
            }
            else if (e.Result.ToString() == "Task Complete")
            {
                imgDelRichTextBox.Text += "\n\nCompleted!!";
            }
               
            if (!startButton.Enabled)
            {
                startButton.Enabled = true;
            }
               
            if (cancelButton.Enabled)
            {
                cancelButton.Enabled = false;
            }               
        }

        /// <summary>
        /// Method to handle the Click event of browse button
        /// </summary>
        /// <param name="sender">Itself that raised the event</param>
        /// <param name="e">Event data pertaining to this event</param>
        private void BrowseButton_Click(object sender, EventArgs e)
        {
            if (imgDelFolderBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                storagePathTextBox.Text = imgDelFolderBrowserDialog.SelectedPath;
            }
        }
    } 

That's all. 
Let me know whether it is helpful to any of the readers od this article.  
Happy coding...