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.