Fixed logs not being written when the bot view is not active
All checks were successful
EllieBotDevs/EllieHub/pipeline/head This commit looks good
All checks were successful
EllieBotDevs/EllieHub/pipeline/head This commit looks good
This commit is contained in:
parent
2a8cd606c5
commit
6fc3eba498
6 changed files with 57 additions and 42 deletions
|
@ -15,35 +15,35 @@ public static partial class AppStatics
|
||||||
/// Defines the default location where the updater configuration and bot instances are stored.
|
/// Defines the default location where the updater configuration and bot instances are stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#if DEBUG
|
#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
|
#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
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the default location where the bot instances are stored.
|
/// Defines the default location where the bot instances are stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AppDefaultBotDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Bots");
|
public static string AppDefaultBotDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Bots");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the default location where the backups of bot instances are stored.
|
/// Defines the default location where the backups of bot instances are stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AppDefaultBotBackupDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Backups");
|
public static string AppDefaultBotBackupDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Backups");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the default location where the logs of bot instances are stored.
|
/// Defines the default location where the logs of bot instances are stored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AppDefaultLogDirectoryUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Logs");
|
public static string AppDefaultLogDirectoryUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Logs");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the location of the application's configuration file.
|
/// Defines the location of the application's configuration file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AppConfigUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "config.json");
|
public static string AppConfigUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "config.json");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the location of the application's dependencies.
|
/// Defines the location of the application's dependencies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AppDepsUri { get; } = Path.Combine(AppDefaultConfigDirectoryUri, "Dependencies");
|
public static string AppDepsUri { get; } = Path.Join(AppDefaultConfigDirectoryUri, "Dependencies");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a transparent color brush.
|
/// Defines a transparent color brush.
|
||||||
|
@ -56,11 +56,11 @@ public static partial class AppStatics
|
||||||
public static FilePickerOpenOptions ImageFilePickerOptions { get; } = new()
|
public static FilePickerOpenOptions ImageFilePickerOptions { get; } = new()
|
||||||
{
|
{
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
FileTypeFilter = new FilePickerFileType[]
|
FileTypeFilter =
|
||||||
{
|
[
|
||||||
new("Image") { Patterns = new[] { "*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp" } },
|
new("Image") { Patterns = ["*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp"]},
|
||||||
new("All") { Patterns = new[] { "*.*" } }
|
new("All") { Patterns = ["*.*"]}
|
||||||
}
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -11,29 +11,29 @@ namespace EllieHub.Common;
|
||||||
internal static class Utilities
|
internal static class Utilities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an image embeded with this application.
|
/// Loads an image embedded with this application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">An uri that starts with "avares://"</param>
|
/// <param name="uri">An uri that starts with "avares://"</param>
|
||||||
/// <remarks>Valid uris must start with "avares://".</remarks>
|
/// <remarks>Valid uris must start with "avares://".</remarks>
|
||||||
/// <returns>The embeded image or the default bot avatar placeholder.</returns>
|
/// <returns>The embedded image or the default bot avatar placeholder.</returns>
|
||||||
/// <exception cref="FileNotFoundException">Occurs when the embeded resource does not exist.</exception>
|
/// <exception cref="FileNotFoundException">Occurs when the embedded resource does not exist.</exception>
|
||||||
public static SKBitmap LoadEmbededImage(string? uri = default)
|
public static SKBitmap LoadEmbeddedImage(string? uri = default)
|
||||||
{
|
{
|
||||||
return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal))
|
return (string.IsNullOrWhiteSpace(uri) || !uri.StartsWith("avares://", StringComparison.Ordinal))
|
||||||
? SKBitmap.Decode(AssetLoader.Open(new Uri(AppConstants.BotAvatarUri)))
|
? SKBitmap.Decode(AssetLoader.Open(new(AppConstants.BotAvatarUri)))
|
||||||
: SKBitmap.Decode(AssetLoader.Open(new Uri(uri)));
|
: SKBitmap.Decode(AssetLoader.Open(new(uri)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the image at the specified location or the bot avatar placeholder if it was not found.
|
/// Loads the image at the specified location or the bot avatar placeholder if it was not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
|
/// <param name="imagePath">The absolute path to the image file or <see langword="null"/> to get the avatar placeholder.</param>
|
||||||
/// <remarks>This fallsback to <see cref="LoadEmbededImage(string?)"/> if <paramref name="uri"/> doesn't point to a valid image file.</remarks>
|
/// <remarks>This fallsback to <see cref="LoadEmbeddedImage(string?)"/> if <paramref name="imagePath"/> doesn't point to a valid image file.</remarks>
|
||||||
/// <returns>The requested image or the default bot avatar placeholder.</returns>
|
/// <returns>The requested image or the default bot avatar placeholder.</returns>
|
||||||
public static SKBitmap LoadLocalImage(string? uri = default)
|
public static SKBitmap LoadLocalImage(string? imagePath)
|
||||||
{
|
{
|
||||||
return (File.Exists(uri))
|
return (File.Exists(imagePath))
|
||||||
? SKBitmap.Decode(uri)
|
? SKBitmap.Decode(imagePath)
|
||||||
: LoadEmbededImage(uri);
|
: LoadEmbeddedImage(imagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@ namespace EllieHub.Features.AppConfig.Services.Mocks;
|
||||||
internal sealed class MockAppConfigManager : IAppConfigManager
|
internal sealed class MockAppConfigManager : IAppConfigManager
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
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) } });
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ValueTask<BotEntry> CreateBotEntryAsync(CancellationToken cToken = default)
|
public ValueTask<BotEntry> CreateBotEntryAsync(CancellationToken cToken = default)
|
||||||
|
|
|
@ -15,14 +15,21 @@ public sealed class BotExitEventArgs : EventArgs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ExitCode { get; }
|
public int ExitCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The exit message.
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the event arguments when a bot process exits.
|
/// Creates the event arguments when a bot process exits.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="botId">The bot's Id.</param>
|
/// <param name="botId">The bot's Id.</param>
|
||||||
/// <param name="exitCode">The exit code.</param>
|
/// <param name="exitCode">The exit code.</param>
|
||||||
public BotExitEventArgs(Guid botId, int exitCode)
|
/// <param name="message">The message for the bot process that just exited.</param>
|
||||||
|
public BotExitEventArgs(Guid botId, int exitCode, string message)
|
||||||
{
|
{
|
||||||
Id = botId;
|
Id = botId;
|
||||||
ExitCode = exitCode;
|
ExitCode = exitCode;
|
||||||
|
Message = message;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,8 +10,9 @@ namespace EllieHub.Features.BotConfig.Services;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EllieOrchestrator : IBotOrchestrator
|
public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
{
|
{
|
||||||
private readonly Dictionary<Guid, Process> _runningBots = new();
|
|
||||||
private readonly ReadOnlyAppSettings _appConfig;
|
private readonly ReadOnlyAppSettings _appConfig;
|
||||||
|
private readonly ILogWriter _logWriter;
|
||||||
|
private readonly Dictionary<Guid, Process> _runningBots = new();
|
||||||
private readonly string _fileName = OperatingSystem.IsWindows() ? "EllieBot.exe" : "EllieBot";
|
private readonly string _fileName = OperatingSystem.IsWindows() ? "EllieBot.exe" : "EllieBot";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -27,8 +28,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
/// Creates an object that coordinates multiple running processes of EllieBot.
|
/// Creates an object that coordinates multiple running processes of EllieBot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appConfig">The application settings.</param>
|
/// <param name="appConfig">The application settings.</param>
|
||||||
public EllieOrchestrator(ReadOnlyAppSettings appConfig)
|
/// <param name="logWriter">The service that writes bot logs to disk.</param>
|
||||||
=> _appConfig = appConfig;
|
public EllieOrchestrator(ReadOnlyAppSettings appConfig, ILogWriter logWriter)
|
||||||
|
{
|
||||||
|
_appConfig = appConfig;
|
||||||
|
_logWriter = logWriter;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool IsBotRunning(Guid botId)
|
public bool IsBotRunning(Guid botId)
|
||||||
|
@ -39,12 +44,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
{
|
{
|
||||||
if (_runningBots.ContainsKey(botId)
|
if (_runningBots.ContainsKey(botId)
|
||||||
|| !_appConfig.BotEntries.TryGetValue(botId, out var botEntry)
|
|| !_appConfig.BotEntries.TryGetValue(botId, out var botEntry)
|
||||||
|| !File.Exists(Path.Combine(botEntry.InstanceDirectoryUri, _fileName)))
|
|| !File.Exists(Path.Join(botEntry.InstanceDirectoryUri, _fileName)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var botProcess = Process.Start(new ProcessStartInfo()
|
var botProcess = Process.Start(new ProcessStartInfo()
|
||||||
{
|
{
|
||||||
FileName = Path.Combine(botEntry.InstanceDirectoryUri, _fileName),
|
FileName = Path.Join(botEntry.InstanceDirectoryUri, _fileName),
|
||||||
WorkingDirectory = botEntry.InstanceDirectoryUri,
|
WorkingDirectory = botEntry.InstanceDirectoryUri,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
|
@ -80,6 +85,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
{
|
{
|
||||||
var amount = _runningBots.Count;
|
var amount = _runningBots.Count;
|
||||||
|
|
||||||
|
// ReSharper disable once EmptyGeneralCatchClause
|
||||||
foreach (var process in _runningBots.Values)
|
foreach (var process in _runningBots.Values)
|
||||||
try { process.Kill(true); } catch { }
|
try { process.Kill(true); } catch { }
|
||||||
|
|
||||||
|
@ -95,7 +101,12 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
private void OnExit(object? sender, EventArgs eventArgs)
|
private void OnExit(object? sender, EventArgs eventArgs)
|
||||||
{
|
{
|
||||||
var (id, process) = _runningBots.First(x => x.Value.Equals(sender));
|
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);
|
_runningBots.Remove(id);
|
||||||
process.CancelOutputRead();
|
process.CancelOutputRead();
|
||||||
|
@ -116,6 +127,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
|
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
|
||||||
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
|
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
|
||||||
|
|
||||||
|
_logWriter.TryAdd(id, eventArgs.Data);
|
||||||
OnStdout?.Invoke(this, newEventArgs);
|
OnStdout?.Invoke(this, newEventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +144,7 @@ public sealed class EllieOrchestrator : IBotOrchestrator
|
||||||
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
|
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
|
||||||
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
|
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
|
||||||
|
|
||||||
|
_logWriter.TryAdd(id, eventArgs.Data);
|
||||||
OnStderr?.Invoke(this, newEventArgs);
|
OnStderr?.Invoke(this, newEventArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -103,7 +103,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
{
|
{
|
||||||
var sanitizedValue = value.ReplaceLineEndings(string.Empty);
|
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);
|
this.RaiseAndSetIfChanged(ref _botName, sanitizedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
if (IsBotRunning)
|
if (IsBotRunning)
|
||||||
EnableButtons(true, false);
|
EnableButtons(true, false);
|
||||||
else
|
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
|
// Dispose when the view is deactivated
|
||||||
this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
|
this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
|
||||||
|
@ -245,7 +245,7 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
{
|
{
|
||||||
_ = LoadUpdateBarAsync(Resolver, UpdateBar);
|
_ = 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);
|
EnableButtons(false, true);
|
||||||
else
|
else
|
||||||
EnableButtons(!wereButtonsUnlocked, true);
|
EnableButtons(!wereButtonsUnlocked, true);
|
||||||
|
@ -466,8 +466,6 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
if (eventArgs.Id != Resolver.Id)
|
if (eventArgs.Id != Resolver.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_logWriter.TryAdd(eventArgs.Id, eventArgs.Output);
|
|
||||||
|
|
||||||
FakeConsole.Content = FakeConsole.Content.Length > 100_000
|
FakeConsole.Content = FakeConsole.Content.Length > 100_000
|
||||||
? FakeConsole.Content[FakeConsole.Content.IndexOf(Environment.NewLine, 60_000, StringComparison.Ordinal)..] + eventArgs.Output + Environment.NewLine
|
? FakeConsole.Content[FakeConsole.Content.IndexOf(Environment.NewLine, 60_000, StringComparison.Ordinal)..] + eventArgs.Output + Environment.NewLine
|
||||||
: FakeConsole.Content + eventArgs.Output + Environment.NewLine;
|
: FakeConsole.Content + eventArgs.Output + Environment.NewLine;
|
||||||
|
@ -483,14 +481,11 @@ public class BotConfigViewModel : ViewModelBase<BotConfigView>, IDisposable
|
||||||
if (eventArgs.Id != Resolver.Id)
|
if (eventArgs.Id != Resolver.Id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var message = Environment.NewLine + ActualBotName + " stopped." + Environment.NewLine;
|
FakeConsole.Content += eventArgs.Message;
|
||||||
|
|
||||||
_logWriter.TryAdd(Resolver.Id, message);
|
|
||||||
FakeConsole.Content += message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="botOrchestrator">The bot orchestrator.</param>
|
/// <param name="botOrchestrator">The bot orchestrator.</param>
|
||||||
/// <param name="eventArgs">The event arguments.</param>
|
/// <param name="eventArgs">The event arguments.</param>
|
||||||
|
|
Loading…
Reference in a new issue