131 lines
5.0 KiB
C#
131 lines
5.0 KiB
C#
namespace WebDrop.Core;
|
|
|
|
public interface IClock
|
|
{
|
|
long NowMs { get; }
|
|
}
|
|
|
|
public sealed class SystemClock : IClock
|
|
{
|
|
public long NowMs => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
}
|
|
|
|
/// <summary>大帧的字节存储。键由服务生成,实现不得解析。</summary>
|
|
public interface IBlobStore
|
|
{
|
|
ValueTask PutAsync(string key, ReadOnlyMemory<byte> data, CancellationToken ct = default);
|
|
ValueTask<byte[]?> ReadAsync(string key, CancellationToken ct = default);
|
|
void Delete(string key);
|
|
void DeletePrefix(string prefix);
|
|
IEnumerable<(string Key, DateTimeOffset LastWrite)> Enumerate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建频道前的人机/滥用校验(Cap 的 PoW 验证码)。验证码本身不属于本项目:
|
|
/// 校验失败抛 CaptchaRequired,验证服务不可用抛 CaptchaUnavailable(两者对用户的含义不同)。
|
|
/// </summary>
|
|
public interface ICaptchaVerifier
|
|
{
|
|
bool Enabled { get; }
|
|
Task VerifyAsync(string? token, CancellationToken ct);
|
|
}
|
|
|
|
/// <summary>不启用验证码(开发和小型自托管)。</summary>
|
|
public sealed class NullCaptchaVerifier : ICaptchaVerifier
|
|
{
|
|
public bool Enabled => false;
|
|
public Task VerifyAsync(string? token, CancellationToken ct) => Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>服务提交后发出的通知,传输层据此唤醒订阅者。</summary>
|
|
public interface IChannelEvents
|
|
{
|
|
void Advanced(Id16 channelId, long seq);
|
|
void SettingsChanged(Id16 channelId);
|
|
void SeatsChanged(Id16 channelId);
|
|
void ChannelDeleted(Id16 channelId);
|
|
}
|
|
|
|
public sealed class NullChannelEvents : IChannelEvents
|
|
{
|
|
public void Advanced(Id16 channelId, long seq) { }
|
|
public void SettingsChanged(Id16 channelId) { }
|
|
public void SeatsChanged(Id16 channelId) { }
|
|
public void ChannelDeleted(Id16 channelId) { }
|
|
}
|
|
|
|
/// <summary>频道和分享资源共用的读写事务入口。Write 串行执行,抛出异常即回滚。</summary>
|
|
public interface IResourceStore
|
|
{
|
|
T Read<T>(Func<IResourceTx, T> action);
|
|
T Write<T>(Func<IResourceTx, T> action);
|
|
}
|
|
|
|
/// <summary>单个数据库事务内的频道、分享与帧操作。</summary>
|
|
public interface IResourceTx
|
|
{
|
|
// 便捷分享
|
|
Share? GetShare(Id16 id);
|
|
void InsertShare(Share share);
|
|
void UpdateShare(Share share);
|
|
IReadOnlyList<Id16> ListExpiredShares(long now, int limit);
|
|
bool HasResourceId(Id16 id);
|
|
int CountShareVisitors(Id16 id);
|
|
bool HasShareVisitor(Id16 id, ReadOnlySpan<byte> endpointHash);
|
|
void InsertShareVisitor(Id16 id, ReadOnlySpan<byte> endpointHash, long joinedAt);
|
|
void DeleteShareRows(Id16 id);
|
|
IReadOnlyList<ShareItem> ListShareItems(Id16 id);
|
|
int CountShareItems(Id16 id);
|
|
void AddShareBytes(Id16 id, long delta);
|
|
IReadOnlyList<ShareItem> ListExpiredShareItems(long now, int limit);
|
|
ShareItem? GetShareItem(Id16 shareId, Id16 itemId);
|
|
void InsertShareItem(ShareItem item);
|
|
void UpdateShareItem(ShareItem item);
|
|
void DeleteShareItemRows(Id16 shareId, Id16 itemId);
|
|
bool HasDownloadGrant(Id16 resourceId, Id16 itemId, ReadOnlySpan<byte> endpointHash);
|
|
void AddDownloadGrant(Id16 resourceId, Id16 itemId, ReadOnlySpan<byte> endpointHash);
|
|
ShareFrame? GetShareFrame(Id16 shareId, Id16 itemId, uint index);
|
|
void InsertShareFrame(ShareFrame frame);
|
|
IReadOnlyList<ShareFrame> ListShareFrames(Id16 shareId, Id16 itemId, uint fromIndex, int limit);
|
|
|
|
// 频道
|
|
Channel? GetChannel(Id16 id);
|
|
void InsertChannel(Channel channel);
|
|
void UpdateChannel(Id16 id, ChannelSettings settings, long expiresAt);
|
|
void TouchChannel(Id16 id, long expiresAt);
|
|
void AddChannelBytes(Id16 id, long delta);
|
|
void SetCompactedThrough(Id16 id, long seq);
|
|
long NextSeq(Id16 id);
|
|
void DeleteChannelRows(Id16 id);
|
|
IReadOnlyList<Id16> ListExpiredChannels(long now, int limit);
|
|
|
|
// 席位
|
|
Seat? GetSeat(Id16 channelId, ReadOnlySpan<byte> endpointHash);
|
|
int CountSeats(Id16 channelId);
|
|
void InsertSeat(Id16 channelId, Seat seat);
|
|
bool DeleteSeat(Id16 channelId, ReadOnlySpan<byte> endpointHash);
|
|
void SetSeatLabel(Id16 channelId, ReadOnlySpan<byte> endpointHash, byte[] label);
|
|
IReadOnlyList<Seat> ListSeats(Id16 channelId);
|
|
|
|
// 条目
|
|
Item? GetItem(Id16 channelId, Id16 itemId);
|
|
void InsertItem(Item item);
|
|
void UpdateItem(Item item);
|
|
void DeleteItemRows(Id16 channelId, Id16 itemId);
|
|
int CountItems(Id16 channelId);
|
|
IReadOnlyList<Item> ListOldestItems(Id16 channelId, int limit);
|
|
Item? FindSnapshotItem(Id16 channelId, Id16 excludeItemId);
|
|
IReadOnlyList<Item> ListExpiredItems(long now, int limit);
|
|
|
|
// 帧
|
|
StoredFrame? GetFrame(Id16 channelId, Id16 itemId, uint index);
|
|
void InsertFrame(StoredFrame frame);
|
|
IReadOnlyList<StoredFrame> ListFrames(Id16 channelId, Id16 itemId, uint fromIndex, int limit);
|
|
bool BlobKeyExists(string blobKey);
|
|
|
|
// 日志
|
|
void InsertLog(Id16 channelId, LogEntry entry);
|
|
IReadOnlyList<LogEntry> ReadLog(Id16 channelId, long fromSeq, int limit);
|
|
long PruneTombstones(Id16 channelId, long olderThanMs);
|
|
}
|