mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-29 03:18:14 +08:00
305 lines
9.5 KiB
Python
305 lines
9.5 KiB
Python
"""
|
||
数据库治理能力的宿主组合根。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
|
||
from app.adapters.system.backup.database import (
|
||
PostgreSQLBackupBackend,
|
||
SQLiteBackupBackend,
|
||
)
|
||
from app.adapters.system.backup.files import BackupFiles
|
||
from app.application.backup import (
|
||
BackupPolicy,
|
||
DatabaseBackupBackend,
|
||
DatabaseBackupService,
|
||
)
|
||
from app.application.database import (
|
||
DatabaseGovernance,
|
||
DatabaseHealthService,
|
||
configure_database_governance,
|
||
reset_database_governance,
|
||
)
|
||
from app.application.maintenance import (
|
||
DataCleanupService,
|
||
read_cleanup_policy,
|
||
)
|
||
from app.application.plugin.runtime import get_existing_plugin_manager
|
||
from app.application.plugin.transaction import (
|
||
PluginPersistenceService,
|
||
configure_plugin_persistence,
|
||
reset_plugin_persistence,
|
||
)
|
||
from app.application.query import (
|
||
DataQueryService,
|
||
configure_data_query_service,
|
||
reset_data_query_service,
|
||
)
|
||
from app.application.workflow import (
|
||
WorkflowQueryService,
|
||
configure_workflow_execution,
|
||
configure_workflow_query,
|
||
reset_workflow_execution,
|
||
reset_workflow_query,
|
||
)
|
||
from app.db.adapters.pluginidentity import TransactionalPluginIdentityStore
|
||
from app.db.adapters.plugininstallation import TransactionalPluginInstallationStore
|
||
from app.db.adapters.query import SqlAlchemyDataQueryAdapter
|
||
from app.db.adapters.transaction import TransactionalWriteRunner
|
||
from app.db.adapters.user import TransactionalUserRepository
|
||
from app.db.adapters.workflow import (
|
||
TransactionalWorkflowExecutionService,
|
||
TransactionalWorkflowQueryRepository,
|
||
)
|
||
from app.db.engine import get_engine
|
||
from app.db.health import probe_database
|
||
from app.db.maintenance import DatabaseCleanupRepository
|
||
from app.db.oper.systemconfig import SystemConfigOper
|
||
from app.db.plugin.registry import database_handles
|
||
from app.db.session import (
|
||
SessionFactory,
|
||
async_session_scope,
|
||
)
|
||
from app.db.uow import (
|
||
configure_transaction_runners,
|
||
reset_transaction_runners,
|
||
)
|
||
from app.db.worker import DatabaseWorker
|
||
from app.runtime.log import logger
|
||
from app.runtime.settings import get_runtime_setting
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class DatabaseRuntime:
|
||
"""
|
||
保存数据库 worker 与兼容写入口共享的事务执行器。
|
||
|
||
:param worker: 在专属线程执行同步数据库短事务的进程级 worker
|
||
:param transaction: 提供单次独占会话的写事务执行器
|
||
"""
|
||
|
||
worker: DatabaseWorker
|
||
transaction: TransactionalWriteRunner
|
||
|
||
|
||
@dataclass(frozen=True, slots=True)
|
||
class DatabaseComposition:
|
||
"""
|
||
保存初始化器后续装配所需的数据库查询能力。
|
||
|
||
:param data_query: 已配置数据库访问端口的数据查询服务
|
||
:param plugin_persistence: 插件安装和身份持久化应用服务
|
||
:param workflow_query: 工作流定义与状态查询服务
|
||
"""
|
||
|
||
data_query: DataQueryService
|
||
plugin_persistence: PluginPersistenceService
|
||
workflow_query: WorkflowQueryService
|
||
|
||
|
||
_database_runtime: DatabaseRuntime | None = None
|
||
_database_runtime_starting = False
|
||
|
||
|
||
async def start_database_runtime() -> DatabaseRuntime:
|
||
"""
|
||
启动进程唯一数据库 worker,并登记兼容写事务 runner。
|
||
"""
|
||
global _database_runtime, _database_runtime_starting
|
||
if _database_runtime is not None or _database_runtime_starting:
|
||
raise RuntimeError("数据库 runtime 已由当前进程持有")
|
||
_database_runtime_starting = True
|
||
try:
|
||
transaction = TransactionalWriteRunner(
|
||
sync_session=SessionFactory,
|
||
async_session=async_session_scope,
|
||
)
|
||
worker = DatabaseWorker()
|
||
await worker.start()
|
||
configure_transaction_runners(
|
||
sync=transaction.sync,
|
||
async_=transaction.async_,
|
||
)
|
||
runtime = DatabaseRuntime(worker=worker, transaction=transaction)
|
||
_database_runtime = runtime
|
||
return runtime
|
||
finally:
|
||
_database_runtime_starting = False
|
||
|
||
|
||
async def stop_database_runtime() -> None:
|
||
"""
|
||
关闭数据库 worker;失败时保留 owner 供诊断和重试。
|
||
"""
|
||
global _database_runtime
|
||
runtime = _database_runtime
|
||
if runtime is not None:
|
||
await runtime.worker.shutdown()
|
||
_database_runtime = None
|
||
|
||
|
||
def database_runtime_active() -> bool:
|
||
"""
|
||
返回数据库 worker 是否仍由当前进程持有。
|
||
"""
|
||
return _database_runtime is not None
|
||
|
||
|
||
def build_transactional_user_repository() -> TransactionalUserRepository:
|
||
"""
|
||
构造供 Chain、Agent 与进程级认证共享的短会话用户仓储。
|
||
"""
|
||
return TransactionalUserRepository(
|
||
sync_session=SessionFactory,
|
||
async_session=async_session_scope,
|
||
)
|
||
|
||
|
||
def compose_database_services(
|
||
*,
|
||
runtime: DatabaseRuntime,
|
||
system_config: SystemConfigOper,
|
||
) -> DatabaseComposition:
|
||
"""
|
||
组合查询、插件持久化与工作流查询所需实现。
|
||
"""
|
||
data_query_adapter = SqlAlchemyDataQueryAdapter(SessionFactory)
|
||
data_query = DataQueryService(
|
||
subscriptions=data_query_adapter,
|
||
histories=data_query_adapter,
|
||
async_executor=runtime.worker,
|
||
)
|
||
plugin_persistence = PluginPersistenceService(
|
||
executor=runtime.worker,
|
||
identities=TransactionalPluginIdentityStore(SessionFactory),
|
||
installations=TransactionalPluginInstallationStore(
|
||
SessionFactory,
|
||
system_config.update_atomically,
|
||
),
|
||
)
|
||
workflow_query = WorkflowQueryService(
|
||
repository=TransactionalWorkflowQueryRepository(
|
||
sync_session=SessionFactory,
|
||
async_session=async_session_scope,
|
||
)
|
||
)
|
||
return DatabaseComposition(
|
||
data_query=data_query,
|
||
plugin_persistence=plugin_persistence,
|
||
workflow_query=workflow_query,
|
||
)
|
||
|
||
|
||
def publish_database_services(composition: DatabaseComposition) -> None:
|
||
"""
|
||
一次性发布当前 lifespan 的查询与插件持久化服务。
|
||
"""
|
||
configure_data_query_service(composition.data_query)
|
||
configure_plugin_persistence(composition.plugin_persistence)
|
||
configure_workflow_query(composition.workflow_query)
|
||
|
||
|
||
def configure_workflow_execution_composition() -> None:
|
||
"""
|
||
构造并发布使用短事务会话的工作流执行服务。
|
||
"""
|
||
configure_workflow_execution(TransactionalWorkflowExecutionService(SessionFactory))
|
||
|
||
|
||
def reset_workflow_execution_composition() -> None:
|
||
"""
|
||
撤销当前 lifespan 发布的工作流执行服务。
|
||
"""
|
||
reset_workflow_execution()
|
||
|
||
|
||
def reset_database_services() -> None:
|
||
"""
|
||
撤销当前 lifespan 发布的全部数据库派生服务。
|
||
"""
|
||
reset_database_governance()
|
||
reset_transaction_runners()
|
||
reset_workflow_query()
|
||
reset_plugin_persistence()
|
||
reset_data_query_service()
|
||
|
||
|
||
def build_database_governance() -> DatabaseGovernance:
|
||
"""
|
||
以缓存同步引擎为事实源构造一个完整数据库治理门面。
|
||
"""
|
||
engine = get_engine()
|
||
dialect = engine.dialect.name
|
||
backup_backend: DatabaseBackupBackend
|
||
if dialect == "sqlite":
|
||
backup_backend = SQLiteBackupBackend(engine)
|
||
elif dialect == "postgresql":
|
||
backup_backend = PostgreSQLBackupBackend(engine)
|
||
else:
|
||
raise RuntimeError(f"不支持的数据库类型:{dialect}")
|
||
|
||
def backup_plugin_databases() -> None:
|
||
"""
|
||
为当前已建立的 SQLite 插件库创建独立制品。
|
||
"""
|
||
if dialect != "sqlite":
|
||
return
|
||
manager = get_existing_plugin_manager()
|
||
if manager is None:
|
||
return
|
||
for handle in database_handles():
|
||
if handle.db_path is None:
|
||
continue
|
||
version = manager.get_local_plugin_version(handle.plugin_id)
|
||
if not version:
|
||
logger.warning(
|
||
"插件 %s 未找到版本,将使用无版本文件名继续备份数据库",
|
||
handle.plugin_id,
|
||
)
|
||
plugin_service = DatabaseBackupService(
|
||
backend=SQLiteBackupBackend(handle.engine),
|
||
artifact_store_factory=BackupFiles,
|
||
policy_reader=lambda: BackupPolicy(
|
||
root=get_runtime_setting("DATABASE_BACKUP_PATH") / "plugins" / handle.plugin_id,
|
||
retention_days=get_runtime_setting("DB_BACKUP_RETENTION_DAYS"),
|
||
max_count=get_runtime_setting("DB_BACKUP_MAX_COUNT"),
|
||
),
|
||
target=handle.plugin_id,
|
||
version=version,
|
||
)
|
||
plugin_service.create()
|
||
|
||
return DatabaseGovernance(
|
||
health=DatabaseHealthService(probe_database),
|
||
cleanup=DataCleanupService(
|
||
repository=DatabaseCleanupRepository(session_factory=SessionFactory),
|
||
policy_reader=read_cleanup_policy,
|
||
),
|
||
backup=DatabaseBackupService(
|
||
backend=backup_backend,
|
||
artifact_store_factory=BackupFiles,
|
||
policy_reader=read_backup_policy,
|
||
),
|
||
plugin_backup=backup_plugin_databases,
|
||
)
|
||
|
||
|
||
def configure_database() -> None:
|
||
"""
|
||
构造并登记宿主进程唯一的数据库治理门面。
|
||
"""
|
||
configure_database_governance(build_database_governance())
|
||
|
||
|
||
def read_backup_policy() -> BackupPolicy:
|
||
"""
|
||
读取一次可热更新的数据库备份目录与保留策略。
|
||
"""
|
||
return BackupPolicy(
|
||
root=get_runtime_setting("DATABASE_BACKUP_PATH"),
|
||
retention_days=get_runtime_setting("DB_BACKUP_RETENTION_DAYS"),
|
||
max_count=get_runtime_setting("DB_BACKUP_MAX_COUNT"),
|
||
)
|