From 6fc3eba49871c11144a46b8147b3f178f00267d3 Mon Sep 17 00:00:00 2001 From: Toastie Date: Tue, 7 Jan 2025 18:55:57 +1300 Subject: [PATCH] Fixed logs not being written when the bot view is not active --- EllieHub/Common/AppStatics.cs | 24 +++++++++--------- EllieHub/Common/Utilities.cs | 24 +++++++++--------- .../Services/Mocks/MockAppConfigManager.cs | 2 +- .../BotConfig/Models/BotExitEventArgs.cs | 9 ++++++- .../BotConfig/Services/EllieOrchestrator.cs | 25 ++++++++++++++----- .../ViewModels/BotConfigViewModel.cs | 15 ++++------- 6 files changed, 57 insertions(+), 42 deletions(-) diff --git a/EllieHub/Common/AppStatics.cs b/EllieHub/Common/AppStatics.cs index 5922cfb..6ec1392 100644 --- a/EllieHub/Common/AppStatics.cs +++ b/EllieHub/Common/AppStatics.cs @@ -15,35 +15,35 @@ public static partial class AppStatics /// Defines the default location where the updater configuration and bot instances are stored. /// #if DEBUG - public static string AppDefaultConfigDirectoryUri { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "EllieHubDebug"); + public static string AppDefaultConfigDirectoryUri { get; } = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "EllieHubDebug"); #else - public static string AppDefaultConfigDirectoryUri { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "EllieHub"); + public static string AppDefaultConfigDirectoryUri { get; } = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "EllieHub"); #endif /// /// Defines the default location where the bot instances are stored. /// - public static string AppDefaultBotDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Bots"); + public static string AppDefaultBotDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Bots"); /// /// Defines the default location where the backups of bot instances are stored. /// - public static string AppDefaultBotBackupDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Backups"); + public static string AppDefaultBotBackupDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Backups"); /// /// Defines the default location where the logs of bot instances are stored. /// - public static string AppDefaultLogDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Logs"); + public static string AppDefaultLogDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Logs"); /// /// Defines the location of the application's configuration file. /// - public static string AppConfigUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "config.json"); + public static string AppConfigUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "config.json"); /// /// Defines the location of the application's dependencies. /// - public static string AppDepsUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Dependencies"); + public static string AppDepsUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Dependencies"); /// /// Defines a transparent color brush. @@ -56,11 +56,11 @@ public static partial class AppStatics public static FilePickerOpenOptions ImageFilePickerOptions { get; } = new() { AllowMultiple = false, - FileTypeFilter = new FilePickerFileType[] - { - new("Image") { Patterns = new[] { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp" } }, - new("All") { Patterns = new[] { "*.*" } } - } + FileTypeFilter = + [ + new("Image") { Patterns = ["*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp"]}, + new("All") { Patterns = ["*.*"]} + ] }; /// diff --git a/EllieHub/Common/Utilities.cs b/EllieHub/Common/Utilities.cs index e79ba96..8037abf 100644 --- a/EllieHub/Common/Utilities.cs +++ b/EllieHub/Common/Utilities.cs @@ -11,29 +11,29 @@ namespace EllieHub.Common; internal static class Utilities { /// - /// Loads an image embeded with this application. + /// Loads an image embedded with this application. /// /// An uri that starts with "avares://" /// Valid uris must start with "avares://". - /// The embeded image or the default bot avatar placeholder. - /// Occurs when the embeded resource does not exist. - public static SKBitmap LoadEmbededImage(string? uri = default) + /// The embedded image or the default bot avatar placeholder. + /// Occurs when the embedded resource does not exist. + public static SKBitmap LoadEmbeddedImage(string? uri = default) { return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal)) - ? SKBitmap.Decode(AssetLoader.Open(new Uri(AppConstants.BotAvatarUri))) - : SKBitmap.Decode(AssetLoader.Open(new Uri(uri))); + ? SKBitmap.Decode(AssetLoader.Open(new(AppConstants.BotAvatarUri))) + : SKBitmap.Decode(AssetLoader.Open(new(uri))); } /// /// Loads the image at the specified location or the bot avatar placeholder if it was not found. /// - /// The absolute path to the image file or to get the avatar placeholder. - /// This fallsback to if doesn't point to a valid image file. + /// The absolute path to the image file or to get the avatar placeholder. + /// This fallsback to if doesn't point to a valid image file. /// The requested image or the default bot avatar placeholder. - public static SKBitmap LoadLocalImage(string? uri = default) + public static SKBitmap LoadLocalImage(string? imagePath) { - return (File.Exists(uri)) - ? SKBitmap.Decode(uri) - : LoadEmbededImage(uri); + return (File.Exists(imagePath)) + ? SKBitmap.Decode(imagePath) + : LoadEmbeddedImage(imagePath); } } \ No newline at end of file diff --git a/EllieHub/Features/AppConfig/Services/Mocks/MockAppConfigManager.cs b/EllieHub/Features/AppConfig/Services/Mocks/MockAppConfigManager.cs index f588916..14f692f 100644 --- a/EllieHub/Features/AppConfig/Services/Mocks/MockAppConfigManager.cs +++ b/EllieHub/Features/AppConfig/Services/Mocks/MockAppConfigManager.cs @@ -10,7 +10,7 @@ namespace EllieHub.Features.AppConfig.Services.Mocks; internal sealed class MockAppConfigManager : IAppConfigManager { /// - public ReadOnlyAppSettings AppConfig { get; } = new(new() { BotEntries = new() { [Guid.Empty] = new("MockBot", Path.Combine(AppStatics.AppDefaultBotDirectoryUri, "MockBot"), 0) } }); + public ReadOnlyAppSettings AppConfig { get; } = new(new() { BotEntries = new() { [Guid.Empty] = new("MockBot", Path.Join(AppStatics.AppDefaultBotDirectoryUri, "MockBot"), 0) } }); /// public ValueTask CreateBotEntryAsync(CancellationToken cToken = default) diff --git a/EllieHub/Features/BotConfig/Models/BotExitEventArgs.cs b/EllieHub/Features/BotConfig/Models/BotExitEventArgs.cs index e7f99bf..aefee70 100644 --- a/EllieHub/Features/BotConfig/Models/BotExitEventArgs.cs +++ b/EllieHub/Features/BotConfig/Models/BotExitEventArgs.cs @@ -15,14 +15,21 @@ public sealed class BotExitEventArgs : EventArgs /// public int ExitCode { get; } + /// + /// The exit message. + /// + public string Message { get; } + /// /// Creates the event arguments when a bot process exits. /// /// The bot's Id. /// The exit code. - public BotExitEventArgs(Guid botId, int exitCode) + /// The message for the bot process that just exited. + public BotExitEventArgs(Guid botId, int exitCode, string message) { Id = botId; ExitCode = exitCode; + Message = message; } } \ No newline at end of file diff --git a/EllieHub/Features/BotConfig/Services/EllieOrchestrator.cs b/EllieHub/Features/BotConfig/Services/EllieOrchestrator.cs index a03ce9f..30d6e9f 100644 --- a/EllieHub/Features/BotConfig/Services/EllieOrchestrator.cs +++ b/EllieHub/Features/BotConfig/Services/EllieOrchestrator.cs @@ -10,8 +10,9 @@ namespace EllieHub.Features.BotConfig.Services; /// public sealed class EllieOrchestrator : IBotOrchestrator { - private readonly Dictionary _runningBots = new(); private readonly ReadOnlyAppSettings _appConfig; + private readonly ILogWriter _logWriter; + private readonly Dictionary _runningBots = new(); private readonly string _fileName = OperatingSystem.IsWindows() ? "EllieBot.exe" : "EllieBot"; /// @@ -27,8 +28,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator /// Creates an object that coordinates multiple running processes of EllieBot. /// /// The application settings. - public EllieOrchestrator(ReadOnlyAppSettings appConfig) - => _appConfig = appConfig; + /// The service that writes bot logs to disk. + public EllieOrchestrator(ReadOnlyAppSettings appConfig, ILogWriter logWriter) + { + _appConfig = appConfig; + _logWriter = logWriter; + } /// public bool IsBotRunning(Guid botId) @@ -39,12 +44,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator { if (_runningBots.ContainsKey(botId) || !_appConfig.BotEntries.TryGetValue(botId, out var botEntry) - || !File.Exists(Path.Combine(botEntry.InstanceDirectoryUri, _fileName))) + || !File.Exists(Path.Join(botEntry.InstanceDirectoryUri, _fileName))) return false; var botProcess = Process.Start(new ProcessStartInfo() { - FileName = Path.Combine(botEntry.InstanceDirectoryUri, _fileName), + FileName = Path.Join(botEntry.InstanceDirectoryUri, _fileName), WorkingDirectory = botEntry.InstanceDirectoryUri, UseShellExecute = false, CreateNoWindow = true, @@ -80,6 +85,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator { var amount = _runningBots.Count; + // ReSharper disable once EmptyGeneralCatchClause foreach (var process in _runningBots.Values) try { process.Kill(true); } catch { } @@ -95,7 +101,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator private void OnExit(object? sender, EventArgs eventArgs) { var (id, process) = _runningBots.First(x => x.Value.Equals(sender)); - OnBotExit?.Invoke(this, new(id, process.ExitCode)); + var message = Environment.NewLine + + $"{_appConfig.BotEntries[id].Name} stopped. Status code: {process.ExitCode}" + + Environment.NewLine; + + _logWriter.TryAdd(id, message); + OnBotExit?.Invoke(this, new(id, process.ExitCode, message)); _runningBots.Remove(id); process.CancelOutputRead(); @@ -116,6 +127,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator var (id, _) = _runningBots.First(x => x.Value.Equals(sender)); var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data); + _logWriter.TryAdd(id, eventArgs.Data); OnStdout?.Invoke(this, newEventArgs); } @@ -132,6 +144,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator var (id, _) = _runningBots.First(x => x.Value.Equals(sender)); var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data); + _logWriter.TryAdd(id, eventArgs.Data); OnStderr?.Invoke(this, newEventArgs); } } \ No newline at end of file diff --git a/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs b/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs index 32baf83..6806d20 100644 --- a/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs +++ b/EllieHub/Features/BotConfig/ViewModels/BotConfigViewModel.cs @@ -103,7 +103,7 @@ public class BotConfigViewModel : ViewModelBase, IDisposable { var sanitizedValue = value.ReplaceLineEndings(string.Empty); - DirectoryHint = $"Select the absolute path to the bot directory. For example: {Path.Combine(_appConfigManager.AppConfig.BotsDirectoryUri, sanitizedValue)}"; + DirectoryHint = $"Select the absolute path to the bot directory. For example: {Path.Join(_appConfigManager.AppConfig.BotsDirectoryUri, sanitizedValue)}"; this.RaiseAndSetIfChanged(ref _botName, sanitizedValue); } } @@ -180,7 +180,7 @@ public class BotConfigViewModel : ViewModelBase, IDisposable if (IsBotRunning) EnableButtons(true, false); else - EnableButtons(!File.Exists(Path.Combine(botEntry.InstanceDirectoryUri, Resolver.FileName)), true); + EnableButtons(!File.Exists(Path.Join(botEntry.InstanceDirectoryUri, Resolver.FileName)), true); // Dispose when the view is deactivated this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables)); @@ -245,7 +245,7 @@ public class BotConfigViewModel : ViewModelBase, IDisposable { _ = LoadUpdateBarAsync(Resolver, UpdateBar); - if (!wereButtonsUnlocked && File.Exists(Path.Combine(BotDirectoryUriBar.CurrentUri, Resolver.FileName))) + if (!wereButtonsUnlocked && File.Exists(Path.Join(BotDirectoryUriBar.CurrentUri, Resolver.FileName))) EnableButtons(false, true); else EnableButtons(!wereButtonsUnlocked, true); @@ -466,8 +466,6 @@ public class BotConfigViewModel : ViewModelBase, IDisposable if (eventArgs.Id != Resolver.Id) return; - _logWriter.TryAdd(eventArgs.Id, eventArgs.Output); - FakeConsole.Content = FakeConsole.Content.Length > 100_000 ? FakeConsole.Content[FakeConsole.Content.IndexOf(Environment.NewLine, 60_000, StringComparison.Ordinal)..] + eventArgs.Output + Environment.NewLine : FakeConsole.Content + eventArgs.Output + Environment.NewLine; @@ -483,14 +481,11 @@ public class BotConfigViewModel : ViewModelBase, IDisposable if (eventArgs.Id != Resolver.Id) return; - var message = Environment.NewLine + ActualBotName + " stopped." + Environment.NewLine; - - _logWriter.TryAdd(Resolver.Id, message); - FakeConsole.Content += message; + FakeConsole.Content += eventArgs.Message; } /// - /// Reenables the buttons when the bot instance associated with this view-model exits. + /// Re-enables the buttons when the bot instance associated with this view-model exits. /// /// The bot orchestrator. /// The event arguments.