livia-test/Livia/Models/ProcessManager.cs
2025-03-28 14:31:53 +08:00

170 lines
5.2 KiB
C#

using System.Diagnostics;
using System.IO;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Livia.Properties;
using Livia.Utility;
using Microsoft.Extensions.Logging;
namespace Livia.Models;
public interface IProcessManager
{
bool Start(ServerFileLocation[] serverFileLocations);
}
public readonly struct ServerFileLocation
{
public ServerFileLocation(string fileName, string workingDirectory)
{
FileName = fileName;
FileNameNoExtension = Path.GetFileNameWithoutExtension(fileName);
WorkingDirectory = workingDirectory;
FullPath = Path.Combine(WorkingDirectory, FileName);
}
public readonly string FileName;
public readonly string FileNameNoExtension;
public readonly string WorkingDirectory;
public readonly string FullPath;
}
public class ProcessManager : ObservableRecipient, IProcessManager, IRecipient<ShutdownMessage>
{
private readonly ILogger _logger;
private ServerFileLocation[] _serverFileLocations = Array.Empty<ServerFileLocation>();
private Process[] _servers = Array.Empty<Process>();
private const int MaxRestartCount = 10;
private int _restartCount;
public ProcessManager(ILogger logger)
{
_logger = logger;
WeakReferenceMessenger.Default.RegisterAll(this);
}
private Process StartProcess(ServerFileLocation fileLocation)
{
_logger.LogInformation("Starting service");
Process process = new();
process.StartInfo.FileName = fileLocation.FileName;
process.StartInfo.WorkingDirectory = fileLocation.WorkingDirectory;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.EnableRaisingEvents = true;
process.StartInfo.WindowStyle = Settings.Default.DeveloperMode ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
process.Exited += ProcessOnExited;
if (!process.Start())
{
throw new Exception("Failed to start process");
}
return process;
}
private void ProcessOnExited(object? sender, EventArgs e)
{
if (_restartCount >= MaxRestartCount)
{
_logger.LogCritical("ProcessManager MaxRestartCount reached");
return;
}
_logger.LogCritical("Server process has exited! Attempting to restart it");
for (int i = 0; i < _servers.Length; i++)
{
if (sender != _servers[i])
continue;
_restartCount++;
_servers[i] = StartProcess(_serverFileLocations[i]);
return;
}
_logger.LogCritical("Cannot determine which process has exited!");
}
private void CleanUp()
{
foreach (Process process in _servers)
{
//disable this so that we are not going to attempt to restart it
process.EnableRaisingEvents = false;
}
KillExisting();
//remove everything from temp
string[] folders = Directory.GetDirectories(Path.GetTempPath());
foreach (string folderPath in folders)
{
DirectoryInfo info = new(folderPath);
string folderName = info.Name;
if (!folderName.StartsWith("_MEI"))
continue;
//pretty new, wait and see if it will be auto deleted
TimeSpan folderAge = DateTime.Now - info.LastWriteTime;
if (folderAge < TimeSpan.FromDays(1))
continue;
try
{
_logger.LogInformation("{folder} is {age} old, deleting it", folderPath, folderAge);
Directory.Delete(folderPath, true);
}
catch (Exception e)
{
_logger.LogInformation(e, "Error Deleting {folder}", folderPath);
}
}
}
private void KillExisting()
{
//find all and kill them
foreach (ServerFileLocation fileLocation in _serverFileLocations)
{
try
{
Process[] processes = Process.GetProcessesByName(fileLocation.FileNameNoExtension);
Process? lastCreatedProcess = processes.Where(process => process.MainModule?.FileName == fileLocation.FullPath).MaxBy(process => process.StartTime);
lastCreatedProcess?.Kill();
lastCreatedProcess?.WaitForExit();
lastCreatedProcess?.Dispose();
}
catch (Exception e)
{
_logger.LogError(e, "Error killing process{process}", fileLocation.FileNameNoExtension);
}
}
}
public bool Start(ServerFileLocation[] serverFileLocations)
{
KillExisting();
_serverFileLocations = serverFileLocations;
_servers = new Process[_serverFileLocations.Length];
try
{
for (int i = 0; i < _serverFileLocations.Length; i++)
{
_servers[i] = StartProcess(_serverFileLocations[i]);
}
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Cannot start server");
return false;
}
return true;
}
public void Receive(ShutdownMessage message)
{
CleanUp();
}
}