mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 07:42:58 +01:00
Compare commits
No commits in common. "4c3b81c54a17aebf67ef1faf0189f8bcef9daa57" and "1b12fd9c3b89de7014fb5a7bb053af76094bdcb3" have entirely different histories.
4c3b81c54a
...
1b12fd9c3b
@ -16,47 +16,32 @@ sealed class MinecraftServerExecutableDownloader {
|
||||
public event EventHandler? Completed;
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource = new ();
|
||||
|
||||
private readonly List<CancellationTokenRegistration> listenerCancellationRegistrations = new ();
|
||||
private int listenerCount = 0;
|
||||
private int listeners = 0;
|
||||
|
||||
public MinecraftServerExecutableDownloader(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath, MinecraftServerExecutableDownloadListener listener) {
|
||||
Register(listener);
|
||||
Task = DownloadAndGetPath(fileDownloadInfo, minecraftVersion, filePath, new DownloadProgressCallback(this), cancellationTokenSource.Token);
|
||||
Task = DownloadAndGetPath(fileDownloadInfo, minecraftVersion, filePath);
|
||||
Task.ContinueWith(OnCompleted, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
public void Register(MinecraftServerExecutableDownloadListener listener) {
|
||||
int newListenerCount;
|
||||
++listeners;
|
||||
Logger.Debug("Registered download listener, current listener count: {Listeners}", listeners);
|
||||
|
||||
lock (this) {
|
||||
newListenerCount = ++listenerCount;
|
||||
|
||||
DownloadProgress += listener.DownloadProgressEventHandler;
|
||||
listenerCancellationRegistrations.Add(listener.CancellationToken.Register(Unregister, listener));
|
||||
}
|
||||
|
||||
Logger.Debug("Registered download listener, current listener count: {Listeners}", newListenerCount);
|
||||
DownloadProgress += listener.DownloadProgressEventHandler;
|
||||
listener.CancellationToken.Register(Unregister, listener);
|
||||
}
|
||||
|
||||
private void Unregister(object? listenerObject) {
|
||||
int newListenerCount;
|
||||
|
||||
lock (this) {
|
||||
MinecraftServerExecutableDownloadListener listener = (MinecraftServerExecutableDownloadListener) listenerObject!;
|
||||
DownloadProgress -= listener.DownloadProgressEventHandler;
|
||||
MinecraftServerExecutableDownloadListener listener = (MinecraftServerExecutableDownloadListener) listenerObject!;
|
||||
DownloadProgress -= listener.DownloadProgressEventHandler;
|
||||
|
||||
newListenerCount = --listenerCount;
|
||||
if (newListenerCount <= 0) {
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (newListenerCount <= 0) {
|
||||
if (--listeners <= 0) {
|
||||
Logger.Debug("Unregistered last download listener, cancelling download.");
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
else {
|
||||
Logger.Debug("Unregistered download listener, current listener count: {Listeners}", newListenerCount);
|
||||
Logger.Debug("Unregistered download listener, current listener count: {Listeners}", listeners);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,19 +51,9 @@ sealed class MinecraftServerExecutableDownloader {
|
||||
|
||||
private void OnCompleted(Task task) {
|
||||
Logger.Debug("Download task completed.");
|
||||
|
||||
lock (this) {
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
Completed = null;
|
||||
DownloadProgress = null;
|
||||
|
||||
foreach (var registration in listenerCancellationRegistrations) {
|
||||
registration.Dispose();
|
||||
}
|
||||
|
||||
listenerCancellationRegistrations.Clear();
|
||||
cancellationTokenSource.Dispose();
|
||||
}
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
Completed = null;
|
||||
DownloadProgress = null;
|
||||
}
|
||||
|
||||
private sealed class DownloadProgressCallback {
|
||||
@ -93,14 +68,15 @@ sealed class MinecraftServerExecutableDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string?> DownloadAndGetPath(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath, DownloadProgressCallback progressCallback, CancellationToken cancellationToken) {
|
||||
private async Task<string?> DownloadAndGetPath(FileDownloadInfo fileDownloadInfo, string minecraftVersion, string filePath) {
|
||||
string tmpFilePath = filePath + ".tmp";
|
||||
|
||||
var cancellationToken = cancellationTokenSource.Token;
|
||||
try {
|
||||
Logger.Information("Downloading server version {Version} from: {Url} ({Size})", minecraftVersion, fileDownloadInfo.DownloadUrl, fileDownloadInfo.Size.ToHumanReadable(decimalPlaces: 1));
|
||||
try {
|
||||
using var http = new HttpClient();
|
||||
await FetchServerExecutableFile(http, progressCallback, fileDownloadInfo, tmpFilePath, cancellationToken);
|
||||
await FetchServerExecutableFile(http, new DownloadProgressCallback(this), fileDownloadInfo, tmpFilePath, cancellationToken);
|
||||
} catch (Exception) {
|
||||
TryDeleteExecutableAfterFailure(tmpFilePath);
|
||||
throw;
|
||||
@ -118,6 +94,8 @@ sealed class MinecraftServerExecutableDownloader {
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "An unexpected error occurred.");
|
||||
return null;
|
||||
} finally {
|
||||
cancellationTokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,10 +67,6 @@ sealed class BackupManager : IDisposable {
|
||||
resultBuilder.Kind = BackupCreationResultKind.BackupCancelled;
|
||||
logger.Warning("Backup creation was cancelled.");
|
||||
return null;
|
||||
} catch (TimeoutException) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.BackupTimedOut;
|
||||
logger.Warning("Backup creation timed out.");
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.UnknownError;
|
||||
logger.Error(e, "Caught exception while creating an instance backup.");
|
||||
@ -80,9 +76,6 @@ sealed class BackupManager : IDisposable {
|
||||
await dispatcher.EnableAutomaticSaving();
|
||||
} catch (OperationCanceledException) {
|
||||
// Ignore.
|
||||
} catch (TimeoutException) {
|
||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
||||
logger.Warning("Timed out waiting for automatic saving to be re-enabled.");
|
||||
} catch (Exception e) {
|
||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotRestoreAutomaticSaving;
|
||||
logger.Error(e, "Caught exception while enabling automatic saving after creating an instance backup.");
|
||||
@ -127,7 +120,6 @@ sealed class BackupManager : IDisposable {
|
||||
BackupCreationResultKind.Success => "Backup created successfully.",
|
||||
BackupCreationResultKind.InstanceNotRunning => "Instance is not running.",
|
||||
BackupCreationResultKind.BackupCancelled => "Backup cancelled.",
|
||||
BackupCreationResultKind.BackupTimedOut => "Backup timed out.",
|
||||
BackupCreationResultKind.BackupAlreadyRunning => "A backup is already being created.",
|
||||
BackupCreationResultKind.BackupFileAlreadyExists => "Backup with the same name already exists.",
|
||||
BackupCreationResultKind.CouldNotCreateBackupFolder => "Could not create backup folder.",
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Phantom.Agent.Minecraft.Command;
|
||||
using Phantom.Agent.Minecraft.Instance;
|
||||
using Phantom.Utils.Tasks;
|
||||
@ -8,26 +7,8 @@ using Serilog;
|
||||
namespace Phantom.Agent.Services.Backups;
|
||||
|
||||
sealed partial class BackupServerCommandDispatcher : IDisposable {
|
||||
[GeneratedRegex(@"^(?:(?:\[.*?\] \[Server thread/INFO\].*?:)|(?:[\d-]+? [\d:]+? \[INFO\])) (.*?)$", RegexOptions.NonBacktracking)]
|
||||
[GeneratedRegex(@"^\[(?:.*?)\] \[Server thread/INFO\]: (.*?)$", RegexOptions.NonBacktracking)]
|
||||
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 InstanceProcess process;
|
||||
@ -51,18 +32,18 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
|
||||
|
||||
public async Task DisableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOff, cancellationToken);
|
||||
await automaticSavingDisabled.Task.WaitAsync(TimeSpan.FromSeconds(30), cancellationToken);
|
||||
await automaticSavingDisabled.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task SaveAllChunks() {
|
||||
// TODO Try if not flushing and waiting a few seconds before flushing reduces lag.
|
||||
await process.SendCommand(MinecraftCommand.SaveAll(flush: true), cancellationToken);
|
||||
await savedTheGame.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
||||
await savedTheGame.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task EnableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOn, cancellationToken);
|
||||
await automaticSavingEnabled.Task.WaitAsync(TimeSpan.FromMinutes(1), cancellationToken);
|
||||
await automaticSavingEnabled.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void OnOutput(object? sender, string? line) {
|
||||
@ -78,19 +59,19 @@ sealed partial class BackupServerCommandDispatcher : IDisposable {
|
||||
string info = match.Groups[1].Value;
|
||||
|
||||
if (!automaticSavingDisabled.Task.IsCompleted) {
|
||||
if (AutomaticSavingDisabledMessages.Contains(info)) {
|
||||
if (info == "Automatic saving is now disabled") {
|
||||
logger.Debug("Detected that automatic saving is disabled.");
|
||||
automaticSavingDisabled.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!savedTheGame.Task.IsCompleted) {
|
||||
if (SavedTheGameMessages.Contains(info)) {
|
||||
if (info == "Saved the game") {
|
||||
logger.Debug("Detected that the game is saved.");
|
||||
savedTheGame.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!automaticSavingEnabled.Task.IsCompleted) {
|
||||
if (AutomaticSavingEnabledMessages.Contains(info)) {
|
||||
if (info == "Automatic saving is now enabled") {
|
||||
logger.Debug("Detected that automatic saving is enabled.");
|
||||
automaticSavingEnabled.SetResult();
|
||||
}
|
||||
|
@ -135,12 +135,7 @@ sealed class InstanceActor : ReceiveActor<InstanceActor.ICommand> {
|
||||
return new BackupCreationResult(BackupCreationResultKind.InstanceNotRunning);
|
||||
}
|
||||
else {
|
||||
SetAndReportStatus(InstanceStatus.BackingUp);
|
||||
try {
|
||||
return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, shutdownCancellationToken);
|
||||
} finally {
|
||||
SetAndReportStatus(InstanceStatus.Running);
|
||||
}
|
||||
return await command.BackupManager.CreateBackup(context.ShortName, runningState.Process, shutdownCancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,15 @@
|
||||
namespace Phantom.Common.Data.Backups;
|
||||
|
||||
public enum BackupCreationResultKind : byte {
|
||||
UnknownError = 0,
|
||||
Success = 1,
|
||||
InstanceNotRunning = 2,
|
||||
BackupTimedOut = 3,
|
||||
BackupCancelled = 4,
|
||||
BackupAlreadyRunning = 5,
|
||||
BackupFileAlreadyExists = 6,
|
||||
CouldNotCreateBackupFolder = 7,
|
||||
CouldNotCopyWorldToTemporaryFolder = 8,
|
||||
CouldNotCreateWorldArchive = 9
|
||||
UnknownError,
|
||||
Success,
|
||||
InstanceNotRunning,
|
||||
BackupCancelled,
|
||||
BackupAlreadyRunning,
|
||||
BackupFileAlreadyExists,
|
||||
CouldNotCreateBackupFolder,
|
||||
CouldNotCopyWorldToTemporaryFolder,
|
||||
CouldNotCreateWorldArchive
|
||||
}
|
||||
|
||||
public static class BackupCreationResultSummaryExtensions {
|
||||
|
@ -9,10 +9,9 @@ namespace Phantom.Common.Data.Instance;
|
||||
[MemoryPackUnion(3, typeof(InstanceIsDownloading))]
|
||||
[MemoryPackUnion(4, typeof(InstanceIsLaunching))]
|
||||
[MemoryPackUnion(5, typeof(InstanceIsRunning))]
|
||||
[MemoryPackUnion(6, typeof(InstanceIsBackingUp))]
|
||||
[MemoryPackUnion(7, typeof(InstanceIsRestarting))]
|
||||
[MemoryPackUnion(8, typeof(InstanceIsStopping))]
|
||||
[MemoryPackUnion(9, typeof(InstanceIsFailed))]
|
||||
[MemoryPackUnion(6, typeof(InstanceIsRestarting))]
|
||||
[MemoryPackUnion(7, typeof(InstanceIsStopping))]
|
||||
[MemoryPackUnion(8, typeof(InstanceIsFailed))]
|
||||
public partial interface IInstanceStatus {}
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
@ -33,9 +32,6 @@ public sealed partial record InstanceIsLaunching : IInstanceStatus;
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record InstanceIsRunning : IInstanceStatus;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record InstanceIsBackingUp : IInstanceStatus;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record InstanceIsRestarting : IInstanceStatus;
|
||||
|
||||
@ -50,7 +46,6 @@ public static class InstanceStatus {
|
||||
public static readonly IInstanceStatus NotRunning = new InstanceIsNotRunning();
|
||||
public static readonly IInstanceStatus Launching = new InstanceIsLaunching();
|
||||
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 Stopping = new InstanceIsStopping();
|
||||
|
||||
@ -63,7 +58,7 @@ public static class InstanceStatus {
|
||||
}
|
||||
|
||||
public static bool IsRunning(this IInstanceStatus status) {
|
||||
return status is InstanceIsRunning or InstanceIsBackingUp;
|
||||
return status is InstanceIsRunning;
|
||||
}
|
||||
|
||||
public static bool IsStopping(this IInstanceStatus status) {
|
||||
@ -75,10 +70,10 @@ public static class InstanceStatus {
|
||||
}
|
||||
|
||||
public static bool CanStop(this IInstanceStatus status) {
|
||||
return status.IsRunning() || status.IsLaunching();
|
||||
return status is InstanceIsDownloading or InstanceIsLaunching or InstanceIsRunning;
|
||||
}
|
||||
|
||||
public static bool CanSendCommand(this IInstanceStatus status) {
|
||||
return status.IsRunning();
|
||||
return status is InstanceIsRunning;
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,6 @@
|
||||
form.SubmitModel.StopSubmitting(result.Map(Messages.ToSentence, InstanceActionFailureExtensions.ToSentence));
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
await commandInputElement.FocusAsync(preventScroll: true);
|
||||
}
|
||||
|
||||
|
@ -28,11 +28,6 @@
|
||||
<span class="fw-semibold text-success">Running</span>
|
||||
break;
|
||||
|
||||
case InstanceIsBackingUp:
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<span class="fw-semibold"> Backing Up</span>
|
||||
break;
|
||||
|
||||
case InstanceIsRestarting:
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<span class="fw-semibold"> Restarting</span>
|
||||
@ -46,10 +41,6 @@
|
||||
case InstanceIsFailed failed:
|
||||
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
|
||||
break;
|
||||
|
||||
default:
|
||||
<span class="fw-semibold">Unknown</span>
|
||||
break;
|
||||
}
|
||||
</nobr>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user