mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-05-12 14:34:03 +02:00
Restore chunk saving immediately after archiving the backup
This commit is contained in:
parent
2b4fa2c902
commit
8f003c6351
Agent/Phantom.Agent.Services/Backups
@ -44,7 +44,7 @@ sealed class BackupArchiver {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ArchiveWorld(BackupCreationResult.Builder resultBuilder) {
|
||||
public async Task<string?> ArchiveWorld(BackupCreationResult.Builder resultBuilder) {
|
||||
string guid = instanceProperties.InstanceGuid.ToString();
|
||||
string currentDateTime = DateTime.Now.ToString("yyyyMMdd-HHmmss");
|
||||
string backupFolderPath = Path.Combine(destinationBasePath, guid);
|
||||
@ -53,7 +53,7 @@ sealed class BackupArchiver {
|
||||
if (File.Exists(backupFilePath)) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.BackupFileAlreadyExists;
|
||||
logger.Warning("Skipping backup, file already exists: {File}", backupFilePath);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -61,18 +61,16 @@ sealed class BackupArchiver {
|
||||
} catch (Exception e) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.CouldNotCreateBackupFolder;
|
||||
logger.Error(e, "Could not create backup folder: {Folder}", backupFolderPath);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
string temporaryFolderPath = Path.Combine(temporaryBasePath, guid + "_" + currentDateTime);
|
||||
if (!await CopyWorldAndCreateTarArchive(temporaryFolderPath, backupFilePath, resultBuilder)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
var compressedFilePath = await BackupCompressor.Compress(backupFilePath, cancellationToken);
|
||||
if (compressedFilePath == null) {
|
||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotCompressWorldArchive;
|
||||
}
|
||||
logger.Verbose("Created world backup: {FilePath}", backupFilePath);
|
||||
return backupFilePath;
|
||||
}
|
||||
|
||||
private async Task<bool> CopyWorldAndCreateTarArchive(string temporaryFolderPath, string backupFilePath, BackupCreationResult.Builder resultBuilder) {
|
||||
|
@ -1,13 +1,11 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Phantom.Agent.Minecraft.Command;
|
||||
using Phantom.Agent.Minecraft.Instance;
|
||||
using Phantom.Agent.Minecraft.Instance;
|
||||
using Phantom.Common.Data.Backups;
|
||||
using Phantom.Common.Logging;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Agent.Services.Backups;
|
||||
|
||||
sealed partial class BackupManager {
|
||||
sealed class BackupManager {
|
||||
private readonly string destinationBasePath;
|
||||
private readonly string temporaryBasePath;
|
||||
|
||||
@ -40,7 +38,6 @@ sealed partial class BackupManager {
|
||||
private readonly string loggerName;
|
||||
private readonly ILogger logger;
|
||||
private readonly InstanceProcess process;
|
||||
private readonly BackupCommandListener listener;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
public BackupCreator(string destinationBasePath, string temporaryBasePath, string loggerName, InstanceProcess process, CancellationToken cancellationToken) {
|
||||
@ -49,52 +46,44 @@ sealed partial class BackupManager {
|
||||
this.loggerName = loggerName;
|
||||
this.logger = PhantomLogger.Create<BackupManager>(loggerName);
|
||||
this.process = process;
|
||||
this.listener = new BackupCommandListener(logger);
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public async Task<BackupCreationResult> CreateBackup() {
|
||||
logger.Information("Backup started.");
|
||||
process.AddOutputListener(listener.OnOutput, maxLinesToReadFromHistory: 0);
|
||||
try {
|
||||
var resultBuilder = new BackupCreationResult.Builder();
|
||||
|
||||
await RunBackupProcedure(resultBuilder);
|
||||
|
||||
var result = resultBuilder.Build();
|
||||
if (result.Kind == BackupCreationResultKind.Success) {
|
||||
var warningCount = result.Warnings.Count();
|
||||
if (warningCount == 0) {
|
||||
logger.Information("Backup finished successfully.");
|
||||
}
|
||||
else {
|
||||
logger.Warning("Backup finished with {Warnings} warning(s).", warningCount);
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.Warning("Backup failed: {Reason}", result.Kind.ToSentence());
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
process.RemoveOutputListener(listener.OnOutput);
|
||||
|
||||
var resultBuilder = new BackupCreationResult.Builder();
|
||||
string? backupFilePath;
|
||||
|
||||
using (var dispatcher = new BackupServerCommandDispatcher(logger, process, cancellationToken)) {
|
||||
backupFilePath = await CreateWorldArchive(dispatcher, resultBuilder);
|
||||
}
|
||||
|
||||
if (backupFilePath != null) {
|
||||
await CompressWorldArchive(backupFilePath, resultBuilder);
|
||||
}
|
||||
|
||||
var result = resultBuilder.Build();
|
||||
LogBackupResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task RunBackupProcedure(BackupCreationResult.Builder resultBuilder) {
|
||||
|
||||
private async Task<string?> CreateWorldArchive(BackupServerCommandDispatcher dispatcher, BackupCreationResult.Builder resultBuilder) {
|
||||
try {
|
||||
await DisableAutomaticSaving();
|
||||
await SaveAllChunks();
|
||||
await new BackupArchiver(destinationBasePath, temporaryBasePath, loggerName, process.InstanceProperties, cancellationToken).ArchiveWorld(resultBuilder);
|
||||
await dispatcher.DisableAutomaticSaving();
|
||||
await dispatcher.SaveAllChunks();
|
||||
return await new BackupArchiver(destinationBasePath, temporaryBasePath, loggerName, process.InstanceProperties, cancellationToken).ArchiveWorld(resultBuilder);
|
||||
} catch (OperationCanceledException) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.BackupCancelled;
|
||||
logger.Warning("Backup creation was cancelled.");
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
resultBuilder.Kind = BackupCreationResultKind.UnknownError;
|
||||
logger.Error(e, "Caught exception while creating an instance backup.");
|
||||
return null;
|
||||
} finally {
|
||||
try {
|
||||
await EnableAutomaticSaving();
|
||||
await dispatcher.EnableAutomaticSaving();
|
||||
} catch (OperationCanceledException) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
@ -104,66 +93,25 @@ sealed partial class BackupManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOff, cancellationToken);
|
||||
await listener.AutomaticSavingDisabled.Task.WaitAsync(cancellationToken);
|
||||
private async Task CompressWorldArchive(string filePath, BackupCreationResult.Builder resultBuilder) {
|
||||
var compressedFilePath = await BackupCompressor.Compress(filePath, cancellationToken);
|
||||
if (compressedFilePath == null) {
|
||||
resultBuilder.Warnings |= BackupCreationWarnings.CouldNotCompressWorldArchive;
|
||||
}
|
||||
}
|
||||
|
||||
private 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 listener.SavedTheGame.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async Task EnableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOn, cancellationToken);
|
||||
await listener.AutomaticSavingEnabled.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed partial class BackupCommandListener {
|
||||
[GeneratedRegex(@"^\[(?:.*?)\] \[Server thread/INFO\]: (.*?)$", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex ServerThreadInfoRegex();
|
||||
|
||||
private readonly ILogger logger;
|
||||
|
||||
public BackupCommandListener(ILogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public TaskCompletionSource AutomaticSavingDisabled { get; } = new ();
|
||||
public TaskCompletionSource SavedTheGame { get; } = new ();
|
||||
public TaskCompletionSource AutomaticSavingEnabled { get; } = new ();
|
||||
|
||||
public void OnOutput(object? sender, string? line) {
|
||||
if (line == null) {
|
||||
private void LogBackupResult(BackupCreationResult result) {
|
||||
if (result.Kind != BackupCreationResultKind.Success) {
|
||||
logger.Warning("Backup failed: {Reason}", result.Kind.ToSentence());
|
||||
return;
|
||||
}
|
||||
|
||||
var match = ServerThreadInfoRegex().Match(line);
|
||||
if (!match.Success) {
|
||||
return;
|
||||
|
||||
var warningCount = result.Warnings.Count();
|
||||
if (warningCount > 0) {
|
||||
logger.Warning("Backup finished with {Warnings} warning(s).", warningCount);
|
||||
}
|
||||
|
||||
string info = match.Groups[1].Value;
|
||||
|
||||
if (!AutomaticSavingDisabled.Task.IsCompleted) {
|
||||
if (info == "Automatic saving is now disabled") {
|
||||
logger.Verbose("Detected that automatic saving is disabled.");
|
||||
AutomaticSavingDisabled.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!SavedTheGame.Task.IsCompleted) {
|
||||
if (info == "Saved the game") {
|
||||
logger.Verbose("Detected that the game is saved.");
|
||||
SavedTheGame.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!AutomaticSavingEnabled.Task.IsCompleted) {
|
||||
if (info == "Automatic saving is now enabled") {
|
||||
logger.Verbose("Detected that automatic saving is enabled.");
|
||||
AutomaticSavingEnabled.SetResult();
|
||||
}
|
||||
else {
|
||||
logger.Information("Backup finished successfully.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Phantom.Agent.Minecraft.Command;
|
||||
using Phantom.Agent.Minecraft.Instance;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Agent.Services.Backups;
|
||||
|
||||
sealed partial class BackupServerCommandDispatcher : IDisposable {
|
||||
[GeneratedRegex(@"^\[(?:.*?)\] \[Server thread/INFO\]: (.*?)$", RegexOptions.NonBacktracking)]
|
||||
private static partial Regex ServerThreadInfoRegex();
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly InstanceProcess process;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
private readonly TaskCompletionSource automaticSavingDisabled = new ();
|
||||
private readonly TaskCompletionSource savedTheGame = new ();
|
||||
private readonly TaskCompletionSource automaticSavingEnabled = new ();
|
||||
|
||||
public BackupServerCommandDispatcher(ILogger logger, InstanceProcess process, CancellationToken cancellationToken) {
|
||||
this.logger = logger;
|
||||
this.process = process;
|
||||
this.cancellationToken = cancellationToken;
|
||||
|
||||
this.process.AddOutputListener(OnOutput, maxLinesToReadFromHistory: 0);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose() {
|
||||
process.RemoveOutputListener(OnOutput);
|
||||
}
|
||||
|
||||
public async Task DisableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOff, 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(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task EnableAutomaticSaving() {
|
||||
await process.SendCommand(MinecraftCommand.SaveOn, cancellationToken);
|
||||
await automaticSavingEnabled.Task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void OnOutput(object? sender, string? line) {
|
||||
if (line == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var match = ServerThreadInfoRegex().Match(line);
|
||||
if (!match.Success) {
|
||||
return;
|
||||
}
|
||||
|
||||
string info = match.Groups[1].Value;
|
||||
|
||||
if (!automaticSavingDisabled.Task.IsCompleted) {
|
||||
if (info == "Automatic saving is now disabled") {
|
||||
logger.Verbose("Detected that automatic saving is disabled.");
|
||||
automaticSavingDisabled.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!savedTheGame.Task.IsCompleted) {
|
||||
if (info == "Saved the game") {
|
||||
logger.Verbose("Detected that the game is saved.");
|
||||
savedTheGame.SetResult();
|
||||
}
|
||||
}
|
||||
else if (!automaticSavingEnabled.Task.IsCompleted) {
|
||||
if (info == "Automatic saving is now enabled") {
|
||||
logger.Verbose("Detected that automatic saving is enabled.");
|
||||
automaticSavingEnabled.SetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user