mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
+159
-2
@@ -1,4 +1,6 @@
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, Optional, Protocol, Union
|
||||
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.schemas.media import resolve_media_identity
|
||||
@@ -9,7 +11,8 @@ from app.runtime.config import settings
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.db.oper.transferhistory import TransferHistoryOper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import FileItem, TransferInfo
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING
|
||||
|
||||
# 失败重试次数的合法区间。下界为 1:一次瞬时故障(网络抖动、TMDB 瞬断、移动失败)
|
||||
@@ -25,6 +28,160 @@ FAILED_RETRY_TTL = 24 * 3600
|
||||
_failed_retry_counts = TTLCache(region="transfer_failed_retry", maxsize=5000, ttl=FAILED_RETRY_TTL)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class HistoryMutationResult:
|
||||
"""描述历史记录维护操作是否成功及兼容提示。"""
|
||||
|
||||
success: bool
|
||||
message: str = ""
|
||||
|
||||
|
||||
class DownloadHistoryMutationRepository(Protocol):
|
||||
"""下载历史删除用例需要的最小持久化端口。"""
|
||||
|
||||
def stage_delete_history(self, history_id: int) -> None:
|
||||
"""暂存下载历史删除。"""
|
||||
...
|
||||
|
||||
|
||||
class TransferHistoryMutationRepository(Protocol):
|
||||
"""整理历史删除与清理用例需要的最小持久化端口。"""
|
||||
|
||||
def get(self, history_id: int) -> Optional[Any]:
|
||||
"""读取整理历史。"""
|
||||
...
|
||||
|
||||
def stage_delete(self, history_id: int) -> None:
|
||||
"""暂存整理历史删除。"""
|
||||
...
|
||||
|
||||
def stage_truncate(self) -> None:
|
||||
"""暂存全部整理历史删除。"""
|
||||
...
|
||||
|
||||
|
||||
class DownloadFileMutationRepository(Protocol):
|
||||
"""整理历史删除时关联下载文件状态更新端口。"""
|
||||
|
||||
def stage_delete_file_by_fullpath(self, fullpath: str) -> None:
|
||||
"""暂存下载文件删除状态。"""
|
||||
...
|
||||
|
||||
|
||||
class HistoryUnitOfWork(Protocol):
|
||||
"""同步历史维护用例使用的事务端口。"""
|
||||
|
||||
def commit(self) -> None:
|
||||
"""提交当前事务。"""
|
||||
...
|
||||
|
||||
def rollback(self) -> None:
|
||||
"""回滚当前事务。"""
|
||||
...
|
||||
|
||||
|
||||
class DownloadHistoryMutationCommand:
|
||||
"""统一提交下载历史删除,避免 API 直接持有数据库事务。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
repository: DownloadHistoryMutationRepository,
|
||||
unit_of_work: HistoryUnitOfWork,
|
||||
) -> None:
|
||||
"""保存下载历史持久化和事务端口。"""
|
||||
self._repository = repository
|
||||
self._unit_of_work = unit_of_work
|
||||
|
||||
def delete(self, history_id: int) -> HistoryMutationResult:
|
||||
"""暂存并提交单条下载历史删除。"""
|
||||
self._repository.stage_delete_history(history_id)
|
||||
self._commit()
|
||||
return HistoryMutationResult(True)
|
||||
|
||||
def _commit(self) -> None:
|
||||
"""提交事务,失败时回滚。"""
|
||||
try:
|
||||
self._unit_of_work.commit()
|
||||
except Exception:
|
||||
self._unit_of_work.rollback()
|
||||
raise
|
||||
|
||||
|
||||
class TransferHistoryMutationCommand:
|
||||
"""协调整理历史、关联文件状态和外部存储删除。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
repository: TransferHistoryMutationRepository,
|
||||
download_repository: DownloadFileMutationRepository,
|
||||
unit_of_work: HistoryUnitOfWork,
|
||||
file_item_factory: Callable[[dict], Any],
|
||||
delete_media_file: Callable[[Any], bool],
|
||||
publish_download_file_deleted: Callable[[dict], None],
|
||||
clear_failures: Callable[[Optional[str], Optional[str]], None],
|
||||
) -> None:
|
||||
"""保存历史事务、存储删除、事件和失败状态清理端口。"""
|
||||
self._repository = repository
|
||||
self._download_repository = download_repository
|
||||
self._unit_of_work = unit_of_work
|
||||
self._file_item_factory = file_item_factory
|
||||
self._delete_media_file = delete_media_file
|
||||
self._publish_download_file_deleted = publish_download_file_deleted
|
||||
self._clear_failures = clear_failures
|
||||
|
||||
def delete(
|
||||
self,
|
||||
history_id: int,
|
||||
*,
|
||||
delete_source: bool = False,
|
||||
delete_destination: bool = False,
|
||||
) -> HistoryMutationResult:
|
||||
"""删除整理记录,并保持源文件失败时不提交数据库变更。"""
|
||||
history = self._repository.get(history_id)
|
||||
if not history:
|
||||
return HistoryMutationResult(False, "记录不存在")
|
||||
|
||||
if delete_destination and history.dest_fileitem:
|
||||
destination = self._file_item_factory(history.dest_fileitem)
|
||||
self._delete_media_file(destination)
|
||||
|
||||
source_deleted = False
|
||||
if delete_source and history.src_fileitem:
|
||||
source = self._file_item_factory(history.src_fileitem)
|
||||
if not self._delete_media_file(source):
|
||||
return HistoryMutationResult(False, f"{source.path} 删除失败")
|
||||
self._download_repository.stage_delete_file_by_fullpath(
|
||||
Path(source.path).as_posix()
|
||||
)
|
||||
source_deleted = True
|
||||
|
||||
self._repository.stage_delete(history_id)
|
||||
self._commit()
|
||||
if source_deleted:
|
||||
self._publish_download_file_deleted({
|
||||
"src": history.src,
|
||||
"hash": history.download_hash,
|
||||
})
|
||||
self._clear_failures(history.src, history.src_storage)
|
||||
return HistoryMutationResult(True)
|
||||
|
||||
def truncate(self) -> HistoryMutationResult:
|
||||
"""在单一事务中清空全部整理历史。"""
|
||||
self._repository.stage_truncate()
|
||||
self._commit()
|
||||
return HistoryMutationResult(True)
|
||||
|
||||
def _commit(self) -> None:
|
||||
"""提交历史事务,失败时回滚且不发布事件或清缓存。"""
|
||||
try:
|
||||
self._unit_of_work.commit()
|
||||
except Exception:
|
||||
self._unit_of_work.rollback()
|
||||
raise
|
||||
|
||||
|
||||
class HistoryGateAction:
|
||||
"""
|
||||
整理历史查重闸的判定结果。
|
||||
|
||||
Reference in New Issue
Block a user