Skip to content

Commit

Permalink
Merge pull request #31 from lars-berger/feat/improve-user-config-erro…
Browse files Browse the repository at this point in the history
…r-handling

feat: improve user config error handling
  • Loading branch information
lars-berger authored Apr 12, 2022
2 parents 5e2aa00 + c1def0b commit c1fffe7
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 82 deletions.
25 changes: 13 additions & 12 deletions GlazeWM.Bootstrapper/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using static GlazeWM.Infrastructure.WindowsApi.WindowsApiService;

namespace GlazeWM.Bootstrapper
{
Expand All @@ -33,21 +32,23 @@ static void Main()
return;
}

// Set the process-default DPI awareness.
SetProcessDpiAwarenessContext(DpiAwarenessContext.Context_PerMonitorAwareV2);

var serviceCollection = new ServiceCollection();
serviceCollection.AddInfrastructureServices();
serviceCollection.AddDomainServices();
serviceCollection.AddBarServices();
serviceCollection.AddSingleton<Startup>();

ServiceLocator.Provider = serviceCollection.BuildServiceProvider();
ServiceLocator.Provider = BuildServiceProvider();

var startup = ServiceLocator.Provider.GetRequiredService<Startup>();
startup.Init();
startup.Run();
Application.Run();
}
}

private static ServiceProvider BuildServiceProvider()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddInfrastructureServices();
serviceCollection.AddDomainServices();
serviceCollection.AddBarServices();
serviceCollection.AddSingleton<Startup>();

return serviceCollection.BuildServiceProvider();
}
}
}
50 changes: 34 additions & 16 deletions GlazeWM.Bootstrapper/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using GlazeWM.Infrastructure.WindowsApi;
using GlazeWM.Infrastructure.WindowsApi.Events;
using System;
using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Forms;
Expand Down Expand Up @@ -55,29 +56,46 @@ SystemEventService systemEventService
_systemEventService = systemEventService;
}

