/// <summary> /// Migrates from version 1 to 2. /// </summary> private async Task <UserDataBundle> UpgradeToVersion2Async(string encryptedData) { // Version 1 can be loaded as version 2. // The difference between 1 and 2 is a vulnerability in the encryption engine. // To fix it, the data should be decrypted and re-encrypted. _logger.LogEvent(Version1ToVersion2EventName, string.Empty); const int oldVersion = 1; const int newVersion = 2; string jsonData = _encryptionProvider.DecryptString(encryptedData); UserDataBundle userDataBundle = _serializationProvider.DeserializeObject <UserDataBundle>(jsonData); var tasks = new List <Task>(); for (int i = 0; i < userDataBundle.Accounts.Count; i++) { Account account = userDataBundle.Accounts[i]; for (int j = 0; j < account.Data.Count; j++) { AccountData accountData = account.Data[j]; if (accountData is IUpgradableAccountData upgradableAccountData) { tasks.Add(upgradableAccountData.UpgradeAsync(oldVersion, newVersion)); } } } await Task.WhenAll(tasks).ConfigureAwait(false); return(userDataBundle); }
public void CloneObjectTest() { var data = new UserDataBundle(); data.Categories.Add(new Category(Guid.NewGuid(), "Category")); var dataCopy = ExportProvider.GetExport <ISerializationProvider>().CloneObject(data); Assert.AreEqual(data.GetHashCode(), data.GetHashCode()); Assert.AreNotEqual(data.GetHashCode(), dataCopy.GetHashCode()); }
/// <summary> /// Creates the default instance of <see cref="UserDataBundle"/>. /// </summary> private void CreateNewUserDataBundle() { _data = new UserDataBundle(); _data.Categories.Add(new Category(new Guid(Constants.CategoryAllId), LanguageManager.Instance.Core.CategoryAll)); _data.Categories.Add(new Category(GenerateUniqueId(), LanguageManager.Instance.Core.CategoryFinancial)); _data.Categories.Add(new Category(GenerateUniqueId(), LanguageManager.Instance.Core.CategoryPersonal)); _data.Categories.Add(new Category(GenerateUniqueId(), LanguageManager.Instance.Core.CategoryProfessional)); _data.Categories.Add(new Category(GenerateUniqueId(), LanguageManager.Instance.Core.CategorySocial)); SortData(); }
public async Task LoadOrCreateLocalUserDataBundleAsync(CancellationToken cancellationToken) { using (await _sempahore.WaitAsync(cancellationToken).ConfigureAwait(false)) { await EnsureInitializedAsync().ConfigureAwait(false); if (await _localUserDataFolder.TryGetItemAsync(Constants.UserDataBundleFileName) == null) { // The local data file doesn't exist, let's create one ! _logger.LogEvent(NewBundleEvent, string.Empty); CreateNewUserDataBundle(); await SaveLocalDataInternalAsync(synchronize : false).ConfigureAwait(false); } // Open and decrypt the file from the hard drive. StorageFile dataFile = await _localUserDataFolder.GetFileAsync(Constants.UserDataBundleFileName); if (!dataFile.IsAvailable) { throw new FileLoadException("The file isn't available."); } // Loads the user data bundle and migrate it (if needed) (bool updated, UserDataBundle data) = await _upgradeService.UpgradeUserDataBundleAsync(dataFile).ConfigureAwait(false); _logger.LogEvent(LoadedEvent, string.Empty); if (_data == null) { // There was no previously loaded data file, let's use the one we loaded. _data = data; } else { // A user data file is already in memory. Maybe we just created it, but usually it probably mean we synchronized the file with the // cloud and should now open the newly downloaded file. // Therefore, we need to "merge" what we have on the hard drive (that comes from the cloud) with // what we already had in RAM. if (MergeUserDataBundle(data)) { updated = true; } } if (updated) { // Something has changed during the merge, and/or the data have been migrated from an older version. // So let's save the data to be sure we save the merge and let's (re)synchronize to be // sure the server has the changes from the merge too. await SaveLocalDataInternalAsync(synchronize : true).ConfigureAwait(false); } } }
public async Task <(bool updated, UserDataBundle userDataBundle)> UpgradeUserDataBundleAsync(StorageFile userDataBundleFile) { string encryptedData = await FileIO.ReadTextAsync(userDataBundleFile); int version = GetVersion(encryptedData); if (version == 1) { return(updated : true, await UpgradeToVersion2Async(encryptedData).ConfigureAwait(false)); } // No migration needed. Just load the data UserDataBundle userDataBundle = Load(encryptedData); _logger.LogEvent(NoUpgradeRequiredEventName, string.Empty); return(updated : false, userDataBundle); }
public async Task ClearLocalDataAsync(CancellationToken cancellationToken) { using (await _sempahore.WaitAsync(cancellationToken).ConfigureAwait(false)) { await CoreHelper.RetryAsync(async() => { await EnsureInitializedAsync().ConfigureAwait(false); IReadOnlyList <StorageFile> storageFiles = await _localUserDataFolder.GetFilesAsync(); for (int i = 0; i < storageFiles.Count; i++) { await storageFiles[i].DeleteAsync(StorageDeleteOption.PermanentDelete); } }).ConfigureAwait(false); _data = null; } _logger.LogEvent(ClearLocalDataEvent, string.Empty); }
/// <summary> /// Merges a given <paramref name="dataFromExternalSource"/> with <see cref="_data"/>. /// </summary> /// <returns>Returns <code>true</code> if something changed following the merge.</returns> private bool MergeUserDataBundle(UserDataBundle dataFromExternalSource) { var differenceDetectedInMerging = false; // Remove categories that exist in memory but not in the one coming from an external source. List <Category> categoriesToRemove = _data.Categories.Except(dataFromExternalSource.Categories).ToList(); for (int i = 0; i < categoriesToRemove.Count; i++) { if (_data.Categories.Remove(categoriesToRemove[i])) { differenceDetectedInMerging = true; } } // Update the categories that exist on the both side. for (var i = 0; i < _data.Categories.Count; i++) { Category category = _data.Categories[i]; Category categoryFromExternalSource = dataFromExternalSource.Categories.FirstOrDefault(item => item == category); // If the local category != category from external source and that the one from the external source // is more recent, then let's take it. if (!category.ExactEquals(categoryFromExternalSource)) { differenceDetectedInMerging = true; if (categoryFromExternalSource.LastModificationDate.ToUniversalTime() > category.LastModificationDate.ToUniversalTime()) { _data.Categories[i] = categoryFromExternalSource; } } } // Add categories that exist in the one coming from an external source but not in memory. List <Category> categoriesToAdd = dataFromExternalSource.Categories.Except(_data.Categories).ToList(); if (categoriesToAdd.Count > 0) { differenceDetectedInMerging = true; } for (int i = 0; i < categoriesToAdd.Count; i++) { _data.Categories.Add(categoriesToAdd[i]); } // Same but for accounts. List <Account> accountsToRemove = _data.Accounts.Except(dataFromExternalSource.Accounts).ToList(); for (int i = 0; i < accountsToRemove.Count; i++) { Account account = accountsToRemove[i]; if (_data.Accounts.Remove(account)) { differenceDetectedInMerging = true; } account.Dispose(); } for (var i = 0; i < _data.Accounts.Count; i++) { Account account = _data.Accounts[i]; Account accountFromExternalSource = dataFromExternalSource.Accounts.FirstOrDefault(item => item == account); // If the local account != account from external source and that the one from the external source // is more recent, then let's take it. if (!account.ExactEquals(accountFromExternalSource)) { differenceDetectedInMerging = true; if (accountFromExternalSource.LastModificationDate.ToUniversalTime() > account.LastModificationDate.ToUniversalTime()) { _data.Accounts[i] = accountFromExternalSource; } } } List <Account> accountsToAdd = dataFromExternalSource.Accounts.Except(_data.Accounts).ToList(); if (accountsToAdd.Count > 0) { differenceDetectedInMerging = true; } for (int i = 0; i < accountsToAdd.Count; i++) { _data.Accounts.Add(accountsToAdd[i]); } if (differenceDetectedInMerging) { // Sort the data. SortData(); } return(differenceDetectedInMerging); }