From 405d79e72f27d59f96695efb86c6b4928dffc82a Mon Sep 17 00:00:00 2001 From: Aaron Junker Date: Tue, 16 Aug 2022 19:32:49 +0200 Subject: [PATCH] [Dev file previewer]Various improvements (#18259) * Made file too big string variable * Performance improvements * Add progress bar to indicate loading * Added Logging * Added name to log files * Push * Updated expect.txt * Push * * Fixes small bug I sometimes encountered by unloading the application * Fixes bug where sometimes the loading bar kept stuck (on "file is too big" screen * Update expect.txt * Resolved review comments Added LogTrace() function * Unifying tasks * Removed unneccesary log message * * Added margin to loading bar and text. * Changed color of background to monaco dark skin color * Centred loading bar * Changed logger path * Changed log path * Fixed align of loading label * Fix label size and position Co-authored-by: Stefan Markovic --- .github/actions/spell-check/expect.txt | 2 +- src/common/SettingsAPI/settings_helpers.cpp | 16 ++ src/common/SettingsAPI/settings_helpers.h | 1 + .../MonacoPreviewHandler.cs | 1 + .../MonacoPreviewHandlerControl.cs | 176 +++++++++++++++--- .../Properties/Resources.resx | 16 +- .../MonacoPreviewHandler/Settings.cs | 2 +- .../MonacoPreviewHandler/helpers/Logger.cs | 84 +++++++++ tools/BugReportTool/BugReportTool/Main.cpp | 17 +- 9 files changed, 273 insertions(+), 42 deletions(-) create mode 100644 src/modules/previewpane/MonacoPreviewHandler/helpers/Logger.cs diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ff2b6f0af3..a4971bbc9e 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -601,7 +601,7 @@ FILESUBTYPE FILESYSPATH filesystem FILETIME -FILETYPE +filetype FILEVERSION Filtergraph Filterkeyboard diff --git a/src/common/SettingsAPI/settings_helpers.cpp b/src/common/SettingsAPI/settings_helpers.cpp index f671d5684c..974b2fff6a 100644 --- a/src/common/SettingsAPI/settings_helpers.cpp +++ b/src/common/SettingsAPI/settings_helpers.cpp @@ -25,6 +25,22 @@ namespace PTSettingsHelper return result; } + std::wstring get_local_low_folder_location() + { + PWSTR local_app_path; + winrt::check_hresult(SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, &local_app_path)); + std::wstring result{ local_app_path }; + CoTaskMemFree(local_app_path); + + result += L"\\Microsoft\\PowerToys"; + std::filesystem::path save_path(result); + if (!std::filesystem::exists(save_path)) + { + std::filesystem::create_directories(save_path); + } + return result; + } + std::wstring get_module_save_folder_location(std::wstring_view powertoy_key) { std::wstring result = get_root_save_folder_location(); diff --git a/src/common/SettingsAPI/settings_helpers.h b/src/common/SettingsAPI/settings_helpers.h index 2c4e3a24af..9e01b3b206 100644 --- a/src/common/SettingsAPI/settings_helpers.h +++ b/src/common/SettingsAPI/settings_helpers.h @@ -12,6 +12,7 @@ namespace PTSettingsHelper std::wstring get_module_save_file_location(std::wstring_view powertoy_key); std::wstring get_module_save_folder_location(std::wstring_view powertoy_name); std::wstring get_root_save_folder_location(); + std::wstring get_local_low_folder_location(); void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings); json::JsonObject load_module_settings(std::wstring_view powertoy_name); diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.cs index d46fa018f4..66888a20d7 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandler.cs @@ -58,6 +58,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null _disposedValue = true; + this.Unload(); } } diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs index d87cf805f5..b420895a4d 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs @@ -5,10 +5,13 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Globalization; using System.IO; using System.Runtime.CompilerServices; +using System.Threading.Tasks; using System.Windows.Forms; using Common; +using Microsoft.PowerToys.PreviewHandler.Monaco.Helpers; using Microsoft.PowerToys.PreviewHandler.Monaco.Properties; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; @@ -43,18 +46,50 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco /// private Label _loading; + /// + /// Loading progress bar + /// + private ProgressBar _loadingBar; + + /// + /// Grey background + /// + private Label _loadingBackground; + /// /// Name of the virtual host /// public const string VirtualHostName = "PowerToysLocalMonaco"; + /// + /// HTML code passed to the file + /// +#nullable enable + private string? _html; +#nullable disable + + /// + /// Id for monaco language + /// + private string _vsCodeLangSet; + + /// + /// The content of the previewing file in base64 + /// + private string _base64FileCode; + [STAThread] public override void DoPreview(T dataSource) { + Logger.LogTrace(); + base.DoPreview(dataSource); + // Sets background color + new Task(SetBackground).Start(); + // Starts loading screen - InitializeLoadingScreen(); + new Task(InitializeLoadingScreen).Start(); // New webview2 element _webView = new WebView2(); @@ -70,10 +105,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco if (fileSize < _settings.MaxFileSize) { + Task initializeIndexFileAndSelectedFileTask = new Task(() => { InitializeIndexFileAndSelectedFile(filePath); }); + initializeIndexFileAndSelectedFileTask.Start(); + try { InvokeOnControlThread(() => { + Logger.LogInfo("Create WebView2 environment"); ConfiguredTaskAwaitable.ConfiguredTaskAwaiter webView2EnvironmentAwaiter = CoreWebView2Environment .CreateAsync(userDataFolder: System.Environment.GetEnvironmentVariable("USERPROFILE") + @@ -81,6 +120,8 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco .ConfigureAwait(true).GetAwaiter(); webView2EnvironmentAwaiter.OnCompleted(() => { + _loadingBar.Value = 60; + this.Update(); InvokeOnControlThread(async () => { try @@ -91,49 +132,43 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco } _webView2Environment = webView2EnvironmentAwaiter.GetResult(); - var vsCodeLangSet = FileHandler.GetLanguage(Path.GetExtension(filePath)); - string fileContent; - using (StreamReader fileReader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) - { - fileContent = fileReader.ReadToEnd(); - fileReader.Close(); - } - var base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent)); - - string html; - - // prepping index html to load in - using (StreamReader htmlFileReader = new StreamReader(new FileStream(Settings.AssemblyDirectory + "\\index.html", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) - { - html = htmlFileReader.ReadToEnd(); - htmlFileReader.Close(); - } - - html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture); - html = html.Replace("[[PT_WRAP]]", _settings.Wrap ? "1" : "0", StringComparison.InvariantCulture); - html = html.Replace("[[PT_THEME]]", Settings.GetTheme(), StringComparison.InvariantCulture); - html = html.Replace("[[PT_CODE]]", base64FileCode, StringComparison.InvariantCulture); - html = html.Replace("[[PT_URL]]", VirtualHostName, StringComparison.InvariantCulture); + _loadingBar.Value = 70; + this.Update(); // Initialize WebView try { await _webView.EnsureCoreWebView2Async(_webView2Environment).ConfigureAwait(true); + + // Wait until html is loaded + initializeIndexFileAndSelectedFileTask.Wait(); + _webView.CoreWebView2.SetVirtualHostNameToFolderMapping(VirtualHostName, Settings.AssemblyDirectory, CoreWebView2HostResourceAccessKind.Allow); - _webView.NavigateToString(html); + + Logger.LogInfo("Navigates to string of HTML file"); + + _webView.NavigateToString(_html); _webView.NavigationCompleted += WebView2Init; _webView.Height = this.Height; _webView.Width = this.Width; Controls.Add(_webView); + _webView.SendToBack(); + _loadingBar.Value = 100; + this.Update(); } - catch (NullReferenceException) + catch (NullReferenceException e) { + Logger.LogError("NullReferenceException catched. Skipping exception.", e); } } - catch (WebView2RuntimeNotFoundException) + catch (WebView2RuntimeNotFoundException e) { + Logger.LogWarning("WebView2 was not found:"); + Logger.LogWarning(e.Message); Controls.Remove(_loading); + Controls.Remove(_loadingBar); + Controls.Remove(_loadingBackground); // WebView2 not installed message Label errorMessage = new Label(); @@ -160,6 +195,8 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco InvokeOnControlThread(() => { Controls.Remove(_loading); + Controls.Remove(_loadingBar); + Controls.Remove(_loadingBackground); Label text = new Label(); text.Text = Resources.Exception_Occurred; text.Text += e.Message; @@ -168,6 +205,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco text.Width = 500; text.Height = 10000; Controls.Add(text); + Logger.LogError(e.Message); }); } @@ -175,11 +213,15 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco } else { + Logger.LogInfo("File is too big to display. Showing error message"); InvokeOnControlThread(() => { Controls.Remove(_loading); + _loadingBar.Dispose(); + Controls.Remove(_loadingBar); + Controls.Remove(_loadingBackground); Label errorMessage = new Label(); - errorMessage.Text = Resources.Max_File_Size_Error; + errorMessage.Text = Resources.Max_File_Size_Error.Replace("%1", (_settings.MaxFileSize / 1000).ToString(CultureInfo.CurrentCulture), StringComparison.InvariantCulture); errorMessage.Width = 500; errorMessage.Height = 50; Controls.Add(errorMessage); @@ -194,6 +236,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco { _webView.Height = this.Height; _webView.Width = this.Width; + this.Update(); } /// @@ -205,6 +248,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco // Checks if already navigated if (!_hasNavigated) { + Logger.LogInfo("Setting WebView2 settings"); CoreWebView2Settings settings = (sender as WebView2).CoreWebView2.Settings; #if DEBUG @@ -234,10 +278,17 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco // Disable status bar settings.IsStatusBarEnabled = false; + Logger.LogInfo("Remove loading elements"); Controls.Remove(_loading); + Controls.Remove(_loadingBar); + Controls.Remove(_loadingBackground); #if DEBUG _webView.CoreWebView2.OpenDevToolsWindow(); + Logger.LogInfo("Opened Dev Tools window, because solution was built in debug mode"); #endif + + _loadingBar.Value = 80; + this.Update(); } } @@ -251,6 +302,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco if (_hasNavigated) { e.Cancel = false; + Logger.LogInfo("Stopped navigation from user"); } // If it has navigated to index.html it stops further navigations @@ -260,24 +312,86 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco } } - private void InitializeLoadingScreen() + private void SetBackground() { + Logger.LogTrace(); InvokeOnControlThread(() => { + this.BackColor = Settings.BackgroundColor; + }); + } + + private void InitializeLoadingScreen() + { + Logger.LogTrace(); + InvokeOnControlThread(() => + { + _loadingBackground = new Label(); + _loadingBackground.BackColor = Settings.BackgroundColor; + _loadingBackground.Width = this.Width; + _loadingBackground.Height = this.Height; + Controls.Add(_loadingBackground); + _loadingBackground.BringToFront(); + + _loadingBar = new ProgressBar(); + _loadingBar.Width = this.Width - 10; + _loadingBar.Location = new Point(5, this.Height / 2); + _loadingBar.Maximum = 100; + _loadingBar.Value = 10; + Controls.Add(_loadingBar); + _loading = new Label(); _loading.Text = Resources.Loading_Screen_Message; _loading.Width = this.Width; - _loading.Height = this.Height; + _loading.Height = 45; + _loading.Location = new Point(0, _loadingBar.Location.Y - _loading.Height); + _loading.TextAlign = ContentAlignment.TopCenter; _loading.Font = new Font("MS Sans Serif", 16, FontStyle.Bold); _loading.ForeColor = Settings.TextColor; - _loading.BackColor = Settings.BackgroundColor; Controls.Add(_loading); + + _loading.BringToFront(); + _loadingBar.BringToFront(); + + this.Update(); }); + Logger.LogInfo("Loading screen initialized"); + } + + private void InitializeIndexFileAndSelectedFile(string filePath) + { + Logger.LogInfo("Starting getting monaco language id out of filetype"); + _vsCodeLangSet = FileHandler.GetLanguage(Path.GetExtension(filePath)); + + using (StreamReader fileReader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + { + Logger.LogInfo("Starting reading requested file"); + var fileContent = fileReader.ReadToEnd(); + fileReader.Close(); + _base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent)); + Logger.LogInfo("Reading requested file ended"); + } + + // prepping index html to load in + using (StreamReader htmlFileReader = new StreamReader(new FileStream(Settings.AssemblyDirectory + "\\index.html", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + { + Logger.LogInfo("Starting reading HTML source file"); + _html = htmlFileReader.ReadToEnd(); + htmlFileReader.Close(); + Logger.LogInfo("Reading HTML source file ended"); + } + + _html = _html.Replace("[[PT_LANG]]", _vsCodeLangSet, StringComparison.InvariantCulture); + _html = _html.Replace("[[PT_WRAP]]", _settings.Wrap ? "1" : "0", StringComparison.InvariantCulture); + _html = _html.Replace("[[PT_THEME]]", Settings.GetTheme(), StringComparison.InvariantCulture); + _html = _html.Replace("[[PT_CODE]]", _base64FileCode, StringComparison.InvariantCulture); + _html = _html.Replace("[[PT_URL]]", VirtualHostName, StringComparison.InvariantCulture); } private async void DownloadLink_Click(object sender, EventArgs e) { await Launcher.LaunchUriAsync(new Uri("https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section")); + Logger.LogTrace(); } } } diff --git a/src/modules/previewpane/MonacoPreviewHandler/Properties/Resources.resx b/src/modules/previewpane/MonacoPreviewHandler/Properties/Resources.resx index 0e872dd186..be4a29da68 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/Properties/Resources.resx +++ b/src/modules/previewpane/MonacoPreviewHandler/Properties/Resources.resx @@ -118,22 +118,22 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - This file is too big to preview. -Max file size: 50KB - Error message which is shown when the file is too big. + This file is too big to preview. +Max file size: %1KB + Error message which is shown when the file is too big. %1 gets replaced by a value. Leave KB (means Kilobyte) - Exception occurred: + Exception occurred: - Will be shown when an exception occurred. + Will be shown when an exception occurred. - Loading... + Loading... - WebView2 not installed or found. + WebView2 not installed or found. - Download WebView2 to display this file. + Download WebView2 to display this file. \ No newline at end of file diff --git a/src/modules/previewpane/MonacoPreviewHandler/Settings.cs b/src/modules/previewpane/MonacoPreviewHandler/Settings.cs index 9779f5c40e..218895b5a8 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/Settings.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/Settings.cs @@ -53,7 +53,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco { if (GetTheme() == "dark") { - return Color.DimGray; + return System.Drawing.ColorTranslator.FromHtml("#1e1e1e"); } else { diff --git a/src/modules/previewpane/MonacoPreviewHandler/helpers/Logger.cs b/src/modules/previewpane/MonacoPreviewHandler/helpers/Logger.cs new file mode 100644 index 0000000000..b2bad4b8f9 --- /dev/null +++ b/src/modules/previewpane/MonacoPreviewHandler/helpers/Logger.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Abstractions; +using interop; + +namespace Microsoft.PowerToys.PreviewHandler.Monaco.Helpers +{ + public static class Logger + { + private static readonly IFileSystem _fileSystem = new FileSystem(); + private static readonly string ApplicationLogPath = System.Environment.GetEnvironmentVariable("USERPROFILE") + "\\AppData\\LocalLow\\Microsoft\\PowerToys\\logs\\FileExplorer_localLow\\Monaco"; + + static Logger() + { + if (!_fileSystem.Directory.Exists(ApplicationLogPath)) + { + _fileSystem.Directory.CreateDirectory(ApplicationLogPath); + } + + // Using InvariantCulture since this is used for a log file name + var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Monaco-log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt"); + + Trace.Listeners.Add(new TextWriterTraceListener(logFilePath)); + + Trace.AutoFlush = true; + } + + public static void LogError(string message) + { + Log(message, "ERROR"); + } + + public static void LogError(string message, Exception ex) + { + Log( + message + Environment.NewLine + + ex?.Message + Environment.NewLine + + "Inner exception: " + Environment.NewLine + + ex?.InnerException?.Message + Environment.NewLine + + "Stack trace: " + Environment.NewLine + + ex?.StackTrace, + "ERROR"); + } + + public static void LogWarning(string message) + { + Log(message, "WARNING"); + } + + public static void LogInfo(string message) + { + Log(message, "INFO"); + } + + private static void Log(string message, string type) + { + Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay); + Trace.Indent(); + Trace.WriteLine(GetCallerInfo()); + Trace.WriteLine(message); + Trace.Unindent(); + } + + public static void LogTrace() + { + Log(string.Empty, "Trace"); + } + + private static string GetCallerInfo() + { + StackTrace stackTrace = new StackTrace(); + + var methodName = stackTrace.GetFrame(3)?.GetMethod(); + var className = methodName?.DeclaringType.Name; + return "[Method]: " + methodName?.Name + " [Class]: " + className; + } + } +} diff --git a/tools/BugReportTool/BugReportTool/Main.cpp b/tools/BugReportTool/BugReportTool/Main.cpp index f36da41be5..91df7f3948 100644 --- a/tools/BugReportTool/BugReportTool/Main.cpp +++ b/tools/BugReportTool/BugReportTool/Main.cpp @@ -297,7 +297,10 @@ int wmain(int argc, wchar_t* argv[], wchar_t*) } auto settingsRootPath = PTSettingsHelper::get_root_save_folder_location(); - settingsRootPath = settingsRootPath + L"\\"; + settingsRootPath += L"\\"; + + auto localLowPath = PTSettingsHelper::get_local_low_folder_location(); + localLowPath += L"\\logs\\"; const auto tempDir = temp_directory_path(); auto reportDir = temp_directory_path() / "PowerToys\\"; @@ -314,12 +317,24 @@ int wmain(int argc, wchar_t* argv[], wchar_t*) // Remove updates folder contents DeleteFolder(reportDir / "Updates"); } + catch (...) { printf("Failed to copy PowerToys folder\n"); return 1; } + try + { + copy(localLowPath, reportDir, copy_options::recursive); + } + + catch (...) + { + printf("Failed to copy logs saved in LocalLow\n"); + return 1; + } + #ifndef _DEBUG InstallationFolder::ReportStructure(reportDir); #endif