From 8ae5c18b58ba15e69a35490b6b99a8e68078524c Mon Sep 17 00:00:00 2001 From: psyGamer Date: Sat, 28 Sep 2024 16:17:46 +0200 Subject: [PATCH] feat: Report Studio installation errors with a text file --- .../Source/EverestInterop/StudioHelper.cs | 74 ++++++++++++++++--- .../Source/Utils/LogUtil.cs | 4 + Studio/CelesteStudio/Controls/Markdown.cs | 1 + Studio/CelesteStudio/Dialog/SnippetDialog.cs | 1 + Studio/CelesteStudio/ErrorLog.cs | 1 + Studio/CelesteStudio/Settings.cs | 1 + Studio/CelesteStudio/Studio.cs | 1 + .../Util/ProcessHelper.cs | 6 +- 8 files changed, 74 insertions(+), 15 deletions(-) rename {Studio/CelesteStudio => StudioCommunication}/Util/ProcessHelper.cs (87%) diff --git a/CelesteTAS-EverestInterop/Source/EverestInterop/StudioHelper.cs b/CelesteTAS-EverestInterop/Source/EverestInterop/StudioHelper.cs index 969e6b762..c4b0dc0fa 100644 --- a/CelesteTAS-EverestInterop/Source/EverestInterop/StudioHelper.cs +++ b/CelesteTAS-EverestInterop/Source/EverestInterop/StudioHelper.cs @@ -8,6 +8,9 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using Celeste.Mod; +using StudioCommunication.Util; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using TAS.Module; @@ -92,10 +95,10 @@ private static void Load() { Task.Run(async () => { // Kill all Studio instances to avoid issues with file usage foreach (var process in Process.GetProcesses().Where(process => process.ProcessName is "CelesteStudio" or "Celeste Studio")) { - $"Killing process {process} ({process.Id})...".Log(LogLevel.Verbose); - process.Kill(); - await process.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(30.0f)).ConfigureAwait(false); - "Process killed".Log(LogLevel.Verbose); + $"Closing process {process} ({process.Id})...".Log(LogLevel.Verbose); + process.Terminate(); + await process.WaitForExitAsync().WaitAsync(TimeSpan.FromSeconds(10.0f)).ConfigureAwait(false); + "Process terminated".Log(LogLevel.Verbose); } // If Studio fails to find the game directory for some reason, that's where "TAS Files" will be placed @@ -117,7 +120,7 @@ private static void Load() { await DownloadStudio().ConfigureAwait(false); installed = true; } catch (Exception ex) { - ex.LogException("Failed to install Studio"); + ReportError("Failed to install Studio", ex.StackTrace); // Cleanup install if (Directory.Exists(TempStudioInstallDirectory)) { @@ -201,8 +204,9 @@ private static async Task DownloadStudio() { }); if (!File.Exists(DownloadPath)) { - "Download failed! The Studio archive went missing".Log(LogLevel.Error); + ReportError("Download failed! The Studio archive went missing"); StudioUpdateBanner.CurrentState = StudioUpdateBanner.State.Failure; + StudioUpdateBanner.FadeoutTimer = 5.0f; return; } "Finished download".Log(); @@ -229,8 +233,9 @@ private static async Task DownloadStudio() { await using (var fs = File.OpenRead(DownloadPath)) { string hash = BitConverter.ToString(await md5.ComputeHashAsync(fs)).Replace("-", ""); if (!Checksum.Equals(hash, StringComparison.OrdinalIgnoreCase)) { - $"Download failed! Invalid checksum for Studio archive file: Expected {Checksum} got {hash}".Log(LogLevel.Error); + ReportError($"Download failed! Invalid checksum for Studio archive file: Expected {Checksum} got {hash}"); StudioUpdateBanner.CurrentState = StudioUpdateBanner.State.Failure; + StudioUpdateBanner.FadeoutTimer = 5.0f; return; } $"Downloaded Studio archive has a valid checksum: {hash}".Log(LogLevel.Verbose); @@ -306,14 +311,24 @@ private static async Task ExecuteCommand(string[] parameters, string error string? line; if (proc.ExitCode != 0) { - $"{errorMessage}: Exit Code {proc.ExitCode}".Log(LogLevel.Error); - + List stdoutLines = [], stderrLines = []; while ((line = await proc.StandardOutput.ReadLineAsync()) != null) { - line.Log(LogLevel.Info); + stdoutLines.Add(line); } while ((line = await proc.StandardError.ReadLineAsync()) != null) { - line.Log(LogLevel.Error); + stderrLines.Add(line); } + + ReportError( + $"{errorMessage}: Exit Code {proc.ExitCode}", + $""" + Standard Out: + {string.Join(Environment.NewLine, stdoutLines)} + Standard Error: + {string.Join(Environment.NewLine, stderrLines)} + """); + stdoutLines.ForEach(l => l.Log()); + stderrLines.ForEach(l => l.Log(LogLevel.Error)); } else { while ((line = await proc.StandardOutput.ReadLineAsync()) != null) { line.Log(LogLevel.Debug); @@ -360,7 +375,42 @@ internal static void LaunchStudio() => Task.Run(async () => { "Successfully launched Studio".Log(); } catch (Exception ex) { - ex.LogException("Failed to launch Studio"); + ReportError("Failed to launch Studio", ex.StackTrace); + ex.LogException(); } }); + + private static void ReportError(string error, string? additionalInfo = null) { + error.Log(LogLevel.Error); + + if (!Directory.Exists(StudioDirectory)) { + Directory.CreateDirectory(StudioDirectory); + } + + string path = Path.Combine(StudioDirectory, "error_report.txt"); + string text = + $""" + === Celeste Studio v{CurrentStudioVersion} - Install failed === + {DateTime.Now.ToString(CultureInfo.InvariantCulture)} + + The following error occured while trying to install Celeste Studio: + {error} + + This may be caused by a bad internet connection. You can manually download Celeste Studio by following these steps: + 1. Close Celeste + 2. Manually download this file: {DownloadURL} + 3. Rename it to be called "CelesteStudio.zip" (ensure you have file extensions enabled!) + 4. Place it under "/CelesteStudio/CelesteStudio.zip" (create the "CelesteStudio" directory if it doesn't already exist. The "" directory is the same as where "Celeste.exe" / "Celeste.dll" is located) + 5. Re-open Celeste + + If the error persists, please report this issue. And send this ENTIRE file along with it. + (This file is located under "{path}") + + Additional Information: + {additionalInfo ?? ""} + """; + + File.WriteAllText(path, text); + ProcessHelper.OpenInDefaultApp(path); + } } diff --git a/CelesteTAS-EverestInterop/Source/Utils/LogUtil.cs b/CelesteTAS-EverestInterop/Source/Utils/LogUtil.cs index 50171f350..2c26431f1 100644 --- a/CelesteTAS-EverestInterop/Source/Utils/LogUtil.cs +++ b/CelesteTAS-EverestInterop/Source/Utils/LogUtil.cs @@ -24,6 +24,10 @@ public static void DebugLog(this object text, bool outputToCommands, LogLevel lo } #endif + public static void LogException(this Exception e) { + Logger.LogDetailed(e, Tag); + } + public static void LogException(this Exception e, string header, LogLevel logLevel = LogLevel.Error) { header.Log(logLevel); Logger.LogDetailed(e, Tag); diff --git a/Studio/CelesteStudio/Controls/Markdown.cs b/Studio/CelesteStudio/Controls/Markdown.cs index 710250890..7d55a2f0b 100644 --- a/Studio/CelesteStudio/Controls/Markdown.cs +++ b/Studio/CelesteStudio/Controls/Markdown.cs @@ -3,6 +3,7 @@ using Eto.Forms; using Markdig.Syntax; using Markdig.Syntax.Inlines; +using StudioCommunication.Util; using System; using System.Collections.Generic; using System.Linq; diff --git a/Studio/CelesteStudio/Dialog/SnippetDialog.cs b/Studio/CelesteStudio/Dialog/SnippetDialog.cs index ec37ab43a..05209abb5 100644 --- a/Studio/CelesteStudio/Dialog/SnippetDialog.cs +++ b/Studio/CelesteStudio/Dialog/SnippetDialog.cs @@ -5,6 +5,7 @@ using CelesteStudio.Util; using Eto.Drawing; using Eto.Forms; +using StudioCommunication.Util; using System; namespace CelesteStudio.Dialog; diff --git a/Studio/CelesteStudio/ErrorLog.cs b/Studio/CelesteStudio/ErrorLog.cs index 82de86000..bf1fc680e 100644 --- a/Studio/CelesteStudio/ErrorLog.cs +++ b/Studio/CelesteStudio/ErrorLog.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using CelesteStudio.Util; +using StudioCommunication.Util; using System.Globalization; namespace CelesteStudio; diff --git a/Studio/CelesteStudio/Settings.cs b/Studio/CelesteStudio/Settings.cs index d8e57e288..b51d9b080 100644 --- a/Studio/CelesteStudio/Settings.cs +++ b/Studio/CelesteStudio/Settings.cs @@ -11,6 +11,7 @@ using Eto; using Eto.Drawing; using Eto.Forms; +using StudioCommunication.Util; using System.Diagnostics; using System.Net; using Tomlet; diff --git a/Studio/CelesteStudio/Studio.cs b/Studio/CelesteStudio/Studio.cs index dc505b139..ef9e739c5 100644 --- a/Studio/CelesteStudio/Studio.cs +++ b/Studio/CelesteStudio/Studio.cs @@ -16,6 +16,7 @@ using FontDialog = CelesteStudio.Dialog.FontDialog; using Eto.Forms.ThemedControls; using StudioCommunication; +using StudioCommunication.Util; using System.Collections.Generic; namespace CelesteStudio; diff --git a/Studio/CelesteStudio/Util/ProcessHelper.cs b/StudioCommunication/Util/ProcessHelper.cs similarity index 87% rename from Studio/CelesteStudio/Util/ProcessHelper.cs rename to StudioCommunication/Util/ProcessHelper.cs index e3e99df09..df515ca07 100644 --- a/Studio/CelesteStudio/Util/ProcessHelper.cs +++ b/StudioCommunication/Util/ProcessHelper.cs @@ -1,8 +1,8 @@ using System.Diagnostics; using System.Runtime.InteropServices; -using Eto.Forms; +using System; -namespace CelesteStudio.Util; +namespace StudioCommunication.Util; public static class ProcessHelper { @@ -14,7 +14,7 @@ public static void OpenInDefaultApp(string path) { } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Process.Start("open", [path]); } else { - MessageBox.Show($"Cannot open '{path}' in it's default app, since platform is not recognized", MessageBoxType.Error); + throw new NotImplementedException($"Unsupported platform: {RuntimeInformation.OSDescription} with {RuntimeInformation.OSArchitecture}"); } }