From db09690f9853ce736d350b22f01bbfd78b9faeb4 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Fri, 19 Feb 2021 15:19:32 +0530 Subject: [PATCH 1/4] Use alternate write method for session snapshot. --- src/Notepads/Core/SessionManager.cs | 2 +- src/Notepads/Utilities/FileSystemUtility.cs | 65 +++++++++++++++++++++ src/Notepads/Utilities/SessionUtility.cs | 11 ---- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/Notepads/Core/SessionManager.cs b/src/Notepads/Core/SessionManager.cs index 5e0727972..2fb89853c 100644 --- a/src/Notepads/Core/SessionManager.cs +++ b/src/Notepads/Core/SessionManager.cs @@ -453,7 +453,7 @@ private static async Task BackupTextAsync(string text, Encoding encoding, { try { - await FileSystemUtility.WriteToFile(LineEndingUtility.ApplyLineEnding(text, lineEnding), encoding, file); + await FileSystemUtility.SafeWriteToFile(LineEndingUtility.ApplyLineEnding(text, lineEnding), encoding, file); return true; } catch (Exception ex) diff --git a/src/Notepads/Utilities/FileSystemUtility.cs b/src/Notepads/Utilities/FileSystemUtility.cs index 833693334..47a3710d6 100644 --- a/src/Notepads/Utilities/FileSystemUtility.cs +++ b/src/Notepads/Utilities/FileSystemUtility.cs @@ -607,6 +607,71 @@ public static async Task WriteToFile(string text, Encoding encoding, StorageFile } } + /// + /// Safely Save text to a file with requested encoding with transaction model + /// https://docs.microsoft.com/en-us/windows/uwp/files/best-practices-for-writing-to-files + /// Exception will be thrown if not succeeded + /// Exception should be caught and handled by caller + /// + /// + /// + /// + /// + public static async Task SafeWriteToFile(string text, Encoding encoding, StorageFile file) + { + bool usedDeferUpdates = true; + + try + { + // Prevent updates to the remote version of the file until we + // finish making changes and call CompleteUpdatesAsync. + CachedFileManager.DeferUpdates(file); + } + catch (Exception) + { + // If DeferUpdates fails, just ignore it and try to save the file anyway + usedDeferUpdates = false; + } + + // Write to file + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + try + { + var content = encoding.GetBytes(text); + var result = encoding.GetPreamble().Concat(content).ToArray(); + if (IsFileReadOnly(file) || !await IsFileWritable(file)) + { + // For file(s) dragged into Notepads, they are read-only + // StorageFile API won't work on read-only files but can be written by Win32 PathIO API (exploit?) + // In case the file is actually read-only, WriteBytesAsync will throw UnauthorizedAccessException + await PathIO.WriteBytesAsync(file.Path, result); + } + else // Use FileIO API to save + { + await FileIO.WriteBytesAsync(file, result); + } + } + finally + { + if (usedDeferUpdates) + { + // Let Windows know that we're finished changing the file so the + // other app can update the remote version of the file. + FileUpdateStatus status = await CachedFileManager.CompleteUpdatesAsync(file); + if (status != FileUpdateStatus.Complete) + { + // Track FileUpdateStatus here to better understand the failed scenarios + // File name, path and content are not included to respect/protect user privacy + Analytics.TrackEvent("CachedFileManager_CompleteUpdatesAsync_Failed", new Dictionary() + { + { "FileUpdateStatus", nameof(status) } + }); + } + } + } + } + internal static async Task DeleteFile(string filePath, StorageDeleteOption deleteOption = StorageDeleteOption.PermanentDelete) { try diff --git a/src/Notepads/Utilities/SessionUtility.cs b/src/Notepads/Utilities/SessionUtility.cs index 8aa9e5061..e9d0dbfa4 100644 --- a/src/Notepads/Utilities/SessionUtility.cs +++ b/src/Notepads/Utilities/SessionUtility.cs @@ -84,17 +84,6 @@ public static async Task GetSerializedSessionMetaDataAsync(string sessio public static async Task SaveSerializedSessionMetaDataAsync(string serializedData, string sessionMetaDataFileName) { StorageFolder localFolder = ApplicationData.Current.LocalFolder; - - // Attempt to delete session meta data file first in case it was not been deleted - try - { - await DeleteSerializedSessionMetaDataAsync(sessionMetaDataFileName); - } - catch (Exception) - { - // ignored - } - await localFolder.WriteTextToFileAsync(serializedData, sessionMetaDataFileName, CreationCollisionOption.ReplaceExisting); } From 91fc4914350414538fad2fe259f396d8c889fc60 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sun, 21 Feb 2021 13:00:48 +0530 Subject: [PATCH 2/4] Implemented recovering tabs from orphaned backup files and fewer IO operations during session snapshot load. --- src/Notepads/Core/SessionManager.cs | 147 +++++++++++++++++--- src/Notepads/Utilities/FileSystemUtility.cs | 4 +- 2 files changed, 130 insertions(+), 21 deletions(-) diff --git a/src/Notepads/Core/SessionManager.cs b/src/Notepads/Core/SessionManager.cs index 2fb89853c..eee86019d 100644 --- a/src/Notepads/Core/SessionManager.cs +++ b/src/Notepads/Core/SessionManager.cs @@ -16,6 +16,7 @@ using Notepads.Models; using Notepads.Services; using Notepads.Utilities; + using Windows.ApplicationModel.Resources; using Windows.Storage; using Windows.Storage.AccessCache; @@ -94,14 +95,15 @@ public async Task LoadLastSessionAsync() } IList recoveredEditor = new List(); + IList backupFiles = (await SessionUtility.GetAllBackupFilesAsync(_backupFolderName)).ToList(); foreach (var textEditorData in sessionData.TextEditors) { - ITextEditor textEditor; + Tuple> recoveredData; try { - textEditor = await RecoverTextEditorAsync(textEditorData); + recoveredData = await RecoverTextEditorAsync(textEditorData, backupFiles); } catch (Exception ex) { @@ -110,13 +112,26 @@ public async Task LoadLastSessionAsync() continue; } - if (textEditor != null) + if (recoveredData != null) { - recoveredEditor.Add(textEditor); - _sessionDataCache.TryAdd(textEditor.Id, textEditorData); + if (recoveredData.Item1 != null) + { + recoveredEditor.Add(recoveredData.Item1); + _sessionDataCache.TryAdd(recoveredData.Item1.Id, textEditorData); + } + + if (recoveredData.Item2?.Count > 0) + { + recoveredData.Item2.All(file => backupFiles.Remove(file)); + } } } + if (backupFiles?.Count > 0) + { + recoveredEditor.Concat(await RecoverOrphanedTextEditors(backupFiles)); + } + _notepadsCore.OpenTextEditors(recoveredEditor.ToArray(), sessionData.SelectedTextEditor); _notepadsCore.SetTabScrollViewerHorizontalOffset(sessionData.TabScrollViewerHorizontalOffset); @@ -387,25 +402,28 @@ private void UnbindEditorContentStateChangeEvent(object sender, ITextEditor text textEditor.FileReloaded -= RemoveTextEditorSessionData; } - private async Task RecoverTextEditorAsync(TextEditorSessionDataV1 editorSessionData) + private async Task>> RecoverTextEditorAsync( + TextEditorSessionDataV1 editorSessionData, + IList backupFiles) { StorageFile editingFile = null; + IList recoveredFiles = new List(); if (editorSessionData.EditingFileFutureAccessToken != null) { editingFile = await FutureAccessListUtility.GetFileFromFutureAccessList(editorSessionData.EditingFileFutureAccessToken); } - string lastSavedFile = editorSessionData.LastSavedBackupFilePath; - string pendingFile = editorSessionData.PendingBackupFilePath; + string lastSavedFilePath = editorSessionData.LastSavedBackupFilePath; + string pendingFilePath = editorSessionData.PendingBackupFilePath; ITextEditor textEditor; - if (editingFile == null && lastSavedFile == null && pendingFile == null) + if (editingFile == null && lastSavedFilePath == null && pendingFilePath == null) { textEditor = null; } - else if (editingFile != null && lastSavedFile == null && pendingFile == null) // File without pending changes + else if (editingFile != null && lastSavedFilePath == null && pendingFilePath == null) // File without pending changes { var encoding = EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding); textEditor = await _notepadsCore.CreateTextEditor(editorSessionData.Id, editingFile, encoding: encoding, ignoreFileSizeLimit: true); @@ -416,11 +434,20 @@ private async Task RecoverTextEditorAsync(TextEditorSessionDataV1 e string lastSavedText = string.Empty; string pendingText = null; - if (lastSavedFile != null) + if (lastSavedFilePath != null) { - TextFile lastSavedTextFile = await FileSystemUtility.ReadFile(lastSavedFile, ignoreFileSizeLimit: true, - EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding)); - lastSavedText = lastSavedTextFile.Content; + var lastSavedFile = backupFiles.First(file => lastSavedFilePath.Equals(file.Path, StringComparison.OrdinalIgnoreCase)); + if (lastSavedFile == null) + { + lastSavedText = (await FileSystemUtility.ReadFile(lastSavedFilePath, ignoreFileSizeLimit: true, + EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding))).Content; + } + else + { + recoveredFiles.Add(lastSavedFile); + lastSavedText = (await FileSystemUtility.ReadFile(lastSavedFile, ignoreFileSizeLimit: true, + EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding))).Content; + } } var textFile = new TextFile(lastSavedText, @@ -435,18 +462,98 @@ private async Task RecoverTextEditorAsync(TextEditorSessionDataV1 e editorSessionData.StateMetaData.FileNamePlaceholder, editorSessionData.StateMetaData.IsModified); - if (pendingFile != null) + if (pendingFilePath != null) { - TextFile pendingTextFile = await FileSystemUtility.ReadFile(pendingFile, - ignoreFileSizeLimit: true, - EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding)); - pendingText = pendingTextFile.Content; + var pendingFile = backupFiles.First(file => pendingFilePath.Equals(file.Path, StringComparison.OrdinalIgnoreCase)); + if (pendingFile == null) + { + pendingText = (await FileSystemUtility.ReadFile(pendingFilePath, ignoreFileSizeLimit: true, + EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding))).Content; + } + else + { + recoveredFiles.Add(pendingFile); + pendingText = (await FileSystemUtility.ReadFile(pendingFile, ignoreFileSizeLimit: true, + EncodingUtility.GetEncodingByName(editorSessionData.StateMetaData.LastSavedEncoding))).Content; + } } textEditor.ResetEditorState(editorSessionData.StateMetaData, pendingText); } - return textEditor; + return Tuple.Create(textEditor, recoveredFiles); + } + + // Recover editos from backup files if they have failed to store metadata + private async Task> RecoverOrphanedTextEditors(IList backupFiles) + { + IList recoveredEditors = new List(); + while (backupFiles.Count > 0) + { + var backupFile = backupFiles[0]; + Guid editorId = Guid.NewGuid(); + TextFile lastSavedTextFile = null; + TextFile pendingTextFile = null; + if (backupFile.Name.EndsWith("-LastSaved")) + { + editorId = Guid.Parse(backupFile.Name.Replace("-LastSaved", "")); + lastSavedTextFile = await FileSystemUtility.ReadFile(backupFile, ignoreFileSizeLimit: true); + var pendingFile = backupFiles.First(file => file.Name.Equals(ToToken(editorId) + "-Pending")); + pendingTextFile = await FileSystemUtility.ReadFile(pendingFile, ignoreFileSizeLimit: true); + + backupFiles.Remove(backupFile); + backupFiles.Remove(pendingFile); + } + else if (backupFile.Name.EndsWith("-Pending")) + { + editorId = Guid.Parse(backupFile.Name.Replace("-Pending", "")); + var lastSavedFile = backupFiles.First(file => file.Name.Equals(ToToken(editorId) + "-LastSaved")); + lastSavedTextFile = await FileSystemUtility.ReadFile(lastSavedFile, ignoreFileSizeLimit: true); + pendingTextFile = await FileSystemUtility.ReadFile(backupFile, ignoreFileSizeLimit: true); + + backupFiles.Remove(backupFile); + backupFiles.Remove(lastSavedFile); + } + else + { + backupFiles.Remove(backupFile); + continue; + } + + if (pendingTextFile == null) + { + // If there are no pending changes no need to recover + continue; + } + + ITextEditor textEditor = null; + var defaultName = ResourceLoader.GetForCurrentView().GetString("TextEditor_DefaultNewFileName"); + var editingFile = await FutureAccessListUtility.GetFileFromFutureAccessList(ToToken(editorId)); + var textFile = await FileSystemUtility.ReadFile(editingFile, ignoreFileSizeLimit: true); + if (lastSavedTextFile == null) + { + textEditor = _notepadsCore.CreateTextEditor(editorId, + textFile ?? pendingTextFile, + editingFile, + editingFile?.Name ?? defaultName, + true); + } + else + { + textEditor = _notepadsCore.CreateTextEditor(editorId, + lastSavedTextFile, + editingFile, + editingFile?.Name ?? defaultName, + true); + } + + if (textEditor == null) continue; + + textEditor.ResetEditorState(textEditor.GetTextEditorStateMetaData(), pendingTextFile.Content); + recoveredEditors.Add(textEditor); + } + + return recoveredEditors; } private static async Task BackupTextAsync(string text, Encoding encoding, LineEnding lineEnding, StorageFile file) diff --git a/src/Notepads/Utilities/FileSystemUtility.cs b/src/Notepads/Utilities/FileSystemUtility.cs index 47a3710d6..44113ee6b 100644 --- a/src/Notepads/Utilities/FileSystemUtility.cs +++ b/src/Notepads/Utilities/FileSystemUtility.cs @@ -315,11 +315,13 @@ public static async Task GetFile(string filePath) public static async Task ReadFile(string filePath, bool ignoreFileSizeLimit, Encoding encoding) { StorageFile file = await GetFile(filePath); - return file == null ? null : await ReadFile(file, ignoreFileSizeLimit, encoding); + return await ReadFile(file, ignoreFileSizeLimit, encoding); } public static async Task ReadFile(StorageFile file, bool ignoreFileSizeLimit, Encoding encoding = null) { + if (file == null) return null; + var fileProperties = await file.GetBasicPropertiesAsync(); if (!ignoreFileSizeLimit && fileProperties.Size > 1000 * 1024) From 5b563b03bb126ccc14c1237c4240d988fdca3b76 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Sun, 21 Feb 2021 16:10:26 +0530 Subject: [PATCH 3/4] Delete temp files after sessions loaded. --- src/Notepads/Core/SessionManager.cs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Notepads/Core/SessionManager.cs b/src/Notepads/Core/SessionManager.cs index eee86019d..2dc335009 100644 --- a/src/Notepads/Core/SessionManager.cs +++ b/src/Notepads/Core/SessionManager.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; + using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -65,8 +66,15 @@ public async Task LoadLastSessionAsync() var data = await SessionUtility.GetSerializedSessionMetaDataAsync(_sessionMetaDataFileName); + IList recoveredEditor = new List(); + IList backupFiles = (await SessionUtility.GetAllBackupFilesAsync(_backupFolderName)).ToList(); + var orphanedEditorCount = 0; + if (data == null) { + recoveredEditor = await RecoverOrphanedTextEditors(backupFiles); + orphanedEditorCount = recoveredEditor.Count; + _notepadsCore.OpenTextEditors(recoveredEditor.ToArray()); return 0; // No session data found } @@ -88,15 +96,16 @@ public async Task LoadLastSessionAsync() } catch (Exception ex) { + recoveredEditor = await RecoverOrphanedTextEditors(backupFiles); + orphanedEditorCount = recoveredEditor.Count; + _notepadsCore.OpenTextEditors(recoveredEditor.ToArray()); + LoggingService.LogError($"[{nameof(SessionManager)}] Failed to load last session metadata: {ex.Message}"); Analytics.TrackEvent("SessionManager_FailedToLoadLastSession", new Dictionary() { { "Exception", ex.Message } }); await ClearSessionDataAsync(); return 0; } - IList recoveredEditor = new List(); - IList backupFiles = (await SessionUtility.GetAllBackupFilesAsync(_backupFolderName)).ToList(); - foreach (var textEditorData in sessionData.TextEditors) { Tuple> recoveredData; @@ -129,7 +138,9 @@ public async Task LoadLastSessionAsync() if (backupFiles?.Count > 0) { - recoveredEditor.Concat(await RecoverOrphanedTextEditors(backupFiles)); + var orphanedEditors = await RecoverOrphanedTextEditors(backupFiles); + orphanedEditorCount = orphanedEditors.Count; + recoveredEditor.Concat(orphanedEditors); } _notepadsCore.OpenTextEditors(recoveredEditor.ToArray(), sessionData.SelectedTextEditor); @@ -139,7 +150,7 @@ public async Task LoadLastSessionAsync() LoggingService.LogInfo($"[{nameof(SessionManager)}] {_sessionDataCache.Count} tab(s) restored from last session."); - return _sessionDataCache.Count; + return _sessionDataCache.Count + orphanedEditorCount; } public async Task SaveSessionAsync(Action actionAfterSaving = null) @@ -592,6 +603,11 @@ private async Task DeleteOrphanedBackupFilesAsync(NotepadsSessionDataV1 sessionD } } } + + foreach (var filePth in Directory.GetFiles((await SessionUtility.GetBackupFolderAsync(_backupFolderName)).Path, "*.~TMP")) + { + File.Delete(filePth); + } } // Cleanup orphaned/dangling entries in FutureAccessList From 9ccceee1ac1105746315e092e287ad3defc13373 Mon Sep 17 00:00:00 2001 From: Soumya Ranjan Mahunt Date: Mon, 22 Feb 2021 11:36:16 +0530 Subject: [PATCH 4/4] Implemeted recovering sessions from .~TMP files. --- src/Notepads/Core/SessionManager.cs | 58 +++++++--------- src/Notepads/Utilities/FileSystemUtility.cs | 45 +++++++++++++ src/Notepads/Utilities/SessionUtility.cs | 73 +++++++++++++++++++-- 3 files changed, 137 insertions(+), 39 deletions(-) diff --git a/src/Notepads/Core/SessionManager.cs b/src/Notepads/Core/SessionManager.cs index 2dc335009..fe35de89b 100644 --- a/src/Notepads/Core/SessionManager.cs +++ b/src/Notepads/Core/SessionManager.cs @@ -64,46 +64,18 @@ public async Task LoadLastSessionAsync() return 0; // Already loaded } - var data = await SessionUtility.GetSerializedSessionMetaDataAsync(_sessionMetaDataFileName); - IList recoveredEditor = new List(); - IList backupFiles = (await SessionUtility.GetAllBackupFilesAsync(_backupFolderName)).ToList(); + var sessionData = await SessionUtility.GetSessionMetaDataAsync(_sessionMetaDataFileName); + var backupFiles = (await SessionUtility.GetAllBackupFilesAsync(_backupFolderName)).ToList(); var orphanedEditorCount = 0; - if (data == null) + if (sessionData == null && backupFiles.Count > 0) { recoveredEditor = await RecoverOrphanedTextEditors(backupFiles); orphanedEditorCount = recoveredEditor.Count; _notepadsCore.OpenTextEditors(recoveredEditor.ToArray()); - return 0; // No session data found - } - - NotepadsSessionDataV1 sessionData; - - try - { - var json = JObject.Parse(data); - var version = (int)json["Version"]; - - if (version == 1) - { - sessionData = JsonConvert.DeserializeObject(data); - } - else - { - throw new Exception($"Invalid version found in session metadata: {version}"); - } - } - catch (Exception ex) - { - recoveredEditor = await RecoverOrphanedTextEditors(backupFiles); - orphanedEditorCount = recoveredEditor.Count; - _notepadsCore.OpenTextEditors(recoveredEditor.ToArray()); - - LoggingService.LogError($"[{nameof(SessionManager)}] Failed to load last session metadata: {ex.Message}"); - Analytics.TrackEvent("SessionManager_FailedToLoadLastSession", new Dictionary() { { "Exception", ex.Message } }); await ClearSessionDataAsync(); - return 0; + return orphanedEditorCount; // No session meta-data found, only recover from orphaned backup files } foreach (var textEditorData in sessionData.TextEditors) @@ -531,10 +503,19 @@ private async Task> RecoverOrphanedTextEditors(IList> RecoverOrphanedTextEditors(IList GetFile(string filePath) } } + public static async Task ReadLocalFile(string filePath, Encoding encoding = null) + { + if (string.IsNullOrWhiteSpace(filePath)) return null; + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + string text; + var bom = new byte[4]; + + using (var stream = File.OpenRead(filePath)) + { + await stream.ReadAsync(bom, 0, 4); // Read BOM values + stream.Position = 0; // Reset stream position + + var reader = CreateStreamReader(stream, bom, encoding); + + async Task PeekAndRead() + { + if (encoding == null) + { + reader.Peek(); + encoding = reader.CurrentEncoding; + } + var str = await reader.ReadToEndAsync(); + reader.Close(); + return str; + } + + try + { + text = await PeekAndRead(); + } + catch (DecoderFallbackException) + { + stream.Position = 0; // Reset stream position + encoding = GetFallBackEncoding(); + reader = new StreamReader(stream, encoding); + text = await PeekAndRead(); + } + } + + encoding = FixUtf8Bom(encoding, bom); + return new TextFile(text, encoding, LineEndingUtility.GetLineEndingTypeFromText(text), File.GetLastWriteTime(filePath).ToFileTime()); + } + public static async Task ReadFile(string filePath, bool ignoreFileSizeLimit, Encoding encoding) { StorageFile file = await GetFile(filePath); diff --git a/src/Notepads/Utilities/SessionUtility.cs b/src/Notepads/Utilities/SessionUtility.cs index e9d0dbfa4..1dd3c16bf 100644 --- a/src/Notepads/Utilities/SessionUtility.cs +++ b/src/Notepads/Utilities/SessionUtility.cs @@ -9,6 +9,10 @@ using Notepads.Services; using Windows.Storage; using Microsoft.AppCenter.Analytics; + using System.IO; + using Newtonsoft.Json.Linq; + using Notepads.Core.SessionDataModels; + using Newtonsoft.Json; internal static class SessionUtility { @@ -45,6 +49,11 @@ public static ISessionManager GetSessionManager(INotepadsCore notepadCore, strin return sessionManager; } + public static string GetBackupFolderPath(string backupFolderName) + { + return Path.Combine(ApplicationData.Current.LocalFolder.Path, backupFolderName); + } + public static async Task GetBackupFolderAsync(string backupFolderName) { return await FileSystemUtility.GetOrCreateAppFolder(backupFolderName); @@ -56,16 +65,72 @@ public static async Task> GetAllBackupFilesAsync(stri return await backupFolder.GetFilesAsync(); } - public static async Task GetSerializedSessionMetaDataAsync(string sessionMetaDataFileName) + public static async Task GetSessionMetaDataAsync(string sessionMetaDataFileName) { try { StorageFolder localFolder = ApplicationData.Current.LocalFolder; if (await localFolder.FileExistsAsync(sessionMetaDataFileName)) { - var data = await localFolder.ReadTextFromFileAsync(sessionMetaDataFileName); - LoggingService.LogInfo($"[{nameof(SessionUtility)}] Session metadata Loaded from {localFolder.Path}"); - return data; + NotepadsSessionDataV1 sessionData = null; + string metadataFilePath = null; + + var tempMetaDataFilePath = Path.Combine(localFolder.Path, sessionMetaDataFileName + "~.TMP"); + if (File.Exists(tempMetaDataFilePath)) + { + var data = await File.ReadAllTextAsync(tempMetaDataFilePath); + try + { + var json = JObject.Parse(data); + var version = (int)json["Version"]; + + if (version == 1) + { + sessionData = JsonConvert.DeserializeObject(data); + } + else + { + throw new Exception($"Invalid version found in temporary session metadata: {version}"); + } + + metadataFilePath = tempMetaDataFilePath; + } + catch (Exception ex) + { + LoggingService.LogError($"[{nameof(SessionManager)}] Failed to load temporary last session metadata: {ex.Message}"); + Analytics.TrackEvent("SessionManager_FailedToLoadLastTempSession", new Dictionary() { { "Exception", ex.Message } }); + } + } + + if (sessionData == null) + { + var data = await localFolder.ReadTextFromFileAsync(sessionMetaDataFileName); + + try + { + var json = JObject.Parse(data); + var version = (int)json["Version"]; + + if (version == 1) + { + sessionData = JsonConvert.DeserializeObject(data); + } + else + { + throw new Exception($"Invalid version found in session metadata: {version}"); + } + + metadataFilePath = Path.Combine(localFolder.Path, sessionMetaDataFileName); + } + catch (Exception ex) + { + LoggingService.LogError($"[{nameof(SessionManager)}] Failed to load last session metadata: {ex.Message}"); + Analytics.TrackEvent("SessionManager_FailedToLoadLastSession", new Dictionary() { { "Exception", ex.Message } }); + } + } + + LoggingService.LogInfo($"[{nameof(SessionUtility)}] Session metadata Loaded from {metadataFilePath}"); + return sessionData; } } catch (Exception ex)