Refactor the logic
MODIFY
WalletWasabi.Gui/CommandLine/CommandInterpreter.cs
WalletWasabi.Gui/CommandLine/CommandInterpreter.cs
@@ -36,7 +36,7 @@ namespace WalletWasabi.Gui.CommandLine
"",
new MixerCommand(daemon),
new PasswordFinderCommand(global.WalletManager),
- new CrashReportCommand(global)
+ new CrashReportCommand(global.CrashReporter)
};
EnsureBackwardCompatibilityWithOldParameters(ref args);

MODIFY
WalletWasabi.Gui/CommandLine/CrashReportCommand.cs
WalletWasabi.Gui/CommandLine/CrashReportCommand.cs
@@ -5,15 +5,15 @@ using System.Threading.Tasks;
using WalletWasabi.Helpers;
using Newtonsoft.Json;
using WalletWasabi.Logging;
-using WalletWasabi.Gui.CrashReporter.Models;
+using WalletWasabi.Gui.Models.CrashReport;
namespace WalletWasabi.Gui.CommandLine
{
internal class CrashReportCommand : Command
{
- public CrashReportCommand(Global global) : base("crashreport", "Activates the internal crash reporting mechanism.")
+ public CrashReportCommand(CrashReporter crashReporter) : base("crashreport", "Activates the internal crash reporting mechanism.")
{
- Global = Guard.NotNull(nameof(Global), global);
+ CrashReporter = Guard.NotNull(nameof(crashReporter), crashReporter);
Options = new OptionSet()
{
@@ -22,33 +22,24 @@ namespace WalletWasabi.Gui.CommandLine
};
}
- private void ExceptionDecode(string exceptionString)
- {
- var exS = Guard.NotNullOrEmptyOrWhitespace(nameof(exceptionString), exceptionString);
-
- exS = exS.Replace("\\X0009", "\'")
- .Replace("\\X0022", "\"")
- .Replace("\\X000A", "\n")
- .Replace("\\X000D", "\r")
- .Replace("\\X0009", "\t")
- .Replace("\\X0020", " ");
-
- Global.CrashReportException = JsonConvert.DeserializeObject<SerializedException>(exS);
- }
-
- public Global Global { get; }
+ public CrashReporter CrashReporter { get; }
public string Attempts { get; private set; }
public string ExceptionString { get; private set; }
- public override async Task<int> InvokeAsync(IEnumerable<string> args)
+ public override Task<int> InvokeAsync(IEnumerable<string> args)
+ {
+ try
{
Options.Parse(args);
- Global.CrashReportStartAttempt = Convert.ToInt32(Attempts);
-
- ExceptionDecode(ExceptionString);
+ CrashReporter.SetException(ExceptionString, int.Parse(Attempts));
+ }
+ catch (Exception ex)
+ {
+ Task.FromException(ex);
+ }
- return 0;
+ return Task.FromResult(0);
}
}
}

