private void IterateOverFiles(List <FolderFileItem> paths, string backupDirectory)
 {
     try
     {
         HadError  = false;
         IsRunning = true;
         if (Directory.Exists(backupDirectory) || _isCalculatingFileSize)
         {
             var backupName = "backup-" + DateTime.Now.ToString("yyyy-MM-dd-H-mm-ss");
             backupDirectory = Path.Combine(backupDirectory, "easy-backup", backupName);
             if (!Directory.Exists(backupDirectory) && !_isCalculatingFileSize)
             {
                 Directory.CreateDirectory(backupDirectory);
             }
             else if (!_isCalculatingFileSize)
             {
                 // ok, somehow they started two backups within the same second >_> wait 1 second and start again
                 Task.Delay(1000);
                 backupName      = "backup-" + DateTime.Now.ToString("yyyy-MM-dd-H-mm-ss");
                 backupDirectory = Path.Combine(backupDirectory, "easy-backup", backupName);
                 if (!Directory.Exists(backupDirectory))
                 {
                     Directory.CreateDirectory(backupDirectory);
                 }
                 else
                 {
                     throw new Exception("Couldn't create backup directory (directory already exists)");
                 }
             }
             // ok, start copying the files if not using compressed file.
             if (!UsesCompressedFile || _isCalculatingFileSize)
             {
                 foreach (FolderFileItem item in paths)
                 {
                     if (HasBeenCanceled)
                     {
                         break;
                     }
                     var directoryName = Path.GetDirectoryName(item.Path);
                     var pathRoot      = Path.GetPathRoot(item.Path);
                     directoryName = directoryName.Replace(pathRoot, "");
                     directoryName = Path.Combine(pathRoot.Replace(":\\", ""), directoryName);
                     var outputDirectoryPath = Path.Combine(backupDirectory, directoryName);
                     if (!Directory.Exists(outputDirectoryPath) && !_isCalculatingFileSize)
                     {
                         Directory.CreateDirectory(outputDirectoryPath);
                     }
                     if (!_isCalculatingFileSize)
                     {
                         StartedCopyingItem?.Invoke(item);
                     }
                     if (item.IsDirectory && Directory.Exists(item.Path))
                     {
                         if (item.OnlyCopiesLatestFile && item.CanEnableOnlyCopiesLatestFile)
                         {
                             // scan directory and copy only the latest file out of it
                             var directoryInfo = new DirectoryInfo(item.Path);
                             var latestFile    = directoryInfo.GetFiles().OrderByDescending(x => x.LastWriteTimeUtc).FirstOrDefault();
                             if (latestFile != null)
                             {
                                 if (_isCalculatingFileSize)
                                 {
                                     CalculatedBytesOfItem?.Invoke(item, (ulong)new FileInfo(latestFile.FullName).Length);
                                 }
                                 else
                                 {
                                     var outputBackupDirectory = Path.Combine(outputDirectoryPath, Path.GetFileName(item.Path));
                                     // create directory if needed in backup path
                                     if (!Directory.Exists(outputBackupDirectory))
                                     {
                                         Directory.CreateDirectory(outputBackupDirectory);
                                     }
                                     if (HasBeenCanceled)
                                     {
                                         break;
                                     }
                                     var outputPath = Path.Combine(outputBackupDirectory, Path.GetFileName(latestFile.FullName));
                                     CopySingleFile(item, latestFile.FullName, outputPath);
                                 }
                             }
                         }
                         else
                         {
                             if (HasBeenCanceled)
                             {
                                 break;
                             }
                             _currentDirectorySize = 0;
                             var outputPath = Path.Combine(outputDirectoryPath, Path.GetFileName(item.Path));
                             CopyDirectory(item, item.Path, outputPath, item.IsRecursive, item.ExcludedPaths);
                             if (_isCalculatingFileSize)
                             {
                                 CalculatedBytesOfItem?.Invoke(item, _currentDirectorySize);
                             }
                         }
                     }
                     else
                     {
                         if (_isCalculatingFileSize)
                         {
                             CalculatedBytesOfItem?.Invoke(item, (ulong)new FileInfo(item.Path).Length);
                         }
                         else
                         {
                             var outputPath = Path.Combine(outputDirectoryPath, Path.GetFileName(item.Path));
                             CopySingleFile(item, item.Path, outputPath);
                         }
                     }
                     if (!HasBeenCanceled && !_isCalculatingFileSize)
                     {
                         FinishedCopyingItem?.Invoke(item);
                     }
                 }
             }
             else
             {
                 // first, figure out each file that needs to be copied into the 7z file. this way we can optimize
                 // the copy to 1 single Process start.
                 var filePaths             = new List <string>();
                 var pathsToFolderFileItem = new Dictionary <string, FolderFileItem>();
                 var pathToFileSize        = new Dictionary <string, ulong>();
                 foreach (FolderFileItem item in paths)
                 {
                     if (HasBeenCanceled)
                     {
                         break;
                     }
                     if (item.IsDirectory && Directory.Exists(item.Path))
                     {
                         if (item.OnlyCopiesLatestFile && item.CanEnableOnlyCopiesLatestFile)
                         {
                             // scan directory and copy only the latest file out of it
                             var directoryInfo = new DirectoryInfo(item.Path);
                             var latestFile    = directoryInfo.GetFiles().OrderByDescending(x => x.LastWriteTimeUtc).FirstOrDefault();
                             if (latestFile != null)
                             {
                                 if (HasBeenCanceled)
                                 {
                                     break;
                                 }
                                 pathsToFolderFileItem.Add(latestFile.FullName, item);
                                 filePaths.Add(latestFile.FullName);
                                 pathToFileSize.Add(item.Path, (ulong)new FileInfo(latestFile.FullName).Length);
                             }
                         }
                         else
                         {
                             if (HasBeenCanceled)
                             {
                                 break;
                             }
                             var filesWithSizesInDirectory = GetFilePathsAndSizesInDirectory(item.Path, item.IsRecursive, item.ExcludedPaths);
                             foreach (KeyValuePair <string, ulong> entry in filesWithSizesInDirectory)
                             {
                                 pathsToFolderFileItem.Add(entry.Key, item);
                                 pathToFileSize.Add(entry.Key, entry.Value);
                                 filePaths.Add(entry.Key);
                             }
                         }
                     }
                     else
                     {
                         pathsToFolderFileItem.Add(item.Path, item);
                         pathToFileSize.Add(item.Path, (ulong)new FileInfo(item.Path).Length);
                         filePaths.Add(item.Path);
                     }
                 }
                 _directoryPathsSeen.Clear();
                 if (!HasBeenCanceled)
                 {
                     // ok, we can do le copy now
                     BackupToCompressedFile(Path.Combine(backupDirectory, backupName + ".7z"), filePaths, pathsToFolderFileItem, pathToFileSize);
                     if (HasBeenCanceled)
                     {
                         try
                         {
                             // not a huge deal if this fails
                             Directory.Delete(backupDirectory);
                         }
                         catch (Exception) { }
                     }
                 }
             }
         }
         else
         {
             throw new Exception("Backup directory doesn't exist");
         }
         IsRunning = false;
     }
     catch (Exception e)
     {
         HadError = true;
         BackupFailed?.Invoke(e);
     }
     finally
     {
         IsRunning = false;
     }
 }
        private void BackupToCompressedFile(string destination, List <string> filePaths,
                                            Dictionary <string, FolderFileItem> pathsToFolderFileItem, Dictionary <string, ulong> pathsToFileSize)
        {
            var quotedFilePaths = new List <string>();

            foreach (string filePath in filePaths)
            {
                quotedFilePaths.Add("\"" + filePath + "\"");
            }
            var     is64BitOS  = Utilities.Is64BitOS();
            Process process    = new Process();
            var     currentDir = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
            var     exePath    = is64BitOS ? currentDir + "/tools/x64/7za.exe" : currentDir + "/tools/x86/7za.exe";

            process.StartInfo.FileName         = exePath;
            process.StartInfo.WorkingDirectory = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;

            // https://stackoverflow.com/a/6522928/3938401
            process.StartInfo.UseShellExecute        = false;
            process.StartInfo.CreateNoWindow         = true;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.StandardOutputEncoding = Encoding.UTF8;

            //process.StartInfo.RedirectStandardError = true;
            process.EnableRaisingEvents = true;
            var            didError                = false;
            var            sizeInBytes             = 0UL;
            var            remainingBytes          = 0UL;
            var            didStartCompressingFile = false;
            var            lastPercent             = 0.0;
            string         currentFilePath         = "";
            FolderFileItem currentItem             = null;
            var            bytesCopiedForItem      = new Dictionary <FolderFileItem, ulong>();
            var            didFinishCancel         = false;
            var            nextMessageIsError      = false;
            string         errorMessage            = "";

            process.OutputDataReceived += new DataReceivedEventHandler(delegate(object sender, DataReceivedEventArgs e) {
                if (string.IsNullOrWhiteSpace(e.Data) || didFinishCancel) // in case more events come through
                {
                    return;
                }
                if (HasBeenCanceled || didError)
                {
                    // ONLY WORKS IF YOU AREN'T ALREADY SHOWING A CONSOLE!
                    // https://stackoverflow.com/a/29274238/3938401
                    if (AttachConsole((uint)process.Id))
                    {
                        SetConsoleCtrlHandler(null, true);
                        try
                        {
                            GenerateConsoleCtrlEvent(ConsoleCtrlEvent.CTRL_C, process.SessionId); // ends the process
                            process.Kill();                                                       // process is canned, so OK to kill
                        }
                        catch { }
                        finally
                        {
                            FreeConsole();
                            SetConsoleCtrlHandler(null, false);
                            didFinishCancel = true;
                        }
                        return;
                    }
                }
                if (e.Data.StartsWith("+ ") && e.Data.Trim() != "+")
                {
                    didStartCompressingFile = true;
                    currentFilePath         = e.Data.Substring(2);
                    if (currentItem != null && remainingBytes > 0)
                    {
                        CopiedBytesOfItem(currentItem, remainingBytes);
                        bytesCopiedForItem[currentItem] += remainingBytes;
                        if (!currentItem.IsDirectory)
                        {
                            FinishedCopyingItem?.Invoke(currentItem);
                        }
                        else
                        {
                            if (bytesCopiedForItem[currentItem] == currentItem.ByteSize)
                            {
                                FinishedCopyingItem?.Invoke(currentItem);
                            }
                        }
                    }
                    if (pathsToFolderFileItem.ContainsKey(currentFilePath))
                    {
                        currentItem = pathsToFolderFileItem[currentFilePath];
                    }
                    else
                    {
                        currentItem = null;
                    }
                    if (currentItem != null && !bytesCopiedForItem.ContainsKey(currentItem))
                    {
                        bytesCopiedForItem[currentItem] = 0;
                    }
                    if (pathsToFileSize.ContainsKey(currentFilePath))
                    {
                        sizeInBytes = remainingBytes = pathsToFileSize[currentFilePath];
                    }
                    else
                    {
                        sizeInBytes = remainingBytes = 0;
                    }
                }
                else if (e.Data.Contains("%") && didStartCompressingFile)
                {
                    var percent       = double.Parse(e.Data.Trim().Split('%')[0]);
                    var actualPercent = percent - lastPercent;
                    lastPercent       = percent;
                    var copiedBytes   = Math.Floor((actualPercent / 100.0) * sizeInBytes); // floor -- would rather underestimate than overestimate
                    if (currentItem != null)
                    {
                        CopiedBytesOfItem(currentItem, (ulong)copiedBytes);
                        bytesCopiedForItem[currentItem] += (ulong)copiedBytes;
                    }
                    remainingBytes -= (ulong)copiedBytes;
                }
                else if (e.Data.Contains("Error:"))
                {
                    nextMessageIsError = true;
                }
                else if (nextMessageIsError)
                {
                    errorMessage       = e.Data;
                    didError           = true;
                    nextMessageIsError = false;
                }
            });

            /**
             * Command line params:
             * -y (yes to prompts)
             * -ssw (Compresses files open for writing by another applications)
             * -bsp1 (output for progress to stdout)
             * -bse1 (output for errors to stdout)
             * -bb1 (log level 1)
             * -spf (Use fully qualified file paths)
             * -mx1 (compression level to fastest)
             * -v2g (split into 2 gb volumes -- https://superuser.com/a/184601)
             * -sccUTF-8 (set console output to UTF-8)
             * -p (set password for file)
             * */
            var args = "-y -ssw -bsp1 -bse1 -bb1 -spf -mx1 -v2g -sccUTF-8";

            if (UsesPasswordForCompressedFile)
            {
                var pass = Utilities.SecureStringToString(CompressedFilePassword);
                if (!string.IsNullOrWhiteSpace(pass))
                {
                    args = "-p" + pass + " " + args; // add password flag
                }
            }
            string inputPaths = string.Join("\n", quotedFilePaths);
            // to circumvent issue where inputPaths is too long for command line, need to write them to a file
            // and then load the file into 7z via command line params (@fileName as last param -- https://superuser.com/a/940894)
            var tmpFileName = Path.GetTempFileName();

            using (StreamWriter sw = new StreamWriter(tmpFileName))
            {
                sw.Write(inputPaths);
            }
            args = "a " + args + " \"" + destination + "\" @\"" + tmpFileName + "\""; // a = add file
            process.StartInfo.Arguments = args;
            process.Start();
            process.BeginOutputReadLine();
            //process.BeginErrorReadLine();
            process.WaitForExit();
            // make sure last item is handled properly
            if (!HasBeenCanceled && currentItem != null && remainingBytes > 0)
            {
                CopiedBytesOfItem(currentItem, remainingBytes);
                FinishedCopyingItem?.Invoke(currentItem);
            }
            if (HasBeenCanceled)
            {
                File.Delete(destination);
            }
            if (didError)
            {
                if (string.IsNullOrWhiteSpace(errorMessage))
                {
                    errorMessage = "Compression operation failed";
                }
                throw new Exception(errorMessage);
            }
        }