// // Summary: // Update the `LastWrittenAt`,`LastSize`,`LastStatus`,`LastUpdatedAt`,`LastChecksum` // properties of each file in `files` according to the actual state of the file in the filesystem. // // NOTE: This function has a side effect - It updates properties of items from `files`. // private void DoUpdateBackupPlanFilesStatus(LinkedList <Models.BackupPlanFile> files, bool isNewVersion) { Assert.IsNotNull(files); ISession session = NHibernateHelper.GetSession(); BackupedFileRepository daoBackupedFile = new BackupedFileRepository(session); BlockPerfStats stats = new BlockPerfStats(); stats.Begin(); // Check all files. LinkedListNode <Models.BackupPlanFile> node = files.First; while (node != null) { var next = node.Next; Models.BackupPlanFile entry = node.Value; // TODO(jweyrich): Measure whether `daoBackupedFile.GetLatestVersion(entry)` is faster or not, // and whether "entry.Versions.anything" would cause all related version to be fetched. #if false Models.BackupedFile lastVersion = entry.Versions != null && entry.Versions.Count > 0 ? entry.Versions.Last() : null; #else // This may be a version that has not COMPLETED the transfer. Models.BackupedFile lastVersion = entry.Id.HasValue ? daoBackupedFile.GetLatestVersion(entry) : null; #endif // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); // // Check what happened to the file. // bool fileExistsOnFilesystem = FileManager.FileExists(entry.Path); Models.BackupFileStatus?changeStatusTo = null; try { // // Update file properties // if (fileExistsOnFilesystem) { try { DateTime fileLastWrittenAt = FileManager.UnsafeGetFileLastWriteTimeUtc(entry.Path); long fileLength = FileManager.UnsafeGetFileSize(entry.Path); entry.LastWrittenAt = fileLastWrittenAt; entry.LastSize = fileLength; } catch (Exception ex) { string message = string.Format("Caught an exception while retrieving file properties: {0}", ex.Message); Results.OnFileFailed(this, new FileVersionerEventArgs { FilePath = entry.Path, FileSize = 0 }, message); logger.Warn(message); throw; } try { // Skip files larger than `MAX_FILESIZE_TO_HASH`. int result = BigInteger.Compare(entry.LastSize, MAX_FILESIZE_TO_HASH); if (result < 0) { entry.LastChecksum = CalculateHashForFile(entry.Path); } } catch (Exception ex) { string message = string.Format("Caught an exception while calculating the file hash: {0}", ex.Message); Results.OnFileFailed(this, new FileVersionerEventArgs { FilePath = entry.Path, FileSize = 0 }, message); logger.Warn(message); throw; } Results.OnFileCompleted(this, new FileVersionerEventArgs { FilePath = entry.Path, FileSize = entry.LastSize }); } // // Update file status // if (lastVersion != null) // File was backed up at least once in the past? { switch (entry.LastStatus) { case Models.BackupFileStatus.DELETED: // File was marked as DELETED by a previous backup? if (fileExistsOnFilesystem) // Exists? { changeStatusTo = Models.BackupFileStatus.ADDED; } break; case Models.BackupFileStatus.REMOVED: // File was marked as REMOVED by a previous backup? if (fileExistsOnFilesystem) // Exists? { changeStatusTo = Models.BackupFileStatus.ADDED; } else { // QUESTION: Do we really care to transition REMOVED to DELETED? changeStatusTo = Models.BackupFileStatus.DELETED; } break; default: // ADDED, MODIFIED, UNCHANGED if (fileExistsOnFilesystem) // Exists? { // DO NOT verify whether the file changed for a `ResumeBackupOperation`, // only for `NewBackupOperation`. if (isNewVersion) { if (IsFileModified(entry, lastVersion)) // Modified? { changeStatusTo = Models.BackupFileStatus.MODIFIED; } else if (NeedsToRetryFile(entry, lastVersion)) // Didn't complete last file transfer? { changeStatusTo = Models.BackupFileStatus.MODIFIED; } else // Not modified? { changeStatusTo = Models.BackupFileStatus.UNCHANGED; } } } else // Deleted from filesystem? { changeStatusTo = Models.BackupFileStatus.DELETED; } break; } } else // Adding to this backup? { if (fileExistsOnFilesystem) // Exists? { changeStatusTo = Models.BackupFileStatus.ADDED; } else { // Error? Can't add a non-existent file to the plan. } } if (changeStatusTo.HasValue) { entry.LastStatus = changeStatusTo.Value; entry.UpdatedAt = DateTime.UtcNow; } } catch (Exception ex) { FailedFile <Models.BackupPlanFile> failedEntry = new FailedFile <Models.BackupPlanFile>(entry, ex.Message, ex); ChangeSet.FailedFiles.AddLast(failedEntry); // Remove this entry from `files` as it clearly failed. files.Remove(node); // Complexity is O(1) } node = next; } stats.End(); }