RENAME
WalletWasabi.Gui/CrashReporter/CrashReportApp.xaml.cs
WalletWasabi.Gui/CrashReport/CrashReportApp.xaml.cs
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
-using WalletWasabi.Gui.CrashReporter.ViewModels;
-using WalletWasabi.Gui.CrashReporter.Views;
+using WalletWasabi.Gui.CrashReport.ViewModels;
+using WalletWasabi.Gui.CrashReport.Views;
-namespace WalletWasabi.Gui.CrashReporter
+namespace WalletWasabi.Gui.CrashReport
{
public class CrashReportApp : Application
{

ADD
WalletWasabi.Gui/CrashReport/CrashReporter.cs
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using WalletWasabi.Gui.CrashReport.Models;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+using WalletWasabi.Microservices;
+
+namespace WalletWasabi.Gui.Models.CrashReport
+{
+ public class CrashReporter
+ {
+ private const int MaxRecursiveCalls = 5;
+
+ public int Attempts { get; private set; }
+ public string ExceptionString { get; private set; } = null;
+ public bool IsReport => ExceptionString is { };
+
+ public void Start()
+ {
+ var exceptionString = ExceptionString;
+ var prevAttempts = Attempts;
+
+ if (prevAttempts >= MaxRecursiveCalls)
+ {
+ Logger.LogCritical($"The crash helper has been called {MaxRecursiveCalls} times. Will not continue to avoid recursion errors.");
+ return;
+ }
+
+ var args = $"crashreport -attempt={prevAttempts + 1} -exception={exceptionString}";
+
+ ProcessBridge processBridge = new ProcessBridge(Process.GetCurrentProcess().MainModule.FileName);
+ processBridge.Start(args, false);
+
+ return;
+ }
+
+ public void SetException(string exceptionString, int attempts)
+ {
+ Attempts = attempts;
+ ExceptionString = exceptionString;
+ }
+
+ /// <summary>
+ /// Sets the exception when it first time occurs and should be reported to the user.
+ /// </summary>
+ public void SetException(Exception ex)
+ {
+ SetException(SerializedException.ToCommandLineArgument(ex), 1);
+ }
+
+ public override string ToString()
+ {
+ var exceptionToDisplay = Guard.NotNullOrEmptyOrWhitespace(nameof(ExceptionString), ExceptionString);
+ exceptionToDisplay = exceptionToDisplay.Replace("\\X0009", "\'")
+ .Replace("\\X0022", "\"")
+ .Replace("\\X000A", "\n")
+ .Replace("\\X000D", "\r")
+ .Replace("\\X0009", "\t")
+ .Replace("\\X0020", " ");
+
+ return exceptionToDisplay;
+ }
+ }
+}

RENAME
WalletWasabi.Gui/CrashReporter/Helpers/StartCrashReporterHelper.cs
WalletWasabi.Gui/CrashReport/Helpers/StartCrashReporterHelper.cs
@@ -3,10 +3,10 @@ using Avalonia;
using System;
using WalletWasabi.Logging;
using Newtonsoft.Json;
-using WalletWasabi.Gui.CrashReporter.Models;
using System.Diagnostics;
+using WalletWasabi.Gui.CrashReport.Models;
-namespace WalletWasabi.Gui.CrashReporter.Helpers
+namespace WalletWasabi.Gui.CrashReport.Helpers
{
public static class StartCrashReporterHelper
{

RENAME
WalletWasabi.Gui/CrashReporter/Models/SerializedException.cs
WalletWasabi.Gui/CrashReport/Models/SerializedException.cs
+using Newtonsoft.Json;
using System;
+using System.Text;
-namespace WalletWasabi.Gui.CrashReporter.Models
+namespace WalletWasabi.Gui.CrashReport.Models
{
- [Serializable]
+ [JsonObject(MemberSerialization.OptIn)]
public class SerializedException
{
+ [JsonProperty(PropertyName = "ExceptionType")]
public string ExceptionType { get; set; }
+
+ [JsonProperty(PropertyName = "Message")]
public string Message { get; set; }
+
+ [JsonProperty(PropertyName = "StackTrace")]
public string StackTrace { get; set; }
- public SerializedException InnerException { get; set; }
- public SerializedException()
- {
- }
+ [JsonProperty(PropertyName = "InnerException")]
+ public SerializedException InnerException { get; set; }
public SerializedException(Exception ex)
{
@@ -31,7 +36,7 @@ namespace WalletWasabi.Gui.CrashReporter.Models
{
int tabLevel = 0;
- var sb = new System.Text.StringBuilder();
+ var sb = new StringBuilder();
if (!string.IsNullOrEmpty(ExceptionType))
{
@@ -68,10 +73,10 @@ namespace WalletWasabi.Gui.CrashReporter.Models
return sb.ToString();
}
- internal string MultiLineTabs(int n, string t, int addedPadding = 0)
+ private string MultiLineTabs(int n, string t, int addedPadding = 0)
{
int i = 0;
- var sb = new System.Text.StringBuilder();
+ var sb = new StringBuilder();
foreach (var line in t.Split(Environment.NewLine))
{
switch (i)
@@ -80,6 +85,7 @@ namespace WalletWasabi.Gui.CrashReporter.Models
sb.AppendLine(line);
i++;
break;
+
default:
var padding = new string(' ', addedPadding);
sb.AppendLine($"{padding}{Tabs(n)}{line}");
@@ -89,9 +95,14 @@ namespace WalletWasabi.Gui.CrashReporter.Models
return sb.ToString();
}
- internal string Tabs(int n)
+ private string Tabs(int n)
{
return n == 0 ? string.Empty : new string(' ', n * 4);
}
+
+ public static string ToCommandLineArgument(Exception serializedException)
+ {
+ return JsonConvert.SerializeObject(new SerializedException(serializedException));
+ }
}
}

RENAME
WalletWasabi.Gui/CrashReporter/ViewModels/CrashReportWindowViewModel.cs
WalletWasabi.Gui/CrashReport/ViewModels/CrashReportWindowViewModel.cs
using Splat;
-using WalletWasabi.Gui.CrashReporter.Models;
-namespace WalletWasabi.Gui.CrashReporter.ViewModels
+namespace WalletWasabi.Gui.CrashReport.ViewModels
{
public class CrashReportWindowViewModel
{
@@ -9,11 +8,11 @@ namespace WalletWasabi.Gui.CrashReporter.ViewModels
{
Global = Locator.Current.GetService<Global>();
}
+
public int WinWidth => 640;
public int WinHeight => 360;
public string Title => "Wasabi Wallet - Crash Reporting";
- public string ReportedException => Global.CrashReportException.ToString();
-
+ public string ReportedException => Global.CrashReporter.ToString();
public Global Global { get; }
}
}

RENAME
WalletWasabi.Gui/CrashReporter/Views/CrashReportWindow.xaml
WalletWasabi.Gui/CrashReport/Views/CrashReportWindow.xaml
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cont="clr-namespace:AvalonStudio.Shell.Controls;assembly=AvalonStudio.Shell"
xmlns:wasabi="clr-namespace:WalletWasabi.Gui.Controls;assembly=WalletWasabi.Gui"
- x:Class="WalletWasabi.Gui.CrashReporter.Views.CrashReportWindow"
+ x:Class="WalletWasabi.Gui.CrashReport.Views.CrashReportWindow"
Icon="avares://WalletWasabi.Gui/Assets/WasabiLogo256.png"
Title="{Binding Title}"
Width="{Binding WinWidth}" Height="{Binding WinHeight}"

RENAME
WalletWasabi.Gui/CrashReporter/Views/CrashReportWindow.xaml.cs
WalletWasabi.Gui/CrashReport/Views/CrashReportWindow.xaml.cs
@@ -5,7 +5,7 @@ using Avalonia.Markup.Xaml;
using AvalonStudio.Shell.Controls;
using Splat;
-namespace WalletWasabi.Gui.CrashReporter.Views
+namespace WalletWasabi.Gui.CrashReport.Views
{
public class CrashReportWindow : MetroWindow
{

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -27,9 +27,9 @@ using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.CoinJoin.Client;
using WalletWasabi.CoinJoin.Client.Clients;
using WalletWasabi.CoinJoin.Client.Clients.Queuing;
-using WalletWasabi.Gui.CrashReporter.Models;
using WalletWasabi.Gui.Helpers;
using WalletWasabi.Gui.Models;
+using WalletWasabi.Gui.Models.CrashReport;
using WalletWasabi.Gui.Rpc;
using WalletWasabi.Helpers;
using WalletWasabi.Hwi.Models;
@@ -110,8 +110,8 @@ namespace WalletWasabi.Gui
private bool InitializationStarted { get; set; } = false;
private CancellationTokenSource StoppingCts { get; }
- public SerializedException CrashReportException { get; set; }
- public int CrashReportStartAttempt { get; internal set; } = 0;
+
+ public CrashReporter CrashReporter { get; set; } = new CrashReporter();
public async Task InitializeNoWalletAsync()
{

MODIFY
WalletWasabi.Gui/Program.cs
WalletWasabi.Gui/Program.cs
@@ -9,8 +9,7 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using WalletWasabi.Gui.CommandLine;
-using WalletWasabi.Gui.CrashReporter.Helpers;
-using WalletWasabi.Gui.CrashReporter.Models;
+using WalletWasabi.Gui.CrashReport;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Logging;
@@ -37,20 +36,19 @@ namespace WalletWasabi.Gui
runGui = CommandInterpreter.ExecuteCommandsAsync(Global, args).GetAwaiter().GetResult();
- if (Global.CrashReportException != null)
+ if (Global.CrashReporter.IsReport)
{
StartCrashReporter(args);
- }
-
- if (!runGui)
- {
return;
}
+ if (runGui)
+ {
Logger.LogSoftwareStarted("Wasabi GUI");
BuildAvaloniaApp().StartShellApp("Wasabi Wallet", AppMainAsync, args);
}
+ }
catch (Exception ex)
{
Logger.LogCritical(ex);
@@ -88,10 +86,12 @@ namespace WalletWasabi.Gui
if (!(ex is OperationCanceledException))
{
Logger.LogCritical(ex);
- StartCrashReporterHelper.Start(ex, Global.CrashReportStartAttempt);
+ Global.CrashReporter.SetException(ex);
}
await Global.DisposeAsync().ConfigureAwait(false);
+
+ // There is no other way to stop the creation of the WasabiWindow.
Environment.Exit(1);
}
}
@@ -108,7 +108,7 @@ namespace WalletWasabi.Gui
private static void StartCrashReporter(string[] args)
{
- var result = AppBuilder.Configure<CrashReporter.CrashReportApp>();
+ var result = AppBuilder.Configure<CrashReportApp>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{

Refactor the logic