mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-12 00:30:28 +08:00
release/destroy 统一经 _close_handle 先清线程局部会话再 dispose,两步失败都 只记日志:单个插件的连接池故障不再让 close_database 跳过宿主同步与异步引擎的 释放,release_all_databases 也逐插件隔离。按模型建表对 __table__ 去重,单表 继承的父子类一并声明时不再对同一张表建两次。 卸载编排先删插件数据、后停插件,停机钩子里的 get_database() 会把刚销毁的库 重新建出来:注册表记下已销毁的插件标识,其后的 release 改为再次销毁并清除 标记,ensure 则撤销标记,宿主编排代码不动。PostgreSQL schema 名在归一改写过 标识或超长时追加插件标识哈希并截到 63 字节,My-Plugin、My_Plugin 与 my_plugin 不再折叠到同一个 schema 而在卸载时互相 DROP。 start 的失败分支释放已经建立、却进不了运行态的句柄,整体停止把只登记在 _classes 里的插件一并释放。声明的迁移目录不存在时在建句柄之前抛 FileNotFoundError,不再留下空库;SQLite 迁移 URL 改由引擎渲染; _get_sqlite_engine 的连接参数下沉到实际使用它的异步分支。
76 lines
2.9 KiB
Python
76 lines
2.9 KiB
Python
"""插件数据库文件路径与 PostgreSQL schema 名的解析,不持有任何状态。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
from app.runtime.settings import get_runtime_setting
|
|
|
|
__all__ = [
|
|
"plugin_schema_name",
|
|
"sqlite_database_path",
|
|
"sqlite_sidecar_paths",
|
|
]
|
|
|
|
# 库文件名固定:所在目录已按插件标识分段,文件名无需再编码插件身份
|
|
DATABASE_FILENAME = "plugin.db"
|
|
# SQLite WAL 模式下与库文件同生共死的边车文件后缀
|
|
SIDECAR_SUFFIXES = ("-wal", "-shm")
|
|
# PostgreSQL 标识符上限 63 字节,超出部分会被静默截断成另一个 schema
|
|
SCHEMA_NAME_MAX_LENGTH = 63
|
|
# 归一改写了插件标识时用于区分两个插件的哈希长度
|
|
SCHEMA_NAME_HASH_LENGTH = 8
|
|
|
|
|
|
def sqlite_database_path(plugin_id: str) -> Path:
|
|
"""
|
|
返回插件 SQLite 库文件路径。
|
|
|
|
落在与 ``_PluginBase.get_data_path()`` 完全相同的插件数据目录下,插件库因此随插件
|
|
数据一起被备份、迁移和删除,不会形成第二份需要单独维护的持久化根。本函数不创建
|
|
目录:``ensure`` 在插件什么都没声明时不得凭空产生目录。
|
|
:param plugin_id: 插件标识
|
|
:return: 库文件绝对路径
|
|
"""
|
|
return Path(get_runtime_setting("PLUGIN_DATA_PATH")) / plugin_id / DATABASE_FILENAME
|
|
|
|
|
|
def sqlite_sidecar_paths(db_path: Path) -> tuple[Path, ...]:
|
|
"""
|
|
返回库文件对应的 WAL/SHM 边车文件路径。
|
|
:param db_path: 库文件路径
|
|
:return: 边车路径元组,不保证文件存在
|
|
"""
|
|
return tuple(db_path.with_name(db_path.name + suffix) for suffix in SIDECAR_SUFFIXES)
|
|
|
|
|
|
def plugin_schema_name(plugin_id: str) -> str:
|
|
"""
|
|
按插件标识拼出 PostgreSQL schema 名。
|
|
|
|
只保留 ASCII 小写字母、数字与下划线,避免插件标识中的字符逃逸成标识符片段,也让
|
|
名字的字符数与字节数一致。归一是多对一的:``My-Plugin``、``My_Plugin`` 与
|
|
``my_plugin`` 会折叠到同一个名字,而卸载一个插件执行的是 ``DROP SCHEMA ...
|
|
CASCADE``,折叠意味着删掉另一个插件的全部数据。
|
|
因此只要归一改写过标识,就追加插件标识的哈希把它们重新分开;超长标识同样追加哈希
|
|
再截断,截断本身也是一次折叠。
|
|
:param plugin_id: 插件标识
|
|
:return: 合法且与插件标识一一对应的 schema 名
|
|
"""
|
|
raw = f"plugin_{plugin_id}"
|
|
sanitized = "".join(
|
|
character
|
|
if (character.isascii() and character.isalnum()) or character == "_"
|
|
else "_"
|
|
for character in raw.lower()
|
|
)
|
|
if sanitized == raw and len(sanitized) <= SCHEMA_NAME_MAX_LENGTH:
|
|
return sanitized
|
|
digest = hashlib.sha1(
|
|
plugin_id.encode("utf-8"),
|
|
usedforsecurity=False,
|
|
).hexdigest()[:SCHEMA_NAME_HASH_LENGTH]
|
|
suffix = f"_{digest}"
|
|
return f"{sanitized[:SCHEMA_NAME_MAX_LENGTH - len(suffix)]}{suffix}"
|