mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
feat(plugin): persist declared metadata snapshots (#6467)
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
"""插件来源身份 Application Port 的 SQLAlchemy 实现。"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Sequence
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.plugin.declaration import PluginDeclaredMetadata
|
||||
from app.application.plugin.identity import (
|
||||
BindLocalPluginIdentityCommand,
|
||||
BindOnlinePluginIdentityCommand,
|
||||
@@ -40,9 +41,11 @@ def _to_record(model: IdentityModel) -> PluginIdentity:
|
||||
payload_source_key=model.payload_source_key,
|
||||
declared_version=model.declared_version,
|
||||
package_generation=model.package_generation,
|
||||
system_version=model.system_version,
|
||||
supports_v3=model.supports_v3,
|
||||
supports_v3t=model.supports_v3t,
|
||||
declared_metadata=(
|
||||
PluginDeclaredMetadata.from_storage(model.declared_metadata)
|
||||
if model.declared_metadata is not None
|
||||
else None
|
||||
),
|
||||
payload_receipt=model.payload_receipt,
|
||||
revision=model.revision,
|
||||
created_at=datetime.fromisoformat(model.created_at),
|
||||
@@ -64,9 +67,11 @@ def _to_model(identity: PluginIdentity) -> IdentityModel:
|
||||
payload_source_key=identity.payload_source_key,
|
||||
declared_version=identity.declared_version,
|
||||
package_generation=identity.package_generation,
|
||||
system_version=identity.system_version,
|
||||
supports_v3=identity.supports_v3,
|
||||
supports_v3t=identity.supports_v3t,
|
||||
declared_metadata=(
|
||||
identity.declared_metadata.to_json()
|
||||
if identity.declared_metadata is not None
|
||||
else None
|
||||
),
|
||||
payload_receipt=identity.payload_receipt,
|
||||
revision=identity.revision,
|
||||
created_at=identity.created_at.isoformat(),
|
||||
@@ -92,6 +97,13 @@ class _SqlAlchemyIdentityRepository:
|
||||
model = self._oper.get_by_plugin_id(plugin_id)
|
||||
return _to_record(model) if model else None
|
||||
|
||||
def list(self, plugin_ids: Sequence[str]) -> list[PluginIdentity]:
|
||||
"""批量读取并映射指定来源身份。"""
|
||||
return [
|
||||
_to_record(model)
|
||||
for model in self._oper.list_by_plugin_ids(plugin_ids)
|
||||
]
|
||||
|
||||
def stage_create(self, identity: PluginIdentity) -> None:
|
||||
"""暂存首次身份。"""
|
||||
try:
|
||||
@@ -131,6 +143,20 @@ class TransactionalPluginIdentityStore:
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def list(self, plugin_ids: Sequence[str]) -> list[PluginIdentity]:
|
||||
"""在一个短会话内批量读取规范化插件身份。"""
|
||||
normalized_ids = tuple(
|
||||
dict.fromkeys(
|
||||
normalize_physical_plugin_id(plugin_id)
|
||||
for plugin_id in plugin_ids
|
||||
)
|
||||
)
|
||||
session = self._session_factory()
|
||||
try:
|
||||
return _SqlAlchemyIdentityRepository(session).list(normalized_ids)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def compare_and_set(
|
||||
self,
|
||||
identity: PluginIdentity,
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.application.plugin.declaration import PluginDeclaredMetadata
|
||||
from app.application.plugin.identity import (
|
||||
PluginBindingBasis,
|
||||
PluginIdentity,
|
||||
@@ -50,9 +51,11 @@ def _identity_from_model(model: IdentityModel) -> PluginIdentity:
|
||||
payload_source_key=model.payload_source_key,
|
||||
declared_version=model.declared_version,
|
||||
package_generation=model.package_generation,
|
||||
system_version=model.system_version,
|
||||
supports_v3=model.supports_v3,
|
||||
supports_v3t=model.supports_v3t,
|
||||
declared_metadata=(
|
||||
PluginDeclaredMetadata.from_storage(model.declared_metadata)
|
||||
if model.declared_metadata is not None
|
||||
else None
|
||||
),
|
||||
payload_receipt=model.payload_receipt,
|
||||
revision=model.revision,
|
||||
created_at=datetime.fromisoformat(model.created_at),
|
||||
@@ -82,9 +85,11 @@ def _identity_model_values(identity: PluginIdentity) -> dict[str, object]:
|
||||
"payload_source_key": identity.payload_source_key,
|
||||
"declared_version": identity.declared_version,
|
||||
"package_generation": identity.package_generation,
|
||||
"system_version": identity.system_version,
|
||||
"supports_v3": identity.supports_v3,
|
||||
"supports_v3t": identity.supports_v3t,
|
||||
"declared_metadata": (
|
||||
identity.declared_metadata.to_json()
|
||||
if identity.declared_metadata is not None
|
||||
else None
|
||||
),
|
||||
"payload_receipt": identity.payload_receipt,
|
||||
"revision": identity.revision,
|
||||
"created_at": identity.created_at.isoformat(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""已安装物理插件来源身份模型。"""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from sqlalchemy import Boolean, CheckConstraint, Integer, String, UniqueConstraint
|
||||
from sqlalchemy import JSON, CheckConstraint, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base, get_id_column
|
||||
@@ -21,9 +21,9 @@ class PluginIdentity(Base):
|
||||
payload_source_key: Mapped[Optional[str]] = mapped_column(String(255))
|
||||
declared_version: Mapped[Optional[str]] = mapped_column(String(64))
|
||||
package_generation: Mapped[Optional[str]] = mapped_column(String(8))
|
||||
system_version: Mapped[Optional[str]] = mapped_column(String(128))
|
||||
supports_v3: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
supports_v3t: Mapped[Optional[bool]] = mapped_column(Boolean)
|
||||
declared_metadata: Mapped[Optional[dict[str, Any]]] = mapped_column(
|
||||
JSON(none_as_null=True)
|
||||
)
|
||||
payload_receipt: Mapped[Optional[str]] = mapped_column(String(71))
|
||||
revision: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
created_at: Mapped[str] = mapped_column(String(40), nullable=False)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""插件来源身份的数据访问原语。"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import cast
|
||||
|
||||
from sqlalchemy import select, update
|
||||
@@ -25,6 +26,23 @@ class PluginIdentityOper(DbOper):
|
||||
)
|
||||
)
|
||||
|
||||
def list_by_plugin_ids(
|
||||
self,
|
||||
plugin_ids: Sequence[str],
|
||||
) -> list[PluginIdentity]:
|
||||
"""批量读取规范化物理插件 ID 对应的身份。"""
|
||||
if not plugin_ids:
|
||||
return []
|
||||
return list(
|
||||
self._execute_sync_query(
|
||||
lambda session: session.execute(
|
||||
select(PluginIdentity).where(
|
||||
PluginIdentity.normalized_plugin_id.in_(plugin_ids)
|
||||
)
|
||||
).scalars()
|
||||
)
|
||||
)
|
||||
|
||||
def stage_create(self, identity: PluginIdentity) -> None:
|
||||
"""暂存首次身份并立即暴露数据库唯一键竞争。"""
|
||||
def stage(session: Session) -> None:
|
||||
|
||||
Reference in New Issue
Block a user