feat(bake): publish validated pak artifacts atomically
This commit is contained in:
parent
90b378cc70
commit
999201cca7
9 changed files with 362 additions and 14 deletions
61
src/AcDream.Bake/BakeOutputTransaction.cs
Normal file
61
src/AcDream.Bake/BakeOutputTransaction.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using AcDream.Content.Pak;
|
||||
|
||||
namespace AcDream.Bake;
|
||||
|
||||
/// <summary>
|
||||
/// Publishes a bake as one same-volume filesystem transaction. Work and
|
||||
/// validation happen against a unique adjacent temporary file; failure or
|
||||
/// cancellation leaves an existing destination untouched.
|
||||
/// </summary>
|
||||
public static class BakeOutputTransaction
|
||||
{
|
||||
public static TResult WriteValidateAndPublish<TResult>(
|
||||
string destinationPath,
|
||||
Func<string, TResult> writeTemporary,
|
||||
Action<string, TResult> validateTemporary,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(destinationPath);
|
||||
ArgumentNullException.ThrowIfNull(writeTemporary);
|
||||
ArgumentNullException.ThrowIfNull(validateTemporary);
|
||||
|
||||
string fullDestination = Path.GetFullPath(destinationPath);
|
||||
string? directory = Path.GetDirectoryName(fullDestination);
|
||||
if (string.IsNullOrEmpty(directory))
|
||||
throw new InvalidOperationException("destination has no parent directory");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
string temporaryPath = Path.Combine(
|
||||
directory,
|
||||
$".{Path.GetFileName(fullDestination)}.{Guid.NewGuid():N}.tmp");
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
TResult result = writeTemporary(temporaryPath);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
validateTemporary(temporaryPath, result);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (File.Exists(fullDestination))
|
||||
File.Replace(temporaryPath, fullDestination, null, ignoreMetadataErrors: true);
|
||||
else
|
||||
File.Move(temporaryPath, fullDestination);
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(temporaryPath))
|
||||
File.Delete(temporaryPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Preserve the original exception. An adjacent .tmp is
|
||||
// recognizable and never mistaken for a published pak.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue