feat(launcher): implement verified atomic updates
This commit is contained in:
parent
2198a0cc8e
commit
2d2a5b5046
34 changed files with 6755 additions and 61 deletions
84
src/AcDream.Launcher.Core/Updates/AtomicJsonFile.cs
Normal file
84
src/AcDream.Launcher.Core/Updates/AtomicJsonFile.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace AcDream.Launcher.Core.Updates;
|
||||
|
||||
internal static class AtomicJsonFile
|
||||
{
|
||||
internal static async Task WriteAsync<T>(
|
||||
string path,
|
||||
T value,
|
||||
JsonSerializerOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
string directory = Path.GetDirectoryName(fullPath)
|
||||
?? throw new InvalidOperationException("The JSON path has no parent directory.");
|
||||
Directory.CreateDirectory(directory);
|
||||
string temporaryPath = Path.Combine(
|
||||
directory,
|
||||
$".{Path.GetFileName(fullPath)}.{Guid.NewGuid():N}.tmp");
|
||||
try
|
||||
{
|
||||
await using (var stream = new FileStream(
|
||||
temporaryPath,
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
16 * 1024,
|
||||
FileOptions.Asynchronous | FileOptions.WriteThrough))
|
||||
{
|
||||
await JsonSerializer.SerializeAsync(
|
||||
stream,
|
||||
value,
|
||||
options,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
stream.Flush(flushToDisk: true);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
File.Move(temporaryPath, fullPath, overwrite: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
VerifiedArtifactDownloader.TryDelete(temporaryPath);
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task WriteBytesAsync(
|
||||
string path,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
string directory = Path.GetDirectoryName(fullPath)
|
||||
?? throw new InvalidOperationException("The file path has no parent directory.");
|
||||
Directory.CreateDirectory(directory);
|
||||
string temporaryPath = Path.Combine(
|
||||
directory,
|
||||
$".{Path.GetFileName(fullPath)}.{Guid.NewGuid():N}.tmp");
|
||||
try
|
||||
{
|
||||
await using (var stream = new FileStream(
|
||||
temporaryPath,
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
16 * 1024,
|
||||
FileOptions.Asynchronous | FileOptions.WriteThrough))
|
||||
{
|
||||
await stream.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
|
||||
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
stream.Flush(flushToDisk: true);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
File.Move(temporaryPath, fullPath, overwrite: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
VerifiedArtifactDownloader.TryDelete(temporaryPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue