Files
MoviePilot/app/application/subscription/mutation.py
T

150 lines
4.9 KiB
Python

"""订阅写操作用例及其数据端口。"""
from dataclasses import dataclass
from typing import Any, Protocol
class SubscriptionMutationRepository(Protocol):
"""订阅写用例需要的异步数据端口。"""
async def async_get(self, subscribe_id: int) -> Any | None:
"""按 ID 获取订阅。"""
async def async_update(self, subscribe_id: int, payload: dict[str, Any]) -> Any | None:
"""更新订阅。"""
def get(self, subscribe_id: int) -> Any | None:
"""同步按 ID 获取订阅。"""
class SubscriptionHistoryMutationRepository(Protocol):
"""订阅历史删除用例需要的最小数据端口。"""
async def async_get(self, history_id: int) -> Any | None:
"""按 ID 获取订阅历史。"""
async def async_delete(self, history_id: int) -> None:
"""删除订阅历史。"""
@dataclass(frozen=True)
class SubscriptionActor:
"""订阅写操作的权限主体。"""
name: str
is_superuser: bool
@dataclass(frozen=True)
class SubscriptionMutation:
"""一次订阅变更前后的稳定快照。"""
old: dict[str, Any]
new: dict[str, Any]
class SubscriptionMutationService:
"""编排订阅访问控制、更新和历史删除。"""
def __init__(
self,
repository: SubscriptionMutationRepository,
history_repository: SubscriptionHistoryMutationRepository | None = None,
) -> None:
"""注入订阅和订阅历史数据端口。"""
self._repository = repository
self._history_repository = history_repository
async def get_accessible(
self,
subscribe_id: int,
actor: SubscriptionActor,
) -> Any | None:
"""读取当前主体可访问的订阅。"""
subscribe = await self._repository.async_get(subscribe_id)
return subscribe if self.can_access(subscribe, actor) else None
def get_accessible_sync(
self,
subscribe_id: int,
actor: SubscriptionActor,
) -> Any | None:
"""同步读取当前主体可访问的订阅。"""
subscribe = self._repository.get(subscribe_id)
return subscribe if self.can_access(subscribe, actor) else None
async def update(
self,
subscribe_id: int,
payload: dict[str, Any],
actor: SubscriptionActor,
existing: Any | None = None,
) -> SubscriptionMutation | None:
"""更新当前主体可访问的订阅并返回前后快照。"""
subscribe = existing or await self.get_accessible(subscribe_id, actor)
if subscribe and not self.can_access(subscribe, actor):
return None
if not subscribe:
return None
old = subscribe.to_dict()
updated = await self._repository.async_update(subscribe_id, payload)
return SubscriptionMutation(old=old, new=updated.to_dict() if updated else {})
async def update_status(
self,
subscribe_id: int,
state: str,
actor: SubscriptionActor,
) -> SubscriptionMutation | None:
"""更新订阅状态并返回前后快照。"""
return await self.update(subscribe_id, {"state": state}, actor)
async def reset(
self,
subscribe_id: int,
actor: SubscriptionActor,
) -> SubscriptionMutation | None:
"""重置订阅进度和手工集数标记。"""
subscribe = await self.get_accessible(subscribe_id, actor)
if not subscribe:
return None
payload = {
"note": [],
"lack_episode": subscribe.total_episode,
"current_priority": None,
"current_audio_format": None,
"current_bitrate": None,
"current_bit_depth": None,
"current_sample_rate": None,
"episode_priority": {},
"manual_total_episode": 0,
"state": "R",
}
old = subscribe.to_dict()
updated = await self._repository.async_update(subscribe_id, payload)
return SubscriptionMutation(old=old, new=updated.to_dict() if updated else {})
async def delete_history(
self,
history_id: int,
actor: SubscriptionActor,
) -> bool:
"""删除当前主体可访问的订阅历史。"""
if self._history_repository is None:
raise RuntimeError("订阅历史数据端口未配置")
history = await self._history_repository.async_get(history_id)
if not self.can_access(history, actor):
return False
await self._history_repository.async_delete(history_id)
return True
@staticmethod
def can_access(subscribe: Any, actor: SubscriptionActor) -> bool:
"""判断主体是否可访问订阅或订阅历史。"""
if not subscribe:
return False
if actor.is_superuser:
return True
username = getattr(subscribe, "username", None)
return bool(username) and username == actor.name