mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 05:17:23 +08:00
Merge pull request #6666 from Aqr-K/feat/plugin-instance-target-enablement
feat(plugin): 插件实例默认调用目标与启停,未指定实例时不再兜底取第一个
This commit is contained in:
110
database/versions/b7d1e4a9c206_3_0_38.py
Normal file
110
database/versions/b7d1e4a9c206_3_0_38.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""3.0.38 插件实例增加启用位,并把本体的装载判据搬到这一列上。
|
||||
|
||||
Revision ID: b7d1e4a9c206
|
||||
Revises: e0e68cbd5756
|
||||
Create Date: 2026-09-13
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "b7d1e4a9c206"
|
||||
down_revision = "e0e68cbd5756"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
_TABLE = "plugininstance"
|
||||
_INSTALLED_KEY = "UserInstalledPlugins"
|
||||
|
||||
|
||||
def _table_names(connection) -> set:
|
||||
"""读取当前数据库已有表名。"""
|
||||
return set(sa.inspect(connection).get_table_names())
|
||||
|
||||
|
||||
def _column_names(connection) -> set:
|
||||
"""读取实例表已有列名;表不存在时为空集。"""
|
||||
if _TABLE not in _table_names(connection):
|
||||
return set()
|
||||
return {column["name"] for column in sa.inspect(connection).get_columns(_TABLE)}
|
||||
|
||||
|
||||
def _installed_plugin_ids(connection) -> list:
|
||||
"""读取安装清单里的插件 ID,载荷不是字符串数组时按空清单处理。"""
|
||||
if "systemconfig" not in _table_names(connection):
|
||||
return []
|
||||
row = connection.execute(
|
||||
sa.text("SELECT value FROM systemconfig WHERE key = :key"),
|
||||
{"key": _INSTALLED_KEY},
|
||||
).fetchone()
|
||||
if row is None or row[0] is None:
|
||||
return []
|
||||
value = row[0]
|
||||
if isinstance(value, (str, bytes, bytearray)):
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except (TypeError, ValueError):
|
||||
return []
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [item for item in value if isinstance(item, str) and item]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""加上启用位,并把存量的分身与已安装插件本体一并登记为启用。
|
||||
|
||||
存量行必须显式补成启用:这一列同时是本体的装载判据,列建出来默认为假而不回填,
|
||||
升级后全部插件都会变成「装着但永不加载」。分身按「行存在即在册」的旧语义一律置真;
|
||||
本体按安装清单补行并置真——清单在此之前一直兼任运行开关,两者在升级这一刻等价。
|
||||
"""
|
||||
connection = op.get_bind()
|
||||
columns = _column_names(connection)
|
||||
if not columns:
|
||||
return
|
||||
|
||||
if "is_enabled" not in columns:
|
||||
op.add_column(
|
||||
_TABLE,
|
||||
sa.Column(
|
||||
"is_enabled",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.false(),
|
||||
),
|
||||
)
|
||||
|
||||
# 布尔列写 Python 布尔而不是整数字面量:PostgreSQL 会拒绝 `= 1`,报
|
||||
# column is of type boolean but expression is of type integer
|
||||
connection.execute(
|
||||
sa.text(f"UPDATE {_TABLE} SET is_enabled = :enabled"),
|
||||
{"enabled": True},
|
||||
)
|
||||
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
existing = {
|
||||
row[0]
|
||||
for row in connection.execute(
|
||||
sa.text(f"SELECT instance_id FROM {_TABLE}")
|
||||
).fetchall()
|
||||
}
|
||||
for plugin_id in _installed_plugin_ids(connection):
|
||||
if plugin_id in existing:
|
||||
continue
|
||||
connection.execute(
|
||||
sa.text(
|
||||
f"INSERT INTO {_TABLE} "
|
||||
"(instance_id, source_plugin_id, is_enabled, created_at, updated_at) "
|
||||
"VALUES (:plugin_id, :plugin_id, :enabled, :now, :now)"
|
||||
),
|
||||
{"plugin_id": plugin_id, "enabled": True, "now": now},
|
||||
)
|
||||
existing.add(plugin_id)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""删除启用位;装载判据随之回落安装清单,停用状态无处表达因而丢弃。"""
|
||||
if "is_enabled" in _column_names(op.get_bind()):
|
||||
op.drop_column(_TABLE, "is_enabled")
|
||||
64
database/versions/e0e68cbd5756_3_0_37.py
Normal file
64
database/versions/e0e68cbd5756_3_0_37.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""3.0.37 插件实例描述符增加默认调用目标标记。
|
||||
|
||||
Revision ID: e0e68cbd5756
|
||||
Revises: 487f7e681955
|
||||
Create Date: 2026-09-13
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision = "e0e68cbd5756"
|
||||
down_revision = "487f7e681955"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
_TABLE = "plugininstance"
|
||||
_DEFAULT_TARGET_INDEX = "ux_plugininstance_default_target"
|
||||
|
||||
|
||||
def _column_names() -> set[str]:
|
||||
"""读取当前表已有列名,兼容重复升级和已由 create_all 建出当前模型的场景。"""
|
||||
return {column["name"] for column in sa.inspect(op.get_bind()).get_columns(_TABLE)}
|
||||
|
||||
|
||||
def _index_names() -> set[str]:
|
||||
"""读取当前表已有索引名,兼容重复升级和已由 create_all 建出当前模型的场景。"""
|
||||
return {index["name"] for index in sa.inspect(op.get_bind()).get_indexes(_TABLE)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""增加默认调用目标标记列,并建出「同一源插件至多一个默认调用目标」的条件唯一索引。
|
||||
|
||||
条件谓词按方言分别给出:布尔列与 ``True`` 比较,SQLite 编译为 ``IS 1``,
|
||||
PostgreSQL 编译为 ``IS true``;谓词整个丢失会退化成「每个源插件只能有一行
|
||||
实例」,把插件分身整个锁死,因此必须两个方言各给一份,不能只给一份共用。
|
||||
"""
|
||||
columns = _column_names()
|
||||
if "is_default_target" not in columns:
|
||||
op.add_column(
|
||||
_TABLE,
|
||||
sa.Column(
|
||||
"is_default_target",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.false(),
|
||||
),
|
||||
)
|
||||
if _DEFAULT_TARGET_INDEX not in _index_names():
|
||||
op.create_index(
|
||||
_DEFAULT_TARGET_INDEX,
|
||||
_TABLE,
|
||||
["source_plugin_id"],
|
||||
unique=True,
|
||||
sqlite_where=sa.column("is_default_target", sa.Boolean()).is_(True),
|
||||
postgresql_where=sa.column("is_default_target", sa.Boolean()).is_(True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""删除条件唯一索引与默认调用目标标记列。"""
|
||||
if _DEFAULT_TARGET_INDEX in _index_names():
|
||||
op.drop_index(_DEFAULT_TARGET_INDEX, table_name=_TABLE)
|
||||
if "is_default_target" in _column_names():
|
||||
op.drop_column(_TABLE, "is_default_target")
|
||||
Reference in New Issue
Block a user