Skip to content

Commit

Permalink
[Core] Added Crash Reporter window when a fatal exception is thrown
Browse files Browse the repository at this point in the history
  • Loading branch information
BAndysc committed Dec 8, 2023
1 parent 0ab313b commit 8b93063
Show file tree
Hide file tree
Showing 37 changed files with 799 additions and 84 deletions.
Expand Up @@ -54,14 +54,15 @@ public bool UpdateCursor(Point point, bool leftPressed)
public abstract bool Draw(int rowIndex, int cellIndex, DrawingContext drawingContext, VirtualizedVeryFastTableView view, ref Rect rect);
public abstract void DrawHeader(int cellIndex, DrawingContext context, VirtualizedVeryFastTableView view, ref Rect rect);

protected void DrawText(DrawingContext context, Rect rect, string text, Color? color = null, float opacity = 0.4f)
protected void DrawText(DrawingContext context, Rect rect, string text, Color? color = null, float opacity = 0.4f, TextAlignment alignment = TextAlignment.Left)
{
var ft = new FormattedText
{
Text = text,
Constraint = new Size(rect.Width, rect.Height),
Typeface = new Typeface(MonoFont),//Typeface.Default,
FontSize = 12
FontSize = 12,
TextAlignment = alignment
};
var textColor = new SolidColorBrush(color ?? TextBrush.Color, opacity);
context.DrawText(textColor, new Point(rect.X, rect.Center.Y - ft.Bounds.Height / 2), ft);
Expand Down
16 changes: 16 additions & 0 deletions Modules/CrashReport/App.axaml
@@ -0,0 +1,16 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:crashReport="clr-namespace:CrashReport"
x:Class="CrashReport.App"
>
<!-- RequestedThemeVariant="Default" -->
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

<Application.DataTemplates>
<crashReport:AvaloniaViewLocator />
</Application.DataTemplates>

<Application.Styles>
<FluentTheme Mode="Dark" />
</Application.Styles>
</Application>
26 changes: 26 additions & 0 deletions Modules/CrashReport/App.axaml.cs
@@ -0,0 +1,26 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace CrashReport;

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow()
{
DataContext = new MainWindowViewModel()
};
}

base.OnFrameworkInitializationCompleted();
}
}
25 changes: 25 additions & 0 deletions Modules/CrashReport/ApplicationImpl.cs
@@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using WDE.Common.Managers;

namespace CrashReport;

public class ApplicationImpl : IApplication
{
public async Task<bool> CanClose()
{
return true;
}

public async Task<bool> TryClose()
{
ForceClose();
return true;
}

public void ForceClose()
{
((IClassicDesktopStyleApplicationLifetime?)Application.Current?.ApplicationLifetime)?.Shutdown();
}
}
10 changes: 10 additions & 0 deletions Modules/CrashReport/ApplicationReleaseConfiguration.cs
@@ -0,0 +1,10 @@
using WDE.Common.Services;

namespace CrashReport;

public class ApplicationReleaseConfiguration : BaseApplicationReleaseConfiguration
{
public ApplicationReleaseConfiguration(IFileSystem fileSystem) : base(fileSystem)
{
}
}
29 changes: 29 additions & 0 deletions Modules/CrashReport/ApplicationVersion.cs
@@ -0,0 +1,29 @@
using WDE.Common.Services;

namespace CrashReport;

public class ApplicationVersion : IApplicationVersion
{
public ApplicationVersion(IApplicationReleaseConfiguration appData)
{
var branch = appData.GetString("BRANCH");
var commit = appData.GetString("COMMIT");
var version = appData.GetInt("BUILD_VERSION");
var marketplace = appData.GetString("MARKETPLACE");
var platform = appData.GetString("PLATFORM");

VersionKnown = branch != null && commit != null && version != null;
Branch = branch ?? "<no build data>";
CommitHash = commit ?? "<no build data>";
Marketplace = marketplace ?? "<no build data>";
Platform = platform ?? "<no build data>";
BuildVersion = version ?? 0;
}

public int BuildVersion { get; }
public string Branch { get; }
public string CommitHash { get; }
public string Marketplace { get; }
public string Platform { get; }
public bool VersionKnown { get; }
}
30 changes: 30 additions & 0 deletions Modules/CrashReport/AvaloniaViewLocator.cs
@@ -0,0 +1,30 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;

namespace CrashReport;

public class AvaloniaViewLocator : IDataTemplate
{
public IControl Build(object? param)
{
if (param == null)
return new TextBlock(){Text = "Null model"};

var typeName = param.GetType().FullName!;
var viewName = typeName.Replace("ViewModel", "View");
var viewType = Type.GetType(viewName);
if (viewType != null)
{
var control = Activator.CreateInstance(viewType) as Control;
control!.DataContext = param;
return control;
}
return new TextBlock(){Text = "Cannot resolve view for " + param.GetType()};
}

public bool Match(object? data)
{
return data is not IControl && data != null && data is not string;
}
}
27 changes: 27 additions & 0 deletions Modules/CrashReport/CrashReport.csproj
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<ApplicationIcon>Icon.ico</ApplicationIcon>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<PropertyGroup>
<SelfContained>false</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<OutputPath>..\..\bin\$(Configuration)\</OutputPath>
</PropertyGroup>
<Import Project="..\..\Avalonia.props" />
<ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Icon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\WDE.Updater\WDE.Updater.csproj" />
</ItemGroup>
</Project>
31 changes: 31 additions & 0 deletions Modules/CrashReport/CrashReportView.axaml
@@ -0,0 +1,31 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:crashReport="clr-namespace:CrashReport"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="crashReport:CrashReportViewModel"
x:Class="CrashReport.CrashReportView">
<UserControl.Styles>
<Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"></Setter>
</Style>
</UserControl.Styles>
<DockPanel Margin="5">

