Скопировать файлы из трех папок в четвертый


У меня простой WinForm приложение, которое копирует все файлы и каталоги из трех папок в четвертый. Все папки выбираются/введенные пользователем.
Безопасность не является проблемой.

Для того, чтобы предотвратить приложения от зримо висит, я добавил BackgroundWorker для обновления значений ProgressBar. Однако поскольку задача создания структуры каталога и копировать файлы, не все так просто, я боюсь, я мог бы написать плохой отменить обработку.

Вторичный интерес - я могла бы пойти за борт с обработкой исключений.

Помимо того, что любой отзыв, комментарий или предложение по улучшению благодарностью!

Некоторые фона:
В ProgressBar называется "pbMain"
Три TextBoxes это-источник копирования называются "tbECheckPath", "tbCorePath" и "tbServicePath"
Есть List<TextBox> DebugTextBoxes переменная, которая содержит все три упомянутые TextBoxes.

На GitHub проекта.
Код в сайт Pastebin

Код:

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

namespace PublishForQA
{
    public partial class FormProgressBar : Form
    {
        static FormPublisher formPublisher = (FormPublisher)Form.ActiveForm;
        static FormProgressBar formProgressBar;
        static List<TextBox> debugTextBoxes = formPublisher.DebugTextBoxesList;
        static BackgroundWorker backgroundWorker = new BackgroundWorker();
        static DoWorkEventArgs WorkArgs;
        static int TotalOperationsCount;
        static int CurrentOpeartionCount;

