public async Task TestGetBackupMetadataAsync_OneFullBackup_AndTwoIncrementalBackup_Present() { //arrange Guid partitionId = Guid.NewGuid(); DateTime now = DateTime.UtcNow; BackupMetadata backupMetadata = new BackupMetadata(partitionId, now, BackupOption.Full); BackupMetadata incrementalMetadata = new BackupMetadata(partitionId, now.AddHours(1), BackupOption.Incremental); BackupMetadata incrementalMetadataTwo = new BackupMetadata(partitionId, now.AddHours(2), BackupOption.Incremental); var mockBackupStoreObject = new Moq.Mock <ICentralBackupStore>(); mockBackupStoreObject .Setup(store => store.RetrieveScheduledBackupAsync(It.IsAny <Guid>())) .Returns(Task.FromResult(incrementalMetadata)); mockBackupStoreObject .Setup(store => store.GetBackupMetadataAsync(null, It.IsAny <Guid>())) .Returns(Task.FromResult <IEnumerable <BackupMetadata> >(new[] { backupMetadata, incrementalMetadata, incrementalMetadataTwo })); var mockServiceObject = new Moq.Mock <IBackupRestoreServiceOperations>(); mockServiceObject.Setup(service => service.CentralBackupStore).Returns(mockBackupStoreObject.Object); mockServiceObject.Setup(service => service.Context).Returns(Mocks.MockStatefulServiceContextFactory.Default); //act var result = await BackupRestoreServiceOperations.GetBackupMetadataAsync(mockServiceObject.Object, incrementalMetadataTwo); //assert Assert.AreEqual(3, result.Count); Assert.AreEqual(backupMetadata, result[0]); Assert.AreEqual(incrementalMetadata, result[1]); Assert.AreEqual(incrementalMetadataTwo, result[2]); }
private async Task ApplyChangesAsync(TextEditor textEditor, BackupMetadata backupMetadata) { TextFile textFile = await FileSystemUtility.ReadFile(backupMetadata.BackupFilePath); textEditor.Init(textFile, textEditor.EditingFile, resetOriginalSnapshot: false, isModified: true); textEditor.TryChangeEncoding(backupMetadata.Encoding); textEditor.TryChangeLineEnding(backupMetadata.LineEnding); }
private async Task <ITextEditor> RecoverTextEditorAsync(TextEditorSessionData textEditorData) { StorageFile sourceFile = await FileSystemUtility.GetFileFromFutureAccessList(ToToken(textEditorData.Id)); BackupMetadata lastSaved = textEditorData.LastSaved; BackupMetadata pending = textEditorData.Pending; ITextEditor textEditor; if (sourceFile == null) // Untitled.txt or file not found { if (lastSaved != null || pending != null) { textEditor = _notepadsCore.OpenNewTextEditor(textEditorData.Id); await ApplyChangesAsync(textEditor, pending ?? lastSaved); } else { textEditor = null; } } else if (lastSaved == null && pending == null) // File without pending changes { textEditor = await _notepadsCore.OpenNewTextEditor(sourceFile, ignoreFileSizeLimit : true, textEditorData.Id); } else // File with pending changes { TextFile textFile = await FileSystemUtility.ReadFile(lastSaved.BackupFilePath, ignoreFileSizeLimit : true); textEditor = _notepadsCore.OpenNewTextEditor( textEditorData.Id, textFile.Content, sourceFile, lastSaved.DateModified, lastSaved.Encoding, lastSaved.LineEnding, false); await ApplyChangesAsync(textEditor, pending); } return(textEditor); }
public async Task TestGetBackupMetadataAsync_OneFullBackup_Present() { //arrange Guid partitionId = Guid.NewGuid(); DateTime now = DateTime.UtcNow; BackupMetadata backupMetadata = new BackupMetadata(partitionId, now, BackupOption.Full); var mockBackupStoreObject = new Moq.Mock <ICentralBackupStore>(); var mockServiceObject = new Moq.Mock <IBackupRestoreServiceOperations>(); mockServiceObject.Setup(service => service.CentralBackupStore).Returns(mockBackupStoreObject.Object); mockServiceObject.Setup(service => service.Context).Returns(Mocks.MockStatefulServiceContextFactory.Default); //act var result = await BackupRestoreServiceOperations.GetBackupMetadataAsync(mockServiceObject.Object, backupMetadata); //assert Assert.AreEqual(1, result.Count); Assert.AreEqual(backupMetadata, result[0]); }
async Task BackupAsync() { this.events.StartingBackup(); Guid backupId = Guid.NewGuid(); string dbBackupDirectory = Path.Combine(this.backupPath, backupId.ToString()); BackupMetadata newBackupMetadata = new BackupMetadata(backupId, this.dataBackupRestore.DataBackupFormat, DateTime.UtcNow, this.dbStores.Keys.ToList()); BackupMetadataList backupMetadataList = new BackupMetadataList(new List <BackupMetadata> { newBackupMetadata }); try { Directory.CreateDirectory(dbBackupDirectory); // Backup other stores. foreach (string store in this.dbStores.Keys) { IDbStore dbStore = this.dbStoreProvider.GetDbStore(store); await this.DbStoreBackupAsync(store, dbStore, dbBackupDirectory); } using (StreamWriter file = File.CreateText(Path.Combine(this.backupPath, BackupMetadataFileName))) { JsonSerializer serializer = new JsonSerializer(); serializer.Serialize(file, backupMetadataList); } this.events.BackupComplete(); // Clean any old backups. this.CleanupUnknownBackups(this.backupPath, backupMetadataList); } catch (Exception exception) { this.events.BackupFailure($"The backup operation failed with error ${exception}."); // Clean up any artifacts of the attempted backup. this.CleanupKnownBackups(this.backupPath, backupMetadataList); } }
private async Task <TextEditor> RecoverTextEditorAsync(TextEditorSessionData textEditorData) { StorageFile sourceFile = await FileSystemUtility.GetFileFromFutureAccessList(ToToken(textEditorData.Id)); BackupMetadata lastSaved = textEditorData.LastSaved; BackupMetadata pending = textEditorData.Pending; TextEditor textEditor; if (sourceFile == null) { textEditor = _notepadsCore.OpenNewTextEditor( textEditorData.Id, string.Empty, null, -1, EditorSettingsService.EditorDefaultEncoding, EditorSettingsService.EditorDefaultLineEnding, false); await ApplyChangesAsync(textEditor, pending ?? lastSaved); } else { TextFile lastSavedContent = await FileSystemUtility.ReadFile(lastSaved.BackupFilePath); textEditor = _notepadsCore.OpenNewTextEditor( textEditorData.Id, lastSavedContent.Content, sourceFile, lastSaved.DateModified, lastSaved.Encoding, lastSaved.LineEnding, false); await ApplyChangesAsync(textEditor, pending); } return(textEditor); }
private static void VerifyListIsAValidBackupChain(List <BackupMetadata> backupChain) { bool foundFull, foundDiff, foundLog; foundFull = foundDiff = foundLog = false; BackupMetadata full = null; BackupMetadata lastBackup = null; BackupMetadata currentBackup; while ((currentBackup = backupChain.FirstOrDefault()) != null) { if (currentBackup.BackupType == BackupFileTools.BackupType.Full) { Assert.True(!foundFull && !foundDiff && !foundLog); foundFull = true; full = currentBackup; } else if (currentBackup.BackupType == BackupFileTools.BackupType.Diff) { Assert.True(foundFull && !foundDiff && !foundLog); Assert.Equal(full.CheckpointLsn, currentBackup.DatabaseBackupLsn); Assert.True(currentBackup.FirstLsn >= lastBackup.LastLsn); foundDiff = true; } else if (currentBackup.BackupType == BackupFileTools.BackupType.Log) { Assert.True(foundFull); Assert.True(currentBackup.FirstLsn >= lastBackup.LastLsn); foundLog = true; } lastBackup = currentBackup; backupChain.RemoveAll(b => b.LastLsn == currentBackup.LastLsn); } }
public async Task SaveSessionAsync() { if (!IsBackupEnabled) { LoggingService.LogInfo("Session backup is disabled."); return; } // Serialize saves await _semaphoreSlim.WaitAsync(); Stopwatch stopwatch = Stopwatch.StartNew(); ITextEditor[] textEditors = _notepadsCore.GetAllTextEditors(); ITextEditor selectedTextEditor = _notepadsCore.GetSelectedTextEditor(); FileSystemUtility.ClearFutureAccessList(); NotepadsSessionDataV1 sessionData = new NotepadsSessionDataV1(); foreach (ITextEditor textEditor in textEditors) { if (textEditor.EditingFile != null) { // Add the opened file to FutureAccessList so we can access it next launch await FileSystemUtility.TryAddToFutureAccessList(ToToken(textEditor.Id), textEditor.EditingFile); } if (!_sessionData.TryGetValue(textEditor.Id, out TextEditorSessionData textEditorData)) { textEditorData = new TextEditorSessionData { Id = textEditor.Id }; if (textEditor.IsModified) { if (textEditor.EditingFile != null) { // Persist the last save known to the app, which might not be up-to-date (if the file was modified outside the app) BackupMetadata lastSaved = await SaveLastSavedChangesAsync(textEditor); if (lastSaved == null) { continue; } textEditorData.LastSaved = lastSaved; } // Persist pending changes relative to the last save BackupMetadata pending = await SavePendingChangesAsync(textEditor); if (pending == null) { continue; } textEditorData.Pending = pending; } // We will not create new backup files for this text editor unless it has changes _sessionData.TryAdd(textEditor.Id, textEditorData); } sessionData.TextEditors.Add(textEditorData); if (textEditor == selectedTextEditor) { sessionData.SelectedTextEditor = textEditor.Id; } } bool sessionDataSaved = false; try { string sessionJsonStr = JsonConvert.SerializeObject(sessionData, _encodingConverter); if (!(ApplicationSettingsStore.Read(SessionDataKey) is string currentValue) || !string.Equals(currentValue, sessionJsonStr, StringComparison.OrdinalIgnoreCase)) { ApplicationSettingsStore.Write(SessionDataKey, sessionJsonStr); sessionDataSaved = true; } } catch (Exception ex) { LoggingService.LogError($"Failed to save session metadata: {ex.Message}"); return; // Failed to save the session - do not proceed to delete backup files } if (sessionDataSaved) { await DeleteOrphanedBackupFilesAsync(sessionData); } stopwatch.Stop(); if (sessionDataSaved) { LoggingService.LogInfo($"Successfully saved the current session. Total time: {stopwatch.Elapsed.TotalMilliseconds} milliseconds.", consoleOnly: true); } _semaphoreSlim.Release(); }
async Task RestoreAsync() { string backupMetadataFilePath = Path.Combine(this.backupPath, BackupMetadataFileName); if (!File.Exists(backupMetadataFilePath)) { this.events.NoBackupsForRestore(); return; } try { string fileText = File.ReadAllText(backupMetadataFilePath); BackupMetadataList backupMetadataList = JsonConvert.DeserializeObject <BackupMetadataList>(fileText); BackupMetadata backupMetadata = backupMetadataList.Backups[0]; this.events.BackupInformation(backupMetadata.Id, backupMetadata.SerializationFormat, backupMetadata.TimestampUtc, backupMetadata.Stores); string latestBackupDirPath = Path.Combine(this.backupPath, backupMetadata.Id.ToString()); if (Directory.Exists(latestBackupDirPath)) { this.events.RestoringFromBackup(backupMetadata.Id); foreach (string store in backupMetadata.Stores) { IDbStore dbStore; if (!store.Equals(DefaultStoreBackupName, StringComparison.OrdinalIgnoreCase)) { dbStore = this.GetDbStore(store); } else { dbStore = this.GetDbStore(); } await this.DbStoreRestoreAsync(store, dbStore, latestBackupDirPath); } this.events.RestoreComplete(); } else { this.events.NoBackupsForRestore(); } } catch (Exception exception) { this.events.RestoreFailure($"The restore operation failed with error ${exception}."); // Clean up any restored state in the dictionary if the backup fails midway. foreach (string store in this.dbStores.Keys.ToList()) { this.RemoveDbStore(store); } this.RemoveDbStore(); } finally { // Delete all other backups as we've either: // 1. Restored successfully. // 2. Failed during restore which would indicate a bad backup. this.CleanupAllBackups(this.backupPath); } }
/// <inheritdoc /> Task IBackupRestoreService.BeginRestoreBackup(BackupMetadata backupMetadata, DataLossMode dataLossMode) { return(BackupRestoreServiceOperations.BeginRestoreBackup(this, backupMetadata, dataLossMode)); }
public async Task SaveSessionAsync() { if (!IsBackupEnabled) { return; } // Serialize saves await _semaphoreSlim.WaitAsync(); NotepadsSessionDataV1 sessionData = new NotepadsSessionDataV1(); HashSet <string> backupPaths = new HashSet <string>(StringComparer.OrdinalIgnoreCase); TextEditor[] textEditors = _notepadsCore.GetAllTextEditors(); TextEditor selectedTextEditor = _notepadsCore.GetSelectedTextEditor(); FileSystemUtility.ClearFutureAccessList(); foreach (TextEditor textEditor in textEditors) { TextEditorSessionData textEditorData = new TextEditorSessionData { Id = textEditor.Id }; if (textEditor.EditingFile != null) { // Add the opened file to FutureAccessList so we can access it next launch FileSystemUtility.TryAddToFutureAccessList(ToToken(textEditor.Id), textEditor.EditingFile); // Persist the last save known to the app, which might not be up-to-date (if the file was modified outside the app) BackupMetadata lastSaved = await SaveLastSavedChangesAsync(textEditor); if (lastSaved == null) { continue; } textEditorData.LastSaved = lastSaved; backupPaths.Add(lastSaved.BackupFilePath); } if (textEditor.IsModified) { // Persist pending changes relative to the last save BackupMetadata pending = await SavePendingChangesAsync(textEditor); if (pending == null) { continue; } textEditorData.Pending = pending; backupPaths.Add(pending.BackupFilePath); } if (textEditorData.LastSaved != null || textEditorData.Pending != null) { sessionData.TextEditors.Add(textEditorData); if (textEditor == selectedTextEditor) { sessionData.SelectedTextEditor = textEditor.Id; } } } try { string sessionJson = JsonConvert.SerializeObject(sessionData, _encodingConverter); ApplicationData.Current.LocalSettings.Values[SessionDataKey] = sessionJson; LoggingService.LogInfo("Successfully saved the current session."); } catch { return; // Failed to save the session - do not proceed to delete backup files } await DeleteOrphanedBackupFilesAsync(backupPaths); _semaphoreSlim.Release(); }
public async Task SaveSessionAsync() { if (!IsBackupEnabled) { LoggingService.LogInfo("Session backup is disabled."); return; } // Serialize saves await _semaphoreSlim.WaitAsync(); StorageFolder backupFolder = await SessionUtility.GetBackupFolderAsync(); LoggingService.LogInfo("Session backup is starting. Backup folder: " + backupFolder.Path); Stopwatch stopwatch = Stopwatch.StartNew(); TextEditor[] textEditors = _notepadsCore.GetAllTextEditors(); TextEditor selectedTextEditor = _notepadsCore.GetSelectedTextEditor(); FileSystemUtility.ClearFutureAccessList(); NotepadsSessionDataV1 sessionData = new NotepadsSessionDataV1(); foreach (TextEditor textEditor in textEditors) { if (textEditor.EditingFile != null) { // Add the opened file to FutureAccessList so we can access it next launch FileSystemUtility.TryAddToFutureAccessList(ToToken(textEditor.Id), textEditor.EditingFile); } if (!_sessionData.TryGetValue(textEditor.Id, out TextEditorSessionData textEditorData)) { textEditorData = new TextEditorSessionData { Id = textEditor.Id }; if (textEditor.EditingFile != null) { // Persist the last save known to the app, which might not be up-to-date (if the file was modified outside the app) BackupMetadata lastSaved = await SaveLastSavedChangesAsync(textEditor); if (lastSaved == null) { continue; } textEditorData.LastSaved = lastSaved; } if (textEditor.IsModified) { // Persist pending changes relative to the last save BackupMetadata pending = await SavePendingChangesAsync(textEditor); if (pending == null) { continue; } textEditorData.Pending = pending; } // We will not create new backup files for this text editor unless it has changes _sessionData.TryAdd(textEditor.Id, textEditorData); } if (textEditorData.LastSaved != null || textEditorData.Pending != null) { sessionData.TextEditors.Add(textEditorData); if (textEditor == selectedTextEditor) { sessionData.SelectedTextEditor = textEditor.Id; } } } try { string sessionJson = JsonConvert.SerializeObject(sessionData, _encodingConverter); ApplicationData.Current.LocalSettings.Values[SessionDataKey] = sessionJson; } catch { return; // Failed to save the session - do not proceed to delete backup files } await DeleteOrphanedBackupFilesAsync(sessionData); stopwatch.Stop(); LoggingService.LogInfo("Successfully saved the current session. Total time: " + stopwatch.Elapsed.TotalMilliseconds + " milliseconds."); _semaphoreSlim.Release(); }