feat: add fnOS packaging, storage workflows and release pipeline
This commit is contained in:
@@ -8,8 +8,16 @@
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
public string CookieId { get; set; }
|
||||
|
||||
public int Total { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Status { get; set; }
|
||||
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public sealed class DouyinFollowLookupRequest
|
||||
{
|
||||
public string CookieId { get; set; }
|
||||
|
||||
public string DouyinNo { get; set; }
|
||||
}
|
||||
|
||||
public sealed class DouyinFollowLookupResult
|
||||
{
|
||||
public string Query { get; set; }
|
||||
|
||||
public List<DouyinFollowCandidate> Candidates { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class DouyinFollowCandidate
|
||||
{
|
||||
public string SecUid { get; set; }
|
||||
|
||||
public string UperId { get; set; }
|
||||
|
||||
public string DouyinNo { get; set; }
|
||||
|
||||
public string UniqueId { get; set; }
|
||||
|
||||
public string ShortId { get; set; }
|
||||
|
||||
public string UperName { get; set; }
|
||||
|
||||
public string UperAvatar { get; set; }
|
||||
|
||||
public string Signature { get; set; }
|
||||
|
||||
public string Enterprise { get; set; }
|
||||
|
||||
public long FollowerCount { get; set; }
|
||||
|
||||
public bool ExactMatch { get; set; }
|
||||
|
||||
public bool AlreadyExists { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum DouyinLiveStatusState
|
||||
{
|
||||
Unknown = 0,
|
||||
Offline = 1,
|
||||
Live = 2
|
||||
}
|
||||
|
||||
public sealed class FollowLiveMonitorUpdateDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public sealed class FollowLiveStatusRefreshDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public sealed class FollowLiveStatusQueryDto
|
||||
{
|
||||
public List<string> Ids { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class FollowLiveStatusDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public bool LiveMonitorEnabled { get; set; }
|
||||
public DouyinLiveStatusState LiveStatus { get; set; }
|
||||
public string LiveRoomId { get; set; }
|
||||
public string LiveWebRid { get; set; }
|
||||
public string LiveTitle { get; set; }
|
||||
public string LiveRoomUrl { get; set; }
|
||||
public DateTime? LiveCheckedAt { get; set; }
|
||||
public DateTime? LiveStatusUpdatedAt { get; set; }
|
||||
public DateTime? LiveStartedAt { get; set; }
|
||||
public string LiveCheckError { get; set; }
|
||||
public bool LiveStatusStale { get; set; }
|
||||
public bool LiveEmailNotificationEnabled { get; set; }
|
||||
public DateTime? LastLiveNotifiedAt { get; set; }
|
||||
public string LastLiveNotificationError { get; set; }
|
||||
}
|
||||
|
||||
public sealed class DouyinLiveStatusProbe
|
||||
{
|
||||
public DouyinLiveStatusState Status { get; set; }
|
||||
public string RoomId { get; set; }
|
||||
public string WebRid { get; set; }
|
||||
public string Title { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Author { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 博主 UID 精确筛选。用于从关注列表直达该博主的已同步视频。
|
||||
/// </summary>
|
||||
public string? AuthorId { get; set; }
|
||||
//public string? Name { get; set; }
|
||||
|
||||
public string? ViedoType { get; set; }
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum EmailSecurityMode
|
||||
{
|
||||
None = 0,
|
||||
StartTls = 1,
|
||||
SslOnConnect = 2
|
||||
}
|
||||
|
||||
public sealed class EmailNotificationSettingsDto
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; } = 465;
|
||||
public EmailSecurityMode SecurityMode { get; set; } = EmailSecurityMode.SslOnConnect;
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool HasPassword { get; set; }
|
||||
public string FromAddress { get; set; }
|
||||
public string FromName { get; set; }
|
||||
public string Recipients { get; set; }
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
public string LastTestMessage { get; set; }
|
||||
}
|
||||
|
||||
public sealed class FollowLiveEmailUpdateDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Net;
|
||||
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum MediaDownloadFailureKind
|
||||
{
|
||||
None = 0,
|
||||
SourceUnavailable = 1,
|
||||
SourceForbidden = 2,
|
||||
SourceUnauthorized = 3,
|
||||
SourceNotFound = 4,
|
||||
SourceRateLimited = 5,
|
||||
SourceInvalidContent = 6,
|
||||
StorageUnavailable = 7,
|
||||
IntegrityCheckFailed = 8,
|
||||
Cancelled = 9
|
||||
}
|
||||
|
||||
public sealed class MediaDownloadResult
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public string ActualSavePath { get; init; }
|
||||
public MediaDownloadFailureKind FailureKind { get; init; }
|
||||
public int? HttpStatusCode { get; init; }
|
||||
public string SourceHost { get; init; }
|
||||
public int AttemptedUrlCount { get; init; }
|
||||
public DateTime? RetryAfter { get; init; }
|
||||
public string Message { get; init; }
|
||||
|
||||
public void Deconstruct(out bool success, out string actualSavePath)
|
||||
{
|
||||
success = Success;
|
||||
actualSavePath = ActualSavePath;
|
||||
}
|
||||
|
||||
public Exception ToException() => FailureKind switch
|
||||
{
|
||||
MediaDownloadFailureKind.StorageUnavailable => new MediaStorageException(Message ?? "媒体存储写入失败。"),
|
||||
MediaDownloadFailureKind.IntegrityCheckFailed => new MediaIntegrityException(Message ?? "媒体完整性校验失败。"),
|
||||
MediaDownloadFailureKind.Cancelled => new OperationCanceledException(Message ?? "媒体下载已取消。"),
|
||||
_ => new MediaSourceException(FailureKind, Message ?? "媒体来源不可用。", HttpStatusCode, SourceHost, RetryAfter)
|
||||
};
|
||||
|
||||
public static MediaDownloadResult Succeeded(string path, string host, int attempted) => new()
|
||||
{
|
||||
Success = true,
|
||||
ActualSavePath = path,
|
||||
SourceHost = host,
|
||||
AttemptedUrlCount = attempted
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class MediaSourceException : Exception
|
||||
{
|
||||
public MediaSourceException(
|
||||
MediaDownloadFailureKind kind,
|
||||
string message,
|
||||
int? statusCode = null,
|
||||
string sourceHost = null,
|
||||
DateTime? retryAfter = null,
|
||||
Exception innerException = null) : base(message, innerException)
|
||||
{
|
||||
FailureKind = kind;
|
||||
StatusCode = statusCode;
|
||||
SourceHost = sourceHost;
|
||||
RetryAfter = retryAfter;
|
||||
}
|
||||
|
||||
public MediaDownloadFailureKind FailureKind { get; }
|
||||
public int? StatusCode { get; }
|
||||
public string SourceHost { get; }
|
||||
public DateTime? RetryAfter { get; }
|
||||
}
|
||||
|
||||
public sealed class MediaStorageException : IOException
|
||||
{
|
||||
public MediaStorageException(string message, Exception innerException = null) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
public sealed class MediaIntegrityException : IOException
|
||||
{
|
||||
public MediaIntegrityException(string message, Exception innerException = null) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
public enum SourceFailureDisposition
|
||||
{
|
||||
Continue = 0,
|
||||
WaitForSource = 1,
|
||||
RequiresAuthorization = 2
|
||||
}
|
||||
|
||||
public sealed class SourceAccessDecision
|
||||
{
|
||||
public bool Allowed { get; init; }
|
||||
public bool IsProbe { get; init; }
|
||||
public string Message { get; init; }
|
||||
public DateTime? RetryAt { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using dy.net.model.entity;
|
||||
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum OpenListDirectoryRepairStatus
|
||||
{
|
||||
Queued = 0,
|
||||
Scanning = 1,
|
||||
AwaitingConfirmation = 2,
|
||||
Cleaning = 3,
|
||||
Completed = 4,
|
||||
PartiallyFailed = 5,
|
||||
Cancelled = 6,
|
||||
Paused = 7
|
||||
}
|
||||
|
||||
public enum OpenListDirectoryRepairItemStatus
|
||||
{
|
||||
Pending = 0,
|
||||
Inspecting = 1,
|
||||
EmptyConfirmed = 2,
|
||||
SkippedNonEmpty = 3,
|
||||
Deleting = 4,
|
||||
Deleted = 5,
|
||||
Failed = 6,
|
||||
Missing = 7
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRepairPreflightRequest
|
||||
{
|
||||
public string LogicalPath { get; set; } = "/collect/Kk";
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRepairPreflightResult
|
||||
{
|
||||
public bool CanStart { get; set; }
|
||||
public string RequestedPath { get; set; }
|
||||
public string CanonicalPath { get; set; }
|
||||
public string CandidatePattern { get; set; }
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
public int CandidateCount { get; set; }
|
||||
public List<string> Errors { get; set; } = new();
|
||||
public List<string> Warnings { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class CreateOpenListDirectoryRepairRequest
|
||||
{
|
||||
public string LogicalPath { get; set; } = "/collect/Kk";
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ConfirmOpenListDirectoryRepairRequest
|
||||
{
|
||||
public string ConfirmationToken { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRepairTaskDetail
|
||||
{
|
||||
public OpenListDirectoryRepairTask Task { get; set; }
|
||||
public string StatusText { get; set; }
|
||||
public string ConfirmationToken { get; set; }
|
||||
public bool CanConfirmCleanup { get; set; }
|
||||
public bool CanCancel { get; set; }
|
||||
public bool CanResume { get; set; }
|
||||
public bool CanRetryFailed { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRepairItemPageRequest
|
||||
{
|
||||
public int PageIndex { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 20;
|
||||
public OpenListDirectoryRepairItemStatus? Status { get; set; }
|
||||
public string Keyword { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRepairItemPage
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
public List<OpenListDirectoryRepairItem> Items { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public class OpenListSettingsDto
|
||||
{
|
||||
public StorageType StorageType { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string LocalStagingPath { get; set; }
|
||||
public string SourcePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool HasPassword { get; set; }
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
public string LastTestMessage { get; set; }
|
||||
public bool RequiresCutover { get; set; }
|
||||
public int LegacyWebDavRecordCount { get; set; }
|
||||
public bool SuggestedFromLegacy { get; set; }
|
||||
}
|
||||
|
||||
public class OpenListTestRequest
|
||||
{
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string LocalStagingPath { get; set; }
|
||||
public string SourcePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryRequest : OpenListTestRequest
|
||||
{
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryItemDto
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public sealed class OpenListDirectoryListDto
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public bool CanWrite { get; set; }
|
||||
public List<OpenListDirectoryItemDto> Directories { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum OpenListTransferStatus
|
||||
{
|
||||
Queued = 0,
|
||||
WaitingForSource = 1,
|
||||
Copying = 2,
|
||||
Verifying = 3,
|
||||
Promoting = 4,
|
||||
WaitingRetry = 5,
|
||||
Succeeded = 6,
|
||||
Failed = 7,
|
||||
Cancelled = 8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using dy.net.model.entity;
|
||||
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum StorageMigrationTaskStatus
|
||||
{
|
||||
Queued = 0,
|
||||
Running = 1,
|
||||
Paused = 2,
|
||||
Completed = 3,
|
||||
PartiallyFailed = 4,
|
||||
Cancelled = 5,
|
||||
Cleaning = 6,
|
||||
Cleaned = 7,
|
||||
RolledBack = 8
|
||||
}
|
||||
|
||||
public enum StorageMigrationItemStage
|
||||
{
|
||||
Pending = 0,
|
||||
Uploading = 1,
|
||||
Verifying = 2,
|
||||
Committing = 3,
|
||||
Succeeded = 4,
|
||||
SucceededWithWarnings = 5,
|
||||
Failed = 6,
|
||||
Cleaned = 7,
|
||||
RolledBack = 8,
|
||||
RecordRemoved = 9
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationPreflightRequest
|
||||
{
|
||||
public bool VerifyCapabilities { get; set; } = true;
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationPreflightResult
|
||||
{
|
||||
public bool CanStart { get; set; }
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
public int RecordCount { get; set; }
|
||||
public int LocalRecordCount { get; set; }
|
||||
public int LegacyWebDavRecordCount { get; set; }
|
||||
public int AdoptableCount { get; set; }
|
||||
public int TransferRequiredCount { get; set; }
|
||||
public int ReadableFileCount { get; set; }
|
||||
public int MissingFileCount { get; set; }
|
||||
public long TotalBytes { get; set; }
|
||||
public int InvalidCookieCount { get; set; }
|
||||
public int MissingTargetPathCount { get; set; }
|
||||
public int ConflictCount { get; set; }
|
||||
public string Capacity { get; set; } = "unknown";
|
||||
public List<string> Errors { get; set; } = new();
|
||||
public List<string> Warnings { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class CreateStorageMigrationRequest
|
||||
{
|
||||
public int Concurrency { get; set; } = 1;
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationItemPageRequest
|
||||
{
|
||||
public int PageIndex { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 20;
|
||||
public StorageMigrationItemStage? Stage { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationTaskDetail
|
||||
{
|
||||
public StorageMigrationTask Task { get; set; }
|
||||
public string StatusText { get; set; }
|
||||
public string CurrentFile { get; set; }
|
||||
public bool CanPause { get; set; }
|
||||
public bool CanResume { get; set; }
|
||||
public bool CanCancel { get; set; }
|
||||
public bool CanRetryFailed { get; set; }
|
||||
public bool CanCleanup { get; set; }
|
||||
public bool CanRollback { get; set; }
|
||||
public bool CanArchive { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StorageRecordInventory
|
||||
{
|
||||
public StorageType CurrentStorageType { get; set; }
|
||||
public int TotalRecordCount { get; set; }
|
||||
public int LocalRecordCount { get; set; }
|
||||
public int WebDavRecordCount { get; set; }
|
||||
public int OpenListRecordCount { get; set; }
|
||||
public int CurrentStorageRecordCount { get; set; }
|
||||
public int OtherStorageRecordCount { get; set; }
|
||||
public long LocalDeclaredBytes { get; set; }
|
||||
public long WebDavDeclaredBytes { get; set; }
|
||||
public long OpenListDeclaredBytes { get; set; }
|
||||
public bool AllRecordsOnCurrentStorage => TotalRecordCount == CurrentStorageRecordCount;
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationArchiveResult
|
||||
{
|
||||
public int ArchivedCount { get; set; }
|
||||
public int RequiresCleanupCount { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public sealed class StorageMigrationItemPage
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
public List<StorageMigrationItem> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class FailedMigrationRecordPreview
|
||||
{
|
||||
public bool CanExecute { get; set; }
|
||||
public int ActiveMigrationCount { get; set; }
|
||||
public int FailedItemCount { get; set; }
|
||||
public int DistinctVideoCount { get; set; }
|
||||
public int EligibleRecordCount { get; set; }
|
||||
public int AlreadyMissingCount { get; set; }
|
||||
public int ChangedRecordCount { get; set; }
|
||||
public int PermanentlyExcludedCount { get; set; }
|
||||
public int InvalidSnapshotCount { get; set; }
|
||||
public string ConfirmationToken { get; set; }
|
||||
public List<string> Errors { get; set; } = new();
|
||||
public List<string> Warnings { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class RemoveFailedMigrationRecordsRequest
|
||||
{
|
||||
public string ConfirmationToken { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RemoveFailedMigrationRecordsResult
|
||||
{
|
||||
public int DeletedRecordCount { get; set; }
|
||||
public int AlreadyMissingCount { get; set; }
|
||||
public int RemovedItemCount { get; set; }
|
||||
public int AffectedTaskCount { get; set; }
|
||||
public int SkippedChangedCount { get; set; }
|
||||
public int SkippedExcludedCount { get; set; }
|
||||
public int InvalidSnapshotCount { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 媒体实际保存的位置。数值 0 必须保留为本地存储,以兼容历史数据库记录。
|
||||
/// </summary>
|
||||
public enum StorageType
|
||||
{
|
||||
Local = 0,
|
||||
WebDav = 1,
|
||||
OpenList = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// StorageType 扩展方法。
|
||||
/// </summary>
|
||||
public static class StorageTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否为远程存储(WebDAV 或 OpenList),与本地存储相对。
|
||||
/// </summary>
|
||||
public static bool IsRemote(this StorageType type) =>
|
||||
type is StorageType.WebDav or StorageType.OpenList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using dy.net.model.entity;
|
||||
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public enum VideoTaskType
|
||||
{
|
||||
Sync = 0,
|
||||
Redownload = 1,
|
||||
ExclusionRestore = 2,
|
||||
StorageMigration = 3,
|
||||
StorageMaintenance = 4
|
||||
}
|
||||
|
||||
public enum VideoTaskTrigger
|
||||
{
|
||||
Scheduled = 0,
|
||||
Manual = 1,
|
||||
UserAction = 2,
|
||||
UpgradeRecovery = 3
|
||||
}
|
||||
|
||||
public enum VideoTaskStatus
|
||||
{
|
||||
Queued = 0,
|
||||
Running = 1,
|
||||
WaitingForStorage = 2,
|
||||
Completed = 3,
|
||||
PartiallyFailed = 4,
|
||||
Failed = 5,
|
||||
Interrupted = 6,
|
||||
Paused = 7,
|
||||
Cancelled = 8,
|
||||
Cleaning = 9,
|
||||
Cleaned = 10,
|
||||
RolledBack = 11,
|
||||
WaitingForSource = 12,
|
||||
Scanning = 13,
|
||||
AwaitingConfirmation = 14
|
||||
}
|
||||
|
||||
public enum VideoTaskItemStage
|
||||
{
|
||||
Pending = 0,
|
||||
Downloading = 1,
|
||||
Verifying = 2,
|
||||
Committing = 3,
|
||||
Succeeded = 4,
|
||||
SucceededWithWarnings = 5,
|
||||
Failed = 6,
|
||||
Skipped = 7,
|
||||
WaitingForStorage = 8,
|
||||
Cleaned = 9,
|
||||
RolledBack = 10,
|
||||
Cancelled = 11,
|
||||
WaitingForSource = 12,
|
||||
RecordRemoved = 13,
|
||||
Inspecting = 14,
|
||||
EmptyConfirmed = 15,
|
||||
SkippedNonEmpty = 16
|
||||
}
|
||||
|
||||
public enum VideoTaskErrorType
|
||||
{
|
||||
None = 0,
|
||||
SourceUnavailable = 1,
|
||||
CookieInvalid = 2,
|
||||
StorageUnavailable = 3,
|
||||
IntegrityCheckFailed = 4,
|
||||
DatabaseCommitFailed = 5,
|
||||
Interrupted = 6,
|
||||
ConfigurationChanged = 7,
|
||||
Excluded = 8,
|
||||
Unknown = 9,
|
||||
SourceForbidden = 10,
|
||||
SourceRateLimited = 11
|
||||
}
|
||||
|
||||
public enum VideoTaskSkipReason
|
||||
{
|
||||
None = 0,
|
||||
AlreadyExists = 1,
|
||||
Deduplicated = 2,
|
||||
PermanentlyExcluded = 3,
|
||||
ConfigurationExcluded = 4,
|
||||
NoMediaSource = 5
|
||||
}
|
||||
|
||||
public enum MediaStorageHealthStatus
|
||||
{
|
||||
Healthy = 0,
|
||||
Unavailable = 1
|
||||
}
|
||||
|
||||
public sealed class VideoTaskPageRequest : PageRequestDto
|
||||
{
|
||||
public VideoTaskType? Type { get; set; }
|
||||
public VideoTaskStatus? Status { get; set; }
|
||||
public VideoTaskTrigger? Trigger { get; set; }
|
||||
public string Keyword { get; set; }
|
||||
public DateTime? From { get; set; }
|
||||
public DateTime? To { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VideoTaskItemPageRequest : PageRequestDto
|
||||
{
|
||||
public VideoTaskItemStage? Stage { get; set; }
|
||||
public VideoTaskErrorType? ErrorType { get; set; }
|
||||
public string Keyword { get; set; }
|
||||
public bool IncludeSkipped { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VideoTaskListPage
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
public List<UnifiedVideoTaskDto> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class VideoTaskItemPage
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
public List<UnifiedVideoTaskItemDto> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class UnifiedVideoTaskDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public VideoTaskType Type { get; set; }
|
||||
public VideoTaskTrigger Trigger { get; set; }
|
||||
public VideoTaskStatus Status { get; set; }
|
||||
public string Title { get; set; }
|
||||
public VideoTypeEnum? VideoType { get; set; }
|
||||
public StorageType StorageType { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int PendingCount { get; set; }
|
||||
public int RunningCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int WarningCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public int SkippedCount { get; set; }
|
||||
public int RemovedCount { get; set; }
|
||||
public string CurrentFile { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public List<string> AvailableActions { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class UnifiedVideoTaskItemDto
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string TaskId { get; set; }
|
||||
public string VideoId { get; set; }
|
||||
public string AwemeId { get; set; }
|
||||
public string CookieName { get; set; }
|
||||
public VideoTypeEnum? VideoType { get; set; }
|
||||
public string VideoTitle { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string TargetPath { get; set; }
|
||||
public VideoTaskItemStage Stage { get; set; }
|
||||
public VideoTaskErrorType ErrorType { get; set; }
|
||||
public VideoTaskSkipReason SkipReason { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public long ExpectedLength { get; set; }
|
||||
public long ActualLength { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public string WarningMessage { get; set; }
|
||||
public string SourceHost { get; set; }
|
||||
public int? HttpStatusCode { get; set; }
|
||||
public DateTime? RetryAfter { get; set; }
|
||||
public string ExclusionId { get; set; }
|
||||
public DateTime? ExclusionReleasedAt { get; set; }
|
||||
public string RelatedTaskId { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public bool CanRetry { get; set; }
|
||||
public bool CanRetryCleanup { get; set; }
|
||||
public string CleanupError { get; set; }
|
||||
public bool CanUnexclude { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VideoTaskSummaryDto
|
||||
{
|
||||
public int Queued { get; set; }
|
||||
public int Running { get; set; }
|
||||
public int WaitingForStorage { get; set; }
|
||||
public int WaitingForSource { get; set; }
|
||||
public int Failed { get; set; }
|
||||
public int Completed { get; set; }
|
||||
public MediaStorageHealth StorageHealth { get; set; }
|
||||
public List<DouyinSourceHealthDto> SourceHealth { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class DouyinSourceHealthDto
|
||||
{
|
||||
public string CookieId { get; set; }
|
||||
public string CookieName { get; set; }
|
||||
public int ConsecutiveForbidden { get; set; }
|
||||
public DateTime? CooldownUntil { get; set; }
|
||||
public bool RequiresAuthorization { get; set; }
|
||||
public bool ProbePending { get; set; }
|
||||
public int? LastStatusCode { get; set; }
|
||||
public string LastError { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UnexcludeVideosRequest
|
||||
{
|
||||
public List<string> Ids { get; set; } = new();
|
||||
public bool CreateDownloadTask { get; set; }
|
||||
}
|
||||
|
||||
public sealed class UnexcludeVideosResult
|
||||
{
|
||||
public int ReleasedCount { get; set; }
|
||||
public int QueuedCount { get; set; }
|
||||
public string TaskId { get; set; }
|
||||
public List<string> CannotQueueIds { get; set; } = new();
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VideoExclusionPageRequest : PageRequestDto
|
||||
{
|
||||
public string Keyword { get; set; }
|
||||
}
|
||||
|
||||
public sealed class VideoExclusionPage
|
||||
{
|
||||
public int TotalCount { get; set; }
|
||||
public List<DouyinVideoDelete> Items { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,11 @@
|
||||
/// </summary>
|
||||
dy_followuser_once = 2000,
|
||||
|
||||
/// <summary>
|
||||
/// 独立直播状态监测任务,不属于视频下载任务。
|
||||
/// </summary>
|
||||
dy_live_monitor = 3000,
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace dy.net.model.dto
|
||||
{
|
||||
public class WebDavSettingsDto
|
||||
{
|
||||
public StorageType StorageType { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool HasPassword { get; set; }
|
||||
public bool AllowInvalidCertificate { get; set; }
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
public string LastTestMessage { get; set; }
|
||||
}
|
||||
|
||||
public class WebDavTestRequest
|
||||
{
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool AllowInvalidCertificate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>统一的存储连接测试请求;新远端存储仅支持 OpenList。</summary>
|
||||
public class StorageTestRequest
|
||||
{
|
||||
public StorageType StorageType { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string LocalStagingPath { get; set; }
|
||||
public string SourcePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool AllowInvalidCertificate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>统一的存储配置保存请求;WebDAV 字段仅为数据库升级兼容保留。</summary>
|
||||
public class StorageSettingsDto
|
||||
{
|
||||
public StorageType StorageType { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string LocalStagingPath { get; set; }
|
||||
public string SourcePath { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool AllowInvalidCertificate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ namespace dy.net.model.entity
|
||||
/// </summary>
|
||||
public int BatchCount { get; set; } = 18;
|
||||
|
||||
/// <summary>
|
||||
/// 新同步任务使用的全局存储。历史记录始终使用自身保存的存储类型。
|
||||
/// </summary>
|
||||
public dy.net.model.dto.StorageType StorageType { get; set; } = dy.net.model.dto.StorageType.Local;
|
||||
|
||||
/// <summary>
|
||||
/// 博主视频是否 直接用标题做文件名
|
||||
/// </summary>
|
||||
|
||||
@@ -145,5 +145,51 @@ namespace dy.net.model.entity
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string SeriesPath { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string WebDavCollectPath { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string WebDavFavoritePath { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string WebDavFollowPath { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string WebDavMixPath { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string WebDavSeriesPath { get; set; }
|
||||
|
||||
public int ConsecutiveSourceForbidden { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? SourceCooldownUntil { get; set; }
|
||||
|
||||
public bool SourceRequiresAuthorization { get; set; }
|
||||
|
||||
public bool SourceProbePending { get; set; }
|
||||
|
||||
public bool SourceProbeInProgress { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? LastSourceStatusCode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LastSourceError { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? SourceHealthUpdatedAt { get; set; }
|
||||
|
||||
public int ConsecutiveLiveCheckFailures { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LiveCheckCooldownUntil { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LastLiveCheckError { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LiveCheckHealthUpdatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using SqlSugar;
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
@@ -72,5 +73,57 @@ namespace dy.net.model.entity
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 100)]
|
||||
public string DouyinNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 直播状态监测独立开关,不影响作品同步开关。
|
||||
/// </summary>
|
||||
public bool LiveMonitorEnabled { get; set; }
|
||||
|
||||
public DouyinLiveStatusState LiveStatus { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 100)]
|
||||
public string LiveRoomId { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 100)]
|
||||
public string LiveWebRid { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 500)]
|
||||
public string LiveTitle { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LiveCheckedAt { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LiveStatusUpdatedAt { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LiveStartedAt { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 1000)]
|
||||
public string LiveCheckError { get; set; }
|
||||
|
||||
public bool LiveEmailNotificationEnabled { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 200)]
|
||||
public string LastLiveNotificationKey { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastLiveNotificationAttemptAt { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastLiveNotifiedAt { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 1000)]
|
||||
public string LastLiveNotificationError { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public bool LiveStatusStale => LiveMonitorEnabled &&
|
||||
(!LiveStatusUpdatedAt.HasValue || DateTime.Now - LiveStatusUpdatedAt.Value > TimeSpan.FromMinutes(10) ||
|
||||
!string.IsNullOrWhiteSpace(LiveCheckError));
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string LiveRoomUrl => LiveStatus == DouyinLiveStatusState.Live && !string.IsNullOrWhiteSpace(LiveWebRid)
|
||||
? "https://live.douyin.com/" + Uri.EscapeDataString(LiveWebRid)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace dy.net.model.entity
|
||||
public string SavePath { get; set; }
|
||||
|
||||
public string CookieId { get; set; }
|
||||
[SugarColumn(IsNullable = true, Length = 100)]
|
||||
public string VideoRecordId { get; set; }
|
||||
/// <summary>
|
||||
/// 0-未下载 1-下载成功 2-下载失败
|
||||
/// </summary>
|
||||
@@ -33,5 +35,12 @@ namespace dy.net.model.entity
|
||||
public DateTime CreateTime { get; set; }
|
||||
public DateTime DownTime { get; set; }
|
||||
public DateTime UpdateTime { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string TaskId { get; set; }
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string TaskItemId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using dy.net.model.dto;
|
||||
using MediaStorageType = dy.net.model.dto.StorageType;
|
||||
using dy.net.utils;
|
||||
using SqlSugar;
|
||||
|
||||
@@ -77,6 +78,11 @@ namespace dy.net.model.entity
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string VideoSavePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 该记录实际所在存储。旧数据默认值 0 表示本地文件。
|
||||
/// </summary>
|
||||
public MediaStorageType StorageType { get; set; } = MediaStorageType.Local;
|
||||
/// <summary>
|
||||
/// 视频封面地址
|
||||
/// </summary>
|
||||
@@ -171,5 +177,23 @@ namespace dy.net.model.entity
|
||||
/// </summary>
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string CateXId { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string PendingStoragePath { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string TemporaryDirectory { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string TaskItemId { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string TaskWarning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 普通同步将旧存储记录安全替换到当前存储时使用的旧记录快照,不写入数据库。
|
||||
/// </summary>
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public DouyinVideo SupersededStorageSnapshot { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using SqlSugar;
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
@@ -25,5 +26,23 @@ namespace dy.net.model.entity
|
||||
public string VideoTitle { get; set; }
|
||||
[SugarColumn(IsNullable = true, Length = 1000)]
|
||||
public string VideoSavePath { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 200)]
|
||||
public string CookieId { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public VideoTypeEnum? VideoType { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 200)]
|
||||
public string AuthorId { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 500)]
|
||||
public string Author { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true, Length = 2000)]
|
||||
public string VideoUrl { get; set; }
|
||||
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string RestoreSnapshotJson { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "dy_email_notification_settings")]
|
||||
public sealed class EmailNotificationSettings
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; } = "default";
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string Host { get; set; }
|
||||
|
||||
public int Port { get; set; } = 465;
|
||||
|
||||
public EmailSecurityMode SecurityMode { get; set; } = EmailSecurityMode.SslOnConnect;
|
||||
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[SugarColumn(Length = -1, IsNullable = true)]
|
||||
public string ProtectedPassword { get; set; }
|
||||
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string FromAddress { get; set; }
|
||||
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string FromName { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4000, IsNullable = true)]
|
||||
public string Recipients { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LastTestMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
using MediaStorageType = dy.net.model.dto.StorageType;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "media_storage_health")]
|
||||
public class MediaStorageHealth
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; } = "default";
|
||||
public MediaStorageHealthStatus Status { get; set; }
|
||||
public MediaStorageType StorageType { get; set; }
|
||||
[SugarColumn(Length = 128, IsNullable = true)]
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
public int ConsecutiveFailures { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string LastError { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastFailedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastProbedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "openlist_directory_repair_item")]
|
||||
[SugarIndex("idx_openlist_repair_item_task_status", nameof(TaskId), OrderByType.Asc,
|
||||
nameof(Status), OrderByType.Asc)]
|
||||
public sealed class OpenListDirectoryRepairItem
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
[SugarColumn(Length = 50)]
|
||||
public string TaskId { get; set; }
|
||||
[SugarColumn(Length = 500)]
|
||||
public string DirectoryName { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string ActualPath { get; set; }
|
||||
public OpenListDirectoryRepairItemStatus Status { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public int EntryCount { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)] public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "openlist_directory_repair_task")]
|
||||
[SugarIndex("idx_openlist_repair_created", nameof(CreatedAt), OrderByType.Desc)]
|
||||
public sealed class OpenListDirectoryRepairTask
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
public OpenListDirectoryRepairStatus Status { get; set; }
|
||||
[SugarColumn(Length = 128)]
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string RequestedPath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string CanonicalPath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string ActualParentPath { get; set; }
|
||||
[SugarColumn(Length = 500)]
|
||||
public string RequestedName { get; set; }
|
||||
[SugarColumn(Length = 1000)]
|
||||
public string CandidatePattern { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int PendingCount { get; set; }
|
||||
public int EmptyCount { get; set; }
|
||||
public int DeletedCount { get; set; }
|
||||
public int SkippedCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public int MissingCount { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string CurrentDirectory { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)] public DateTime? StartedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)] public DateTime? ScanCompletedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)] public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "dy_openlist_settings")]
|
||||
public class OpenListSettings
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; } = "default";
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string BasePath { get; set; } = "/dysync";
|
||||
|
||||
/// <summary>本应用写入中转文件的本地绝对路径。</summary>
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LocalStagingPath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>OpenList 中映射到 LocalStagingPath 的源挂载根目录。</summary>
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string SourcePath { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>旧草稿字段,仅用于平滑升级;新版本统一使用 BasePath 作为目标目录。</summary>
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string DestinationPath { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[SugarColumn(Length = -1, IsNullable = true)]
|
||||
public string ProtectedPassword { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LastTestMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "openlist_transfer_job")]
|
||||
[SugarIndex("idx_openlist_transfer_target", nameof(ConfigurationFingerprint), OrderByType.Asc,
|
||||
nameof(LogicalTargetPath), OrderByType.Asc, nameof(Status), OrderByType.Asc)]
|
||||
public sealed class OpenListTransferJob
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
public OpenListTransferStatus Status { get; set; }
|
||||
[SugarColumn(Length = 128)]
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string LogicalTargetPath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string ActualTargetPath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string LocalSourcePath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string OpenListSourcePath { get; set; }
|
||||
[SugarColumn(Length = 2000)]
|
||||
public string StagedTargetPath { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string BackupTargetPath { get; set; }
|
||||
public long ExpectedLength { get; set; }
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string ExternalTaskId { get; set; }
|
||||
public double ExternalProgress { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? ExternalTaskStartedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastProgressAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? NextAttemptAt { get; set; }
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string LeaseOwner { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LeaseUntil { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
using AppStorageType = dy.net.model.dto.StorageType;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "storage_migration_item")]
|
||||
[SugarIndex("idx_migration_item_task_stage", nameof(TaskId), OrderByType.Asc, nameof(Stage), OrderByType.Asc)]
|
||||
public class StorageMigrationItem
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
[SugarColumn(Length = 50)]
|
||||
public string TaskId { get; set; }
|
||||
[SugarColumn(Length = 200)]
|
||||
public string VideoId { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public AppStorageType? SourceStorageType { get; set; }
|
||||
public bool AdoptExistingTarget { get; set; }
|
||||
public StorageMigrationItemStage Stage { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public long ExpectedLength { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string OldVideoPath { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string TargetVideoPath { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string TargetCoverPath { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1)]
|
||||
public string OldSnapshotJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string AttachmentPlanJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string UploadedPathsJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string OwnedRemotePathsJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string WarningMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
using AppStorageType = dy.net.model.dto.StorageType;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "storage_migration_task")]
|
||||
public class StorageMigrationTask
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
public StorageMigrationTaskStatus Status { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public AppStorageType? TargetStorageType { get; set; }
|
||||
public int Concurrency { get; set; } = 1;
|
||||
public int TotalCount { get; set; }
|
||||
public int PendingCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int WarningCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public int RemovedCount { get; set; }
|
||||
public long TotalBytes { get; set; }
|
||||
[SugarColumn(Length = 128)]
|
||||
public string ConfigurationFingerprint { get; set; }
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string CurrentVideoId { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string CurrentFile { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CleanedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? RolledBackAt { get; set; }
|
||||
/// <summary>
|
||||
/// 已归档的终态任务不再出现在系统配置和任务中心,但保留条目用于审计及失败记录处理。
|
||||
/// </summary>
|
||||
public bool IsArchived { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
using MediaStorageType = dy.net.model.dto.StorageType;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "video_download_task")]
|
||||
[SugarIndex("idx_video_task_created", nameof(CreatedAt), OrderByType.Desc)]
|
||||
public class VideoDownloadTask
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
public VideoTaskType Type { get; set; }
|
||||
public VideoTaskTrigger Trigger { get; set; }
|
||||
public VideoTaskStatus Status { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public VideoTypeEnum? VideoType { get; set; }
|
||||
public MediaStorageType StorageType { get; set; }
|
||||
[SugarColumn(Length = 500)]
|
||||
public string Title { get; set; }
|
||||
[SugarColumn(Length = 128, IsNullable = true)]
|
||||
public string StorageFingerprint { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int PendingCount { get; set; }
|
||||
public int RunningCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int WarningCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public int SkippedCount { get; set; }
|
||||
public bool IsArchived { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string CurrentFile { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using dy.net.model.dto;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "video_download_task_item")]
|
||||
[SugarIndex("idx_video_task_item_task_stage", nameof(TaskId), OrderByType.Asc, nameof(Stage), OrderByType.Asc)]
|
||||
public class VideoDownloadTaskItem
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; }
|
||||
[SugarColumn(Length = 50)]
|
||||
public string TaskId { get; set; }
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string VideoId { get; set; }
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string AwemeId { get; set; }
|
||||
[SugarColumn(Length = 200, IsNullable = true)]
|
||||
public string CookieId { get; set; }
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string CookieName { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public VideoTypeEnum? VideoType { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string VideoTitle { get; set; }
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string Author { get; set; }
|
||||
[SugarColumn(Length = 2000, IsNullable = true)]
|
||||
public string TargetPath { get; set; }
|
||||
public VideoTaskItemStage Stage { get; set; }
|
||||
public VideoTaskErrorType ErrorType { get; set; }
|
||||
public VideoTaskSkipReason SkipReason { get; set; }
|
||||
public int Attempts { get; set; }
|
||||
public bool WorkerManaged { get; set; }
|
||||
public long ExpectedLength { get; set; }
|
||||
public long ActualLength { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string SourceUrlsJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string RetrySnapshotJson { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string ErrorMessage { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string WarningMessage { get; set; }
|
||||
public bool CleanupPending { get; set; }
|
||||
[SugarColumn(ColumnDataType = "TEXT", Length = -1, IsNullable = true)]
|
||||
public string CleanupError { get; set; }
|
||||
[SugarColumn(Length = 255, IsNullable = true)]
|
||||
public string SourceHost { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? HttpStatusCode { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? RetryAfter { get; set; }
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string ExclusionId { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? ExclusionReleasedAt { get; set; }
|
||||
[SugarColumn(Length = 50, IsNullable = true)]
|
||||
public string RelatedTaskId { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model.entity
|
||||
{
|
||||
[SugarTable(TableName = "dy_webdav_settings")]
|
||||
public class WebDavSettings
|
||||
{
|
||||
[SugarColumn(IsPrimaryKey = true, Length = 50)]
|
||||
public string Id { get; set; } = "default";
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string Endpoint { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string BasePath { get; set; } = "/dysync";
|
||||
|
||||
[SugarColumn(Length = 500, IsNullable = true)]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[SugarColumn(Length = -1, IsNullable = true)]
|
||||
public string ProtectedPassword { get; set; }
|
||||
|
||||
public bool AllowInvalidCertificate { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? LastTestedAt { get; set; }
|
||||
|
||||
[SugarColumn(Length = 1000, IsNullable = true)]
|
||||
public string LastTestMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace dy.net.model.response
|
||||
{
|
||||
@@ -115,6 +116,12 @@ namespace dy.net.model.response
|
||||
[JsonProperty("short_id")]
|
||||
public string ShortId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户主页展示的自定义抖音号。short_id 只是没有自定义抖音号时的数字兜底。
|
||||
/// </summary>
|
||||
[JsonProperty("unique_id")]
|
||||
public string UniqueId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签名
|
||||
/// </summary>
|
||||
@@ -123,6 +130,18 @@ namespace dy.net.model.response
|
||||
|
||||
[JsonProperty("uid")]
|
||||
public string UperId { get; set; }
|
||||
|
||||
[JsonProperty("live_status")]
|
||||
public int? LiveStatus { get; set; }
|
||||
|
||||
[JsonProperty("room_id")]
|
||||
public string RoomId { get; set; }
|
||||
|
||||
[JsonProperty("room_id_str")]
|
||||
public string RoomIdStr { get; set; }
|
||||
|
||||
[JsonProperty("room_data")]
|
||||
public JToken RoomData { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user