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

View File

@@ -19,6 +19,7 @@ from app.db.models.user import User
from app.db.systemconfig_oper import SystemConfigOper
from app.db.user_oper import get_current_active_user_async
from app.helper.server import MoviePilotServerHelper
from app.log import logger
from app.scheduler import Scheduler
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])
async def read_subscribes(
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)
if subscribe:
delete_subscribes.append(subscribe)
delete_events = []
for subscribe in delete_subscribes:
# 在删除之前获取订阅信息
subscribe_info = subscribe.to_dict()
subscribe_id = subscribe.id
await Subscribe.async_delete(db, subscribe_id)
# 发送事件
await eventmanager.async_send_event(
EventType.SubscribeDeleted,
{"subscribe_id": subscribe_id, "subscribe_info": subscribe_info},
)
subscribe_info = build_subscribe_event_payload(subscribe)
subscribe_id = subscribe_info.get("id")
if not subscribe_id:
continue
delete_events.append((subscribe_id, subscribe_info))
await db.delete(subscribe)
try:
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)
@@ -726,8 +746,13 @@ async def delete_subscribe(
subscribe = await Subscribe.async_get(db, subscribe_id)
if subscribe:
# 在删除之前获取订阅信息
subscribe_info = subscribe.to_dict()
await Subscribe.async_delete(db, subscribe_id)
subscribe_info = build_subscribe_event_payload(subscribe)
await db.delete(subscribe)
try:
await db.commit()
except Exception:
await db.rollback()
raise
# 发送事件
await eventmanager.async_send_event(
EventType.SubscribeDeleted,
@@ -735,6 +760,6 @@ async def delete_subscribe(
)
# 统计订阅
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)

View File

@@ -582,8 +582,8 @@ class SubscribeChain(ChainBase):
"include") else kwargs.get("include"),
'exclude': self.__get_default_subscribe_config(mtype, "exclude") if not kwargs.get(
"exclude") else kwargs.get("exclude"),
'best_version': self.__get_default_subscribe_config(mtype, "best_version") if not kwargs.get(
"best_version") else kwargs.get("best_version"),
'best_version': self.__get_default_subscribe_config(mtype, "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")
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(

View File

@@ -62,9 +62,9 @@ class Subscribe(BaseModel):
# 下载器
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
# 洗版时已下载剧集的优先级状态

View File

@@ -372,6 +372,25 @@ class SubscribeChainTest(TestCase):
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):
"""订阅列表展示必须把 S0 当作合法季号,而不是回退到第 1 季。"""
subscribe = self._build_subscribe(season=0, total_episode=5, lack_episode=2)