feat: integrate client and server sharing workflows

This commit is contained in:
justme committed 2026-10-02 10:49:45 +08:00
1 parent 42b93ae2d8
commit 7406e2fe37
101 files changed
+3153 -253

No files matched your search

@@ -10,7 +10,7 @@ namespace WebDrop.Storage;
/// </summary>
public sealed class SqliteChannelStore : IResourceStore, IDisposable
{
private const int SchemaVersion = 2;
private const int SchemaVersion = 3;
private readonly string _connectionString;
private readonly SqliteConnection _writeConn;
@@ -93,6 +93,7 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
using var tx = _writeConn.BeginTransaction();
if (version == 0) Exec(_writeConn, SchemaV1);
if (version < 2) Exec(_writeConn, SchemaV2);
if (version < 3) Exec(_writeConn, SchemaV3);
Exec(_writeConn, $"PRAGMA user_version={SchemaVersion};");
tx.Commit();
}
@@ -222,6 +223,16 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
CREATE INDEX ix_share_frames_blob ON share_frames(blob_key) WHERE blob_key IS NOT NULL;
""";
private const string SchemaV3 = """
CREATE TABLE download_grants (
resource_id BLOB NOT NULL,
item_id BLOB NOT NULL,
endpoint_hash BLOB NOT NULL,
PRIMARY KEY (resource_id, item_id, endpoint_hash)
) WITHOUT ROWID;
CREATE INDEX ix_download_grants_resource ON download_grants(resource_id);
""";
private sealed class SqliteTx(SqliteConnection conn) : IResourceTx
{
private const string ChannelCols = "id, mode, settings, read_hash, write_hash, owner_hash, created_at, expires_at, last_seq, compacted_through, bytes";
@@ -319,6 +330,7 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
public void DeleteShareRows(Id16 id)
{
byte[] key = id.ToArray();
Run("DELETE FROM download_grants WHERE resource_id=@id", ("@id", key));
Run("DELETE FROM share_frames WHERE share_id=@id", ("@id", key));
Run("DELETE FROM share_items WHERE share_id=@id", ("@id", key));
Run("DELETE FROM share_visitors WHERE share_id=@id", ("@id", key));
@@ -351,10 +363,19 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
("@ex", i.ExpiresAt), ("@cd", i.CloseDeadline), ("@s", i.ShareId.ToArray()), ("@i", i.ItemId.ToArray()));
public void DeleteShareItemRows(Id16 shareId, Id16 itemId)
{
Run("DELETE FROM download_grants WHERE resource_id=@s AND item_id=@i", ("@s", shareId.ToArray()), ("@i", itemId.ToArray()));
Run("DELETE FROM share_frames WHERE share_id=@s AND item_id=@i", ("@s", shareId.ToArray()), ("@i", itemId.ToArray()));
Run("DELETE FROM share_items WHERE share_id=@s AND item_id=@i", ("@s", shareId.ToArray()), ("@i", itemId.ToArray()));
}
public bool HasDownloadGrant(Id16 resourceId, Id16 itemId, ReadOnlySpan<byte> endpointHash) => Scalar(
"SELECT 1 FROM download_grants WHERE resource_id=@r AND item_id=@i AND endpoint_hash=@e",
("@r", resourceId.ToArray()), ("@i", itemId.ToArray()), ("@e", endpointHash.ToArray())) is not null;
public void AddDownloadGrant(Id16 resourceId, Id16 itemId, ReadOnlySpan<byte> endpointHash) => Run(
"INSERT OR IGNORE INTO download_grants (resource_id,item_id,endpoint_hash) VALUES (@r,@i,@e)",
("@r", resourceId.ToArray()), ("@i", itemId.ToArray()), ("@e", endpointHash.ToArray()));
private static ShareFrame MapShareFrame(SqliteDataReader r) => new(
IdAt(r, 0), IdAt(r, 1), (uint)r.GetInt64(2), r.GetInt64(3), r.GetInt32(4), Blob(r, 5), BlobOrNull(r, 6),
r.IsDBNull(7) ? null : r.GetString(7));
@@ -411,6 +432,7 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
public void DeleteChannelRows(Id16 id)
{
byte[] key = id.ToArray();
Run("DELETE FROM download_grants WHERE resource_id=@c", ("@c", key));
Run("DELETE FROM frames WHERE channel_id=@c", ("@c", key));
Run("DELETE FROM log WHERE channel_id=@c", ("@c", key));
Run("DELETE FROM items WHERE channel_id=@c", ("@c", key));
@@ -476,6 +498,7 @@ public sealed class SqliteChannelStore : IResourceStore, IDisposable
public void DeleteItemRows(Id16 channelId, Id16 itemId)
{
byte[] c = channelId.ToArray(), i = itemId.ToArray();
Run("DELETE FROM download_grants WHERE resource_id=@c AND item_id=@i", ("@c", c), ("@i", i));
Run("DELETE FROM frames WHERE channel_id=@c AND item_id=@i", ("@c", c), ("@i", i));
// 墓碑保留,其余记录(帧、announce)随条目一起删除
Run("DELETE FROM log WHERE channel_id=@c AND item_id=@i AND kind<>@t", ("@c", c), ("@i", i), ("@t", (int)LogKind.Tombstone));