refactor(host): single-source ChannelPolicy, space-signed file grants, whole-file registration
- all authorization rules (owner/creator, per-channel read/write grants, who may remove whom, delete rules, structure management) live in ChannelPolicy; a test fails if they are reimplemented elsewhere - ChannelInfo carries what the caller may do (can_*), member and invitation lists say who can be removed; clients display it and never derive it - the host no longer stores or calls the file service: it only signs short-lived tickets from space permissions (create/put/get/delete/domain), and keeps an opaque fileDomain reference on the channel - a whole file is registered in one request (last frame header + per-frame length and digest) instead of one request per 1 MiB frame; the signed manifest still has to match every digest - drop file quota/threshold, receipts, reconcile and delete-queue code from the host - contract generator emits constants for enums, error codes, token roles, permissions and share states
This commit is contained in:
1 parent
ccb2c074d6
commit
17638b6e20
36 files changed
+926
-589
No files matched your search
@@ -17,7 +17,7 @@ public sealed class BackupWorker(WebDropOptions options, SqliteChannelStore stor
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var backup = options.Backup;
|
||||
if (!backup.Enabled || !backup.WebDav.Configured) return;
|
||||
if (!backup.Enabled || !backup.S3.Configured) return;
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try { await Task.Delay(DailySchedule.Next(DateTime.Now, backup.TimeOfDay) - DateTime.Now, stoppingToken).ConfigureAwait(false); } catch (OperationCanceledException) { break; }
|
||||
@@ -30,7 +30,7 @@ public sealed class BackupWorker(WebDropOptions options, SqliteChannelStore stor
|
||||
public async Task<(string Name, int Purged)> RunOnceAsync(DateTime now, CancellationToken ct)
|
||||
{
|
||||
var backup = options.Backup;
|
||||
var dav = new WebDavClient(_http, backup.WebDav);
|
||||
var s3 = new S3Client(_http, backup.S3);
|
||||
string tmp = Path.Combine(options.DataDir, "v2", "backup-tmp");
|
||||
Directory.CreateDirectory(tmp);
|
||||
string name = $"{Prefix}{now:yyyyMMdd-HHmm}.db.gz";
|
||||
@@ -43,7 +43,7 @@ public sealed class BackupWorker(WebDropOptions options, SqliteChannelStore stor
|
||||
await using (var zip = new GZipStream(target, CompressionLevel.SmallestSize))
|
||||
await source.CopyToAsync(zip, ct).ConfigureAwait(false);
|
||||
await using (var upload = File.OpenRead(gz))
|
||||
await dav.PutAsync($"chat/{name}", upload, ct).ConfigureAwait(false);
|
||||
await s3.PutAsync($"chat/{name}", upload, ct).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -53,11 +53,11 @@ public sealed class BackupWorker(WebDropOptions options, SqliteChannelStore stor
|
||||
// 保留期:按文件名里的日期清理更早的
|
||||
int purged = 0;
|
||||
var cutoff = now.Date.AddDays(-Math.Max(0, backup.RetainDays));
|
||||
foreach (var existing in await dav.ListAsync("chat", ct).ConfigureAwait(false))
|
||||
foreach (var existing in await s3.ListAsync("chat", ct).ConfigureAwait(false))
|
||||
{
|
||||
if (!existing.StartsWith(Prefix, StringComparison.Ordinal) || existing.Length < Prefix.Length + 8) continue;
|
||||
if (!DateTime.TryParseExact(existing.AsSpan(Prefix.Length, 8), "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date) || date >= cutoff) continue;
|
||||
if (await dav.DeleteAsync($"chat/{existing}", ct).ConfigureAwait(false)) purged++;
|
||||
if (await s3.DeleteAsync($"chat/{existing}", ct).ConfigureAwait(false)) purged++;
|
||||
}
|
||||
if (purged > 0) logger.LogInformation("清理了 {Count} 份过期的数据库备份", purged);
|
||||
return (name, purged);
|
||||
|
||||
Reference in new issue
Block a user