public void Init()
public void Run()
{
// Launch bar WPF application. Spawns bar window when monitors are added, so the service needs
// to be initialized before populating initial state.
_barService.StartApp();
try
{
// Set the process-default DPI awareness.
SetProcessDpiAwarenessContext(DpiAwarenessContext.Context_PerMonitorAwareV2);

// Launch bar WPF application. Spawns bar window when monitors are added, so the service needs
// to be initialized before populating initial state.
_barService.StartApp();

// Populate initial monitors, windows, workspaces and user config.
PopulateInitialState();
// Populate initial monitors, windows, workspaces and user config.
PopulateInitialState();

// Listen on registered keybindings.
_keybindingService.Start();
// Listen on registered keybindings.
_keybindingService.Start();

// Listen for window events (eg. close, focus).
_windowEventService.Start();
// Listen for window events (eg. close, focus).
_windowEventService.Start();

// Listen for system-related events (eg. changes to display settings).
_systemEventService.Start();
// Listen for system-related events (eg. changes to display settings).
_systemEventService.Start();

// Add application to system tray.
_systemTrayService.AddToSystemTray();
// Add application to system tray.
_systemTrayService.AddToSystemTray();

_bus.Events.Where(@event => @event is ApplicationExitingEvent)
.Subscribe((@event) => OnApplicationExit());
_bus.Events.Where(@event => @event is ApplicationExitingEvent)
.Subscribe((@event) => OnApplicationExit());
}
catch (Exception error)
{
// Alert the user of the error.
// TODO: This throws duplicate errors if a command errors and it was invoked by another
// handler.
if (error is FatalUserException)
MessageBox.Show(error.Message);

File.AppendAllText("./errors.log", $"\n\n{error.Message + error.StackTrace}");
throw error;
}
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion GlazeWM.Domain/UserConfigs/BarComponentConfigConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
{
"workspaces" => new WorkspacesComponentConfig(),
"clock" => new ClockComponentConfig(),
_ => throw new ArgumentException(),
_ => throw new ArgumentException($"Invalid workspace type '{type}'."),
};

serializer.Populate(jObject.CreateReader(), target);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using GlazeWM.Domain.UserConfigs.Commands;
using GlazeWM.Domain.Workspaces.Commands;
using GlazeWM.Infrastructure.Bussing;
using GlazeWM.Infrastructure.WindowsApi;
using GlazeWM.Infrastructure.Yaml;
using Newtonsoft.Json;
using System;
using System.IO;
using System.Reflection;
Expand Down Expand Up @@ -74,13 +74,15 @@ private void InitializeSampleUserConfig(string userConfigPath)
var assembly = Assembly.GetEntryAssembly();
var sampleConfigResourceName = "GlazeWM.Bootstrapper.sample-config.yaml";

// Create containing directory. Needs to be created before writing to the file.
Directory.CreateDirectory(Path.GetDirectoryName(userConfigPath));

// Get the embedded sample user config from the entry assembly.
using (Stream stream = assembly.GetManifestResourceStream(sampleConfigResourceName))
{
// Write the sample user config to the appropriate destination.
using (var fileStream = new FileStream(userConfigPath, FileMode.Create, FileAccess.Write))
{
Directory.CreateDirectory(Path.GetDirectoryName(userConfigPath));
stream.CopyTo(fileStream);
}
}
Expand All @@ -96,19 +98,21 @@ private UserConfig DeserializeUserConfig(string userConfigPath)

private string FormatErrorMessage(Exception exception)
{
var errorMessage = exception.Message;
var errorMessage = "Failed to parse user config. ";

if (exception.InnerException?.Message != null)
{
var unknownPropertyRegex = new Regex(@"Property '(?<property>.*?)' not found on type");
var match = unknownPropertyRegex.Match(exception.InnerException.Message);

// Improve error message shown in case of unknown property error.
if (match.Success)
errorMessage = $"Unknown property in config: {match.Groups["property"]}.";
else
errorMessage += $". {exception.InnerException.Message}";
}
var unknownPropertyRegex = new Regex(@"Could not find member '(?<property>.*?)' on object");
var unknownPropertyMatch = unknownPropertyRegex.Match(exception.Message);

// Improve error message in case of unknown property errors.
if (unknownPropertyMatch.Success)
errorMessage += $"Unknown property: '{unknownPropertyMatch.Groups["property"]}'.";

// Improve error message of generic deserialization errors.
else if (exception is JsonReaderException)
errorMessage += $"Invalid value at property: '{(exception as JsonReaderException).Path}'.";

else
errorMessage += exception.Message;

return errorMessage;
}
Expand Down
54 changes: 15 additions & 39 deletions GlazeWM.Infrastructure/Bussing/Bus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reactive.Subjects;
using System.Windows;

namespace GlazeWM.Infrastructure.Bussing
{
Expand All @@ -21,28 +19,17 @@ public sealed class Bus
/// </summary>
public CommandResponse Invoke<T>(T command) where T : Command
{
Debug.WriteLine($"Command {command.Name} invoked.");

// Create a `Type` object representing the constructed `ICommandHandler` generic.
var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());

Debug.WriteLine($"Command {command.Name} invoked.");
var handlerInstance = ServiceLocator.Provider.GetRequiredService(handlerType)
as ICommandHandler<T>;

try
lock (_lockObj)
{
var handlerInstance = ServiceLocator.Provider.GetRequiredService(handlerType) as ICommandHandler<T>;
lock (_lockObj)
{
return handlerInstance.Handle(command);
}
}
catch (Exception error)
{
// Alert the user of the error.
// TODO: This throws duplicate errors if a command errors and it was invoked by another handler.
if (error is FatalUserException)
MessageBox.Show(error.Message);

File.AppendAllText("./errors.log", $"\n\n{error.Message + error.StackTrace}");
throw error;
return handlerInstance.Handle(command);
}
}

Expand All @@ -51,35 +38,24 @@ public CommandResponse Invoke<T>(T command) where T : Command
/// </summary>
public void RaiseEvent<T>(T @event) where T : Event
{
Debug.WriteLine($"Event {@event.Name} emitted.");

// Create a `Type` object representing the constructed `IEventHandler` generic.
var handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());

Debug.WriteLine($"Event {@event.Name} emitted.");
var handlerInstances = ServiceLocator.Provider.GetServices(handlerType)
as IEnumerable<IEventHandler<T>>;

try
foreach (var handler in handlerInstances)
{
var handlerInstances = ServiceLocator.Provider.GetServices(handlerType) as IEnumerable<IEventHandler<T>>;

foreach (var handler in handlerInstances)
lock (_lockObj)
{
lock (_lockObj)
{
handler.Handle(@event);
}
handler.Handle(@event);
}

// Emit event through subject.
Events.OnNext(@event);
}
catch (Exception error)
{
// Alert the user of the error.
if (error is FatalUserException)
MessageBox.Show(error.Message);

File.AppendAllText("./errors.log", $"\n\n{error.Message + error.StackTrace}");
throw error;
}
// Emit event through subject.
Events.OnNext(@event);
}
}
}
1 change: 1 addition & 0 deletions GlazeWM.Infrastructure/Yaml/YamlDeserializationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class YamlDeserializationService

JsonSerializerSettings _jsonDeserializerSettings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
Expand Down

0 comments on commit c1fffe7

Please sign in to comment.