mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
fix(plugin): 按绑定仓库处理插件更新 (#6472)
This commit is contained in:
@@ -120,15 +120,15 @@ def admit_plugin_install(
|
||||
bound_at: datetime | None
|
||||
if request.source_change:
|
||||
if not request.explicit_source or not request.requested_repo_url:
|
||||
raise PluginSourceAdmissionError("显式换源必须指定目标在线来源")
|
||||
raise PluginSourceAdmissionError("请选择要更换到的插件仓库")
|
||||
if request.requested_repo_url.startswith("local://"):
|
||||
raise PluginSourceAdmissionError("显式换源只接受在线插件仓库")
|
||||
raise PluginSourceAdmissionError("只能更换为在线插件仓库")
|
||||
if identity is None or identity.trusted_source_type is TrustedPluginSourceType.UNKNOWN:
|
||||
raise PluginSourceAdmissionError("显式换源要求插件已经绑定在线来源")
|
||||
raise PluginSourceAdmissionError("当前插件尚未绑定仓库")
|
||||
if request.expected_revision != identity.revision:
|
||||
raise PluginSourceAdmissionError("显式换源的身份 revision 已失效")
|
||||
raise PluginSourceAdmissionError("插件状态已变化,请重新打开页面后再试")
|
||||
elif request.expected_revision is not None:
|
||||
raise PluginSourceAdmissionError("普通安装不能携带换源 revision")
|
||||
raise PluginSourceAdmissionError("插件操作参数无效,请重新打开页面后再试")
|
||||
|
||||
requested_source_key = None
|
||||
local_candidates = None
|
||||
@@ -142,7 +142,7 @@ def admit_plugin_install(
|
||||
or referenced_plugin_id.lower() != request.plugin_id.lower()
|
||||
):
|
||||
raise PluginSourceAdmissionError(
|
||||
"明确选择的本地来源与目标插件不一致"
|
||||
"所选本地插件与安装目标不一致"
|
||||
)
|
||||
available_local_candidates = inventory.local_candidates_for(
|
||||
request.plugin_id
|
||||
@@ -154,7 +154,7 @@ def admit_plugin_install(
|
||||
)
|
||||
local_candidates = exact_candidates or available_local_candidates
|
||||
if not local_candidates:
|
||||
raise PluginSourceAdmissionError("明确选择的本地来源没有当前插件候选")
|
||||
raise PluginSourceAdmissionError("所选本地仓库中没有该插件")
|
||||
else:
|
||||
requested_source_key, _repo_url = normalize_github_plugin_source(
|
||||
request.requested_repo_url
|
||||
@@ -171,10 +171,10 @@ def admit_plugin_install(
|
||||
allow_source_change=request.source_change,
|
||||
)
|
||||
if selection.status is not PluginSelectionStatus.SELECTED or selection.candidate is None:
|
||||
raise PluginSourceAdmissionError(selection.reason or "插件来源准入失败")
|
||||
raise PluginSourceAdmissionError(selection.reason or "当前无法确认插件仓库")
|
||||
candidate = selection.candidate
|
||||
if not candidate.plugin_version:
|
||||
raise PluginSourceAdmissionError("插件候选缺少可持久化的版本声明")
|
||||
raise PluginSourceAdmissionError("插件包缺少版本信息,无法安装")
|
||||
|
||||
if isinstance(candidate, PluginLocalCandidate):
|
||||
if identity is not None and identity.trusted_source_type is not TrustedPluginSourceType.UNKNOWN:
|
||||
@@ -201,7 +201,7 @@ def admit_plugin_install(
|
||||
and identity.trusted_source_type is candidate.source_type
|
||||
and identity.trusted_source_key == candidate.source_key
|
||||
):
|
||||
raise PluginSourceAdmissionError("显式换源的目标必须不同于当前来源")
|
||||
raise PluginSourceAdmissionError("所选仓库与当前绑定仓库相同")
|
||||
basis = PluginBindingBasis.EXPLICIT_SOURCE_CHANGE
|
||||
bound_at = now
|
||||
elif identity is not None and identity.trusted_source_type is not TrustedPluginSourceType.UNKNOWN:
|
||||
|
||||
@@ -7,8 +7,12 @@ import concurrent.futures
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from typing import Any, Optional
|
||||
|
||||
from app.application.plugin.identity import PluginIdentity
|
||||
from app.schemas.plugin import Plugin
|
||||
from app.application.plugin.identity import (
|
||||
PluginBindingBasis,
|
||||
PluginIdentity,
|
||||
TrustedPluginSourceType,
|
||||
)
|
||||
from app.schemas.plugin import Plugin, PluginSourceBindingStatus
|
||||
|
||||
MarketLoader = Callable[[str, Optional[str], bool], Optional[dict[str, dict]]]
|
||||
AsyncMarketLoader = Callable[
|
||||
@@ -27,17 +31,28 @@ def apply_declared_metadata_fallback(
|
||||
result: list[Plugin] = []
|
||||
for plugin in plugins:
|
||||
identity = identities.get((plugin.id or "").lower())
|
||||
updates: dict[str, object] = {}
|
||||
if plugin.installed and not plugin.is_instance:
|
||||
if identity is None:
|
||||
updates["source_binding_status"] = PluginSourceBindingStatus.BINDING_REQUIRED
|
||||
elif identity.trusted_source_type is TrustedPluginSourceType.UNKNOWN:
|
||||
updates["source_binding_status"] = (
|
||||
PluginSourceBindingStatus.LOCAL_ONLY
|
||||
if identity.binding_basis is PluginBindingBasis.LOCAL_ONLY
|
||||
else PluginSourceBindingStatus.BINDING_REQUIRED
|
||||
)
|
||||
else:
|
||||
updates["source_binding_status"] = PluginSourceBindingStatus.BOUND
|
||||
if (
|
||||
identity is None
|
||||
or identity.declared_metadata is None
|
||||
or identity.declared_version is None
|
||||
):
|
||||
result.append(plugin)
|
||||
result.append(plugin.model_copy(update=updates) if updates else plugin)
|
||||
continue
|
||||
fallback = identity.declared_metadata.display_fallback(
|
||||
installed_version=identity.declared_version
|
||||
)
|
||||
updates: dict[str, str] = {}
|
||||
if not plugin.plugin_version:
|
||||
updates["plugin_version"] = fallback["plugin_version"]
|
||||
if (
|
||||
|
||||
@@ -115,7 +115,7 @@ class PluginInstallGateway:
|
||||
)
|
||||
if not compatible:
|
||||
raise PluginSourceAdmissionError(
|
||||
message or "插件候选与当前 MoviePilot 版本不兼容"
|
||||
message or "插件包与当前 MoviePilot 版本不兼容"
|
||||
)
|
||||
return await self.__executor.execute(
|
||||
admission=admission,
|
||||
|
||||
@@ -524,7 +524,7 @@ def _select_local_candidate(
|
||||
):
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.INCOMPLETE,
|
||||
reason="本地插件仓库读取失败,不能自动选择在线载荷",
|
||||
reason="部分插件仓库暂时无法读取,请稍后重试",
|
||||
)
|
||||
if not local:
|
||||
return None
|
||||
@@ -532,12 +532,12 @@ def _select_local_candidate(
|
||||
if selected_local is None:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="本地候选没有符合当前运行代际的版本",
|
||||
reason="本地插件不支持当前 MoviePilot 版本",
|
||||
)
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.SELECTED,
|
||||
candidate=selected_local,
|
||||
reason="优先使用本地载荷",
|
||||
reason="当前使用本地插件",
|
||||
)
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ def select_plugin_candidate(
|
||||
if not online:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason=f"没有找到插件 {plugin_id} 的在线候选",
|
||||
reason=f"没有找到插件 {plugin_id} 的可用安装包",
|
||||
)
|
||||
|
||||
allowed_source = _allowed_source(identity, normalized_id)
|
||||
@@ -600,7 +600,7 @@ def select_plugin_candidate(
|
||||
if not requested_online:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="明确选择的在线来源没有当前插件候选",
|
||||
reason="所选仓库中没有该插件的可用安装包",
|
||||
)
|
||||
if allowed_source is not None:
|
||||
_source_type, allowed_key = allowed_source
|
||||
@@ -608,22 +608,22 @@ def select_plugin_candidate(
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.CONFLICT,
|
||||
conflict_source_keys=(allowed_key, requested_source),
|
||||
reason="普通安装不能改变已绑定的在线来源",
|
||||
reason="该插件已绑定其他仓库,请先确认更换",
|
||||
)
|
||||
if explicit_source or allow_source_change:
|
||||
selected_requested = _select_best(requested_online, generation_order)
|
||||
if selected_requested is None:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="明确选择的来源没有符合当前运行代际的版本",
|
||||
reason="所选仓库没有适用于当前 MoviePilot 版本的插件包",
|
||||
)
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.SELECTED,
|
||||
candidate=selected_requested,
|
||||
reason=(
|
||||
"按显式换源目标选择在线载荷"
|
||||
"已选择目标仓库中的插件包"
|
||||
if allow_source_change
|
||||
else "按管理员明确选择的来源安装在线载荷"
|
||||
else "已选择指定仓库中的插件包"
|
||||
),
|
||||
)
|
||||
if allowed_source is not None:
|
||||
@@ -636,24 +636,24 @@ def select_plugin_candidate(
|
||||
if not online:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="当前来源身份没有可用候选",
|
||||
reason="已绑定仓库中暂无可用插件包",
|
||||
)
|
||||
selected_online = _select_best(online, generation_order)
|
||||
if selected_online is None:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="在线候选没有符合当前运行代际的版本",
|
||||
reason="已绑定仓库没有适用于当前 MoviePilot 版本的插件包",
|
||||
)
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.SELECTED,
|
||||
candidate=selected_online,
|
||||
reason="按已绑定来源选择在线载荷",
|
||||
reason="已使用绑定仓库中的插件包",
|
||||
)
|
||||
|
||||
if identity is not None:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.INCOMPLETE,
|
||||
reason="插件来源身份尚未绑定,不能自动选择在线载荷",
|
||||
reason="当前插件尚未绑定仓库",
|
||||
)
|
||||
|
||||
source_pairs = {(candidate.source_type, candidate.source_key) for candidate in online}
|
||||
@@ -661,25 +661,25 @@ def select_plugin_candidate(
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.CONFLICT,
|
||||
conflict_source_keys=tuple(source_key for _source_type, source_key in source_pairs),
|
||||
reason="未安装插件存在多个在线来源,不能静默选择",
|
||||
reason="该插件存在于多个仓库,请选择仓库",
|
||||
)
|
||||
|
||||
source_type = next(iter(source_pairs))[0]
|
||||
if source_type is TrustedPluginSourceType.THIRD_PARTY and not inventory.can_use_for_tofu:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.INCOMPLETE,
|
||||
reason="市场读取不完整,不能建立唯一第三方来源的 TOFU",
|
||||
reason="部分插件仓库暂时无法读取,无法安全确认仓库",
|
||||
)
|
||||
selected_online = _select_best(online, generation_order)
|
||||
if selected_online is None:
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.UNAVAILABLE,
|
||||
reason="在线候选没有符合当前运行代际的版本",
|
||||
reason="可用仓库中没有适用于当前 MoviePilot 版本的插件包",
|
||||
)
|
||||
return PluginSelection(
|
||||
status=PluginSelectionStatus.SELECTED,
|
||||
candidate=selected_online,
|
||||
reason="唯一在线来源候选",
|
||||
reason="已找到唯一可用仓库",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user