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