mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-24 22:42:53 +01:00
Compare commits
8 Commits
651a660cfc
...
599177409e
Author | SHA1 | Date | |
---|---|---|---|
599177409e | |||
4c3b81c54a | |||
8e2b019aa1 | |||
9a2c13c1e0 | |||
991b32032c | |||
875fd9a766 | |||
f7f08ec55c | |||
1b12fd9c3b |
@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
using Phantom.Agent.Minecraft.Instance;
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
using Phantom.Agent.Minecraft.Java;
|
using Phantom.Agent.Minecraft.Java;
|
||||||
using Phantom.Agent.Minecraft.Server;
|
using Phantom.Agent.Minecraft.Server;
|
||||||
@ -11,6 +12,7 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
private readonly InstanceProperties instanceProperties;
|
private readonly InstanceProperties instanceProperties;
|
||||||
|
|
||||||
protected string MinecraftVersion => instanceProperties.ServerVersion;
|
protected string MinecraftVersion => instanceProperties.ServerVersion;
|
||||||
|
protected string InstanceFolder => instanceProperties.InstanceFolder;
|
||||||
|
|
||||||
private protected BaseLauncher(InstanceProperties instanceProperties) {
|
private protected BaseLauncher(InstanceProperties instanceProperties) {
|
||||||
this.instanceProperties = instanceProperties;
|
this.instanceProperties = instanceProperties;
|
||||||
@ -51,16 +53,14 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
|
|
||||||
var processConfigurator = new ProcessConfigurator {
|
var processConfigurator = new ProcessConfigurator {
|
||||||
FileName = javaRuntimeExecutable.ExecutablePath,
|
FileName = javaRuntimeExecutable.ExecutablePath,
|
||||||
WorkingDirectory = instanceProperties.InstanceFolder,
|
WorkingDirectory = InstanceFolder,
|
||||||
RedirectInput = true,
|
RedirectInput = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
};
|
};
|
||||||
|
|
||||||
var processArguments = processConfigurator.ArgumentList;
|
var processArguments = processConfigurator.ArgumentList;
|
||||||
PrepareJvmArguments(serverJar).Build(processArguments);
|
PrepareJvmArguments(serverJar).Build(processArguments);
|
||||||
processArguments.Add("-jar");
|
PrepareJavaProcessArguments(processArguments, serverJar.FilePath);
|
||||||
processArguments.Add(serverJar.FilePath);
|
|
||||||
processArguments.Add("nogui");
|
|
||||||
|
|
||||||
var process = processConfigurator.CreateProcess();
|
var process = processConfigurator.CreateProcess();
|
||||||
var instanceProcess = new InstanceProcess(instanceProperties, process);
|
var instanceProcess = new InstanceProcess(instanceProperties, process);
|
||||||
@ -99,6 +99,12 @@ public abstract class BaseLauncher : IServerLauncher {
|
|||||||
|
|
||||||
private protected virtual void CustomizeJvmArguments(JvmArgumentBuilder arguments) {}
|
private protected virtual void CustomizeJvmArguments(JvmArgumentBuilder arguments) {}
|
||||||
|
|
||||||
|
protected virtual void PrepareJavaProcessArguments(Collection<string> processArguments, string serverJarFilePath) {
|
||||||
|
processArguments.Add("-jar");
|
||||||
|
processArguments.Add(serverJarFilePath);
|
||||||
|
processArguments.Add("nogui");
|
||||||
|
}
|
||||||
|
|
||||||
private protected virtual Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
private protected virtual Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
||||||
return Task.FromResult(new ServerJarInfo(serverJarPath));
|
return Task.FromResult(new ServerJarInfo(serverJarPath));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
|
using Phantom.Agent.Minecraft.Java;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Phantom.Agent.Minecraft.Launcher.Types;
|
||||||
|
|
||||||
|
public sealed class ForgeLauncher : BaseLauncher {
|
||||||
|
public ForgeLauncher(InstanceProperties instanceProperties) : base(instanceProperties) {}
|
||||||
|
|
||||||
|
private protected override void CustomizeJvmArguments(JvmArgumentBuilder arguments) {
|
||||||
|
arguments.AddProperty("terminal.ansi", "true"); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareJavaProcessArguments(Collection<string> processArguments, string serverJarFilePath) {
|
||||||
|
if (OperatingSystem.IsWindows()) {
|
||||||
|
processArguments.Add("@libraries/net/minecraftforge/forge/1.20.1-47.2.0/win_args.txt");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processArguments.Add("@libraries/net/minecraftforge/forge/1.20.1-47.2.0/unix_args.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
processArguments.Add("nogui");
|
||||||
|
}
|
||||||
|
|
||||||
|
private protected override Task<ServerJarInfo> PrepareServerJar(ILogger logger, string serverJarPath, CancellationToken cancellationToken) {
|
||||||
|
return Task.FromResult(new ServerJarInfo(Path.Combine(InstanceFolder, "run.sh")));
|
||||||
|
}
|
||||||
|
}
|
@ -16,32 +16,47 @@ sealed class MinecraftServerExecutableDownloader {
|
|||||||
public event EventHandler? Completed;
|
public event EventHandler? Completed;
|
||||||
|
|
||||||
private readonly CancellationTokenSource cancellationTokenSource = new ();
|
private readonly CancellationTokenSource cancellationTokenSource = new ();
|
||||||
private int listeners = 0;
|
|
||||||
|
private readonly List<CancellationTokenRegistration> listenerCancellationRegistrations = new ();
|
||||||
|
private int listenerCount = 0;
|
||||||
|
|
||||||
public MinecraftServerExecutableDownloader(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath, MinecraftServerExecutableDownloadListener listener) {
|
public MinecraftServerExecutableDownloader(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath, MinecraftServerExecutableDownloadListener listener) {
|
||||||
Register(listener);
|
Register(listener);
|
||||||
Task = DownloadAndGetPath(fileDownloadInfo, minecraftVersion, filePath);
|
Task = DownloadAndGetPath(fileDownloadInfo, minecraftVersion, filePath, new DownloadProgressCallback(this), cancellationTokenSource.Token);
|
||||||
Task.ContinueWith(OnCompleted, TaskScheduler.Default);
|
Task.ContinueWith(OnCompleted, TaskScheduler.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Register(MinecraftServerExecutableDownloadListener listener) {
|
public void Register(MinecraftServerExecutableDownloadListener listener) {
|
||||||
++listeners;
|
int newListenerCount;
|
||||||
Logger.Debug("Registered download listener, current listener count: {Listeners}", listeners);
|
|
||||||
|
|
||||||
DownloadProgress += listener.DownloadProgressEventHandler;
|
lock (this) {
|
||||||
listener.CancellationToken.Register(Unregister, listener);
|
newListenerCount = ++listenerCount;
|
||||||
|
|
||||||
|
DownloadProgress += listener.DownloadProgressEventHandler;
|
||||||
|
listenerCancellationRegistrations.Add(listener.CancellationToken.Register(Unregister, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug("Registered download listener, current listener count: {Listeners}", newListenerCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Unregister(object? listenerObject) {
|
private void Unregister(object? listenerObject) {
|
||||||
MinecraftServerExecutableDownloadListener listener = (MinecraftServerExecutableDownloadListener) listenerObject!;
|
int newListenerCount;
|
||||||
DownloadProgress -= listener.DownloadProgressEventHandler;
|
|
||||||
|
lock (this) {
|
||||||
|
MinecraftServerExecutableDownloadListener listener = (MinecraftServerExecutableDownloadListener) listenerObject!;
|
||||||
|
DownloadProgress -= listener.DownloadProgressEventHandler;
|
||||||
|
|
||||||
if (--listeners <= 0) {
|
newListenerCount = --listenerCount;
|
||||||
|
if (newListenerCount <= 0) {
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newListenerCount <= 0) {
|
||||||
Logger.Debug("Unregistered last download listener, cancelling download.");
|
Logger.Debug("Unregistered last download listener, cancelling download.");
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Logger.Debug("Unregistered download listener, current listener count: {Listeners}", listeners);
|
Logger.Debug("Unregistered download listener, current listener count: {Listeners}", newListenerCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,9 +66,19 @@ sealed class MinecraftServerExecutableDownloader {
|
|||||||
|
|
||||||
private void OnCompleted(Task task) {
|
private void OnCompleted(Task task) {
|
||||||
Logger.Debug("Download task completed.");
|
Logger.Debug("Download task completed.");
|
||||||
Completed?.Invoke(this, EventArgs.Empty);
|
|
||||||
Completed = null;
|
lock (this) {
|
||||||
DownloadProgress = null;
|
Completed?.Invoke(this, EventArgs.Empty);
|
||||||
|
Completed = null;
|
||||||
|
DownloadProgress = null;
|
||||||
|
|
||||||
|
foreach (var registration in listenerCancellationRegistrations) {
|
||||||
|
registration.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerCancellationRegistrations.Clear();
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class DownloadProgressCallback {
|
private sealed class DownloadProgressCallback {
|
||||||
@ -68,15 +93,14 @@ sealed class MinecraftServerExecutableDownloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> DownloadAndGetPath(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath) {
|
private static async Task<string?> DownloadAndGetPath(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath, DownloadProgressCallback progressCallback, CancellationToken cancellationToken) {
|
||||||
string tmpFilePath = filePath + ".tmp";
|
string tmpFilePath = filePath + ".tmp";
|
||||||
|
|
||||||
var cancellationToken = cancellationTokenSource.Token;
|
|
||||||
try {
|
try {
|
||||||
Logger.Information("Downloading server version {Version} from: {Url} ({Size})", minecraftVersion, fileDownloadInfo.DownloadUrl, fileDownloadInfo.Size.ToHumanReadable(decimalPlaces: 1));
|
Logger.Information("Downloading server version {Version} from: {Url} ({Size})", minecraftVersion, fileDownloadInfo.DownloadUrl, fileDownloadInfo.Size.ToHumanReadable(decimalPlaces: 1));
|
||||||
try {
|
try {
|
||||||
using var http = new HttpClient();
|
using var http = new HttpClient();
|
||||||
await FetchServerExecutableFile(http, new DownloadProgressCallback(this), fileDownloadInfo, tmpFilePath, cancellationToken);
|
await FetchServerExecutableFile(http, progressCallback, fileDownloadInfo, tmpFilePath, cancellationToken);
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
TryDeleteExecutableAfterFailure(tmpFilePath);
|
TryDeleteExecutableAfterFailure(tmpFilePath);
|
||||||
throw;
|
throw;
|
||||||
@ -94,8 +118,6 @@ sealed class MinecraftServerExecutableDownloader {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Error(e, "An unexpected error occurred.");
|
Logger.Error(e, "An unexpected error occurred.");
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
|
||||||
cancellationTokenSource.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,10 @@ sealed class BackupManager : IDisposable {
|
|||||||
resultBuilder.Kind = BackupCreationResultKind.BackupCancelled;
|
resultBuilder.Kind = BackupCreationResultKind.BackupCancelled;
|
||||||
logger.Warning("Backup creation was cancelled.");
|
logger.Warning("Backup creation was cancelled.");
|
||||||
return null;
|
return null;
|
||||||
|
} catch (TimeoutException) {
|
||||||
|
resultBuilder.Kind = BackupCreationResultKind.BackupTimedOut;
|
||||||
|
logger.Warning("Backup creation timed out.");
|
||||||
|
return null;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
resultBuilder.Kind = BackupCreationResultKind.UnknownError;
|
resultBuilder.Kind = BackupCreationResultKind.UnknownError;
|
||||||
logger.Error(e, "Caught exception while creating an instance backup.");
|
logger.Error(e, "Caught exception while creating an instance backup.");
|
||||||
@ -76,6 +80,9 @@ sealed class BackupManager : IDisposable {
|
|||||||
await dispatcher.EnableAutomaticSaving();
|
await dispatcher.EnableAutomaticSaving();
|
||||||
} catch (OperationCanceledException) {
|
} catch (OperationCanceledException) {
|
||||||
// Ignore.
|
// Ignore.
|
||||||
|
} catch (TimeoutException) {
|
||||||
|
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
||||||
|
logger.Warning("Timed out waiting for automatic saving to be re-enabled.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
||||||
logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup.");
|
logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup.");
|
||||||
@ -120,6 +127,7 @@ sealed class BackupManager : IDisposable {
|
|||||||
BackupCreationResultKind.Success => "Backup created successfully.",
|
BackupCreationResultKind.Success => "Backup created successfully.",
|
||||||
BackupCreationResultKind.InstanceNotRunning => "Instance is not running.",
|
BackupCreationResultKind.InstanceNotRunning => "Instance is not running.",
|
||||||
BackupCreationResultKind.BackupCancelled => "Backup cancelled.",
|
BackupCreationResultKind.BackupCancelled => "Backup cancelled.",
|
||||||
|
BackupCreationResultKind.BackupTimedOut => "Backup timed out.",
|
||||||
BackupCreationResultKind.BackupAlreadyRunning => "A backup is already being created.",
|
BackupCreationResultKind.BackupAlreadyRunning => "A backup is already being created.",
|
||||||
BackupCreationResultKind.BackupFileAlreadyExists => "Backup with the same name already exists.",
|
BackupCreationResultKind.BackupFileAlreadyExists => "Backup with the same name already exists.",
|
||||||
BackupCreationResultKind.CouldNotCreateBackupFolder => "Could not create backup folder.",
|
BackupCreationResultKind.CouldNotCreateBackupFolder => "Could not create backup folder.",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Collections.Immutable;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Phantom.Agent.Minecraft.Command;
|
using Phantom.Agent.Minecraft.Command;
|
||||||
using Phantom.Agent.Minecraft.Instance;
|
using Phantom.Agent.Minecraft.Instance;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
@ -7,8 +8,26 @@ using Serilog;
|
|||||||
namespace Phantom.Agent.Services.Backups;
|
namespace Phantom.Agent.Services.Backups;
|
||||||
|
|
||||||
sealed partial class BackupServerCommandDispatcher : IDisposable {
|
sealed partial class BackupServerCommandDispatcher : IDisposable {
|
||||||
[GeneratedRegex(@"^\[(?:.*?)\] \[Server thread/INFO\]: (.*?)$", RegexOptions.NonBacktracking)]
|
[GeneratedRegex(@"^(?:(?:\[.*?\] \[Server thread/INFO\].*?:)|(?:[\d-]+? [\d:]+? \[INFO\])) (.*?)$", RegexOptions.NonBacktracking)]
|
||||||
private static partial Regex ServerThreadInfoRegex();
|
private static partial Regex ServerThreadInfoRegex();
|
||||||
|
|
||||||
|
private static readonly ImmutableHashSet<string> AutomaticSavingDisabledMessages = ImmutableHashSet.Create(
|
||||||
|
"Automatic saving is now disabled",
|
||||||
|
"Turned off world auto-saving",
|
||||||
|
"CONSOLE: Disabling level saving.."
|
||||||
|
);
|
||||||
|
|
||||||
|
private static readonly ImmutableHashSet<string> SavedTheGameMessages = ImmutableHashSet.Create(
|
||||||
|
"Saved the game",
|
||||||
|
"Saved the world",
|
||||||
|
"CONSOLE: Save complete."
|
||||||
|
);
|
||||||
|
|
||||||
|
private static readonly ImmutableHashSet<string> AutomaticSavingEnabledMessages = ImmutableHashSet.Create(
|
||||||
|
"Automatic saving is now enabled",
|
||||||
|
"Turned on world auto-saving",
|
||||||
|
"CONSOLE: Enabling level saving.."
|
||||||
|
);
|
||||||
|
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
private readonly InstanceProcess process;
|
private readonly InstanceProcess process;
|
||||||
@ -32,18 +51,18 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
|
|||||||
|
|
||||||
public async Task DisableAutomaticSaving() {
|
public async Task DisableAutomaticSaving() {
|
||||||
await process.SendCommand(MinecraftCommand.SaveOff, cancellationToken);
|
await process.SendCommand(MinecraftCommand.SaveOff, cancellationToken);
|
||||||
await automaticSavingDisabled.Task.WaitAsync(cancellationToken);
|
await automaticSavingDisabled.Task.WaitAsync(TimeSpan.FromSeconds(30), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveAllChunks() {
|
public async Task SaveAllChunks() {
|
||||||
// TODO Try if not flushing and waiting a few seconds before flushing reduces lag.
|
// TODO Try if not flushing and waiting a few seconds before flushing reduces lag.
|
||||||
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
|
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
|
||||||
await savedTheGame.Task.WaitAsync(cancellationToken);
|
await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnableAutomaticSaving() {
|
public async Task EnableAutomaticSaving() {
|
||||||
await process.SendCommand(MinecraftCommand.SaveOn, cancellationToken);
|
await process.SendCommand(MinecraftCommand.SaveOn, cancellationToken);
|
||||||
await automaticSavingEnabled.Task.WaitAsync(cancellationToken);
|
await automaticSavingEnabled.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOutput(object? sender, string? line) {
|
private void OnOutput(object? sender, string? line) {
|
||||||
@ -59,19 +78,19 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
|
|||||||
string info = match.Groups[1].Value;
|
string info = match.Groups[1].Value;
|
||||||
|
|
||||||
if (!automaticSavingDisabled.Task.IsCompleted) {
|
if (!automaticSavingDisabled.Task.IsCompleted) {
|
||||||
if (info == "Automatic saving is now disabled") {
|
if (AutomaticSavingDisabledMessages.Contains(info)) {
|
||||||
logger.Debug("Detected that automatic saving is disabled.");
|
logger.Debug("Detected that automatic saving is disabled.");
|
||||||
automaticSavingDisabled.SetResult();
|
automaticSavingDisabled.SetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!savedTheGame.Task.IsCompleted) {
|
else if (!savedTheGame.Task.IsCompleted) {
|
||||||
if (info == "Saved the game") {
|
if (SavedTheGameMessages.Contains(info)) {
|
||||||
logger.Debug("Detected that the game is saved.");
|
logger.Debug("Detected that the game is saved.");
|
||||||
savedTheGame.SetResult();
|
savedTheGame.SetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!automaticSavingEnabled.Task.IsCompleted) {
|
else if (!automaticSavingEnabled.Task.IsCompleted) {
|
||||||
if (info == "Automatic saving is now enabled") {
|
if (AutomaticSavingEnabledMessages.Contains(info)) {
|
||||||
logger.Debug("Detected that automatic saving is enabled.");
|
logger.Debug("Detected that automatic saving is enabled.");
|
||||||
automaticSavingEnabled.SetResult();
|
automaticSavingEnabled.SetResult();
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,12 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
|||||||
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
|
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, shutdownCancellationToken);
|
SetAndReportStatus(InstanceStatus.BackingUp);
|
||||||
|
try {
|
||||||
|
return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, shutdownCancellationToken);
|
||||||
|
} finally {
|
||||||
|
SetAndReportStatus(InstanceStatus.Running);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +102,7 @@ sealed class InstanceManagerActor : ReceiveActor<InstanceManagerActor.ICommand>
|
|||||||
IServerLauncher launcher = configuration.MinecraftServerKind switch {
|
IServerLauncher launcher = configuration.MinecraftServerKind switch {
|
||||||
MinecraftServerKind.Vanilla => new VanillaLauncher(properties),
|
MinecraftServerKind.Vanilla => new VanillaLauncher(properties),
|
||||||
MinecraftServerKind.Fabric => new FabricLauncher(properties),
|
MinecraftServerKind.Fabric => new FabricLauncher(properties),
|
||||||
|
MinecraftServerKind.Forge => new ForgeLauncher(properties),
|
||||||
_ => InvalidLauncher.Instance
|
_ => InvalidLauncher.Instance
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
namespace Phantom.Common.Data.Backups;
|
namespace Phantom.Common.Data.Backups;
|
||||||
|
|
||||||
public enum BackupCreationResultKind : byte {
|
public enum BackupCreationResultKind : byte {
|
||||||
UnknownError,
|
UnknownError = 0,
|
||||||
Success,
|
Success = 1,
|
||||||
InstanceNotRunning,
|
InstanceNotRunning = 2,
|
||||||
BackupCancelled,
|
BackupTimedOut = 3,
|
||||||
BackupAlreadyRunning,
|
BackupCancelled = 4,
|
||||||
BackupFileAlreadyExists,
|
BackupAlreadyRunning = 5,
|
||||||
CouldNotCreateBackupFolder,
|
BackupFileAlreadyExists = 6,
|
||||||
CouldNotCopyWorldToTemporaryFolder,
|
CouldNotCreateBackupFolder = 7,
|
||||||
CouldNotCreateWorldArchive
|
CouldNotCopyWorldToTemporaryFolder = 8,
|
||||||
|
CouldNotCreateWorldArchive = 9
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BackupCreationResultSummaryExtensions {
|
public static class BackupCreationResultSummaryExtensions {
|
||||||
|
@ -9,9 +9,10 @@ namespace Phantom.Common.Data.Instance;
|
|||||||
[MemoryPackUnion(3, typeof(InstanceIsDownloading))]
|
[MemoryPackUnion(3, typeof(InstanceIsDownloading))]
|
||||||
[MemoryPackUnion(4, typeof(InstanceIsLaunching))]
|
[MemoryPackUnion(4, typeof(InstanceIsLaunching))]
|
||||||
[MemoryPackUnion(5, typeof(InstanceIsRunning))]
|
[MemoryPackUnion(5, typeof(InstanceIsRunning))]
|
||||||
[MemoryPackUnion(6, typeof(InstanceIsRestarting))]
|
[MemoryPackUnion(6, typeof(InstanceIsBackingUp))]
|
||||||
[MemoryPackUnion(7, typeof(InstanceIsStopping))]
|
[MemoryPackUnion(7, typeof(InstanceIsRestarting))]
|
||||||
[MemoryPackUnion(8, typeof(InstanceIsFailed))]
|
[MemoryPackUnion(8, typeof(InstanceIsStopping))]
|
||||||
|
[MemoryPackUnion(9, typeof(InstanceIsFailed))]
|
||||||
public partial interface IInstanceStatus {}
|
public partial interface IInstanceStatus {}
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
@ -32,6 +33,9 @@ public sealed partial record InstanceIsLaunching : IInstanceStatus;
|
|||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record InstanceIsRunning : IInstanceStatus;
|
public sealed partial record InstanceIsRunning : IInstanceStatus;
|
||||||
|
|
||||||
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
|
public sealed partial record InstanceIsBackingUp : IInstanceStatus;
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||||
public sealed partial record InstanceIsRestarting : IInstanceStatus;
|
public sealed partial record InstanceIsRestarting : IInstanceStatus;
|
||||||
|
|
||||||
@ -46,6 +50,7 @@ public static class InstanceStatus {
|
|||||||
public static readonly IInstanceStatus NotRunning = new InstanceIsNotRunning();
|
public static readonly IInstanceStatus NotRunning = new InstanceIsNotRunning();
|
||||||
public static readonly IInstanceStatus Launching = new InstanceIsLaunching();
|
public static readonly IInstanceStatus Launching = new InstanceIsLaunching();
|
||||||
public static readonly IInstanceStatus Running = new InstanceIsRunning();
|
public static readonly IInstanceStatus Running = new InstanceIsRunning();
|
||||||
|
public static readonly IInstanceStatus BackingUp = new InstanceIsBackingUp();
|
||||||
public static readonly IInstanceStatus Restarting = new InstanceIsRestarting();
|
public static readonly IInstanceStatus Restarting = new InstanceIsRestarting();
|
||||||
public static readonly IInstanceStatus Stopping = new InstanceIsStopping();
|
public static readonly IInstanceStatus Stopping = new InstanceIsStopping();
|
||||||
|
|
||||||
@ -58,7 +63,7 @@ public static class InstanceStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsRunning(this IInstanceStatus status) {
|
public static bool IsRunning(this IInstanceStatus status) {
|
||||||
return status is InstanceIsRunning;
|
return status is InstanceIsRunning or InstanceIsBackingUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsStopping(this IInstanceStatus status) {
|
public static bool IsStopping(this IInstanceStatus status) {
|
||||||
@ -70,10 +75,10 @@ public static class InstanceStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool CanStop(this IInstanceStatus status) {
|
public static bool CanStop(this IInstanceStatus status) {
|
||||||
return status is InstanceIsDownloading or InstanceIsLaunching or InstanceIsRunning;
|
return status.IsRunning() || status.IsLaunching();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CanSendCommand(this IInstanceStatus status) {
|
public static bool CanSendCommand(this IInstanceStatus status) {
|
||||||
return status is InstanceIsRunning;
|
return status.IsRunning();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
public enum MinecraftServerKind : ushort {
|
public enum MinecraftServerKind : ushort {
|
||||||
Vanilla = 1,
|
Vanilla = 1,
|
||||||
Fabric = 2
|
Fabric = 2,
|
||||||
|
Forge = 3
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,13 @@ using Phantom.Common.Data.Web.Minecraft;
|
|||||||
using Phantom.Common.Messages.Agent;
|
using Phantom.Common.Messages.Agent;
|
||||||
using Phantom.Common.Messages.Agent.ToAgent;
|
using Phantom.Common.Messages.Agent.ToAgent;
|
||||||
using Phantom.Controller.Database;
|
using Phantom.Controller.Database;
|
||||||
|
using Phantom.Controller.Database.Entities;
|
||||||
using Phantom.Controller.Minecraft;
|
using Phantom.Controller.Minecraft;
|
||||||
using Phantom.Controller.Services.Instances;
|
using Phantom.Controller.Services.Instances;
|
||||||
using Phantom.Utils.Actor;
|
using Phantom.Utils.Actor;
|
||||||
using Phantom.Utils.Actor.Mailbox;
|
using Phantom.Utils.Actor.Mailbox;
|
||||||
using Phantom.Utils.Actor.Tasks;
|
using Phantom.Utils.Actor.Tasks;
|
||||||
|
using Phantom.Utils.Collections;
|
||||||
using Phantom.Utils.Logging;
|
using Phantom.Utils.Logging;
|
||||||
using Phantom.Utils.Rpc.Runtime;
|
using Phantom.Utils.Rpc.Runtime;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@ -194,21 +196,29 @@ sealed class AgentActor : ReceiveActor<AgentActor.ICommand> {
|
|||||||
public sealed record ReceiveInstanceDataCommand(Instance Instance) : ICommand, IJumpAhead;
|
public sealed record ReceiveInstanceDataCommand(Instance Instance) : ICommand, IJumpAhead;
|
||||||
|
|
||||||
private async Task Initialize(InitializeCommand command) {
|
private async Task Initialize(InitializeCommand command) {
|
||||||
await using var ctx = dbProvider.Eager();
|
ImmutableArray<InstanceEntity> instanceEntities;
|
||||||
await foreach (var entity in ctx.Instances.Where(instance => instance.AgentGuid == agentGuid).AsAsyncEnumerable().WithCancellation(cancellationToken)) {
|
await using (var ctx = dbProvider.Eager()) {
|
||||||
|
instanceEntities = await ctx.Instances.Where(instance => instance.AgentGuid == agentGuid).AsAsyncEnumerable().ToImmutableArrayCatchingExceptionsAsync(OnException, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnException(Exception e) {
|
||||||
|
Logger.Error(e, "Could not load instance from database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var instanceEntity in instanceEntities) {
|
||||||
var instanceConfiguration = new InstanceConfiguration(
|
var instanceConfiguration = new InstanceConfiguration(
|
||||||
entity.AgentGuid,
|
instanceEntity.AgentGuid,
|
||||||
entity.InstanceName,
|
instanceEntity.InstanceName,
|
||||||
entity.ServerPort,
|
instanceEntity.ServerPort,
|
||||||
entity.RconPort,
|
instanceEntity.RconPort,
|
||||||
entity.MinecraftVersion,
|
instanceEntity.MinecraftVersion,
|
||||||
entity.MinecraftServerKind,
|
instanceEntity.MinecraftServerKind,
|
||||||
entity.MemoryAllocation,
|
instanceEntity.MemoryAllocation,
|
||||||
entity.JavaRuntimeGuid,
|
instanceEntity.JavaRuntimeGuid,
|
||||||
JvmArgumentsHelper.Split(entity.JvmArguments)
|
JvmArgumentsHelper.Split(instanceEntity.JvmArguments)
|
||||||
);
|
);
|
||||||
|
|
||||||
CreateNewInstance(Instance.Offline(entity.InstanceGuid, instanceConfiguration, entity.LaunchAutomatically));
|
CreateNewInstance(Instance.Offline(instanceEntity.InstanceGuid, instanceConfiguration, instanceEntity.LaunchAutomatically));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,27 @@ public static class EnumerableExtensions {
|
|||||||
|
|
||||||
return builder.ToImmutable();
|
return builder.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<ImmutableArray<TSource>> ToImmutableArrayCatchingExceptionsAsync<TSource>(this IAsyncEnumerable<TSource> source, Action<Exception> onException, CancellationToken cancellationToken = default) {
|
||||||
|
var builder = ImmutableArray.CreateBuilder<TSource>();
|
||||||
|
|
||||||
|
await using (var enumerator = source.GetAsyncEnumerator(cancellationToken)) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (!await enumerator.MoveNextAsync()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
onException(e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Add(enumerator.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<ImmutableHashSet<TSource>> ToImmutableSetAsync<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken = default) {
|
public static async Task<ImmutableHashSet<TSource>> ToImmutableSetAsync<TSource>(this IAsyncEnumerable<TSource> source, CancellationToken cancellationToken = default) {
|
||||||
var builder = ImmutableHashSet.CreateBuilder<TSource>();
|
var builder = ImmutableHashSet.CreateBuilder<TSource>();
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
form.SubmitModel.StopSubmitting(result.Map(Messages.ToSentence, InstanceActionFailureExtensions.ToSentence));
|
form.SubmitModel.StopSubmitting(result.Map(Messages.ToSentence, InstanceActionFailureExtensions.ToSentence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
await commandInputElement.FocusAsync(preventScroll: true);
|
await commandInputElement.FocusAsync(preventScroll: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,11 @@
|
|||||||
<span class="fw-semibold text-success">Running</span>
|
<span class="fw-semibold text-success">Running</span>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case InstanceIsBackingUp:
|
||||||
|
<div class="spinner-border" role="status"></div>
|
||||||
|
<span class="fw-semibold"> Backing Up</span>
|
||||||
|
break;
|
||||||
|
|
||||||
case InstanceIsRestarting:
|
case InstanceIsRestarting:
|
||||||
<div class="spinner-border" role="status"></div>
|
<div class="spinner-border" role="status"></div>
|
||||||
<span class="fw-semibold"> Restarting</span>
|
<span class="fw-semibold"> Restarting</span>
|
||||||
@ -41,6 +46,10 @@
|
|||||||
case InstanceIsFailed failed:
|
case InstanceIsFailed failed:
|
||||||
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
|
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
<span class="fw-semibold">Unknown</span>
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
</nobr>
|
</nobr>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user