From 0593275a623ed2842775b42fbf02146846497e37 Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:46:41 +0800 Subject: [PATCH 1/4] feat(module): add ServiceBaseHelper for service and instance --- app/helper/downloader.py | 15 +++++- app/helper/mediaserver.py | 15 +++++- app/helper/notification.py | 18 +++++-- app/helper/servicebase.py | 97 +++++++++++++++++++++++++++++++++++++ app/helper/serviceconfig.py | 2 +- app/modules/__init__.py | 8 +++ app/schemas/system.py | 20 +++++++- 7 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 app/helper/servicebase.py diff --git a/app/helper/downloader.py b/app/helper/downloader.py index 86d3d5572..6ffcab13e 100644 --- a/app/helper/downloader.py +++ b/app/helper/downloader.py @@ -1,5 +1,16 @@ -class DownloaderHelper: +from app.helper.servicebase import ServiceBaseHelper +from app.schemas import DownloaderConf +from app.schemas.types import SystemConfigKey + + +class DownloaderHelper(ServiceBaseHelper[DownloaderConf]): """ 下载器帮助类 """ - pass + + def __init__(self): + super().__init__( + config_key=SystemConfigKey.Downloaders, + conf_type=DownloaderConf, + modules=["QbittorrentModule", "TransmissionModule"] + ) diff --git a/app/helper/mediaserver.py b/app/helper/mediaserver.py index a21a11550..1c17eaa03 100644 --- a/app/helper/mediaserver.py +++ b/app/helper/mediaserver.py @@ -1,5 +1,16 @@ -class MediaServerHelper: +from app.helper.servicebase import ServiceBaseHelper +from app.schemas import MediaServerConf +from app.schemas.types import SystemConfigKey + + +class MediaServerHelper(ServiceBaseHelper[MediaServerConf]): """ 媒体服务器帮助类 """ - pass + + def __init__(self): + super().__init__( + config_key=SystemConfigKey.MediaServers, + conf_type=MediaServerConf, + modules=["PlexModule", "EmbyModule", "JellyfinModule"] + ) diff --git a/app/helper/notification.py b/app/helper/notification.py index 8d32d4529..e5fcd2809 100644 --- a/app/helper/notification.py +++ b/app/helper/notification.py @@ -1,5 +1,17 @@ -class NotificationHelper: +from app.helper.servicebase import ServiceBaseHelper +from app.schemas import NotificationConf +from app.schemas.types import SystemConfigKey + + +class NotificationHelper(ServiceBaseHelper[NotificationConf]): """ - 消息通知渠道帮助类 + 消息通知帮助类 """ - pass + + def __init__(self): + super().__init__( + config_key=SystemConfigKey.Notifications, + conf_type=NotificationConf, + modules=["WechatModule", "WebPushModule", "VoceChatModule", "TelegramModule", "SynologyChatModule", + "SlackModule"] + ) diff --git a/app/helper/servicebase.py b/app/helper/servicebase.py new file mode 100644 index 000000000..bfd831f72 --- /dev/null +++ b/app/helper/servicebase.py @@ -0,0 +1,97 @@ +from typing import Dict, List, Optional, Type, TypeVar, Generic, Iterator + +from app.core.module import ModuleManager +from app.helper.serviceconfig import ServiceConfigHelper +from app.schemas import ServiceInfo +from app.schemas.types import SystemConfigKey + +TConf = TypeVar("TConf") + + +class ServiceBaseHelper(Generic[TConf]): + """ + 通用服务帮助类,抽象获取配置和服务实例的通用逻辑 + """ + + def __init__(self, config_key: SystemConfigKey, conf_type: Type[TConf], modules: List[str]): + self.modulemanager = ModuleManager() + self.config_key = config_key + self.conf_type = conf_type + self.modules = modules + + def get_configs(self, include_disabled: bool = False) -> Dict[str, TConf]: + """ + 获取配置列表 + + :param include_disabled: 是否包含禁用的配置,默认 False(仅返回启用的配置) + :return: 配置字典 + """ + configs: List[TConf] = ServiceConfigHelper.get_configs(self.config_key, self.conf_type) + return { + config.name: config + for config in configs + if (config.name and config.type and config.enabled) or include_disabled + } if configs else {} + + def get_config(self, name: str) -> Optional[TConf]: + """ + 获取指定名称配置 + """ + if not name: + return None + configs = self.get_configs() + return configs.get(name) + + def iterate_module_instances(self) -> Iterator[ServiceInfo]: + """ + 迭代所有模块的实例及其对应的配置,返回 ServiceInfo 实例 + """ + configs = self.get_configs() + for module_name in self.modules: + module = self.modulemanager.get_running_module(module_name) + if not module: + continue + module_instances = module.get_instances() + if not isinstance(module_instances, dict): + continue + for name, instance in module_instances.items(): + if not instance: + continue + config = configs.get(name) + service_info = ServiceInfo( + name=name, + instance=instance, + module=module, + type=config.type if config else None, + config=config + ) + yield service_info + + def get_services(self, type_filter: Optional[str] = None) -> Dict[str, ServiceInfo]: + """ + 获取服务信息列表,并根据类型过滤 + + :param type_filter: 需要过滤的服务类型 + :return: 过滤后的服务信息字典 + """ + return { + service_info.name: service_info + for service_info in self.iterate_module_instances() + if service_info.config and (type_filter is None or service_info.type == type_filter) + } + + def get_service(self, name: str, type_filter: Optional[str] = None) -> Optional[ServiceInfo]: + """ + 获取指定名称的服务信息,并根据类型过滤 + + :param name: 服务名称 + :param type_filter: 需要过滤的服务类型 + :return: 对应的服务信息,若不存在或类型不匹配则返回 None + """ + if not name: + return None + for service_info in self.iterate_module_instances(): + if service_info.name == name: + if service_info.config and (type_filter is None or service_info.type == type_filter): + return service_info + return None diff --git a/app/helper/serviceconfig.py b/app/helper/serviceconfig.py index c65cdc12d..6822d22e9 100644 --- a/app/helper/serviceconfig.py +++ b/app/helper/serviceconfig.py @@ -56,7 +56,7 @@ class ServiceConfigHelper: @staticmethod def get_notification_switch(mtype: NotificationType) -> Optional[str]: """ - 获取消息通知场景开关 + 获取指定类型的消息通知场景的开关 """ switchs = ServiceConfigHelper.get_notification_switches() for switch in switchs: diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 3a54d817e..d8263e10b 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -90,6 +90,14 @@ class ServiceBase(Generic[TService, TConf], metaclass=ABCMeta): # 如果传入的是工厂函数,直接调用工厂函数 self._instances[conf.name] = service_type(conf) + def get_instances(self) -> Dict[str, TService]: + """ + 获取服务实例列表 + + :return: 返回服务实例列表 + """ + return self._instances + def get_instance(self, name: str) -> Optional[TService]: """ 获取服务实例 diff --git a/app/schemas/system.py b/app/schemas/system.py index f39be609f..3680957ff 100644 --- a/app/schemas/system.py +++ b/app/schemas/system.py @@ -1,8 +1,26 @@ -from typing import Optional +from dataclasses import dataclass +from typing import Optional, Any from pydantic import BaseModel +@dataclass +class ServiceInfo: + """ + 封装服务相关信息的数据类 + """ + # 名称 + name: Optional[str] = None + # 实例 + instance: Optional[Any] = None + # 模块 + module: Optional[Any] = None + # 类型 + type: Optional[str] = None + # 配置 + config: Optional[Any] = None + + class MediaServerConf(BaseModel): """ 媒体服务器配置 From 709f8ef3edf210d1faed66453c61fb52c351a2c2 Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Mon, 30 Sep 2024 00:38:01 +0800 Subject: [PATCH 2/4] chore: Update .gitignore to exclude all log files and archives --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9d135e494..7ec674ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ app/plugins/** config/cookies/** config/user.db config/sites/** +config/logs *.pyc *.log .vscode From 61ecc175f39e2735655d33e01797843fa239a9cc Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:13:45 +0800 Subject: [PATCH 3/4] chore: Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7ec674ab0..705c952f2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,9 @@ app/plugins/** config/cookies/** config/user.db config/sites/** -config/logs +config/logs/ +config/temp/ +config/cache/ *.pyc *.log .vscode From 838e17bf6ebcc5ebe36f7d1e7647cb85ec240bfc Mon Sep 17 00:00:00 2001 From: InfinityPacer <160988576+InfinityPacer@users.noreply.github.com> Date: Mon, 30 Sep 2024 02:59:09 +0800 Subject: [PATCH 4/4] fix(sync): have module return results directly instead of using yield --- app/modules/emby/__init__.py | 2 +- app/modules/jellyfin/__init__.py | 2 +- app/modules/plex/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index 11d22b771..0760b7821 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -187,7 +187,7 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]): """ server: Emby = self.get_instance(server) if server: - yield from server.get_items(library_id, start_index, limit) + return server.get_items(library_id, start_index, limit) return None def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 35718fe1d..814f6cae7 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -185,7 +185,7 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]): """ server: Jellyfin = self.get_instance(server) if server: - yield from server.get_items(library_id, start_index, limit) + return server.get_items(library_id, start_index, limit) return None def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index dfe339e05..0643a544f 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -173,7 +173,7 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]): """ server: Plex = self.get_instance(server) if server: - yield from server.get_items(library_id, start_index, limit) + return server.get_items(library_id, start_index, limit) return None def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: