Пример #1
0
        }         // end BackupProcessor constructor

        // doBackup():
        /// <summary>Run the actual backup job.</summary>
        public void doBackup()
        {
            // Empty the list of warnings, and create a new object to track the total expected size of the backup job.
            backupProcessWarnings.Clear();
            BackupSizeInfo totalExpectedSizeInfo = new BackupSizeInfo()
            {
                fileCount_All = 0, fileCount_Unique = 0, byteCount_All = 0, byteCount_Unique = 0
            };

            // Scan all the source paths to figure out the expected size of the backup job.
            userInterface.report("Scanning source directory trees:", ConsoleOutput.Verbosity.NormalEvents);
            foreach (SourcePathInfo sourcePath in sourcePaths)
            {
                // Get the expected size for this source path.
                userInterface.report(1, $"Scanning {sourcePath.BaseItemFullPath}...", ConsoleOutput.Verbosity.NormalEvents);
                BackupSizeInfo currentTreeSizeInfo = sourcePath.calculateSize();
                userInterface.report(2, $"Files found: {currentTreeSizeInfo.fileCount_All:n0}; Bytes: {currentTreeSizeInfo.byteCount_All:n0}", ConsoleOutput.Verbosity.NormalEvents);

                // Add the results to the total expected size of the entire backup job.
                totalExpectedSizeInfo += currentTreeSizeInfo;
            }

            userInterface.report(1, $"Total files found: {totalExpectedSizeInfo.fileCount_All:n0}; Total Bytes: {totalExpectedSizeInfo.byteCount_All:n0}", ConsoleOutput.Verbosity.NormalEvents);

            // Set up variables used for tracking the backup process.
            BackupSizeInfo completedBackupSizeInfo = new BackupSizeInfo {
                fileCount_All = 0, fileCount_Unique = 0, byteCount_All = 0, byteCount_Unique = 0
            };
            DateTime copyStartTime = DateTime.Now;

            try
            {
                // Get the backups root directory and ensure it exists.
                DirectoryInfo backupsRootDirectory = new DirectoryInfo(backupsDestinationRootPath);
                backupsRootDirectory.Create();

                // Open or create the backups database for this backups destination.
                using (BackupsDatabase database = new BackupsDatabase(backupsDestinationRootPath, userInterface))
                {
                    // Create subdirectory for this new backup job, based on the current date and time.
                    DirectoryInfo destinationBaseDirectory = createBackupTimeSubdirectory(backupsRootDirectory);
                    userInterface.report($"Backing up to {destinationBaseDirectory.FullName}", ConsoleOutput.Verbosity.NormalEvents);

                    // Copy all the files from each of the source paths.
                    foreach (SourcePathInfo currentSourcePath in sourcePaths)
                    {
                        BackupSizeInfo currentSourceBackupSizeInfo;
                        string         driveName;

                        // Call makeFolderTreeBackup() to do the copying work.
                        // Usually we make a copy of the base source path item within the timestamp directory
                        // (e.g., if the source path is "/foo/bar/", we create a "bar/" directory within the timestamped destination), and everything goes inside that.
                        // But if if the source path is a root directory, that doesn't work, because the source directory doesn't have an actual name.
                        // In that case, we get the drive name (e.g. "C" on Windows), or an empty string if there is no drive name (e.g., on Linux), append "_root",
                        // and use that as the destination directory name.
                        if (pathIsRootDirectory(currentSourcePath.BaseItemFullPath, out driveName))                         // CHANGE CODE HERE: handle exceptions
                        {
                            currentSourceBackupSizeInfo =
                                makeFolderTreeBackup(
                                    currentSourcePath, Path.Combine(destinationBaseDirectory.FullName, driveName + "_root"),
                                    database, totalExpectedSizeInfo, completedBackupSizeInfo);
                        }
                        else
                        {
                            currentSourceBackupSizeInfo =
                                makeFolderTreeBackup(
                                    currentSourcePath, destinationBaseDirectory.FullName,
                                    database, totalExpectedSizeInfo, completedBackupSizeInfo);
                        }

                        // Update the total completed backup size info with the size of this now-completed individual source path.
                        completedBackupSizeInfo += currentSourceBackupSizeInfo;
                    }
                }                 // end using(database)
            }
            catch (System.IO.PathTooLongException ex)
            {
                userInterface.report($"Error: Path too long: {ex.Message}", ConsoleOutput.Verbosity.ErrorsAndWarnings);
                userInterface.report("You may need to enable long path support for your operating system, use a file system which supports longer paths (e.g., NTFS, ext3, or ext4 rather than FAT), or create a symbolic link to the destination directory to shorten the path string.", ConsoleOutput.Verbosity.ErrorsAndWarnings);
            }

            // Grab the end time of the copy process.
            DateTime copyEndTime = DateTime.Now;

            // Calculate how long, in seconds, the copy process took.
            int totalTime = (int)Math.Round(copyEndTime.Subtract(copyStartTime).TotalSeconds);

            // Figure out how many files and bytes we copied in total, as physical copies, and as hard links.
            long totalFiles            = completedBackupSizeInfo.fileCount_All,
                 physicallyCopiedFiles = completedBackupSizeInfo.fileCount_Unique,
                 skippedFiles          = completedBackupSizeInfo.fileCount_Skip,
                 linkedFiles           = totalFiles - physicallyCopiedFiles - skippedFiles,
                 allCopiedFiles        = totalFiles - skippedFiles;
            long totalBytes            = completedBackupSizeInfo.byteCount_All,
                 physicallyCopiedBytes = completedBackupSizeInfo.byteCount_Unique,
                 skippedBytes          = completedBackupSizeInfo.byteCount_Skip,
                 linkedBytes           = totalBytes - physicallyCopiedBytes - skippedBytes,
                 allCopiedBytes        = totalBytes - skippedBytes;

            userInterface.report($"Backup complete.", ConsoleOutput.Verbosity.NormalEvents);

            // If there were any warnings generated (e.g., directories skipped due to access permissions), report those.
            if (backupProcessWarnings.Count > 0)
            {
                userInterface.report("", ConsoleOutput.Verbosity.NormalEvents);
                foreach (string warning in backupProcessWarnings)
                {
                    userInterface.report($"Warning: {warning}", ConsoleOutput.Verbosity.ErrorsAndWarnings);
                }
                userInterface.report("", ConsoleOutput.Verbosity.NormalEvents);
            }

            // Report the final totals from the backup process.
            userInterface.report(1, $"Copy process duration: {totalTime:n0} seconds.", ConsoleOutput.Verbosity.NormalEvents);
            userInterface.report(1, $"Total files copied: {allCopiedFiles:n0} ({physicallyCopiedFiles:n0} new physical copies needed, {linkedFiles:n0} hardlinks utilized)", ConsoleOutput.Verbosity.NormalEvents);
            userInterface.report(1, $"Total bytes copied: {allCopiedBytes:n0} ({physicallyCopiedBytes:n0} physically copied, {linkedBytes:n0} hardlinked)", ConsoleOutput.Verbosity.NormalEvents);
            if (skippedFiles > 0)
            {
                userInterface.report(1, $"Skipped: {skippedFiles:n} files ({skippedBytes:n} bytes)", ConsoleOutput.Verbosity.NormalEvents);
            }
        }         // end doBackup()