        public FormProgressBar()
        {
            TotalOperationsCount = 0;
            CurrentOpeartionCount = 0;
            InitializeComponent();
            formProgressBar = this;
            backgroundWorker.DoWork += backgroundWorker_DoWork;
            backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
            backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;
            backgroundWorker.RunWorkerAsync();
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            WorkArgs = e;
            pbMain.Maximum = GetOperationsCount();
            CopyFilesAndDirectories();
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pbMain.Value = e.ProgressPercentage;
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show("An error occurred during copying:" + Environment.NewLine + e.Error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                Cleanup();
            }
            else if (e.Cancelled)
            {
                MessageBox.Show("Copy operation aborted.", "Abort", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                Cleanup();
            }
            else
            {
                MessageBox.Show("Copy operation completed successfully!", "Operation success", MessageBoxButtons.OK, MessageBoxIcon.Information);
                Cleanup();
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            backgroundWorker.CancelAsync();
        }

        /// <summary>
        /// Checks if "CancellationPending" is true and if it is aborts the thread and sets
        /// the DoWorkEventArgs's Cancel property to true.
        /// </summary>
        static void CheckForCancel()
        {
            if (backgroundWorker.CancellationPending)
            {
                WorkArgs.Cancel = true;
                Thread.CurrentThread.Abort();
            }
        }

        /// <summary>
        /// Marks the BackgroundWorker to be disposed and closes the progress bar form.
        /// </summary>
        static void Cleanup()
        {
            backgroundWorker.Dispose();
            formProgressBar.Close();
        }

        /// <summary>
        /// Counts all the directories that need to be created and all the
        /// files that need to be copied from each designated folder.
        /// </summary>
        /// <returns></returns>
        public static int GetOperationsCount()
        {
            formProgressBar.lblCurrentOperation.Text = "Counting the total number of operations...";
            foreach (var tb in debugTextBoxes)
            {
                CheckForCancel();
                TotalOperationsCount += Directory.GetFiles(tb.Text, "*", SearchOption.AllDirectories).Length;
                CheckForCancel();
                TotalOperationsCount += Directory.GetDirectories(tb.Text, "*", SearchOption.AllDirectories).Length;
                formProgressBar.lblCurrentPath.Text = tb.Text;
            }

            return TotalOperationsCount;
        }

        /// <summary>
        /// Recreates the directory structure at the target location and copies all files from the source recursively.
        /// </summary>
        public static void CopyFilesAndDirectories()
        {
            foreach (var tb in debugTextBoxes)
            {
                CheckForCancel();
                //If there is a task name provided we add a backslash, otherwise the QA Folder path's
                //last backslash will suffice.
                string destinationPath = formPublisher.tbQAFolderPath.Text + ((formPublisher.tbTaskName.Text.Length > 0) ? formPublisher.tbTaskName.Text + "\\" : string.Empty);

                //We set the name of the destination folder, depending
                //on the TextBox we are iterating over.
                switch (tb.Name)
                {
                    case "tbECheckPath":
                        destinationPath += "E-Check\\";
                        break;
                    case "tbCorePath":
                        destinationPath += "E-CheckCore\\";
                        break;
                    case "tbServicePath":
                        destinationPath += "E-CheckService\\";
                        break;
                    default:
                        break;
                }

                if (!CreateDirectoryStructure(tb.Text, destinationPath)) return;
                if (!CopyFiles(tb.Text, destinationPath)) return;
            }
        }

        /// <summary>
        /// Gets all the directories in a target path and recreates the same
        /// directory structure in the destination path.
        /// </summary>
        /// <param name="sourcePath">The path from which to read the directory structure.</param>
        /// <param name="destinationPath">The path where to recreate the directory structure.</param>
        /// <returns>"True" if the operation was successful, "false" if an exception was raised.</returns>
        public static bool CreateDirectoryStructure(string sourcePath, string destinationPath)
        {
            formProgressBar.lblCurrentOperation.Text = "Creating directory structure...";
            //These variables will hold the current source and target path of the "for" iteration.
            //They will be used to show more information in the exception catching.
            string sourceDir = FormPublisher.ErrorBeforeDirectoryLoop;
            string targetDir = FormPublisher.ErrorBeforeDirectoryLoop;
            try
            {
                //First we create the directory structure.
                Directory.CreateDirectory(destinationPath);
                foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
                {
                    CheckForCancel();
                    sourceDir = dirPath;
                    targetDir = dirPath.Replace(sourcePath, destinationPath);
                    formProgressBar.lblCurrentPath.Text = targetDir;
                    Directory.CreateDirectory(targetDir);
                    CurrentOpeartionCount++;
                    backgroundWorker.ReportProgress(CurrentOpeartionCount);
                }
                return true;
            }
            #region catch block
            catch (UnauthorizedAccessException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("The caller does not have the required permission for \"" + targetDir + "\".", sourceDir, targetDir, ex), "Unauthorized Access Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ArgumentNullException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("The path passed for directory creation is null.", sourceDir, targetDir, ex), "Argument Null Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ArgumentException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("The path passed for directory creation is invalid.", sourceDir, targetDir, ex), "Argument Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (PathTooLongException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("Cannot create target directory, path is too long.", sourceDir, targetDir, ex), "Path Too Long Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (DirectoryNotFoundException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("The path passed for directory creation could not be found.", sourceDir, targetDir, ex), "Directory Not Found Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (IOException ex)
            {
                //IO Exception can be either the passed path is a file or the network name is not known.
                //Since we have previous checks in place to make sure the path is a directory,
                //the second possible error is shown.
                MessageBox.Show(ExceptionMessageBuilder.Directory("The network name is not known.", sourceDir, targetDir, ex), "IO Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (NotSupportedException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("The path passed contains an illegal colon character.", sourceDir, targetDir, ex), "Not Supported Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
                return false;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.Directory("Unexpected exception occurred:" + Environment.NewLine + ex.Message, sourceDir, targetDir, ex), "Unexpected Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            #endregion
        }

        /// <summary>
        /// Copies all files from the target path to the destination path,
        /// overriding any existing ones.
        /// </summary>
        /// <param name="sourcePath">The path from which to copy all the files.</param>
        /// <param name="destinationPath">The path where to copy all the files.</param>
        /// <returns>"True" if the operation was successful, "false" if an exception was raised.</returns>
        public static bool CopyFiles(string sourcePath, string destinationPath)
        {
            formProgressBar.lblCurrentOperation.Text = "Copying files...";
            //These variables will hold the current source and target path of the "for" iteration.
            //They will be used to show more information in the exception catching.
            //But first they are set to the string used to indicate an error before the loop.
            string sourceFile = FormPublisher.ErrorBeforeFileLoop;
            string targetFileDir = FormPublisher.ErrorBeforeFileLoop;
            try
            {
                //We copy all files, overwriting any existing ones.
                foreach (string filePath in Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories))
                {
                    CheckForCancel();
                    sourceFile = filePath;
                    targetFileDir = filePath.Replace(sourcePath, destinationPath);
                    formProgressBar.lblCurrentPath.Text = filePath;
                    File.Copy(filePath, targetFileDir, true);
                    CurrentOpeartionCount++;
                    backgroundWorker.ReportProgress(CurrentOpeartionCount);
                }
                return true;
            }
            #region catch block
            catch (UnauthorizedAccessException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("The caller does not have the required permission for \"" + targetFileDir + "\".", sourceFile, targetFileDir, ex), "Unauthorized Access Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ArgumentNullException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("Either the source or destination file paths are null.", sourceFile, targetFileDir, ex), "Argument Null Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ArgumentException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("Either the source or destination file paths are invalid.", sourceFile, targetFileDir, ex), "Argument Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (PathTooLongException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("Either the source or destination file paths are too long.", sourceFile, targetFileDir, ex), "Path Too Long Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (DirectoryNotFoundException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("Either the source or destination file paths could not be found.", sourceFile, targetFileDir, ex), "Directory Not Found Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (NotSupportedException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("Either the source or destination file paths are invalid.", sourceFile, targetFileDir, ex), "Not Supported Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (FileNotFoundException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("\"" + sourceFile + "\" was not found.", sourceFile, targetFileDir, ex), "File Not Found Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (IOException ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("An I/O error has occurred.", sourceFile, targetFileDir, ex), "IO Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
                return false;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ExceptionMessageBuilder.File("An unexpected exception has occurred:" + Environment.NewLine + ex.Message, sourceFile, targetFileDir, ex), "Unexpected Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            #endregion
        }
    }
}


516
4
задан 25 февраля 2018 в 03:02 Источник Поделиться
Комментарии
1 ответ

1) поведение и сроков Thread.Abort метод трудно предсказать, поэтому вы должны избегать его использования, если вы 100% знаете, что вы делаете. По моему опыту, он используется только как крайняя мера в редких экзотических случаях. Она не должна быть использована как часть регулярного процесса вашего приложения. В случае, например, я сильно сомневаюсь, что прерывание потока делает ничего полезного, так как File.Copy родной звонок, и она, вероятно, не может быть прервано среды CLR.

2) последний раз я проверил, что не было File.Copy метод, который поддерживает отмену. Но это было давно, может все изменилось с тех пор. Но если это все-таки дело, есть и другие варианты:


  • Windows, имеет собственный метод для копирования файлов, поддерживает отмену. См.: http://www.pinvoke.net/default.aspx/kernel32.copyfileex

  • Вы можете использовать потоки для копирования файлов в чанки. См.: https://stackoverflow.com/a/7680710/1386995

  • Использовать стороннюю библиотеку для асинхронного ввода/вывода есть много.

3) В общем, вы должны попытаться отделить бизнес-логику от интерфейса. Там должен быть другой класс, который делает копирование, и ваши Form следует подозревают о ее реализации.

4) Почему ты используешь static поля? Не, если вы должны по каким-то причинам.

5) ИМХО, на сегодняшний день фон рабочего используется редко (async Программирование, сделали это устарело), но так WinForms. Так что, кроме кода, хотя и несколько устарел, вы используете фоновый поток worker выглядит хорошо для меня, я бы не назвал ее чрезмерно сложной.

6) Что касается исключений:


  • Проверки входных данных заранее, где это возможно, не дожидаясь исключения. Например, вы можете легко проверить, является ли файл путь null или же он имеет правильный формат, прежде чем пользователь пытается скопировать что-нибудь. Это может резко уменьшить количество исключений приходится ловить в дальнейшем.

  • Подумайте о том, кто ваши пользователи. Они действительно должны знать, что пошло не так? Они могут что-то сделать? Если так, то да, то, как вы обрабатывать исключения, является правильным. Если нет, то вместо создания 100500 разных сообщений, вы можете просто хочу показать один, что позволит пользователю легко отправить базового исключения и трассировку стека для вашей электронной почтой :)

2
ответ дан 27 февраля 2018 в 11:02 Источник Поделиться