private PathScanResults <string> DoWork(Models.Backup backup, CancellationToken cancellationToken) { PathScanResults <string> results = new PathScanResults <string>(); BackupedFileRepository daoBackupedFile = new BackupedFileRepository(); // Load pending `BackupedFiles` from `Backup`. IList <Models.BackupedFile> pendingFiles = daoBackupedFile.GetByBackupAndStatus(backup, // 1. We don't want to resume a file that FAILED. // 2. We don't want to resume a file that was CANCELED. // Theoretically, a CANCELED file should never be present in a backup that is still RUNNING, // unless the backup was being CANCELED, and the PlanExecutor was terminated by some reason // after it updated the files as CANCELED but before it got the chance to update the backup // itself as CANCELED. TransferStatus.STOPPED, // The transfer didn't begin. TransferStatus.RUNNING // The transfer did begin but did not complete. ); cancellationToken.ThrowIfCancellationRequested(); // Convert them to a list of paths. results.Files = pendingFiles.ToLinkedList <string, Models.BackupedFile>(p => p.File.Path); return(results); }
// // Summary: // ... // private IEnumerable <CustomVersionedFile> GetFilesToTransfer(Models.Backup backup, LinkedList <Models.BackupPlanFile> files) { // Update files version. foreach (Models.BackupPlanFile entry in files) { // Throw if the operation was canceled. CancellationToken.ThrowIfCancellationRequested(); switch (entry.LastStatus) { // Skip REMOVED, DELETED, and UNCHANGED files. default: break; case Models.BackupFileStatus.ADDED: case Models.BackupFileStatus.MODIFIED: { IFileVersion version = new FileVersion { Version = entry.LastWrittenAt.ToString(Models.BackupedFile.VersionFormat) }; yield return(new CustomVersionedFile { Path = entry.Path, Size = entry.LastSize, Checksum = null, //Checksum = entry.LastChecksum, Version = version, LastWriteTimeUtc = entry.LastWrittenAt, }); break; // YES, it's required! } } } }
protected override Task <PathScanResults <string> > GetFilesToProcess(Models.Backup backup) { return(ExecuteOnBackround(() => { return DoWork(backup, CancellationTokenSource.Token); }, CancellationTokenSource.Token)); }
/// <exception cref="System.InvalidOperationException"> /// Thrown when the previous backup operation has not finished yet. /// </exception> private BackupOperation CreateBackupOperation(Models.BackupPlan plan) { var dao = new BackupRepository(); Models.Backup latest = dao.GetLatestByPlan(plan); MustResumeLastOperation = latest != null && latest.NeedsResume(); if (MustResumeLastOperation && !Options.Resume) { string message = string.Format("The backup (#{0}) has not finished yet." + " If it's still running, please, wait until it finishes," + " otherwise you should resume it manually.", latest.Id); throw new InvalidOperationException(message); } // Create new backup or resume the last unfinished one. BackupOperation obj = MustResumeLastOperation ? new ResumeBackupOperation(latest) as BackupOperation : new NewBackupOperation(plan) as BackupOperation; obj.Updated += (sender2, e2) => BackupUpdateStatsInfo(e2.Status, e2.TransferStatus); //obj.EventLog = ... //obj.TransferListControl = ... BackupUpdateStatsInfo(BackupOperationStatus.Unknown, TransferStatus.STOPPED); return(obj); }
public ResumeBackupOperation(Models.Backup backup, BackupOperationOptions options) : base(options) { Assert.IsNotNull(backup); Assert.AreEqual(TransferStatus.RUNNING, backup.Status); Backup = backup; }
public async Task <FileVersionerResults> DoVersion(Models.Backup backup, LinkedList <string> filePaths, bool newVersion) { Assert.IsNotNull(backup); Assert.AreEqual(TransferStatus.RUNNING, backup.Status); Assert.IsNotNull(filePaths); Results.Reset(); await ExecuteOnBackround(() => { ISession session = NHibernateHelper.GetSession(); try { BackupRepository daoBackup = new BackupRepository(session); BackupPlanFileRepository daoBackupPlanFile = new BackupPlanFileRepository(session); Backup = daoBackup.Get(backup.Id); IList <Models.BackupPlanFile> list = newVersion ? daoBackupPlanFile.GetAllByBackupPlan(Backup.BackupPlan) : daoBackupPlanFile.GetAllPendingByBackup(Backup); AllFilesFromPlan = list.ToDictionary <Models.BackupPlanFile, string>(p => p.Path); Execute(Backup, filePaths, newVersion); Save(session); } catch (Exception ex) { string message = string.Format("File versioning FAILED with an exception: {0}", ex.Message); Results.OnError(this, message); logger.Log(LogLevel.Error, ex, message); throw ex; } finally { //session.Close(); if (session.IsConnected) { session.Disconnect(); } } }, CancellationToken); return(Results); }
public override void OnStart(CustomBackupAgent agent, Models.Backup backup) { base.OnStart(agent, backup); _daoBackup.Update(backup); var message = string.Format("Backup resumed at {0}", StartedAt); Info(message); //StatusInfo.Update(BackupStatusLevel.OK, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Resumed, Message = message }); }
public void OnCancelation(CustomBackupAgent agent, Models.Backup backup, Exception exception) { IsRunning = false; var message = string.Format("Backup canceled: {0}", exception != null ? exception.Message : "Exception not informed"); Report.AddErrorMessage(message); Error(message); //StatusInfo.Update(BackupStatusLevel.ERROR, message); backup.WasCanceled(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Canceled, Message = message }); }
public void OnFailure(CustomBackupAgent agent, Models.Backup backup, Exception exception) { IsRunning = false; logger.Log(LogLevel.Error, exception, "Caught exception: {0}", exception.Message); var message = string.Format("Backup failed: {0}", exception != null ? exception.Message : "Exception not informed"); Report.AddErrorMessage(message); Error(message); //StatusInfo.Update(BackupStatusLevel.ERROR, message); backup.DidFail(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Failed, Message = message }); }
private PathScanResults <string> DoWork(Models.Backup backup, CancellationToken cancellationToken) { // Scan files. DefaultPathScanner scanner = new DefaultPathScanner(backup.BackupPlan, cancellationToken); #if DEBUG scanner.FileAdded += (object sender, string file) => { logger.Debug("ADDED: File {0}", file); }; scanner.EntryScanFailed += (object sender, string path, string message, Exception ex) => { logger.Debug("FAILED: {0} - Reason: {1}", path, message); }; #endif scanner.Scan(); return(scanner.Results); }
private void Execute(Models.Backup backup, LinkedList <string> filePaths, bool isNewVersion) { // The `filePaths` argument contains the filesystem paths informed by the user for this backup. // // NOTE: The methods call ORDER is important! // SuppliedFiles = DoLoadOrCreateBackupPlanFiles(backup.BackupPlan, filePaths); DoUpdateBackupPlanFilesStatus(SuppliedFiles, isNewVersion); ChangeSet.AddedFiles = GetAddedFiles(SuppliedFiles); #if false foreach (var item in ChangeSet.AddedFiles) { Console.WriteLine("BackupPlanAddedFiles: {0}", item.Path); } #endif ChangeSet.ModifiedFiles = GetModifiedFiles(SuppliedFiles); #if false foreach (var item in changeset.modifiedfiles) { console.writeline("backupplanmodifiedfiles: {0}", item.path); } #endif // DO NOT update files removal and deletion status for `ResumeBackupOperation`, // only for `NewBackupOperation`. if (isNewVersion) { ChangeSet.RemovedFiles = GetRemovedFiles(AllFilesFromPlan.Values); #if false foreach (var item in ChangeSet.RemovedFiles) { Console.WriteLine("BackupPlanRemovedFiles: {0}", item.Path); } #endif ChangeSet.DeletedFiles = GetDeletedFilesAndUpdateTheirStatus(SuppliedFiles); } //throw new Exception("Simulating failure."); }
// Update specific `BackupPlanFile`s that exist and are NOT yet associated to a `BackupPlan`. protected void DoUpdateSyncedFiles(Models.Backup backup, LinkedList <string> filesToProcess) { BackupPlanFileRepository dao = new BackupPlanFileRepository(); long totalUpdates = 0; foreach (var path in filesToProcess) { // There's NO NEED to SELECT and UPDATE if we can UPDATE directly using a WHERE clause. totalUpdates += dao.AssociateSyncedFileToBackupPlan(backup.BackupPlan, path); } if (totalUpdates > 0) { logger.Info("Associated {0} synced files to Backup Plan {1}", totalUpdates, backup.BackupPlan.Name); } else { logger.Info("There are no synced files to associate to Backup Plan {0}", backup.BackupPlan.Name); } }
public async Task <FileVersionerResults> ResumeVersion(Models.Backup backup, LinkedList <string> files) { return(await DoVersion(backup, files, false)); }
protected override Task <FileVersionerResults> DoVersionFiles(Models.Backup backup, LinkedList <string> filesToProcess) { return(Versioner.ResumeVersion(backup, filesToProcess)); }
private void OnControlPlanQuery(object sender, ServerCommandEventArgs e) { string planType = e.Command.GetArgumentValue <string>("planType"); Int32 planId = e.Command.GetArgumentValue <Int32>("planId"); ValidatePlanType(planType); bool isRunning = IsPlanRunning(planType, planId); bool needsResume = false; bool isFinished = false; bool isBackup = planType.Equals(PlanTypeEnum.BACKUP.ToString().ToLowerInvariant()); bool isRestore = planType.Equals(PlanTypeEnum.RESTORE.ToString().ToLowerInvariant()); // Report to GUI. Commands.GuiReportPlanStatus report = new Commands.GuiReportPlanStatus(); if (isBackup) { BackupRepository daoBackup = new BackupRepository(); Models.Backup latest = daoBackup.GetLatestByPlan(new Models.BackupPlan { Id = planId }); needsResume = latest != null && latest.NeedsResume(); isFinished = latest != null && latest.IsFinished(); if (isRunning) { report.StartedAt = latest.StartedAt; } else if (isFinished) { report.FinishedAt = latest.FinishedAt; } } else if (isRestore) { RestoreRepository daoRestore = new RestoreRepository(); Models.Restore latest = daoRestore.GetLatestByPlan(new Models.RestorePlan { Id = planId }); needsResume = latest != null && latest.NeedsResume(); isFinished = latest != null && latest.IsFinished(); if (isRunning) { report.StartedAt = latest.StartedAt; } else if (isFinished) { report.FinishedAt = latest.FinishedAt; } } bool isInterrupted = !isRunning && needsResume; Commands.OperationStatus status; // The condition order below is important because more than one flag might be true. if (isInterrupted) { status = Commands.OperationStatus.INTERRUPTED; } else if (needsResume) { status = Commands.OperationStatus.RESUMED; } else if (isRunning) { status = Commands.OperationStatus.STARTED; } else { status = Commands.OperationStatus.NOT_RUNNING; } report.Status = status; Handler.Send(e.Context, Commands.GuiReportOperationStatus(planType, planId, report)); }
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 }); }; }
protected abstract Task <PathScanResults <string> > GetFilesToProcess(Models.Backup backup);
public ResumeBackupOperation(Models.Backup backup) : this(backup, new BackupOperationOptions()) { }
protected abstract Task <FileVersionerResults> DoVersionFiles(Models.Backup backup, LinkedList <string> filesToProcess);
public virtual void OnStart(CustomBackupAgent agent, Models.Backup backup) { IsRunning = true; backup.DidStart(); }
protected async void DoBackup(CustomBackupAgent agent, Models.Backup backup, BackupOperationOptions options) { try { CurrentState = BackupOperationState.STARTING; OnStart(agent, backup); // Mount all network mappings and abort if there is any network mapping failure. CurrentState = BackupOperationState.MAPPING_NETWORK_DRIVES; Helper.MountAllNetworkDrives(); // Execute pre-actions CurrentState = BackupOperationState.EXECUTING_PRE_ACTIONS; Helper.ExecutePreActions(); // // Scanning // CurrentState = BackupOperationState.SCANNING_FILES; LinkedList <string> filesToProcess = null; { Task <PathScanResults <string> > filesToProcessTask = GetFilesToProcess(backup); { var message = string.Format("Scanning files started."); Info(message); //StatusInfo.Update(BackupStatusLevel.INFO, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.ScanningFilesStarted, Message = message }); } try { await filesToProcessTask; } catch (Exception ex) { if (ex.IsCancellation()) { string message = string.Format("Scanning files was canceled."); Report.AddErrorMessage(message); logger.Warn(message); } else { string message = string.Format("Caught exception during scanning files: {0}", ex.Message); Report.AddErrorMessage(message); logger.Log(LogLevel.Error, ex, message); } if (filesToProcessTask.IsFaulted || filesToProcessTask.IsCanceled) { if (filesToProcessTask.IsCanceled) { OnCancelation(agent, backup, ex); // filesToProcessTask.Exception } else { OnFailure(agent, backup, ex); // filesToProcessTask.Exception } return; } } filesToProcess = filesToProcessTask.Result.Files; { foreach (var entry in filesToProcessTask.Result.FailedFiles) { Report.AddErrorMessage(entry.Value); } if (filesToProcessTask.Result.FailedFiles.Count > 0) { StringBuilder sb = new StringBuilder(); sb.AppendLine("Scanning failed for the following drives/files/directories:"); foreach (var entry in filesToProcessTask.Result.FailedFiles) { sb.AppendLine(string.Format(" Path: {0} - Reason: {1}", entry.Key, entry.Value)); } Warn(sb.ToString()); } var message = string.Format("Scanning files finished."); Info(message); //StatusInfo.Update(BackupStatusLevel.INFO, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.ScanningFilesFinished, Message = message }); } } // // Update synced files // CurrentState = BackupOperationState.UPDATING_SYNCED_FILES; { Task updateSyncedFilesTask = ExecuteOnBackround(() => { DoUpdateSyncedFiles(backup, filesToProcess); }, CancellationTokenSource.Token); { var message = string.Format("Update of synced files started."); Info(message); } try { await updateSyncedFilesTask; } catch (Exception ex) { if (ex.IsCancellation()) { string message = string.Format("Update of synced files was canceled."); Report.AddErrorMessage(message); logger.Warn(message); } else { string message = string.Format("Caught exception during update of synced files: {0}", ex.Message); Report.AddErrorMessage(message); logger.Log(LogLevel.Error, ex, message); } if (updateSyncedFilesTask.IsFaulted || updateSyncedFilesTask.IsCanceled) { Versioner.Undo(); if (updateSyncedFilesTask.IsCanceled) { OnCancelation(agent, backup, ex); // updateSyncedFilesTask.Exception } else { OnFailure(agent, backup, ex); // updateSyncedFilesTask.Exception } return; } } { var message = string.Format("Update of synced files finished."); Info(message); } } // // Versioning // CurrentState = BackupOperationState.VERSIONING_FILES; { Task <FileVersionerResults> versionerTask = DoVersionFiles(backup, filesToProcess); Report.VersionerResults = versionerTask.Result; { var message = string.Format("Processing files started."); Info(message); //StatusInfo.Update(BackupStatusLevel.INFO, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.ProcessingFilesStarted, Message = message }); } try { await versionerTask; } catch (Exception ex) { if (ex.IsCancellation()) { string message = string.Format("Processing files was canceled."); Report.AddErrorMessage(message); logger.Warn(message); } else { string message = string.Format("Caught exception during processing files: {0}", ex.Message); Report.AddErrorMessage(message); logger.Log(LogLevel.Error, ex, message); } if (versionerTask.IsFaulted || versionerTask.IsCanceled) { Versioner.Undo(); if (versionerTask.IsCanceled) { OnCancelation(agent, backup, ex); // versionerTask.Exception } else { OnFailure(agent, backup, ex); // versionerTask.Exception } return; } } agent.Files = Versioner.FilesToTransfer; { var message = string.Format("Processing files finished."); Info(message); //StatusInfo.Update(BackupStatusLevel.INFO, message); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.ProcessingFilesFinished, Message = message }); } { agent.Results.Stats.BytesTotal = agent.EstimatedTransferSize; var message = string.Format("Estimated backup size: {0} files, {1}", agent.Files.Count(), FileSizeUtils.FileSizeToString(agent.EstimatedTransferSize)); Info(message); } } // // Transfer files // CurrentState = BackupOperationState.TRANSFERRING_FILES; { Task <TransferResults> transferTask = agent.Start(); Report.TransferResults = transferTask.Result; { var message = string.Format("Transfer files started."); Info(message); } try { await transferTask; } catch (Exception ex) { if (ex.IsCancellation()) { string message = string.Format("Transfer files was canceled."); Report.TransferResults.ErrorMessages.Add(message); logger.Warn(message); } else { string message = string.Format("Caught exception during transfer files: {0}", ex.Message); Report.TransferResults.ErrorMessages.Add(message); logger.Log(LogLevel.Error, ex, message); } if (transferTask.IsFaulted || transferTask.IsCanceled) { if (transferTask.IsCanceled) { OnCancelation(agent, backup, ex); // transferTask.Exception } else { OnFailure(agent, backup, ex); // transferTask.Exception } return; } } { var message = string.Format("Transfer files finished."); Info(message); } } CurrentState = BackupOperationState.EXECUTING_POST_ACTIONS; Helper.ExecutePostActions(Report.TransferResults); CurrentState = BackupOperationState.FINISHING; OnFinish(agent, backup); } catch (Exception ex) { OnFinish(agent, backup, ex); } }
public void OnFinish(CustomBackupAgent agent, Models.Backup backup, Exception ex = null) { IsRunning = false; switch (CurrentState) { default: { var message = string.Format("Backup failed: {0}", ex.Message); Warn(message); //StatusInfo.Update(BackupStatusLevel.WARN, message); Report.AddErrorMessage(ex.Message); Report.OperationStatus = OperationStatus.FAILED; backup.DidFail(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Failed, Message = message }); break; } case BackupOperationState.FINISHING: { TransferResults.Statistics stats = agent.Results.Stats; var message = string.Format( "Backup finished! Stats: {0} completed, {1} failed, {2} canceled, {3} pending, {4} running", stats.Completed, stats.Failed, stats.Canceled, stats.Pending, stats.Running); Info(message); //StatusInfo.Update(BackupStatusLevel.OK, message); switch (agent.Results.OverallStatus) //switch (backup.Status) { default: throw new InvalidOperationException("Unexpected TransferStatus"); case TransferStatus.CANCELED: Report.OperationStatus = OperationStatus.CANCELED; backup.WasCanceled(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Canceled, Message = message }); break; case TransferStatus.FAILED: Report.OperationStatus = OperationStatus.FAILED; backup.DidFail(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Failed, Message = message }); break; case TransferStatus.COMPLETED: Report.OperationStatus = OperationStatus.COMPLETED; backup.DidComplete(); _daoBackup.Update(backup); OnUpdate(new BackupOperationEvent { Status = BackupOperationStatus.Finished, Message = message }); break; } break; } } Report.StartedAt = Backup.StartedAt; Report.FinishedAt = Backup.FinishedAt.Value; }
public NewBackupOperation(Models.BackupPlan plan, BackupOperationOptions options) : base(options) { Backup = new Models.Backup(plan); }