LumaTunnel
【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。
关注
0
Fork
0
Star
0
using System.Text.Json;
using LumaTunnel.Shared.Protocol;
namespace LumaTunnel.Server.Core.Persistence;
public sealed class AtomicJsonStore<T>(string path) : IDisposable where T : class
{
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly string _path = path;
public async Task<T> LoadOrCreateAsync(Func<T> factory, CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!File.Exists(_path))
{
var value = factory();
await SaveCoreAsync(value, cancellationToken).ConfigureAwait(false);
return value;
}
await using var stream = File.OpenRead(_path);
return await JsonSerializer.DeserializeAsync<T>(stream, TunnelMessageSerializer.Options, cancellationToken).ConfigureAwait(false)
?? throw new InvalidDataException($"The JSON document at '{_path}' is empty.");
}
finally
{
_gate.Release();
}
}
public async Task SaveAsync(T value, CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await SaveCoreAsync(value, cancellationToken).ConfigureAwait(false);
}
finally
{
_gate.Release();
}
}
private async Task SaveCoreAsync(T value, CancellationToken cancellationToken)
{
var directory = Path.GetDirectoryName(_path) ?? throw new InvalidOperationException("Store path has no directory.");
Directory.CreateDirectory(directory);
var tempPath = Path.Combine(directory, $".{Path.GetFileName(_path)}.{Guid.NewGuid():N}.tmp");
try
{
await using (var stream = new FileStream(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 16 * 1024, FileOptions.WriteThrough | FileOptions.Asynchronous))
{
await JsonSerializer.SerializeAsync(stream, value, TunnelMessageSerializer.Options, cancellationToken).ConfigureAwait(false);
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
File.Move(tempPath, _path, true);
}
finally
{
if (File.Exists(tempPath))
File.Delete(tempPath);
}
}
public void Dispose()
{
_gate.Dispose();
GC.SuppressFinalize(this);
}
}
using System.Text.Json;
using LumaTunnel.Shared.Protocol;
namespace LumaTunnel.Server.Core.Persistence;
public sealed class AtomicJsonStore<T>(string path) : IDisposable where T : class
{
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly string _path = path;
public async Task<T> LoadOrCreateAsync(Func<T> factory, CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!File.Exists(_path))
{
var value = factory();
await SaveCoreAsync(value, cancellationToken).ConfigureAwait(false);
return value;
}
await using var stream = File.OpenRead(_path);
return await JsonSerializer.DeserializeAsync<T>(stream, TunnelMessageSerializer.Options, cancellationToken).ConfigureAwait(false)
?? throw new InvalidDataException($"The JSON document at '{_path}' is empty.");
}
finally
{
_gate.Release();
}
}
public async Task SaveAsync(T value, CancellationToken cancellationToken = default)
{
await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
await SaveCoreAsync(value, cancellationToken).ConfigureAwait(false);
}
finally
{
_gate.Release();
}
}
private async Task SaveCoreAsync(T value, CancellationToken cancellationToken)
{
var directory = Path.GetDirectoryName(_path) ?? throw new InvalidOperationException("Store path has no directory.");
Directory.CreateDirectory(directory);
var tempPath = Path.Combine(directory, $".{Path.GetFileName(_path)}.{Guid.NewGuid():N}.tmp");
try
{
await using (var stream = new FileStream(tempPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, 16 * 1024, FileOptions.WriteThrough | FileOptions.Asynchronous))
{
await JsonSerializer.SerializeAsync(stream, value, TunnelMessageSerializer.Options, cancellationToken).ConfigureAwait(false);
await stream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
File.Move(tempPath, _path, true);
}
finally
{
if (File.Exists(tempPath))
File.Delete(tempPath);
}
}
public void Dispose()
{
_gate.Dispose();
GC.SuppressFinalize(this);
}
}