fix: respect explicit subscribe best version settings (#6001)

* fix(subscribe): respect explicit best version settings

* fix(subscribe): isolate delete event failures
This commit is contained in:
InfinityPacer
2026-06-25 11:31:13 +08:00
committed by GitHub
parent bc52653ec1
commit 43e89ebf77
4 changed files with 60 additions and 16 deletions
+37 -12
View File
@@ -19,6 +19,7 @@ from app.db.models.user import User
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.db.user_oper import get_current_active_user_async from app.db.user_oper import get_current_active_user_async
from app.helper.server import MoviePilotServerHelper from app.helper.server import MoviePilotServerHelper
from app.log import logger
from app.scheduler import Scheduler from app.scheduler import Scheduler
from app.schemas.types import MediaType, EventType, SystemConfigKey from app.schemas.types import MediaType, EventType, SystemConfigKey
@@ -41,6 +42,14 @@ def start_subscribe_add(
) )
def build_subscribe_event_payload(subscribe: Subscribe) -> dict:
"""
从 ORM 已加载字段构造订阅事件快照,避免异步接口里属性懒加载触发隐式 IO。
"""
values = subscribe.__dict__
return {column.name: values.get(column.name) for column in subscribe.__table__.columns}
@router.get("/", summary="查询所有订阅", response_model=List[schemas.Subscribe]) @router.get("/", summary="查询所有订阅", response_model=List[schemas.Subscribe])
async def read_subscribes( async def read_subscribes(
db: AsyncSession = Depends(get_async_db), db: AsyncSession = Depends(get_async_db),
@@ -349,16 +358,27 @@ async def delete_subscribe_by_mediaid(
subscribe = await Subscribe.async_get_by_mediaid(db, mediaid) subscribe = await Subscribe.async_get_by_mediaid(db, mediaid)
if subscribe: if subscribe:
delete_subscribes.append(subscribe) delete_subscribes.append(subscribe)
delete_events = []
for subscribe in delete_subscribes: for subscribe in delete_subscribes:
# 在删除之前获取订阅信息 subscribe_info = build_subscribe_event_payload(subscribe)
subscribe_info = subscribe.to_dict() subscribe_id = subscribe_info.get("id")
subscribe_id = subscribe.id if not subscribe_id:
await Subscribe.async_delete(db, subscribe_id) continue
# 发送事件 delete_events.append((subscribe_id, subscribe_info))
await eventmanager.async_send_event( await db.delete(subscribe)
EventType.SubscribeDeleted, try:
{"subscribe_id": subscribe_id, "subscribe_info": subscribe_info}, await db.commit()
) except Exception:
await db.rollback()
raise
for subscribe_id, subscribe_info in delete_events:
try:
await eventmanager.async_send_event(
EventType.SubscribeDeleted,
{"subscribe_id": subscribe_id, "subscribe_info": subscribe_info},
)
except Exception as err:
logger.error(f"发送订阅删除事件失败:{subscribe_id} - {err}", exc_info=True)
return schemas.Response(success=True) return schemas.Response(success=True)
@@ -726,8 +746,13 @@ async def delete_subscribe(
subscribe = await Subscribe.async_get(db, subscribe_id) subscribe = await Subscribe.async_get(db, subscribe_id)
if subscribe: if subscribe:
# 在删除之前获取订阅信息 # 在删除之前获取订阅信息
subscribe_info = subscribe.to_dict() subscribe_info = build_subscribe_event_payload(subscribe)
await Subscribe.async_delete(db, subscribe_id) await db.delete(subscribe)
try:
await db.commit()
except Exception:
await db.rollback()
raise
# 发送事件 # 发送事件
await eventmanager.async_send_event( await eventmanager.async_send_event(
EventType.SubscribeDeleted, EventType.SubscribeDeleted,
@@ -735,6 +760,6 @@ async def delete_subscribe(
) )
# 统计订阅 # 统计订阅
MoviePilotServerHelper.sub_done_async( MoviePilotServerHelper.sub_done_async(
{"tmdbid": subscribe.tmdbid, "doubanid": subscribe.doubanid} {"tmdbid": subscribe_info.get("tmdbid"), "doubanid": subscribe_info.get("doubanid")}
) )
return schemas.Response(success=True) return schemas.Response(success=True)
+2 -2
View File
@@ -582,8 +582,8 @@ class SubscribeChain(ChainBase):
"include") else kwargs.get("include"), "include") else kwargs.get("include"),
'exclude': self.__get_default_subscribe_config(mtype, "exclude") if not kwargs.get( 'exclude': self.__get_default_subscribe_config(mtype, "exclude") if not kwargs.get(
"exclude") else kwargs.get("exclude"), "exclude") else kwargs.get("exclude"),
'best_version': self.__get_default_subscribe_config(mtype, "best_version") if not kwargs.get( 'best_version': self.__get_default_subscribe_config(mtype, "best_version")
"best_version") else kwargs.get("best_version"), if kwargs.get("best_version") is None else kwargs.get("best_version"),
'best_version_full': self.__get_default_subscribe_config(mtype, "best_version_full") 'best_version_full': self.__get_default_subscribe_config(mtype, "best_version_full")
if kwargs.get("best_version_full") is None else kwargs.get("best_version_full"), if kwargs.get("best_version_full") is None else kwargs.get("best_version_full"),
'search_imdbid': self.__get_default_subscribe_config(mtype, "search_imdbid") if not kwargs.get( 'search_imdbid': self.__get_default_subscribe_config(mtype, "search_imdbid") if not kwargs.get(
+2 -2
View File
@@ -62,9 +62,9 @@ class Subscribe(BaseModel):
# 下载器 # 下载器
downloader: Optional[str] = None downloader: Optional[str] = None
# 是否洗版 # 是否洗版
best_version: Optional[int] = 0 best_version: Optional[int] = None
# 是否只洗全集整包 # 是否只洗全集整包
best_version_full: Optional[int] = 0 best_version_full: Optional[int] = None
# 当前优先级 # 当前优先级
current_priority: Optional[int] = None current_priority: Optional[int] = None
# 洗版时已下载剧集的优先级状态 # 洗版时已下载剧集的优先级状态
+19
View File
@@ -372,6 +372,25 @@ class SubscribeChainTest(TestCase):
media_info=SimpleNamespace(type=MediaType.TV, tmdb_id=1, douban_id=None), media_info=SimpleNamespace(type=MediaType.TV, tmdb_id=1, douban_id=None),
) )
def test_default_kwargs_respects_explicit_zero_best_version(self):
"""显式关闭洗版时必须保留 0,仅未传值才应用默认订阅规则。"""
def _default_config(_mtype, key):
return 1 if key in {"best_version", "best_version_full"} else None
with patch.object(SubscribeChain, "_SubscribeChain__get_default_subscribe_config", side_effect=_default_config):
explicit = SubscribeChain()._SubscribeChain__get_default_kwargs(
MediaType.TV,
best_version=0,
best_version_full=0,
)
omitted = SubscribeChain()._SubscribeChain__get_default_kwargs(MediaType.TV)
self.assertEqual(explicit["best_version"], 0)
self.assertEqual(explicit["best_version_full"], 0)
self.assertEqual(omitted["best_version"], 1)
self.assertEqual(omitted["best_version_full"], 1)
def test_format_subscribe_progress_preserves_special_season_zero(self): def test_format_subscribe_progress_preserves_special_season_zero(self):
"""订阅列表展示必须把 S0 当作合法季号,而不是回退到第 1 季。""" """订阅列表展示必须把 S0 当作合法季号,而不是回退到第 1 季。"""
subscribe = self._build_subscribe(season=0, total_episode=5, lack_episode=2) subscribe = self._build_subscribe(season=0, total_episode=5, lack_episode=2)