mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor: finish transactional runtime migration
This commit is contained in:
+58
-36
@@ -24,14 +24,16 @@ class AgentTaskOper(DbOper):
|
||||
新增 Agent 定时任务。
|
||||
"""
|
||||
now = self._now()
|
||||
task_id = AgentTask.add_task(
|
||||
self._db,
|
||||
**kwargs,
|
||||
enabled=True,
|
||||
last_status="waiting",
|
||||
run_count=0,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
task_id = self._execute_sync_write(
|
||||
lambda session: AgentTask.add_task(
|
||||
session,
|
||||
**kwargs,
|
||||
enabled=True,
|
||||
last_status="waiting",
|
||||
run_count=0,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
)
|
||||
return self.get(task_id)
|
||||
|
||||
@@ -81,38 +83,50 @@ class AgentTaskOper(DbOper):
|
||||
if not normalized_payload:
|
||||
return False
|
||||
normalized_payload["updated_at"] = self._now()
|
||||
return AgentTask.update_task(
|
||||
self._db,
|
||||
task_id=task_id,
|
||||
payload=normalized_payload,
|
||||
user_id=user_id,
|
||||
return self._execute_sync_write(
|
||||
lambda session: AgentTask.update_task(
|
||||
session,
|
||||
task_id=task_id,
|
||||
payload=normalized_payload,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
|
||||
def delete(self, task_id: int, user_id: Optional[str] = None) -> bool:
|
||||
"""
|
||||
删除非运行中的 Agent 定时任务及其运行历史。
|
||||
"""
|
||||
return AgentTaskRun.delete_task_and_runs(
|
||||
self._db,
|
||||
task_id=task_id,
|
||||
user_id=user_id,
|
||||
return self._execute_sync_write(
|
||||
lambda session: AgentTaskRun.delete_task_and_runs(
|
||||
session,
|
||||
task_id=task_id,
|
||||
user_id=user_id,
|
||||
)
|
||||
)
|
||||
|
||||
def begin_run(
|
||||
self,
|
||||
task_id: int,
|
||||
trigger_source: str = "scheduled",
|
||||
*,
|
||||
run_id: Optional[str] = None,
|
||||
started_at: Optional[str] = None,
|
||||
) -> Optional[AgentTaskRun]:
|
||||
"""
|
||||
原子创建一次运行并返回其任务快照。
|
||||
|
||||
可选运行 ID 和开始时间用于恢复/幂等验证;正常调度入口由本方法生成。
|
||||
"""
|
||||
run_id = uuid4().hex
|
||||
created_run_id = AgentTaskRun.begin_run(
|
||||
self._db,
|
||||
task_id=task_id,
|
||||
run_id=run_id,
|
||||
trigger_source=trigger_source,
|
||||
started_at=self._now(),
|
||||
resolved_run_id = run_id or uuid4().hex
|
||||
resolved_started_at = started_at or self._now()
|
||||
created_run_id = self._execute_sync_write(
|
||||
lambda session: AgentTaskRun.begin_run(
|
||||
session,
|
||||
task_id=task_id,
|
||||
run_id=resolved_run_id,
|
||||
trigger_source=trigger_source,
|
||||
started_at=resolved_started_at,
|
||||
)
|
||||
)
|
||||
return self.get_run(created_run_id) if created_run_id else None
|
||||
|
||||
@@ -124,11 +138,15 @@ class AgentTaskOper(DbOper):
|
||||
"""
|
||||
将遗留的运行中任务标记为中断且结果未知。
|
||||
"""
|
||||
return AgentTaskRun.interrupt_task(
|
||||
self._db,
|
||||
task_id=task_id,
|
||||
result=(result or "")[:20000],
|
||||
finished_at=self._now(),
|
||||
finished_at = self._now()
|
||||
normalized_result = (result or "")[:20000]
|
||||
return self._execute_sync_write(
|
||||
lambda session: AgentTaskRun.interrupt_task(
|
||||
session,
|
||||
task_id=task_id,
|
||||
result=normalized_result,
|
||||
finished_at=finished_at,
|
||||
)
|
||||
)
|
||||
|
||||
def get_run(self, run_id: str) -> Optional[AgentTaskRun]:
|
||||
@@ -157,13 +175,17 @@ class AgentTaskOper(DbOper):
|
||||
disable_date_task: bool = False,
|
||||
) -> bool:
|
||||
"""收口精确运行并更新仍匹配的任务投影。"""
|
||||
return AgentTaskRun.finish_run(
|
||||
self._db,
|
||||
run_id=run_id,
|
||||
success=success,
|
||||
result=(result or "")[:20000],
|
||||
finished_at=self._now(),
|
||||
disable_date_task=disable_date_task,
|
||||
finished_at = self._now()
|
||||
normalized_result = (result or "")[:20000]
|
||||
return self._execute_sync_write(
|
||||
lambda session: AgentTaskRun.finish_run(
|
||||
session,
|
||||
run_id=run_id,
|
||||
success=success,
|
||||
result=normalized_result,
|
||||
finished_at=finished_at,
|
||||
disable_date_task=disable_date_task,
|
||||
)
|
||||
)
|
||||
|
||||
def finish(
|
||||
|
||||
@@ -54,8 +54,10 @@ class DownloadFailureOper(DbOper):
|
||||
"""
|
||||
删除已过期较久的失败记录。
|
||||
"""
|
||||
return DownloadFailure.delete_expired(
|
||||
self._db,
|
||||
before_time=before_time,
|
||||
limit=limit,
|
||||
return self._execute_sync_write(
|
||||
lambda session: DownloadFailure.delete_expired(
|
||||
session,
|
||||
before_time=before_time,
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -127,7 +127,9 @@ class DownloadHistoryOper(DbOper):
|
||||
按fullpath删除下载文件记录
|
||||
:param fullpath: 数据key
|
||||
"""
|
||||
DownloadFiles.delete_by_fullpath(self._db, fullpath)
|
||||
self._execute_sync_write(
|
||||
lambda session: DownloadFiles.delete_by_fullpath(session, fullpath)
|
||||
)
|
||||
|
||||
def stage_delete_file_by_fullpath(self, fullpath: str) -> None:
|
||||
"""暂存指定完整路径的下载文件记录删除。"""
|
||||
|
||||
@@ -61,19 +61,32 @@ class MediaServerOper(DbOper):
|
||||
"""
|
||||
清空媒体服务器数据
|
||||
"""
|
||||
MediaServerItem.empty(self._db, server)
|
||||
self._execute_sync_write(
|
||||
lambda session: MediaServerItem.empty(session, server)
|
||||
)
|
||||
|
||||
def delete_stale(self, server: str, sync_time: str) -> int:
|
||||
"""
|
||||
删除本轮同步未更新的旧数据
|
||||
"""
|
||||
return MediaServerItem.delete_stale(self._db, server, sync_time)
|
||||
return self._execute_sync_write(
|
||||
lambda session: MediaServerItem.delete_stale(
|
||||
session,
|
||||
server,
|
||||
sync_time,
|
||||
)
|
||||
)
|
||||
|
||||
def delete_excluded_servers(self, servers: list[str]) -> int:
|
||||
"""
|
||||
删除未启用或已移除媒体服务器的数据
|
||||
"""
|
||||
return MediaServerItem.delete_excluded_servers(self._db, servers)
|
||||
return self._execute_sync_write(
|
||||
lambda session: MediaServerItem.delete_excluded_servers(
|
||||
session,
|
||||
servers,
|
||||
)
|
||||
)
|
||||
|
||||
def exists(self, **kwargs) -> Optional[MediaServerItem]:
|
||||
"""
|
||||
|
||||
@@ -62,7 +62,8 @@ class MessageOper(DbOper):
|
||||
if k not in Message.__table__.columns.keys(): # noqa
|
||||
kwargs.pop(k)
|
||||
|
||||
return Message(**kwargs).create_and_to_dict(self._db)
|
||||
message = Message(**kwargs)
|
||||
return self._execute_sync_write(message.create_and_to_dict)
|
||||
|
||||
async def async_add(self,
|
||||
channel: Optional[NotificationChannel] = None,
|
||||
|
||||
+21
-3
@@ -24,13 +24,31 @@ class PassKeyOper(DbOper):
|
||||
def create(self, payload: dict[str, Any]) -> PassKey:
|
||||
"""创建 PassKey 凭证。"""
|
||||
passkey = PassKey(**payload)
|
||||
passkey.create(self._db)
|
||||
self._execute_sync_write(lambda session: self._stage_create(session, passkey))
|
||||
return passkey
|
||||
|
||||
@staticmethod
|
||||
def _stage_create(session: Any, passkey: PassKey) -> None:
|
||||
"""在调用方事务中暂存凭证并分配主键。"""
|
||||
session.add(passkey)
|
||||
session.flush()
|
||||
|
||||
def update_last_used(self, passkey: PassKey, sign_count: int) -> bool:
|
||||
"""更新凭证最后使用时间和签名计数。"""
|
||||
return bool(passkey.update_last_used(self._db, sign_count))
|
||||
return bool(self._execute_sync_write(
|
||||
lambda session: passkey.update_last_used(session, sign_count)
|
||||
))
|
||||
|
||||
def delete_by_id(self, passkey_id: int, user_id: int) -> bool:
|
||||
"""删除指定用户的凭证。"""
|
||||
return bool(PassKey.delete_by_id(self._db, passkey_id, user_id))
|
||||
return bool(self._execute_sync_write(
|
||||
lambda session: PassKey.delete_by_id(session, passkey_id, user_id)
|
||||
))
|
||||
|
||||
async def async_delete_by_id(self, passkey_id: int, user_id: int) -> bool:
|
||||
"""在独立异步事务中删除指定用户的凭证。"""
|
||||
return bool(await self._execute_async_write(
|
||||
lambda session: PassKey.async_delete_by_id(
|
||||
session, passkey_id, user_id
|
||||
)
|
||||
))
|
||||
|
||||
@@ -80,10 +80,14 @@ class PluginDataOper(DbOper):
|
||||
:param plugin_id: 插件id
|
||||
:param key: 数据key
|
||||
"""
|
||||
if key:
|
||||
PluginData.del_plugin_data_by_key(self._db, plugin_id, key)
|
||||
else:
|
||||
PluginData.del_plugin_data(self._db, plugin_id)
|
||||
def stage(session: Session) -> None:
|
||||
"""把兼容删除入口映射到调用方或组合根持有的事务。"""
|
||||
if key:
|
||||
PluginData.del_plugin_data_by_key(session, plugin_id, key)
|
||||
else:
|
||||
PluginData.del_plugin_data(session, plugin_id)
|
||||
|
||||
self._execute_sync_write(stage)
|
||||
|
||||
def stage_delete(self, plugin_id: str) -> None:
|
||||
"""暂存目标插件全部数据删除并 flush,不提交调用方事务。"""
|
||||
|
||||
+2
-2
@@ -116,8 +116,8 @@ class SiteOper(DbOper):
|
||||
Site.delete(self._db, sid)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""清空站点表,保留站点模型细节在数据库适配层。"""
|
||||
Site.reset(self._db)
|
||||
"""清空站点表;兼容入口的事务由组合根统一持有。"""
|
||||
self._execute_sync_write(Site.reset)
|
||||
|
||||
async def stage_reset(self) -> None:
|
||||
"""暂存清空站点表,由应用事务统一提交。"""
|
||||
|
||||
@@ -264,14 +264,18 @@ class TransferHistoryOper(DbOper):
|
||||
kwargs.update({
|
||||
"date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
})
|
||||
TransferHistory.replace_by_src(self._db, **kwargs)
|
||||
def stage(session: Session) -> Optional[TransferHistory]:
|
||||
"""在同一事务替换记录并返回兼容查询投影。"""
|
||||
TransferHistory.replace_by_src(session, **kwargs)
|
||||
return TransferHistory.get_by_src(
|
||||
session,
|
||||
kwargs.get("src"),
|
||||
kwargs["src_storage"],
|
||||
)
|
||||
|
||||
# 保持 add_force 的既有返回契约:返回可被调用方安全读取字段的查询结果,
|
||||
# 而非事务提交后可能已脱离会话的新建实例。
|
||||
return TransferHistory.get_by_src(
|
||||
self._db,
|
||||
kwargs.get("src"),
|
||||
kwargs["src_storage"],
|
||||
)
|
||||
return self._execute_sync_write(stage)
|
||||
|
||||
def stage_replace_by_src(self, **kwargs) -> TransferHistory:
|
||||
"""在调用方事务内按源路径替换整理历史并返回已分配 ID 的新记录。"""
|
||||
@@ -295,7 +299,13 @@ class TransferHistoryOper(DbOper):
|
||||
"""
|
||||
补充转移记录download_hash
|
||||
"""
|
||||
TransferHistory.update_download_hash(self._db, historyid, download_hash)
|
||||
self._execute_sync_write(
|
||||
lambda session: TransferHistory.update_download_hash(
|
||||
session,
|
||||
historyid,
|
||||
download_hash,
|
||||
)
|
||||
)
|
||||
|
||||
def list_by_date(self, date: str) -> List[TransferHistory]:
|
||||
"""
|
||||
|
||||
@@ -20,11 +20,14 @@ class TransferPendingOper(DbOper):
|
||||
:param src_path: 源文件路径
|
||||
:return: 登记记录
|
||||
"""
|
||||
return TransferPending.register(
|
||||
self._db,
|
||||
storage=storage,
|
||||
src_path=src_path,
|
||||
now_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
return self._execute_sync_write(
|
||||
lambda session: TransferPending.register(
|
||||
session,
|
||||
storage=storage,
|
||||
src_path=src_path,
|
||||
now_time=now_time,
|
||||
)
|
||||
)
|
||||
|
||||
def discard(self, storage: str, src_path: str) -> int:
|
||||
@@ -34,7 +37,13 @@ class TransferPendingOper(DbOper):
|
||||
:param src_path: 源文件路径
|
||||
:return: 删除的记录数
|
||||
"""
|
||||
return TransferPending.discard(self._db, storage=storage, src_path=src_path)
|
||||
return self._execute_sync_write(
|
||||
lambda session: TransferPending.discard(
|
||||
session,
|
||||
storage=storage,
|
||||
src_path=src_path,
|
||||
)
|
||||
)
|
||||
|
||||
def list_all(self, limit: Optional[int] = 5000) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
@@ -56,4 +65,4 @@ class TransferPendingOper(DbOper):
|
||||
清空全部待整理登记。
|
||||
:return: 删除的记录数
|
||||
"""
|
||||
return TransferPending.clear(self._db)
|
||||
return self._execute_sync_write(TransferPending.clear)
|
||||
|
||||
+34
-6
@@ -11,6 +11,8 @@ runtime 兼容映射指向 SDK 薄门面;canonical 数据访问模块仍只依
|
||||
"""
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.base import DbOper
|
||||
from app.db.models.user import User
|
||||
|
||||
@@ -49,27 +51,53 @@ class UserOper(DbOper):
|
||||
|
||||
async def async_create(self, payload: dict) -> Optional[User]:
|
||||
"""异步创建用户。"""
|
||||
return await User(**payload).async_create(self._db)
|
||||
user = User(**payload)
|
||||
|
||||
async def stage(session: AsyncSession) -> User:
|
||||
"""在当前异步事务中暂存用户并分配主键。"""
|
||||
session.add(user)
|
||||
await session.flush()
|
||||
return user
|
||||
|
||||
return await self._execute_async_write(stage)
|
||||
|
||||
async def async_update(self, user_id: int, payload: dict) -> Optional[User]:
|
||||
"""异步更新用户。"""
|
||||
user = await self.async_get_by_id(user_id)
|
||||
if user:
|
||||
await user.async_update(self._db, payload)
|
||||
async def stage(session: AsyncSession) -> User:
|
||||
"""在当前事务中更新用户字段,必要时重新附加游离对象。"""
|
||||
for key, value in payload.items():
|
||||
setattr(user, key, value)
|
||||
return await session.merge(user)
|
||||
|
||||
await self._execute_async_write(stage)
|
||||
return user
|
||||
|
||||
async def async_delete(self, user_id: int) -> None:
|
||||
async def async_delete(self, user_id: int) -> bool:
|
||||
"""异步删除用户。"""
|
||||
await User.async_delete_by_id(self._db, user_id)
|
||||
return bool(await self._execute_async_write(
|
||||
lambda session: User.async_delete_by_id(session, user_id)
|
||||
))
|
||||
|
||||
async def async_delete_by_name(self, name: str) -> bool:
|
||||
"""在独立异步事务中按用户名删除用户。"""
|
||||
return bool(await self._execute_async_write(
|
||||
lambda session: User().async_delete_by_name(session, name)
|
||||
))
|
||||
|
||||
async def async_update_otp_by_name(
|
||||
self,
|
||||
name: str,
|
||||
otp: bool,
|
||||
secret: str,
|
||||
) -> None:
|
||||
) -> bool:
|
||||
"""异步更新用户 OTP 状态。"""
|
||||
await User.async_update_otp_by_name(self._db, name, otp, secret)
|
||||
return bool(await self._execute_async_write(
|
||||
lambda session: User.async_update_otp_by_name(
|
||||
session, name, otp, secret
|
||||
)
|
||||
))
|
||||
|
||||
async def async_get_by_name(self, name: str) -> Optional[User]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user