private void Save(ISession session) { Assert.IsFalse(IsSaved); BatchProcessor batchProcessor = new BatchProcessor(); BackupRepository daoBackup = new BackupRepository(session); BackupPlanFileRepository daoBackupPlanFile = new BackupPlanFileRepository(session); BackupedFileRepository daoBackupedFile = new BackupedFileRepository(session); BackupPlanPathNodeRepository daoBackupPlanPathNode = new BackupPlanPathNodeRepository(session); #if false var FilesToTrack = SuppliedFiles.Union(ChangeSet.DeletedFiles); var FilesToInsertOrUpdate = from f in FilesToTrack where // Keep it so we'll later add or update a `BackupedFile`. ((f.LastStatus == Models.BackupFileStatus.ADDED || f.LastStatus == Models.BackupFileStatus.MODIFIED)) // Keep it if `LastStatus` is different from `PreviousLastStatus`. || ((f.LastStatus == Models.BackupFileStatus.REMOVED || f.LastStatus == Models.BackupFileStatus.DELETED) && (f.LastStatus != f.PreviousLastStatus)) // Skip all UNCHANGED files. select f; #else var FilesToTrack = SuppliedFiles; var FilesToInsertOrUpdate = from f in FilesToTrack where // Keep it so we'll later add or update a `BackupedFile`. ((f.LastStatus == Models.BackupFileStatus.ADDED || f.LastStatus == Models.BackupFileStatus.MODIFIED)) // Skip all UNCHANGED/DELETED/REMOVED files. select f; #endif BlockPerfStats stats = new BlockPerfStats(); using (ITransaction tx = session.BeginTransaction()) { try { // ------------------------------------------------------------------------------------ stats.Begin("STEP 1"); BackupPlanPathNodeCreator pathNodeCreator = new BackupPlanPathNodeCreator(daoBackupPlanPathNode, tx); // 1 - Split path into its components and INSERT new path nodes if they don't exist yet. foreach (Models.BackupPlanFile entry in FilesToInsertOrUpdate) { // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); try { entry.PathNode = pathNodeCreator.CreateOrUpdatePathNodes(Backup.BackupPlan.StorageAccount, entry); } catch (Exception ex) { string message = string.Format("BUG: Failed to create/update {0} => {1}", typeof(Models.BackupPlanPathNode).Name, CustomJsonSerializer.SerializeObject(entry, 1)); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); throw; } batchProcessor.ProcessBatch(session); } batchProcessor.ProcessBatch(session, true); stats.End(); // ------------------------------------------------------------------------------------ stats.Begin("STEP 2"); // 2 - Insert/Update `BackupPlanFile`s as necessary. foreach (Models.BackupPlanFile entry in FilesToInsertOrUpdate) { // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); // IMPORTANT: It's important that we guarantee the referenced `BackupPlanFile` has a valid `Id` // before we reference it elsewhere, otherwise NHibernate won't have a valid value to put on // the `backup_plan_file_id` column. try { daoBackupPlanFile.InsertOrUpdate(tx, entry); // Guarantee it's saved } catch (Exception ex) { string message = string.Format("BUG: Failed to insert/update {0} => {1}", typeof(Models.BackupPlanFile).Name, CustomJsonSerializer.SerializeObject(entry, 1)); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); logger.Error("Dump of failed object: {0}", entry.DumpMe()); throw; } batchProcessor.ProcessBatch(session); } batchProcessor.ProcessBatch(session, true); stats.End(); // ------------------------------------------------------------------------------------ stats.Begin("STEP 3"); // 3 - Insert/Update `BackupedFile`s as necessary and add them to the `Backup`. //List<Models.BackupedFile> backupedFiles = new List<Models.BackupedFile>(FilesToInsertOrUpdate.Count()); foreach (Models.BackupPlanFile entry in FilesToInsertOrUpdate) { // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); Models.BackupedFile backupedFile = daoBackupedFile.GetByBackupAndPath(Backup, entry.Path); if (backupedFile == null) // If we're resuming, this should already exist. { // Create `BackupedFile`. backupedFile = new Models.BackupedFile(Backup, entry); } backupedFile.FileSize = entry.LastSize; backupedFile.FileStatus = entry.LastStatus; backupedFile.FileLastWrittenAt = entry.LastWrittenAt; backupedFile.FileLastChecksum = entry.LastChecksum; switch (entry.LastStatus) { default: backupedFile.TransferStatus = default(TransferStatus); break; case Models.BackupFileStatus.REMOVED: case Models.BackupFileStatus.DELETED: backupedFile.TransferStatus = TransferStatus.COMPLETED; break; } backupedFile.UpdatedAt = DateTime.UtcNow; try { daoBackupedFile.InsertOrUpdate(tx, backupedFile); } catch (Exception ex) { logger.Log(LogLevel.Error, ex, "BUG: Failed to insert/update {0} => {1}", typeof(Models.BackupedFile).Name, CustomJsonSerializer.SerializeObject(backupedFile, 1)); throw; } //backupedFiles.Add(backupedFile); batchProcessor.ProcessBatch(session); } batchProcessor.ProcessBatch(session, true); stats.End(); // ------------------------------------------------------------------------------------ stats.Begin("STEP 4"); // 4 - Update all `BackupPlanFile`s that already exist for the backup plan associated with this backup operation. { var AllFilesFromPlanThatWerentUpdatedYet = AllFilesFromPlan.Values.Except(FilesToInsertOrUpdate); foreach (Models.BackupPlanFile file in AllFilesFromPlanThatWerentUpdatedYet) { // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); //Console.WriteLine("2: {0}", file.Path); try { daoBackupPlanFile.Update(tx, file); } catch (Exception ex) { string message = string.Format("BUG: Failed to update {0} => {1} ", typeof(Models.BackupPlanFile).Name, CustomJsonSerializer.SerializeObject(file, 1)); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); throw; } batchProcessor.ProcessBatch(session); } } batchProcessor.ProcessBatch(session, true); stats.End(); // ------------------------------------------------------------------------------------ stats.Begin("STEP 5"); // 5 - Insert/Update `Backup` and its `BackupedFile`s into the database, also saving // the `BackupPlanFile`s instances that may have been changed by step 2. { //foreach (var bf in backupedFiles) //{ // // Throw if the operation was canceled. // CancellationToken.ThrowIfCancellationRequested(); // // Backup.Files.Add(bf); // // ProcessBatch(session); //} try { daoBackup.Update(tx, Backup); } catch (Exception ex) { string message = string.Format("BUG: Failed to update {0} => {1}", typeof(Models.Backup).Name, CustomJsonSerializer.SerializeObject(Backup, 1)); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); throw; } } batchProcessor.ProcessBatch(session, true); stats.End(); // ------------------------------------------------------------------------------------ tx.Commit(); } catch (OperationCanceledException) { string message = "Operation cancelled"; Results.OnError(this, message); logger.Warn(message); tx.Rollback(); // Rollback the transaction throw; } catch (Exception ex) { string message = string.Format("Caught Exception: {0}", ex.Message); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); tx.Rollback(); // Rollback the transaction throw; } finally { // ... } } IsSaved = true; // 6 - Create versioned files and remove files that won't belong to this backup. TransferSet.Files = GetFilesToTransfer(Backup, SuppliedFiles); // Test to see if things are okay! { var transferCount = TransferSet.Files.Count(); var filesCount = ChangeSet.AddedFiles.Count() + ChangeSet.ModifiedFiles.Count(); Assert.IsTrue(transferCount == filesCount, "TransferSet.Files must be equal (ChangeSet.AddedFiles + ChangeSet.ModifiedFiles)"); } }
protected void RegisterResultsEventHandlers(Models.Backup backup, TransferResults results) { BackupedFileRepository daoBackupedFile = new BackupedFileRepository(); results.DeleteCompleted += (object sender, DeletionArgs e) => { Int64?backupedFileId = (Int64?)e.UserData; // TODO(jweyrich): We could get rid of the SELECT and perform just the UPDATE. Models.BackupedFile backupedFile = daoBackupedFile.Get(backupedFileId.Value); backupedFile.TransferStatus = TransferStatus.PURGED; backupedFile.UpdatedAt = DateTime.UtcNow; daoBackupedFile.Update(backupedFile); //var message = string.Format("Purged {0}", e.FilePath); //Info(message); //OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = message }); }; results.Failed += (object sender, TransferFileProgressArgs args) => { Models.BackupedFile backupedFile = daoBackupedFile.GetByBackupAndPath(backup, args.FilePath); backupedFile.TransferStatus = TransferStatus.FAILED; backupedFile.UpdatedAt = DateTime.UtcNow; daoBackupedFile.Update(backupedFile); var message = string.Format("Failed {0} - {1}", args.FilePath, args.Exception != null ? args.Exception.Message : "Unknown reason"); Warn(message); //StatusInfo.Update(BackupStatusLevel.ERROR, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = message, TransferStatus = TransferStatus.FAILED }); }; results.Canceled += (object sender, TransferFileProgressArgs args) => { Models.BackupedFile backupedFile = daoBackupedFile.GetByBackupAndPath(backup, args.FilePath); backupedFile.TransferStatus = TransferStatus.CANCELED; backupedFile.UpdatedAt = DateTime.UtcNow; daoBackupedFile.Update(backupedFile); var message = string.Format("Canceled {0} - {1}", args.FilePath, args.Exception != null ? args.Exception.Message : "Unknown reason"); Warn(message); //StatusInfo.Update(BackupStatusLevel.ERROR, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = message, TransferStatus = TransferStatus.CANCELED }); }; results.Completed += (object sender, TransferFileProgressArgs args) => { Models.BackupedFile backupedFile = daoBackupedFile.GetByBackupAndPath(backup, args.FilePath); backupedFile.TransferStatus = TransferStatus.COMPLETED; backupedFile.UpdatedAt = DateTime.UtcNow; daoBackupedFile.Update(backupedFile); var message = string.Format("Completed {0}", args.FilePath); Info(message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = message, TransferStatus = TransferStatus.COMPLETED }); Models.BackupPlan plan = Backup.BackupPlan; //backupedFile.Backup.BackupPlan; if (plan.PurgeOptions != null && plan.PurgeOptions.IsTypeCustom && plan.PurgeOptions.EnabledKeepNumberOfVersions) { // Purge the oldest versioned files if the count of versions exceeds the maximum specified for the Backup Plan. IList <Models.BackupedFile> previousVersions = daoBackupedFile.GetCompleteByPlanAndPath(plan, args.FilePath); int found = previousVersions.Count; int keep = plan.PurgeOptions.NumberOfVersionsToKeep; int diff = found - keep; if (diff > 0) { // Delete the oldest Count-N versions. List <Models.BackupedFile> versionsToPurge = previousVersions.Skip(keep).ToList(); foreach (var vp in versionsToPurge) { DeleteVersionedFile(vp.File.Path, new FileVersion { Version = vp.Version }, vp.Id); } } } }; results.Started += (object sender, TransferFileProgressArgs args) => { Models.BackupedFile backupedFile = daoBackupedFile.GetByBackupAndPath(backup, args.FilePath); backupedFile.TransferStatus = TransferStatus.RUNNING; backupedFile.UpdatedAt = DateTime.UtcNow; daoBackupedFile.Update(backupedFile); var message = string.Format("Started {0}", args.FilePath); Info(message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = message }); }; results.Progress += (object sender, TransferFileProgressArgs args) => { #if DEBUG var message = string.Format("Progress {0}% {1} ({2}/{3} bytes)", args.PercentDone, args.FilePath, args.TransferredBytes, args.TotalBytes); //Info(message); #endif OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Updated, Message = null }); }; }