Пример #2
0
        }         // end completionPercentage()

        // makeFolderTreeBackup():
        /// <summary>Does the backup copying from a specified source path to a specified destination, storing info in the specified database.</summary>
        /// <returns>A <c>BackupSizeInfo</c> object containing the total size of the copy job that was completed.</returns>
        /// <param name="sourceInfo">The source to copy from.</param>
        /// <param name="destinationBasePath">The base destination path.</param>
        /// <param name="database">The database to use for looking up and storing copy and hard link match info.</param>
        /// <param name="totalExpectedBackupSize">The total expected size of the backup job that is in progress.</param>
        /// <param name="previouslyCompleteSizeInfo">The total size of the backup job completed up to this point.</param>
        private BackupSizeInfo makeFolderTreeBackup(
            SourcePathInfo sourceInfo, string destinationBasePath,
            BackupsDatabase database, BackupSizeInfo totalExpectedBackupSize, BackupSizeInfo previouslyCompleteSizeInfo)
        {
            // Set up variable to track the size of copying done within this directory tree.
            BackupSizeInfo thisTreeCompletedSizeInfo = new BackupSizeInfo()
            {
                fileCount_All = 0, fileCount_Unique = 0, byteCount_All = 0, byteCount_Unique = 0
            };

            // Set up variables for tracking ongoing changes to the completion percentage, for the purpose of reporting updates to the user.
            int previousPercentComplete,
                percentComplete = getCompletionPercentage(totalExpectedBackupSize.byteCount_All, previouslyCompleteSizeInfo.byteCount_All);

            // Iterate through each item in the source, copying it to the destination.
            foreach (BackupItemInfo item in sourceInfo.getAllItems())
            {
                // Get the full path string for the object as it will exist in the destination.
                string fullItemDestinationPath = Path.Combine(destinationBasePath, item.RelativePath);

                if (item.Type == BackupItemInfo.ItemType.Directory)
                {
                    (new DirectoryInfo(fullItemDestinationPath)).Create();                     // Item is a directory, so simply create it at the destination location.
                }
                else if (item.Type == BackupItemInfo.ItemType.UnreadableDirectory)
                {
                    backupProcessWarnings.Add($"Directory skipped due to unauthorized access error: {item.RelativePath}"); // Item is a directory but can't be read, so skip it and add a warning to show the user at the end.
                }
                else                                                                                                       // Item is a file.
                {
                    // Calculate the source file hash.
                    FileInfo currentSourceFile = new FileInfo(item.FullPath);
                    string   fileHash;
                    try
                    { fileHash = getHash(currentSourceFile); }                     // CHANGE CODE HERE: Handle all the possible exceptions
                    catch (Exception ex) when(ex is UnauthorizedAccessException || ex is System.IO.IOException)
                    {
                        if (ex is UnauthorizedAccessException)
                        {
                            backupProcessWarnings.Add($"File skipped due to unauthorized access error: {item.RelativePath}");
                        }
                        else
                        {
                            backupProcessWarnings.Add($"File skipped, unable to read. Another process may be using the file: {item.RelativePath}");
                        }
                        thisTreeCompletedSizeInfo.fileCount_Skip++;
                        thisTreeCompletedSizeInfo.fileCount_All++;
                        thisTreeCompletedSizeInfo.byteCount_Skip += currentSourceFile.Length;
                        thisTreeCompletedSizeInfo.byteCount_All  += currentSourceFile.Length;
                        previousPercentComplete = percentComplete;
                        percentComplete         = getCompletionPercentage(totalExpectedBackupSize.byteCount_All, previouslyCompleteSizeInfo.byteCount_All + thisTreeCompletedSizeInfo.byteCount_All);
                        userInterface.reportProgress(percentComplete, previousPercentComplete, ConsoleOutput.Verbosity.NormalEvents);
                        continue;                         // Skip to the next item in the foreach loop.
                    }

                    // Look in the database and find an existing, previously backed up file to create a hard link to,
                    // if any exists within the current run's rules for using links.
                    HardLinkMatch hardLinkMatch =
                        database.getAvailableHardLinkMatch(
                            fileHash, currentSourceFile.Length,
                            currentSourceFile.LastWriteTimeUtc, maxHardLinksPerFile, maxDaysBeforeNewFullFileCopy);

                    // Ensure the destination directory for this file exists.
                    (new FileInfo(fullItemDestinationPath)).Directory.Create();

                    // Make a full copy of the file if needed, but otherwise create a hard link from a previous backup
                    if (hardLinkMatch == null)
                    {
                        // No hard link match found in the database, so do a copy operation.
                        userInterface.report(1, $"Backing up file {item.FullPath} to {fullItemDestinationPath} [copying]", ConsoleOutput.Verbosity.LowImportanceEvents);
                        currentSourceFile.CopyTo(fullItemDestinationPath);
                        thisTreeCompletedSizeInfo.fileCount_Unique++;
                        thisTreeCompletedSizeInfo.byteCount_Unique += currentSourceFile.Length;
                    }
                    else
                    {
                        // A usable hard link match was found in the database, so create a hard link instead of making a new full copy.
                        string linkFilePath = hardLinkMatch.MatchingFilePath;
                        userInterface.report(1, $"Backing up file {item.FullPath} to {fullItemDestinationPath} [identical existing file found; creating hardlink to {linkFilePath}]", ConsoleOutput.Verbosity.LowImportanceEvents);
                        hardLinker.createHardLink(fullItemDestinationPath, linkFilePath);                         // CHANGE CODE HERE: Handle exceptions.
                    }

                    // Add this file to the total copy amounts being tracked.
                    thisTreeCompletedSizeInfo.fileCount_All++;
                    thisTreeCompletedSizeInfo.byteCount_All += currentSourceFile.Length;

                    // Record in the backups database the new copy or link that was made.
                    FileInfo newFile = new FileInfo(fullItemDestinationPath);
                    database.addFileBackupRecord(fullItemDestinationPath, newFile.Length, fileHash, newFile.LastWriteTimeUtc, (hardLinkMatch == null ? null : hardLinkMatch.ID));
                }                 // end if/else on (item.Type == BackupItemInfo.ItemType.Directory)

                // Figure out the new completion percentage, and update the user on the progress.
                previousPercentComplete = percentComplete;
                percentComplete         = getCompletionPercentage(totalExpectedBackupSize.byteCount_All, previouslyCompleteSizeInfo.byteCount_All + thisTreeCompletedSizeInfo.byteCount_All);
                userInterface.reportProgress(percentComplete, previousPercentComplete, ConsoleOutput.Verbosity.NormalEvents);
            }             // end foreach (BackupItemInfo item in sourcePath.Items)

            return(thisTreeCompletedSizeInfo);
        }         // end makeFolderTreeBackup()