<DockPanel DockPanel.Dock="Top" Margin="0,5,0,5" >
<Image Source="Icon.png" Width="32" Height="32" />
<TextBlock FontWeight="Bold" FontSize="16" DockPanel.Dock="Top">The editor has crashed 😭</TextBlock>
</DockPanel>

<TextBlock Margin="0,5,0,5" DockPanel.Dock="Top">I am very sorry, but due to some internal problem, the editor has crashed. This report will help me prevent it in the future, so if it happens again, please report the error on github.</TextBlock>

<TextBlock Margin="0,5,0,5" FontWeight="Bold" DockPanel.Dock="Top">Crash report:</TextBlock>


<TextBox IsReadOnly="False" Text="{CompiledBinding CrashReport}">

</TextBox>

</DockPanel>
</UserControl>
18 changes: 18 additions & 0 deletions Modules/CrashReport/CrashReportView.axaml.cs
@@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace CrashReport;

public partial class CrashReportView : UserControl
{
public CrashReportView()
{
InitializeComponent();
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
25 changes: 25 additions & 0 deletions Modules/CrashReport/CrashReportViewModel.cs
@@ -0,0 +1,25 @@
using System;
using System.IO;

namespace CrashReport;

public class CrashReportViewModel
{
public string CrashReport { get; }

private static string APPLICATION_FOLDER = "WoWDatabaseEditor";
private static string LOG_FILE = "WDE.log.txt";
private static string LOG_FILE_OLD = "WDE.log.old.txt";

private static string WDE_DATA_FOLDER => Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPLICATION_FOLDER);
private static string FATAL_LOG_FILE => Path.Join(WDE_DATA_FOLDER, LOG_FILE);
private static string FATAL_LOG_FILE_OLD => Path.Join(WDE_DATA_FOLDER, LOG_FILE_OLD);

public CrashReportViewModel()
{
if (File.Exists(FATAL_LOG_FILE))
CrashReport = File.ReadAllText(FATAL_LOG_FILE);
else
CrashReport = "(crash log not found :/)";
}
}
24 changes: 24 additions & 0 deletions Modules/CrashReport/FileSystem.cs
@@ -0,0 +1,24 @@
using System;
using System.IO;
using WDE.Common.Services;

namespace CrashReport;

public class FileSystem : IFileSystem
{
public bool Exists(string path) => ResolvePhysicalPath(path).Exists;
public string ReadAllText(string path) => File.ReadAllText(ResolvePhysicalPath(path).FullName);
public void WriteAllText(string path, string text) => File.WriteAllText(ResolvePhysicalPath(path).FullName, text);
public Stream OpenRead(string path) => File.OpenRead(ResolvePhysicalPath(path).FullName);
public Stream OpenWrite(string path) => File.OpenWrite(ResolvePhysicalPath(path).FullName);
public FileInfo ResolvePhysicalPath(string path)
{
if (path.StartsWith("~"))
{
var localDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var dir = Path.Join(localDataPath, "WoWDatabaseEditor");
return new FileInfo(Path.Join(dir, path.Substring(1)));
}
return new FileInfo(path);
}
}
24 changes: 24 additions & 0 deletions Modules/CrashReport/HttpClientFactory.cs
@@ -0,0 +1,24 @@
using System;
using System.Net.Http;
using WDE.Common.Factories;
using WDE.Common.Services;

namespace CrashReport;

public class HttpClientFactory : IHttpClientFactory
{
private readonly ApplicationVersion version;

public HttpClientFactory(ApplicationVersion version)
{
this.version = version;
}

public HttpClient Factory()
{
var client = new HttpClient();
var os = $"{Environment.OSVersion.Platform} {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
client.DefaultRequestHeaders.Add("User-Agent", $"WoWDatabaseEditor/{version.BuildVersion} CrashReporter (branch: {version.Branch}, marketplace: {version.Marketplace}, platform: {version.Platform}, OS: {os})");
return client;
}
}
Binary file added Modules/CrashReport/Icon.ico
Binary file not shown.
Binary file added Modules/CrashReport/Icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions Modules/CrashReport/MainWindow.axaml
@@ -0,0 +1,32 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:crashReport="clr-namespace:CrashReport"
mc:Ignorable="d" Width="600" Height="500"
x:Class="CrashReport.MainWindow"
x:DataType="crashReport:MainWindowViewModel"
ShowActivated="True"
Icon="../Icon.png"
Title="WoW Database Editor Crash Report">

<DockPanel LastChildFill="True">

<StackPanel HorizontalAlignment="Right" Margin="5,5,5,5" Orientation="Horizontal" DockPanel.Dock="Bottom" Spacing="10">
<Button Command="{CompiledBinding RestartEditorCommand}" Content="Restart the editor">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Command="{CompiledBinding RestartEditorWithConsoleCommand}" Header="Restart the editor with console"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
</Button>
<Button Command="{CompiledBinding DownloadEditorCommand}" IsEnabled="{CompiledBinding !IsDownloading}">Download the latest version</Button>
</StackPanel>

<ProgressBar DockPanel.Dock="Bottom" Margin="20,5" ShowProgressText="True" IsVisible="{CompiledBinding IsDownloading}" Value="{CompiledBinding CurrentProgress}" Maximum="{CompiledBinding MaxProgress}">
</ProgressBar>

<ContentControl Content="{CompiledBinding MainContent}" />

</DockPanel>
</Window>
17 changes: 17 additions & 0 deletions Modules/CrashReport/MainWindow.axaml.cs
@@ -0,0 +1,17 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace CrashReport;

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

0 comments on commit 8b93063

Please sign in to comment.