From db9960d9b90ad9125d39a8347bfd652991032e99 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Mon, 6 Jul 2026 09:33:49 +0800 Subject: [PATCH] refactor: enhance customization and streaming platform handling --- app/core/meta/customization.py | 11 ++++++-- app/core/meta/releasegroup.py | 22 +++++++++------- app/core/meta/streamingplatform.py | 6 +++++ app/core/metainfo.py | 41 ++++++++++++++++++++++-------- requirements.in | 2 +- scripts/benchmark_rss_rust.py | 2 ++ tests/test_rust_accel.py | 12 +++------ 7 files changed, 66 insertions(+), 30 deletions(-) diff --git a/app/core/meta/customization.py b/app/core/meta/customization.py index f26f8b02..ed97f96a 100644 --- a/app/core/meta/customization.py +++ b/app/core/meta/customization.py @@ -17,7 +17,7 @@ class CustomizationMatcher(metaclass=Singleton): self._customization_re_cache = {} @staticmethod - def _normalize_customization(customization): + def normalize_customization(customization): """ 规范化自定义占位符配置,兼容历史字符串与列表两种保存格式。 """ @@ -27,6 +27,13 @@ class CustomizationMatcher(metaclass=Singleton): return [] return list(filter(None, customization)) + @staticmethod + def _normalize_customization(customization): + """ + 兼容旧调用,统一转到公开的自定义占位符规范化入口。 + """ + return CustomizationMatcher.normalize_customization(customization) + def match(self, title=None): """ :param title: 资源标题或文件名 @@ -35,7 +42,7 @@ class CustomizationMatcher(metaclass=Singleton): if not title: return "" # 自定义占位符需要跟随系统配置实时生效,避免单例缓存导致保存后仍沿用旧规则。 - customization = self._normalize_customization( + customization = self.normalize_customization( self.systemconfig.get(SystemConfigKey.Customization) ) if not customization: diff --git a/app/core/meta/releasegroup.py b/app/core/meta/releasegroup.py index 0f11dd54..adacd91d 100644 --- a/app/core/meta/releasegroup.py +++ b/app/core/meta/releasegroup.py @@ -89,6 +89,18 @@ class ReleaseGroupsMatcher(metaclass=Singleton): self.systemconfig = SystemConfigOper() self.__groups_re_cache = {} + def get_release_groups(self) -> str: + """ + 返回内置与用户自定义制作组组成的匹配规则。 + """ + custom_release_groups = self.systemconfig.get(SystemConfigKey.CustomReleaseGroups) + if isinstance(custom_release_groups, list): + custom_release_groups = list(filter(None, custom_release_groups)) + if custom_release_groups: + custom_release_groups_str = '|'.join(custom_release_groups) + return f"{self.__release_groups}|{custom_release_groups_str}" + return self.__release_groups + def __get_groups_re(self, groups: str): """ 发布组规则通常很长,按规则文本缓存编译结果,避免每个标题都重复编译。 @@ -108,15 +120,7 @@ class ReleaseGroupsMatcher(metaclass=Singleton): if not title: return "" if not groups: - # 自定义组 - custom_release_groups = self.systemconfig.get(SystemConfigKey.CustomReleaseGroups) - if isinstance(custom_release_groups, list): - custom_release_groups = list(filter(None, custom_release_groups)) - if custom_release_groups: - custom_release_groups_str = '|'.join(custom_release_groups) - groups = f"{self.__release_groups}|{custom_release_groups_str}" - else: - groups = self.__release_groups + groups = self.get_release_groups() title = f"{title} " groups_re = self.__get_groups_re(groups) unique_groups = [] diff --git a/app/core/meta/streamingplatform.py b/app/core/meta/streamingplatform.py index e63b3a1d..2a05799a 100644 --- a/app/core/meta/streamingplatform.py +++ b/app/core/meta/streamingplatform.py @@ -297,6 +297,12 @@ class StreamingPlatforms(metaclass=Singleton): if alias: self._lookup_cache[alias.upper()] = canonical_name + def get_lookup_cache(self) -> dict: + """ + 返回流媒体平台查询表副本,供批量解析配置复用。 + """ + return dict(self._lookup_cache) + def get_streaming_platform_name(self, platform_code: str) -> Optional[str]: """ 根据流媒体平台简称或全称获取标准名称。 diff --git a/app/core/metainfo.py b/app/core/metainfo.py index 3d642009..8bd1c8e6 100644 --- a/app/core/metainfo.py +++ b/app/core/metainfo.py @@ -1,3 +1,4 @@ +import hashlib from pathlib import Path from functools import lru_cache from typing import Tuple, List, Optional @@ -40,6 +41,7 @@ _EMBY_TMDB_RE_LIST = ( re.compile(r'\{tmdbid[=\-](\d+)\}'), re.compile(r'\{tmdb[=\-](\d+)\}'), ) +_RUST_PARSE_OPTIONS_CACHE_KEY = "_cache_key" def _empty_metainfo() -> dict: @@ -72,6 +74,28 @@ def _apply_range_total(metainfo: dict, begin_key: str, end_key: str, total_key: metainfo[total_key] = 1 +def _rust_parse_options_cache_key(options: dict) -> str: + """ + 生成 Rust Meta 配置缓存键,避免扩展层每次重新展开大配置。 + """ + digest = hashlib.blake2b(digest_size=16) + + def update(value) -> None: + digest.update(repr(value).encode("utf-8")) + digest.update(b"\0") + + streaming_platforms = options.get("streaming_platforms") or {} + update(tuple(options.get("custom_words") or [])) + update(tuple(options.get("media_exts") or [])) + update(options.get("release_groups") or "") + update(tuple(options.get("customization") or [])) + update(tuple(sorted( + (str(key), str(value)) + for key, value in streaming_platforms.items() + ))) + return digest.hexdigest() + + def _find_metainfo_python(title: str) -> Tuple[str, dict]: """ 使用 Python 解析标题中的显式媒体标签,作为 Rust 入口不可用时的兜底。 @@ -209,24 +233,20 @@ def _rust_default_parse_options() -> dict: from app.schemas.types import SystemConfigKey systemconfig = SystemConfigOper() - custom_release_groups = systemconfig.get(SystemConfigKey.CustomReleaseGroups) - if isinstance(custom_release_groups, list): - custom_release_groups = list(filter(None, custom_release_groups)) - release_matcher = ReleaseGroupsMatcher() - release_groups = release_matcher._ReleaseGroupsMatcher__release_groups - if custom_release_groups: - release_groups = f"{release_groups}|{'|'.join(custom_release_groups)}" + release_groups = ReleaseGroupsMatcher().get_release_groups() - customization = CustomizationMatcher._normalize_customization( + customization = CustomizationMatcher.normalize_customization( systemconfig.get(SystemConfigKey.Customization) ) - return { + options = { "custom_words": systemconfig.get(SystemConfigKey.CustomIdentifiers) or [], "media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT, "release_groups": release_groups, "customization": customization, - "streaming_platforms": StreamingPlatforms()._lookup_cache, + "streaming_platforms": StreamingPlatforms().get_lookup_cache(), } + options[_RUST_PARSE_OPTIONS_CACHE_KEY] = _rust_parse_options_cache_key(options) + return options @lru_cache(maxsize=256) @@ -236,6 +256,7 @@ def _rust_custom_parse_options(custom_words: Tuple[str, ...]) -> dict: """ options = dict(_rust_default_parse_options()) options["custom_words"] = list(custom_words) + options[_RUST_PARSE_OPTIONS_CACHE_KEY] = _rust_parse_options_cache_key(options) return options diff --git a/requirements.in b/requirements.in index 1e8ca9f4..655854bf 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ -moviepilot-rust~=0.1.14 +moviepilot-rust~=0.2.0 pydantic>=2.13.4,<3.0.0 pydantic-settings>=2.14.1,<3.0.0 SQLAlchemy~=2.0.50 diff --git a/scripts/benchmark_rss_rust.py b/scripts/benchmark_rss_rust.py index aa60d31b..de09ab2e 100644 --- a/scripts/benchmark_rss_rust.py +++ b/scripts/benchmark_rss_rust.py @@ -12,6 +12,7 @@ sys.path.insert(0, str(PROJECT_ROOT)) from app.helper import rss as rss_module from app.helper.rss import RssHelper from app.utils import rust_accel +from app.utils.http import RequestUtils class FakeRequestUtils: @@ -20,6 +21,7 @@ class FakeRequestUtils: """ xml_text = "" + get_decoded_xml_content = staticmethod(RequestUtils.get_decoded_xml_content) def __init__(self, **_kwargs): """ diff --git a/tests/test_rust_accel.py b/tests/test_rust_accel.py index 9669ec54..42b85062 100644 --- a/tests/test_rust_accel.py +++ b/tests/test_rust_accel.py @@ -12,6 +12,7 @@ from app.core import metainfo as metainfo_module from app.core.config import settings from app.core.meta.customization import CustomizationMatcher from app.core.meta.releasegroup import ReleaseGroupsMatcher +from app.core.meta.streamingplatform import StreamingPlatforms from app.db.systemconfig_oper import SystemConfigOper from app.modules.indexer.spider import SiteSpider from app.schemas.types import SystemConfigKey @@ -188,13 +189,8 @@ def _metainfo_options(custom_words=None): 构造 Rust MetaInfo 测试所需的配置,保持和生产入口一致。 """ systemconfig = SystemConfigOper() - custom_release_groups = systemconfig.get(SystemConfigKey.CustomReleaseGroups) - if isinstance(custom_release_groups, list): - custom_release_groups = list(filter(None, custom_release_groups)) - release_groups = ReleaseGroupsMatcher()._ReleaseGroupsMatcher__release_groups - if custom_release_groups: - release_groups = f"{release_groups}|{'|'.join(custom_release_groups)}" - customization = CustomizationMatcher._normalize_customization( + release_groups = ReleaseGroupsMatcher().get_release_groups() + customization = CustomizationMatcher.normalize_customization( systemconfig.get(SystemConfigKey.Customization) ) return { @@ -202,7 +198,7 @@ def _metainfo_options(custom_words=None): "media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT, "release_groups": release_groups, "customization": customization, - "streaming_platforms": metainfo_module._rust_parse_options()["streaming_platforms"], + "streaming_platforms": StreamingPlatforms().get_lookup_cache(), }