using System.Text.Json.Serialization; namespace WebDrop.Core; /// 服务端限制,由配置绑定。 public sealed class ServerLimits { public int MaxPayloadBytes { get; set; } = (1 << 20) + 16; public int MaxFramesPerItem { get; set; } = 65_536; public long MaxChannelBytes { get; set; } = 1L << 30; public long MaxWorkspaceBytes { get; set; } = 1L << 40; public long MaxWorkspaceChannelBytes { get; set; } = 10L << 30; /// 最大账号成员数。属性名和 info 字段保持旧线格式兼容。 public int MaxMembers { get; set; } = 64; public int MaxVisitors { get; set; } = 10_000; public int MinTtlSeconds { get; set; } = 60; public int MaxTtlSeconds { get; set; } = 604_800; public int MaxItemsLimit { get; set; } = 10_000; public int InlineThresholdBytes { get; set; } = 64 * 1024; public int DownloadGraceSeconds { get; set; } = 300; public int TombstoneRetentionSeconds { get; set; } = 604_800; } public static class ServerSettingChoices { public static readonly int[] FileMaxDownloads = [0, 1, 3, 5, 10, 20]; public static bool IsFileMaxDownloadsAllowed(long value, int currentDefault) => value == currentDefault || value is >= int.MinValue and <= int.MaxValue && Array.IndexOf(FileMaxDownloads, (int)value) >= 0; } public sealed class ChannelSettings { [JsonPropertyName("join_policy")] public JoinPolicy JoinPolicy { get; set; } = JoinPolicy.Open; [JsonPropertyName("name")] public string? Name { get; set; } /// 成员人数上限;线字段名保留为 seats 以兼容已有频道设置。 [JsonPropertyName("max_members")] public int MaxMembers { get; set; } = 2; [JsonPropertyName("members_can_upload")] public bool MembersCanUpload { get; set; } = true; [JsonPropertyName("delete_policy")] public DeletePolicy DeletePolicy { get; set; } = DeletePolicy.Own; [JsonPropertyName("ttl_seconds")] public int TtlSeconds { get; set; } = 86_400; [JsonPropertyName("max_items")] public int MaxItems { get; set; } = 1000; [JsonPropertyName("file_max_downloads")] public int FileMaxDownloads { get; set; } = 10; [JsonPropertyName("ui_restrict_copy_download")] public bool UiRestrictCopyDownload { get; set; } public void Validate(ServerLimits limits) { if (!Enum.IsDefined(JoinPolicy)) throw new WebDropException(ErrorCode.BadRequest); if (Name is { } name && (name.Length > 48 || name.Any(char.IsControl))) throw new WebDropException(ErrorCode.BadRequest); if (MaxMembers < 1 || MaxMembers > limits.MaxMembers) throw new WebDropException(ErrorCode.BadRequest); if (TtlSeconds < limits.MinTtlSeconds || TtlSeconds > limits.MaxTtlSeconds) throw new WebDropException(ErrorCode.BadRequest); if (MaxItems < 1 || MaxItems > limits.MaxItemsLimit) throw new WebDropException(ErrorCode.BadRequest); if (FileMaxDownloads < 0) throw new WebDropException(ErrorCode.BadRequest); } } public sealed class ChannelSettingsPatch { [JsonPropertyName("join_policy")] public JoinPolicy? JoinPolicy { get; init; } [JsonPropertyName("join_password")] public string? JoinPassword { get; init; } [JsonPropertyName("name")] public string? Name { get; init; } [JsonPropertyName("max_members")] public int? MaxMembers { get; init; } [JsonPropertyName("members_can_upload")] public bool? MembersCanUpload { get; init; } [JsonPropertyName("delete_policy")] public DeletePolicy? DeletePolicy { get; init; } [JsonPropertyName("ttl_seconds")] public int? TtlSeconds { get; init; } [JsonPropertyName("max_items")] public int? MaxItems { get; init; } [JsonPropertyName("file_max_downloads")] public int? FileMaxDownloads { get; init; } [JsonPropertyName("ui_restrict_copy_download")] public bool? UiRestrictCopyDownload { get; init; } /// 空间头像 data URL;不传表示不改,空字符串表示移除。只有空间主能改。 [JsonPropertyName("avatar")] public string? Avatar { get; init; } /// 空间头像的有损小图(data URL):空间列表、标题默认只分发它,原图在预览时才下载 [JsonPropertyName("avatar_thumb")] public string? AvatarThumb { get; init; } public ChannelSettings ApplyTo(ChannelSettings s) => new() { JoinPolicy = JoinPolicy ?? s.JoinPolicy, Name = Name is null ? s.Name : string.IsNullOrWhiteSpace(Name) ? null : Name.Trim(), MaxMembers = MaxMembers ?? s.MaxMembers, MembersCanUpload = MembersCanUpload ?? s.MembersCanUpload, DeletePolicy = DeletePolicy ?? s.DeletePolicy, TtlSeconds = TtlSeconds ?? s.TtlSeconds, MaxItems = MaxItems ?? s.MaxItems, FileMaxDownloads = FileMaxDownloads ?? s.FileMaxDownloads, UiRestrictCopyDownload = UiRestrictCopyDownload ?? s.UiRestrictCopyDownload, }; } public sealed class ShareSettings { [JsonPropertyName("ttl_seconds")] public int TtlSeconds { get; set; } = 86_400; [JsonPropertyName("max_visitors")] public int MaxVisitors { get; set; } [JsonPropertyName("file_max_downloads")] public int FileMaxDownloads { get; set; } = 10; public void Validate(ServerLimits limits) { if (TtlSeconds < limits.MinTtlSeconds || TtlSeconds > limits.MaxTtlSeconds || MaxVisitors < 0 || MaxVisitors > limits.MaxVisitors || FileMaxDownloads < 0) throw new WebDropException(ErrorCode.BadRequest); } } public sealed class ShareSettingsPatch { [JsonPropertyName("ttl_seconds")] public int? TtlSeconds { get; init; } [JsonPropertyName("max_visitors")] public int? MaxVisitors { get; init; } [JsonPropertyName("file_max_downloads")] public int? FileMaxDownloads { get; init; } public ShareSettings ApplyTo(ShareSettings s) => new() { TtlSeconds = TtlSeconds ?? s.TtlSeconds, MaxVisitors = MaxVisitors ?? s.MaxVisitors, FileMaxDownloads = FileMaxDownloads ?? s.FileMaxDownloads, }; } /// 创建接口与 /info 使用同一个配置来源;旧存储对象的字段默认值不作迁移。 public static class CreationDefaults { public const ChannelMode ChannelMode = WebDrop.Core.ChannelMode.Enc; public static ChannelSettings Channel(ServerLimits limits) => new() { JoinPolicy = JoinPolicy.Invite, MaxMembers = Math.Min(10, limits.MaxMembers), TtlSeconds = Math.Clamp(86_400, limits.MinTtlSeconds, limits.MaxTtlSeconds), MaxItems = Math.Min(1000, limits.MaxItemsLimit), }; public static ShareSettings Share(ServerLimits limits) => new() { TtlSeconds = Math.Clamp(86_400, limits.MinTtlSeconds, limits.MaxTtlSeconds), MaxVisitors = Math.Min(10, limits.MaxVisitors), }; }