mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
refactor: 推进后端分层架构治理
This commit is contained in:
@@ -10,7 +10,8 @@ from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.rules import RuleHelper
|
||||
from app.application.rules import RuleParser
|
||||
from app.application.rules import BUILTIN_RULE_SET
|
||||
from app.schemas import CustomRule, FilterRuleGroup
|
||||
from app.schemas.rule import CustomRule
|
||||
from app.schemas.system import FilterRuleGroup
|
||||
from app.schemas.event import ConfigChangeEventData
|
||||
from app.schemas.types import EventType, SystemConfigKey
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
"""Agent 音乐工具共享的实体校验与结果精简函数。"""
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from app.domain.context import (
|
||||
MusicAlbumInfo,
|
||||
MusicArtistInfo,
|
||||
MusicInfo,
|
||||
)
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_TYPES,
|
||||
MUSIC_SUBSCRIBABLE_TYPES,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.schemas.types import media_type_to_agent
|
||||
|
||||
|
||||
MUSIC_TRACK_PREVIEW_LIMIT = 100
|
||||
|
||||
@@ -6,9 +6,11 @@ from typing import Any, Optional
|
||||
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.extensions.plugin_manager import PluginManager
|
||||
from app.application.plugin.install import PluginInstallCommand
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.adapters.external.market import PluginHelper
|
||||
from app.adapters.system.plugin.package import PluginPackageManager
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
# 默认只向智能体返回一个可读预览,避免超大插件数据挤爆上下文窗口。
|
||||
@@ -65,22 +67,24 @@ def build_preview_payload(value: Any, max_chars: Optional[int]) -> tuple[bool, i
|
||||
return True, len(serialized), len(preview), preview
|
||||
|
||||
|
||||
def reload_plugin_runtime(plugin_id: str) -> None:
|
||||
"""
|
||||
重载插件并重新注册其命令、定时任务和 API。
|
||||
"""
|
||||
def refresh_plugin_registrations(plugin_id: str) -> None:
|
||||
"""重新注册插件的定时任务、命令和动态 API 路由。"""
|
||||
# 这些依赖只在真正执行重载时才导入,避免普通查询工具引入不必要的初始化开销。
|
||||
from app.application.plugins import register_plugin_api
|
||||
from app.application.commands import init_commands
|
||||
from app.application.scheduling import update_plugin_job
|
||||
|
||||
plugin_manager = PluginManager()
|
||||
plugin_manager.reload_plugin(plugin_id)
|
||||
update_plugin_job(plugin_id)
|
||||
init_commands(plugin_id)
|
||||
register_plugin_api(plugin_id)
|
||||
|
||||
|
||||
def reload_plugin_runtime(plugin_id: str) -> None:
|
||||
"""重载插件实例并重新注册其命令、定时任务和 API。"""
|
||||
PluginManager().reload_plugin(plugin_id)
|
||||
refresh_plugin_registrations(plugin_id)
|
||||
|
||||
|
||||
def summarize_plugin(plugin: Any) -> dict[str, Any]:
|
||||
"""
|
||||
提取插件对象中对 Agent 有价值的摘要字段。
|
||||
@@ -296,37 +300,80 @@ async def install_plugin_runtime(
|
||||
"""
|
||||
按现有插件接口的行为安装插件,并刷新运行态注册信息。
|
||||
"""
|
||||
install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
plugin_manager = PluginManager()
|
||||
plugin_helper = PluginHelper()
|
||||
|
||||
refreshed_only = False
|
||||
if not force and plugin_id in plugin_manager.get_plugin_ids():
|
||||
refreshed_only = True
|
||||
await MoviePilotServerHelper.async_install_plugin_reg(plugin_id=plugin_id, repo_url=repo_url)
|
||||
message = "插件已存在,已刷新加载"
|
||||
else:
|
||||
if not repo_url:
|
||||
return False, "没有传入仓库地址,无法正确安装插件,请检查配置", False
|
||||
state, message = await plugin_helper.async_install(
|
||||
pid=plugin_id,
|
||||
repo_url=repo_url,
|
||||
force_install=force,
|
||||
)
|
||||
if not state:
|
||||
return False, message, False
|
||||
await MoviePilotServerHelper.async_install_plugin_reg(plugin_id=plugin_id, repo_url=repo_url)
|
||||
|
||||
if plugin_id not in install_plugins:
|
||||
install_plugins.append(plugin_id)
|
||||
await SystemConfigOper().async_set(
|
||||
SystemConfigKey.UserInstalledPlugins, install_plugins
|
||||
)
|
||||
package_manager = PluginPackageManager(plugin_helper)
|
||||
|
||||
from app.agent.tools.base import run_agent_blocking
|
||||
|
||||
await run_agent_blocking("plugin", reload_plugin_runtime, plugin_id)
|
||||
return True, message or "插件安装成功", refreshed_only
|
||||
async def save_installed_plugins(plugin_ids: list[str]) -> object:
|
||||
"""保存智能体安装用例确认后的插件列表。"""
|
||||
return await SystemConfigOper().async_set(
|
||||
SystemConfigKey.UserInstalledPlugins,
|
||||
plugin_ids,
|
||||
)
|
||||
|
||||
async def install_package(
|
||||
target_id: str,
|
||||
target_repo: str,
|
||||
_release_version: Optional[str],
|
||||
force_install: bool,
|
||||
) -> tuple[bool, str]:
|
||||
"""调用插件包适配器执行异步安装。"""
|
||||
return await package_manager.async_install(
|
||||
plugin_id=target_id,
|
||||
repo_url=target_repo,
|
||||
force_install=force_install,
|
||||
)
|
||||
|
||||
async def skip_compatibility_check(
|
||||
_target_id: str,
|
||||
_target_repo: str,
|
||||
) -> None:
|
||||
"""保持 Agent 旧安装入口不额外执行系统版本预检查。"""
|
||||
return None
|
||||
|
||||
async def reload_runtime(target_id: str) -> object:
|
||||
"""通过 Agent 阻塞任务适配器重建插件实例。"""
|
||||
return await run_agent_blocking(
|
||||
"plugin",
|
||||
plugin_manager.reload_plugin,
|
||||
target_id,
|
||||
)
|
||||
|
||||
async def refresh_registrations(target_id: str) -> object:
|
||||
"""通过 Agent 阻塞任务适配器刷新服务、命令和动态路由。"""
|
||||
return await run_agent_blocking(
|
||||
"plugin",
|
||||
refresh_plugin_registrations,
|
||||
target_id,
|
||||
)
|
||||
|
||||
result = await PluginInstallCommand(
|
||||
installed_plugins_reader=lambda: SystemConfigOper().get(
|
||||
SystemConfigKey.UserInstalledPlugins
|
||||
) or [],
|
||||
installed_plugins_writer=save_installed_plugins,
|
||||
plugin_ids_provider=plugin_manager.get_plugin_ids,
|
||||
compatibility_checker=skip_compatibility_check,
|
||||
package_installer=install_package,
|
||||
package_checkpointer=package_manager.async_checkpoint,
|
||||
package_committer=package_manager.async_commit,
|
||||
package_rollback=package_manager.async_rollback,
|
||||
install_reporter=lambda target_id, target_repo: (
|
||||
MoviePilotServerHelper.async_install_plugin_reg(
|
||||
plugin_id=target_id,
|
||||
repo_url=target_repo,
|
||||
)
|
||||
),
|
||||
plugin_reloader=reload_runtime,
|
||||
registration_refresher=refresh_registrations,
|
||||
).execute(
|
||||
plugin_id=plugin_id,
|
||||
repo_url=repo_url,
|
||||
force=force,
|
||||
)
|
||||
return result.success, result.message, result.refreshed_only
|
||||
|
||||
|
||||
async def uninstall_plugin_runtime(plugin_id: str) -> dict[str, Any]:
|
||||
|
||||
@@ -18,7 +18,7 @@ from app.domain.metainfo import MetaInfo
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.application.directory import DirectoryHelper, validate_download_save_path
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import FileURI
|
||||
from app.schemas.file import FileURI
|
||||
from app.foundation.crypto import HashUtils
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.chain.subscribe import SubscribeChain
|
||||
from app.db.oper.user import UserOper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, NotificationChannel
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
|
||||
class AddSubscribeInput(BaseModel):
|
||||
|
||||
@@ -12,7 +12,8 @@ from app.application.messaging.agent import (
|
||||
build_agent_choice_callback,
|
||||
)
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import Message, MessageType
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.message import MessageType
|
||||
from app.schemas.notification import ChannelCapabilityManager
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.chain.storage import StorageChain
|
||||
from app.db.oper.transferhistory import TransferHistoryOper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import FileItem
|
||||
from app.schemas.workflow import FileItem
|
||||
|
||||
|
||||
class DeleteTransferHistoryInput(BaseModel):
|
||||
|
||||
@@ -20,7 +20,8 @@ from app.schemas.types import (
|
||||
MediaType,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from ._music_utils import normalize_music_type, simplify_music_info
|
||||
from app.domain.media import normalize_music_type
|
||||
from ._music_utils import simplify_music_info
|
||||
|
||||
|
||||
class GetRecommendationsInput(BaseModel):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.chain.download import DownloadChain
|
||||
from app.db.oper.downloadhistory import DownloadHistoryOper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import DownloaderTorrent
|
||||
from app.schemas.transfer import DownloaderTorrent
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, TorrentQueryStatus, media_type_to_agent
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import json
|
||||
from typing import Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
|
||||
@@ -18,7 +18,7 @@ from app.schemas.types import (
|
||||
MediaType,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
|
||||
def _sort_seasons(seasons: Optional[dict]) -> dict:
|
||||
|
||||
@@ -17,8 +17,8 @@ from app.schemas.types import (
|
||||
MediaSource,
|
||||
MediaType,
|
||||
)
|
||||
from app.domain.media import normalize_music_type
|
||||
from ._music_utils import (
|
||||
normalize_music_type,
|
||||
simplify_music_album,
|
||||
simplify_music_artist,
|
||||
simplify_music_info,
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.domain.context import MediaInfo
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
MAX_PAGE_SIZE = 50
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.db.oper.subscribehistory import SubscribeHistoryOper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaType, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
PAGE_SIZE = 20
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MUSIC_ENTITY_RECORDING, media_type_to_agent
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
MAX_PAGE_SIZE = 50
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from app.schemas.types import (
|
||||
MediaType,
|
||||
media_type_to_agent,
|
||||
)
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
PAGE_SIZE = 100
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.chain.media import MediaChain
|
||||
from app.chain.scraping import ScrapingChain
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import FileItem
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ARTIST,
|
||||
MediaSource,
|
||||
@@ -20,7 +20,8 @@ from app.schemas.types import (
|
||||
media_type_to_agent,
|
||||
)
|
||||
from app.schemas.media import normalize_media_source
|
||||
from ._music_utils import normalize_music_type, simplify_music_info
|
||||
from app.domain.media import normalize_music_type
|
||||
from ._music_utils import simplify_music_info
|
||||
|
||||
|
||||
class ScrapeMetadataInput(BaseModel):
|
||||
|
||||
@@ -11,7 +11,8 @@ from app.chain.media import MediaChain
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MediaType, media_type_to_agent
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from ._music_utils import normalize_music_type, simplify_music_info
|
||||
from app.domain.media import normalize_music_type
|
||||
from ._music_utils import simplify_music_info
|
||||
|
||||
|
||||
class SearchMediaInput(BaseModel):
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=no-name-in-module
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.types import MediaSource, MediaType, SystemConfigKey
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
from ._torrent_search_utils import (
|
||||
SEARCH_RESULT_CACHE_FILE,
|
||||
build_filter_options,
|
||||
|
||||
@@ -8,7 +8,8 @@ from pydantic import BaseModel, Field, model_validator
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import Message, MessageType
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.message import MessageType
|
||||
from app.schemas.notification import ChannelCapabilityManager, ChannelCapability
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from pydantic import BaseModel, Field, model_validator
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import Message
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.types import MessageType
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.runtime.config import settings
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import Message, MessageType
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.message import MessageType
|
||||
|
||||
|
||||
class SendVoiceMessageInput(BaseModel):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""切换当前激活人格工具。"""
|
||||
|
||||
import json
|
||||
from typing import Type, Optional
|
||||
from typing import Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ from pydantic import BaseModel, Field
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.agent.tools.tags import ToolTag
|
||||
from app.runtime.log import logger
|
||||
from app.schemas import FileItem, MediaType
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MUSIC_ENTITY_RECORDING, MediaSource
|
||||
from ._music_utils import normalize_music_type
|
||||
from app.domain.media import normalize_music_type
|
||||
|
||||
|
||||
class TransferFileInput(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user