EllieHub/EllieHub/Services/EllieOrchestrator.cs
2023-10-05 00:44:51 +13:00

139 lines
No EOL
4.6 KiB
C#

using EllieHub.Models.Config;
using EllieHub.Models.EventArguments;
using EllieHub.Services.Abstractions;
using System.Diagnostics;
namespace EllieHub.Services;
/// <summary>
/// Defines an object that coordinates multiple running processes of EllieBot.
/// </summary>
public sealed class EllieOrchestrator : IBotOrchestrator
{
private readonly Dictionary<Guid, Process> _runningBots = new();
private readonly ReadOnlyAppConfig _appConfig;
private readonly string _fileName = (OperatingSystem.IsWindows()) ? "NadekoBot.exe" : "NadekoBot";
/// <inheritdoc/>
public event EventHandler<IBotOrchestrator, BotExitEventArgs>? OnBotExit;
/// <inheritdoc/>
public event EventHandler<IBotOrchestrator, ProcessStdWriteEventArgs>? OnStderr;
/// <inheritdoc/>
public event EventHandler<IBotOrchestrator, ProcessStdWriteEventArgs>? OnStdout;
/// <summary>
/// Creates an object that coordinates multiple running processes of EllieBot.
/// </summary>
/// <param name="appConfig">The application settings.</param>
public EllieOrchestrator(ReadOnlyAppConfig appConfig)
=> _appConfig = appConfig;
/// <inheritdoc/>
public bool IsBotRunning(Guid botId)
=> _runningBots.ContainsKey(botId);
/// <inheritdoc/>
public bool Start(Guid botId)
{
if (_runningBots.ContainsKey(botId)
|| !_appConfig.BotEntries.TryGetValue(botId, out var botEntry)
|| !File.Exists(Path.Combine(botEntry.InstanceDirectoryUri, _fileName)))
return false;
var botProcess = Process.Start(new ProcessStartInfo()
{
FileName = Path.Combine(botEntry.InstanceDirectoryUri, _fileName),
WorkingDirectory = botEntry.InstanceDirectoryUri,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
});
if (botProcess is null)
return false;
botProcess.EnableRaisingEvents = true;
botProcess.OutputDataReceived += EmitStdout;
botProcess.ErrorDataReceived += EmitStderr;
botProcess.Exited += OnExit;
botProcess.BeginOutputReadLine();
botProcess.BeginErrorReadLine();
return _runningBots.TryAdd(botId, botProcess);
}
/// <inheritdoc/>
public bool Stop(Guid botId)
{
if (!_runningBots.TryGetValue(botId, out var botProcess))
return false;
botProcess.Kill(true);
return true;
}
/// <inheritdoc/>
public bool StopAll()
{
var amount = _runningBots.Count;
foreach (var process in _runningBots.Values)
try { process.Kill(true); } catch { }
return amount is not 0;
}
/// <summary>
/// Finalizes a process when it stops running.
/// </summary>
/// <param name="sender">The <see cref="Process"/>.</param>
/// <param name="eventArgs">The event arguments.</param>
/// <exception cref="InvalidOperationException">Occurs when <paramref name="sender"/> is not of type <see cref="Process"/>.</exception>
private void OnExit(object? sender, EventArgs eventArgs)
{
var (id, process) = _runningBots.First(x => x.Value.Equals(sender));
OnBotExit?.Invoke(this, new(id, process.ExitCode));
_runningBots.Remove(id);
process.CancelOutputRead();
process.CancelErrorRead();
process.Dispose();
}
/// <summary>
/// Raises <see cref="OnStdout"/> with its appropriate event arguments.
/// </summary>
/// <param name="sender">The <see cref="Process"/>.</param>
/// <param name="eventArgs">The event arguments.</param>
private void EmitStdout(object sender, DataReceivedEventArgs eventArgs)
{
if (string.IsNullOrWhiteSpace(eventArgs.Data))
return;
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
OnStdout?.Invoke(this, newEventArgs);
}
/// <summary>
/// Raises <see cref="OnStderr"/> with its appropriate event arguments.
/// </summary>
/// <param name="sender">The <see cref="Process"/>.</param>
/// <param name="eventArgs">The event arguments.</param>
private void EmitStderr(object sender, DataReceivedEventArgs eventArgs)
{
if (string.IsNullOrWhiteSpace(eventArgs.Data))
return;
var (id, _) = _runningBots.First(x => x.Value.Equals(sender));
var newEventArgs = new ProcessStdWriteEventArgs(id, eventArgs.Data);
OnStderr?.Invoke(this, newEventArgs);
}
}