mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
refactor: strengthen architecture CI gates and runtime contracts
This commit is contained in:
+14
-15
@@ -5,7 +5,7 @@ import traceback
|
||||
from abc import ABCMeta
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any, Tuple, List, Set, Union, Dict
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from app.application.chain.context import ChainRuntimeContext, get_chain_runtime_context
|
||||
from app.application.chain.data import get_chain_data_ports
|
||||
@@ -18,24 +18,22 @@ from app.chain._recognition import RecognitionMixin
|
||||
from app.domain.context import Context, MediaInfo, SubtitleInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.exception import RateLimitExceededException
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.mediaserver import ExistMediaInfo
|
||||
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
|
||||
from app.schemas.message import IncomingMessage
|
||||
from app.schemas.mediaserver import WebhookEventInfo
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.context import MediaPerson
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.category import CategoryConfig
|
||||
from app.schemas.context import MediaPerson
|
||||
from app.schemas.exception import RateLimitExceededException
|
||||
from app.schemas.mediaserver import ExistMediaInfo, WebhookEventInfo
|
||||
from app.schemas.message import IncomingMessage
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.transfer import DownloaderFile, DownloaderTorrent, TransferInfo
|
||||
from app.schemas.types import (
|
||||
TorrentStatus,
|
||||
MediaType,
|
||||
MediaSourceSelection,
|
||||
MediaImageType,
|
||||
EventType,
|
||||
MediaImageType,
|
||||
MediaSourceSelection,
|
||||
MediaType,
|
||||
TorrentStatus,
|
||||
)
|
||||
from app.schemas.workflow import FileItem
|
||||
|
||||
|
||||
class ChainBase(RecognitionMixin, MessageProcessingMixin, NotificationMixin,
|
||||
@@ -57,6 +55,7 @@ class ChainBase(RecognitionMixin, MessageProcessingMixin, NotificationMixin,
|
||||
self.filecache = context.file_cache
|
||||
self.async_filecache = context.async_file_cache
|
||||
self.runtime_config = context.configuration
|
||||
self.stop_state = context.stop_state
|
||||
self.data_ports = context.data_ports or get_chain_data_ports()
|
||||
self.durable_event_writer = context.durable_event_writer
|
||||
self._module_dispatcher = context.module_dispatcher_factory(
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Chain mixin 对宿主能力的静态契约。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
|
||||
class ChainRuntimeMixinHost(Protocol):
|
||||
"""识别与消息 mixin 共用的 Chain 运行能力。"""
|
||||
|
||||
runtime_config: Any
|
||||
eventmanager: Any
|
||||
messageoper: Any
|
||||
messagequeue: Any
|
||||
|
||||
def run_module(self, method: str, **kwargs: Any) -> Any:
|
||||
"""调用同步模块能力。"""
|
||||
...
|
||||
|
||||
async def async_run_module(self, method: str, **kwargs: Any) -> Any:
|
||||
"""调用异步模块能力。"""
|
||||
...
|
||||
|
||||
|
||||
class MusicSubscribeMixinHost(Protocol):
|
||||
"""音乐订阅 mixin 对 SubscribeChain 的最小要求。"""
|
||||
|
||||
@classmethod
|
||||
def _music_media_chain(cls) -> Any:
|
||||
"""构造媒体识别链。"""
|
||||
...
|
||||
|
||||
def _music_download_chain(self) -> Any:
|
||||
"""构造下载链。"""
|
||||
...
|
||||
|
||||
def _music_search_chain(self) -> Any:
|
||||
"""构造搜索链。"""
|
||||
...
|
||||
|
||||
def _music_site_keywords(self, mediainfo: Any) -> list[str]:
|
||||
"""构造音乐站点搜索关键字。"""
|
||||
...
|
||||
|
||||
def _matches_music_resource(self, mediainfo: Any, *texts: Any) -> bool:
|
||||
"""判断站点资源文本是否匹配音乐目标。"""
|
||||
...
|
||||
|
||||
def get_sub_sites(self, subscribe: Any) -> list[int]: ...
|
||||
|
||||
def get_params(self, subscribe: Any) -> Any: ...
|
||||
|
||||
def filter_torrents(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
def check_and_handle_existing_media(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
def finish_subscribe_or_not(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
def get_subscribe_source_keyword(self, subscribe: Any) -> str: ...
|
||||
|
||||
|
||||
class InteractionMixinHost(Protocol):
|
||||
"""交互委托 mixin 对业务 Chain 的最小要求。"""
|
||||
|
||||
_interaction_handler_type: type
|
||||
|
||||
def _interaction_handler(self) -> Any:
|
||||
"""构造业务交互处理器。"""
|
||||
...
|
||||
|
||||
|
||||
class TransferMixinHost(ChainRuntimeMixinHost, Protocol):
|
||||
"""整理辅助 mixin 对 TransferChain 的最小要求。"""
|
||||
|
||||
@classmethod
|
||||
def _transfer_media_chain(cls) -> Any:
|
||||
"""构造媒体识别链。"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def _transfer_storage_chain(cls) -> Any:
|
||||
"""构造存储链。"""
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def _transfer_subscribe_chain(cls) -> Any:
|
||||
"""构造订阅链。"""
|
||||
...
|
||||
|
||||
def post_message(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
async def async_post_message(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
def obtain_images(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
def do_transfer(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
@@ -1,9 +1,11 @@
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
from app.chain._contracts import InteractionMixinHost
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
|
||||
class InteractionChainMixin:
|
||||
__mixin_host_protocol__ = InteractionMixinHost
|
||||
"""
|
||||
斜杠命令交互四件套委托:remote_list / parse_callback /
|
||||
handle_callback_interaction / handle_text_interaction。
|
||||
|
||||
@@ -9,20 +9,21 @@ from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from app.application.chain.data import get_chain_user_port
|
||||
from app.application.messaging.message import MessageTemplateHelper
|
||||
from app.application.notification import get_notification_switch
|
||||
from app.chain._contracts import ChainRuntimeMixinHost
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.foundation.identity import normalize_internal_user_id
|
||||
from app.application.messaging.message import MessageTemplateHelper
|
||||
from app.application.notification import get_notification_switch
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import MessageResponse
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.message import Message, MessageResponse
|
||||
from app.schemas.notification import ChannelCapability, ChannelCapabilityManager
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.types import EventType, NotificationChannel
|
||||
|
||||
|
||||
class MessageProcessingMixin:
|
||||
__mixin_host_protocol__ = ChainRuntimeMixinHost
|
||||
"""消息输入/处理状态机与通知派发规范化。"""
|
||||
|
||||
def start_message_processing_status(
|
||||
@@ -117,6 +118,7 @@ class MessageProcessingMixin:
|
||||
|
||||
|
||||
class NotificationMixin:
|
||||
__mixin_host_protocol__ = ChainRuntimeMixinHost
|
||||
"""通知消息发送域:渲染、隔离路由、队列发送与消息编辑。"""
|
||||
|
||||
def post_message(
|
||||
|
||||
+10
-3
@@ -1,16 +1,22 @@
|
||||
import copy
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.application.chain.data import get_chain_subscribe_port
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.subscription.contract import (
|
||||
build_subscribe_meta,
|
||||
subscribe_media_key,
|
||||
)
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.chain._contracts import MusicSubscribeMixinHost
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.application.chain.data import get_chain_subscribe_port
|
||||
from app.application.configuration import get_configured_system_config
|
||||
|
||||
# 旧测试与插件补丁入口;正式依赖通过宿主工厂逐步收敛。
|
||||
MediaChain = MediaChain
|
||||
DownloadChain = DownloadChain
|
||||
SearchChain = SearchChain
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo
|
||||
from app.domain.media import MUSIC_SUBSCRIBABLE_TYPES
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
@@ -35,6 +41,7 @@ def _normalize_music_total_tracks(value: Any) -> Optional[int]:
|
||||
|
||||
|
||||
class MusicSubscribeMixin:
|
||||
__mixin_host_protocol__ = MusicSubscribeMixinHost
|
||||
"""
|
||||
音乐订阅功能域 mixin:单曲/专辑目标识别、实体快照同步、候选筛选、
|
||||
择优下载与完成推进。
|
||||
|
||||
@@ -7,20 +7,22 @@
|
||||
import copy
|
||||
from typing import Optional
|
||||
|
||||
from app.runtime.execution import run_in_threadpool
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.chain._contracts import ChainRuntimeMixinHost
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.runtime.cache import fresh, async_fresh
|
||||
from app.runtime.cache import async_fresh, fresh
|
||||
from app.runtime.events import Event
|
||||
from app.runtime.execution import run_in_threadpool
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.media import normalize_media_source, resolve_media_identity
|
||||
from app.schemas.types import ChainEventType, MediaSource, MediaType, SystemConfigKey
|
||||
|
||||
|
||||
class RecognitionMixin:
|
||||
__mixin_host_protocol__ = ChainRuntimeMixinHost
|
||||
|
||||
def _can_use_media_recognize_share(
|
||||
self,
|
||||
|
||||
+22
-10
@@ -7,21 +7,12 @@ TransferChain 中。mixin 方法运行时经 MRO 解析,共享 TransferChain
|
||||
注意:这里的方法均已去掉私有名前缀双下划线(__ -> _),因为 Python 的名字
|
||||
改编按定义类生效,方法迁到 mixin 后 __ 前缀会改变改编目标,导致跨类调用失败。
|
||||
"""
|
||||
import asyncio
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from app.schemas.history import DownloadHistory as _SchemaDownloadHistory
|
||||
from app.schemas.transfer import EpisodeFormatRule as _SchemaEpisodeFormatRule
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.agent import build_manual_redo_prompt, get_running_agent_manager
|
||||
from app.application.formatting import EpisodeFormatRuleHelper
|
||||
from app.application.history import clear_transfer_failures, resolve_history
|
||||
from app.application.transfer import TransferTask, job_lock
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_history_port,
|
||||
get_chain_transfer_history_port,
|
||||
@@ -30,6 +21,18 @@ from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.application.formatting import EpisodeFormatRuleHelper
|
||||
from app.application.history import clear_transfer_failures, resolve_history
|
||||
from app.application.transfer import TransferTask, job_lock
|
||||
from app.chain._contracts import TransferMixinHost
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
|
||||
# 旧测试与插件补丁入口;正式依赖通过宿主工厂逐步收敛。
|
||||
MediaChain = MediaChain
|
||||
StorageChain = StorageChain
|
||||
SubscribeChain = SubscribeChain
|
||||
from app.domain.context import MediaInfo, MusicInfo
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
@@ -38,9 +41,10 @@ from app.foundation import text as text_tools
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.tasks import get_task_registry
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.history import DownloadHistory as _SchemaDownloadHistory
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.transfer import EpisodeFormatRule as _SchemaEpisodeFormatRule
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
@@ -51,6 +55,7 @@ from app.schemas.types import (
|
||||
ReplyMode,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.schemas.workflow import FileItem
|
||||
|
||||
DownloadFiles = Any
|
||||
DownloadHistory = Any
|
||||
@@ -100,6 +105,7 @@ SUBTITLE_STEM_TAGS = {
|
||||
|
||||
|
||||
class FileFilterMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
@staticmethod
|
||||
def _requires_automatic_category(task: TransferTask) -> bool:
|
||||
"""
|
||||
@@ -446,6 +452,7 @@ class FileFilterMixin:
|
||||
|
||||
|
||||
class ScrapeBatchMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
|
||||
def _send_metadata_scrape_event(
|
||||
self, task: TransferTask, transferinfo: TransferInfo
|
||||
@@ -647,6 +654,7 @@ class ScrapeBatchMixin:
|
||||
|
||||
|
||||
class EpisodeFormatMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
|
||||
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
||||
"""
|
||||
@@ -837,6 +845,7 @@ class EpisodeFormatMixin:
|
||||
|
||||
|
||||
class HistoryMatchMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
@staticmethod
|
||||
def _match_download_file(
|
||||
download_file: DownloadFiles,
|
||||
@@ -1023,6 +1032,7 @@ class HistoryMatchMixin:
|
||||
|
||||
|
||||
class FileKeyMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
@staticmethod
|
||||
def _get_file_key(fileitem: FileItem) -> Tuple[str, str]:
|
||||
"""
|
||||
@@ -1110,6 +1120,7 @@ class FileKeyMixin:
|
||||
|
||||
|
||||
class ManualHistoryMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
@staticmethod
|
||||
def _get_subscribe_custom_words(
|
||||
history_record: Optional[DownloadHistory],
|
||||
@@ -1234,6 +1245,7 @@ class ManualHistoryMixin:
|
||||
|
||||
|
||||
class FailedRetryMixin:
|
||||
__mixin_host_protocol__ = TransferMixinHost
|
||||
@staticmethod
|
||||
def build_failed_transfer_buttons(
|
||||
history_id: Optional[int],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.chain import ChainBase
|
||||
from app.domain.context import MediaInfo
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
|
||||
|
||||
class AniListChain(ChainBase):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.chain import ChainBase
|
||||
from app.domain.context import MediaInfo
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
|
||||
|
||||
class BangumiChain(ChainBase):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
|
||||
from app.schemas.dashboard import Statistic as _SchemaStatistic
|
||||
from app.chain import ChainBase
|
||||
|
||||
|
||||
class DashboardChain(ChainBase):
|
||||
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.chain import ChainBase
|
||||
from app.domain.context import MediaInfo, MusicAlbumInfo, MusicInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType
|
||||
|
||||
|
||||
|
||||
+44
-37
@@ -6,18 +6,25 @@ import re
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Set, Dict, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
|
||||
from urllib.parse import parse_qs, urlencode, urljoin, urlparse
|
||||
|
||||
from app.schemas.transfer import DownloaderTorrent as _SchemaDownloaderTorrent
|
||||
from app.schemas.system import TransferDirectoryConf as _SchemaTransferDirectoryConf
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_failure_port,
|
||||
get_chain_download_history_port,
|
||||
get_chain_media_server_port,
|
||||
)
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.application.directory import DirectoryHelper, validate_download_save_path
|
||||
from app.application.download import selection as _selection
|
||||
from app.application.download.tasks import DownloadTaskService
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.cache import FileCache
|
||||
from app.runtime.config import global_vars
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.domain import episode as episode_rules
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
@@ -25,36 +32,36 @@ from app.domain.context import (
|
||||
SubtitleInfo,
|
||||
TorrentInfo,
|
||||
)
|
||||
from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_failure_port,
|
||||
get_chain_download_history_port,
|
||||
get_chain_media_server_port,
|
||||
)
|
||||
from app.application.directory import DirectoryHelper, validate_download_save_path
|
||||
from app.application.download.tasks import DownloadTaskService
|
||||
from app.application.download import selection as _selection
|
||||
from app.runtime.thread import ThreadHelper
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.mediaserver import ExistMediaInfo
|
||||
from app.schemas.file import FileURI
|
||||
from app.schemas.mediaserver import NotExistMediaInfo
|
||||
from app.schemas.transfer import DownloaderTorrent
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.event import ResourceSelectionEventData
|
||||
from app.schemas.event import ResourceDownloadEventData
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, TorrentStatus, EventType, NotificationChannel, MessageType, ContentType, \
|
||||
ChainEventType
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.domain import episode as episode_rules
|
||||
from app.foundation import size as size_tools
|
||||
from app.foundation import text as text_tools
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.runtime.cache import FileCache
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.runtime.thread import ThreadHelper
|
||||
from app.schemas.event import ResourceDownloadEventData, ResourceSelectionEventData
|
||||
from app.schemas.file import FileURI
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.schemas.mediaserver import ExistMediaInfo, NotExistMediaInfo
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.system import TransferDirectoryConf as _SchemaTransferDirectoryConf
|
||||
from app.schemas.transfer import DownloaderTorrent
|
||||
from app.schemas.transfer import DownloaderTorrent as _SchemaDownloaderTorrent
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
ChainEventType,
|
||||
ContentType,
|
||||
EventType,
|
||||
MediaSource,
|
||||
MediaType,
|
||||
MessageType,
|
||||
NotificationChannel,
|
||||
TorrentStatus,
|
||||
)
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
@@ -1000,7 +1007,7 @@ class DownloadChain(ChainBase):
|
||||
MediaType.MUSIC: set(),
|
||||
}
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
media_type = context.media_info.type
|
||||
if media_type not in downloaded_keys:
|
||||
@@ -1657,7 +1664,7 @@ class DownloadChain(ChainBase):
|
||||
for need_mid, need_season in need_seasons.items():
|
||||
# 循环种子
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
# 媒体信息
|
||||
media = context.media_info
|
||||
@@ -1801,7 +1808,7 @@ class DownloadChain(ChainBase):
|
||||
need_episodes = list(range(start_episode, total_episode + 1))
|
||||
# 循环种子
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
# 媒体信息
|
||||
media = context.media_info
|
||||
@@ -1889,7 +1896,7 @@ class DownloadChain(ChainBase):
|
||||
continue
|
||||
# 循环种子
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
# 媒体信息
|
||||
media = context.media_info
|
||||
@@ -2040,7 +2047,7 @@ class DownloadChain(ChainBase):
|
||||
media_id=media_id,
|
||||
episode_group=mediainfo.episode_group)
|
||||
if not mediainfo:
|
||||
logger.error(f"媒体信息识别失败!")
|
||||
logger.error("媒体信息识别失败!")
|
||||
return False, {}
|
||||
if not mediainfo.seasons:
|
||||
logger.error(f"媒体信息中没有季集信息:{mediainfo.title_year}")
|
||||
|
||||
@@ -2,18 +2,18 @@ import math
|
||||
import re
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.application.chain.data import get_chain_user_port
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.messaging.media import (
|
||||
PendingMediaInteraction,
|
||||
media_interaction_manager,
|
||||
)
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.application.chain.data import get_chain_user_port
|
||||
from app.chain import ChainBase
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.domain import episode as episode_rules
|
||||
from app.domain import title as title_rules
|
||||
from app.domain.context import Context, MediaInfo
|
||||
@@ -22,9 +22,9 @@ from app.foundation import url as url_tools
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.download import DownloadDirectory
|
||||
from app.schemas.file import FileURI
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.schemas.mediaserver import NotExistMediaInfo
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.schemas.notification import ChannelCapabilityManager
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.types import MediaType, NotificationChannel
|
||||
|
||||
+16
-16
@@ -3,15 +3,15 @@ from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Any, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from app.runtime.execution import run_in_threadpool
|
||||
from app.schemas.event import MediaRecognizeConvertEventData as _SchemaMediaRecognizeConvertEventData
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.application.music.catalog import MusicCatalogService
|
||||
from app.chain import ChainBase
|
||||
from app.chain.acoustid import AcoustIdChain
|
||||
from app.chain.douban import DoubanChain
|
||||
from app.chain.musicbrainz import MusicBrainzChain, _MusicMetadataSourceChain
|
||||
from app.chain.theaudiodb import TheAudioDbChain
|
||||
from app.runtime.cache import async_fresh, fresh
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.domain import title as title_rules
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
@@ -19,13 +19,18 @@ from app.domain.context import (
|
||||
MusicArtistInfo,
|
||||
MusicInfo,
|
||||
)
|
||||
from app.runtime.events import Event
|
||||
from app.domain.media import is_music_media_source
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.application.music.catalog import MusicCatalogService
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.foundation.text import convert as zhconv_convert
|
||||
from app.runtime.cache import async_fresh, fresh
|
||||
from app.runtime.events import Event
|
||||
from app.runtime.execution import run_in_threadpool
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import MediaRecognizeConvertEventData as _SchemaMediaRecognizeConvertEventData
|
||||
from app.schemas.media import normalize_media_source, resolve_media_identity
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
ChainEventType,
|
||||
@@ -33,11 +38,6 @@ from app.schemas.types import (
|
||||
MediaSourceSelection,
|
||||
MediaType,
|
||||
)
|
||||
from app.domain.media import is_music_media_source
|
||||
from app.schemas.media import normalize_media_source, resolve_media_identity
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.foundation.text import convert as zhconv_convert
|
||||
from app.domain import title as title_rules
|
||||
|
||||
recognize_lock = Lock()
|
||||
|
||||
@@ -786,9 +786,9 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
year = None
|
||||
# 结果赋值
|
||||
if title == org_meta.name and year == org_meta.year:
|
||||
logger.info(f"辅助识别与原始识别结果一致,无需重新识别媒体信息")
|
||||
logger.info("辅助识别与原始识别结果一致,无需重新识别媒体信息")
|
||||
return None
|
||||
logger.info(f"辅助识别结果与原始识别结果不一致,重新匹配媒体信息 ...")
|
||||
logger.info("辅助识别结果与原始识别结果不一致,重新匹配媒体信息 ...")
|
||||
org_meta.name = title
|
||||
org_meta.year = year
|
||||
org_meta.begin_season = season_number
|
||||
@@ -1725,9 +1725,9 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
year = None
|
||||
# 结果赋值
|
||||
if title == org_meta.name and year == org_meta.year:
|
||||
logger.info(f"辅助识别与原始识别结果一致,无需重新识别媒体信息")
|
||||
logger.info("辅助识别与原始识别结果一致,无需重新识别媒体信息")
|
||||
return None
|
||||
logger.info(f"辅助识别结果与原始识别结果不一致,重新匹配媒体信息 ...")
|
||||
logger.info("辅助识别结果与原始识别结果不一致,重新匹配媒体信息 ...")
|
||||
org_meta.name = title
|
||||
org_meta.year = year
|
||||
org_meta.begin_season = season_number
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from typing import Callable, Dict, List, Union, Optional, Generator, Any, Tuple
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.config import global_vars
|
||||
from app.application.chain.data import get_chain_media_server_port
|
||||
from app.application.mediaserver import get_mediaserver_configs
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.mediaserver import MediaServerLibrary
|
||||
from app.schemas.mediaserver import MediaServerItem
|
||||
from app.schemas.mediaserver import MediaServerSeasonInfo
|
||||
from app.schemas.mediaserver import MediaServerPlayItem
|
||||
from app.schemas.types import MediaType
|
||||
from app.application.security.url import SecurityUtils
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.mediaserver import MediaServerItem, MediaServerLibrary, MediaServerPlayItem, MediaServerSeasonInfo
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
lock = threading.Lock()
|
||||
|
||||
@@ -355,7 +352,7 @@ class MediaServerChain(ChainBase):
|
||||
library_media_total = library_media_counts.get(str(library.id))
|
||||
library_count = 0
|
||||
for item in self.items(server=server_name, library_id=library.id):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
return total_count, global_media_finished
|
||||
if not item or not item.item_id:
|
||||
continue
|
||||
@@ -554,7 +551,7 @@ class MediaServerChain(ChainBase):
|
||||
global_media_total=global_media_total,
|
||||
global_media_finished=global_media_finished,
|
||||
)
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
return
|
||||
total_count += server_count
|
||||
logger.info(f"媒体服务器 {server_name} 数据同步完成,总同步数量:{total_count}")
|
||||
|
||||
+10
-11
@@ -7,22 +7,16 @@ from concurrent.futures import CancelledError as FutureCancelledError
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Dict, Union, List, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.application.agent import (
|
||||
get_running_agent_manager,
|
||||
is_audio_input_available,
|
||||
supports_image_input,
|
||||
transcribe_audio,
|
||||
)
|
||||
from app.chain import ChainBase
|
||||
from app.chain.site import SiteChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.chain.interaction import MediaInteractionChain as _MediaInteractionChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.tasks import get_task_registry
|
||||
from app.application.messaging.agent import agent_interaction_manager, parse_agent_choice_callback
|
||||
from app.application.messaging.interaction import InteractionContext, InteractionDispatch
|
||||
from app.application.messaging.media import media_interaction_manager
|
||||
@@ -32,12 +26,17 @@ from app.application.messaging.session import MessageSessionService
|
||||
from app.application.messaging.site import site_interaction_manager
|
||||
from app.application.messaging.skill import SkillInteractionHandler, skill_interaction_manager
|
||||
from app.application.messaging.subscribe import subscribe_interaction_manager
|
||||
from app.chain import ChainBase
|
||||
from app.chain.interaction import MediaInteractionChain as _MediaInteractionChain
|
||||
from app.chain.site import SiteChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import IncomingMessage
|
||||
from app.schemas.message import Message
|
||||
from app.runtime.tasks import get_task_registry
|
||||
from app.schemas.message import IncomingMessage, Message
|
||||
from app.schemas.notification import ChannelCapabilityManager
|
||||
from app.schemas.types import EventType, NotificationChannel
|
||||
from app.adapters.network.http import RequestUtils
|
||||
|
||||
|
||||
class MessageChain(ChainBase):
|
||||
|
||||
@@ -2,25 +2,25 @@ from typing import Callable, List, Optional
|
||||
|
||||
import pillow_avif # noqa: F401 # pylint: disable=unused-import # AVIF 注册副作用
|
||||
|
||||
from app.application.image import ImageHelper
|
||||
from app.chain import ChainBase
|
||||
from app.chain.bangumi import BangumiChain
|
||||
from app.chain.douban import DoubanChain
|
||||
from app.chain.listenbrainz import ListenBrainzChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.cache import cached, fresh
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import MusicInfo
|
||||
from app.application.image import ImageHelper
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.runtime.cache import cached, fresh
|
||||
from app.runtime.execution import log_execution_time
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.media import normalize_media_source
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
MediaSource,
|
||||
MediaType,
|
||||
)
|
||||
from app.runtime.execution import log_execution_time
|
||||
from app.schemas.media import normalize_media_source
|
||||
from app.foundation.singleton import Singleton
|
||||
|
||||
|
||||
class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
@@ -222,7 +222,7 @@ class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
# 这里避免区间内连续调用相同来源,因此遍历方案为每页遍历所有推荐来源,再进行页数遍历
|
||||
for page in range(1, self.cache_max_pages + 1):
|
||||
for method in recommend_methods:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
return
|
||||
if method in methods_finished:
|
||||
continue
|
||||
@@ -277,7 +277,7 @@ class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
|
||||
total_num = len(datas)
|
||||
for index, data in enumerate(datas, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
return
|
||||
poster_path = data.get("poster_path")
|
||||
if poster_path:
|
||||
|
||||
+15
-18
@@ -7,47 +7,44 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory
|
||||
from threading import Lock
|
||||
from typing import Any, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.chain import ChainBase
|
||||
from app.chain.lrclib import LrclibChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.runtime.cache import cached
|
||||
from app.domain.context import (
|
||||
MediaInfo,
|
||||
MusicAlbumInfo,
|
||||
MusicInfo,
|
||||
MusicLyrics,
|
||||
)
|
||||
from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.application.audio import AudioMetadataHelper
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.runtime.cache import cached
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.runtime.reload import ConfigReloadMixin
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
MUSIC_ENTITY_RECORDING,
|
||||
EventType,
|
||||
MediaSource,
|
||||
MediaType,
|
||||
ScrapingTarget,
|
||||
ScrapingMetadata,
|
||||
ScrapingPolicy,
|
||||
ScrapingTarget,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.runtime.reload import ConfigReloadMixin
|
||||
from app.foundation.singleton import Singleton
|
||||
|
||||
|
||||
|
||||
from app.chain.media import MediaChain
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
|
||||
scraping_lock = Lock()
|
||||
|
||||
|
||||
+22
-25
@@ -7,34 +7,34 @@ import time
|
||||
from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, as_completed, wait
|
||||
from contextlib import aclosing
|
||||
from datetime import datetime
|
||||
from typing import AsyncIterator, Any, Awaitable, Callable, Dict, Iterable, Tuple
|
||||
from typing import List, Optional
|
||||
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, Iterable, List, Optional, Tuple
|
||||
from unicodedata import normalize
|
||||
|
||||
from app.runtime.execution import run_in_threadpool, submit_with_context
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import Context
|
||||
from app.domain.context import MediaInfo, SubtitleInfo, TorrentInfo
|
||||
from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.domain.context import MusicInfo
|
||||
from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
||||
from app.runtime.tasks import get_task_registry
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from app.application.search.state import (
|
||||
SearchStateService,
|
||||
normalize_search_params,
|
||||
stringify_sites,
|
||||
)
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo, SubtitleInfo, TorrentInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.foundation import size as size_tools
|
||||
from app.foundation.text import convert as zhconv_convert
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.execution import run_in_threadpool, submit_with_context
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.progress import AsyncProgressHelper, ProgressHelper
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.runtime.tasks import get_task_registry
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.schemas.mediaserver import NotExistMediaInfo
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
@@ -44,9 +44,6 @@ from app.schemas.types import (
|
||||
ProgressKey,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.schemas.media import build_media_key, resolve_media_identity
|
||||
from app.foundation import size as size_tools
|
||||
from app.foundation.text import convert as zhconv_convert
|
||||
|
||||
|
||||
class SearchChain(ChainBase):
|
||||
@@ -1355,7 +1352,7 @@ class SearchChain(ChainBase):
|
||||
logger.info(f"开始匹配结果 标题:{mediainfo.title},原标题:{mediainfo.original_title},别名:{mediainfo.names}")
|
||||
progress.update(value=51, text=f'开始匹配,总 {_total} 个资源 ...')
|
||||
for torrent in torrents:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
_count += 1
|
||||
progress.update(value=(_count / _total) * 96,
|
||||
@@ -1709,7 +1706,7 @@ class SearchChain(ChainBase):
|
||||
**self._media_recognize_kwargs(mediainfo),
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.error(f'媒体信息识别失败!')
|
||||
logger.error('媒体信息识别失败!')
|
||||
return []
|
||||
|
||||
# 准备搜索参数
|
||||
@@ -1802,7 +1799,7 @@ class SearchChain(ChainBase):
|
||||
**self._media_recognize_kwargs(mediainfo),
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.error(f'媒体信息识别失败!')
|
||||
logger.error('媒体信息识别失败!')
|
||||
return []
|
||||
|
||||
# 准备搜索参数
|
||||
@@ -1885,7 +1882,7 @@ class SearchChain(ChainBase):
|
||||
**self._media_recognize_kwargs(mediainfo),
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.error(f'媒体信息识别失败!')
|
||||
logger.error('媒体信息识别失败!')
|
||||
yield {
|
||||
"type": "error",
|
||||
"success": False,
|
||||
@@ -2071,7 +2068,7 @@ class SearchChain(ChainBase):
|
||||
match_subtitles = []
|
||||
logger.info(f"开始匹配字幕 标题:{mediainfo.title},原标题:{mediainfo.original_title},别名:{mediainfo.names}")
|
||||
for subtitle in subtitles:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
subtitle_names = self.__build_subtitle_names(subtitle)
|
||||
if not subtitle_names:
|
||||
@@ -2384,7 +2381,7 @@ class SearchChain(ChainBase):
|
||||
|
||||
try:
|
||||
while pending_tasks:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
done_tasks, _ = wait(pending_tasks, return_when=FIRST_COMPLETED)
|
||||
for future in done_tasks:
|
||||
@@ -2460,7 +2457,7 @@ class SearchChain(ChainBase):
|
||||
|
||||
try:
|
||||
while pending_tasks:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
done_tasks, _ = await asyncio.wait(
|
||||
pending_tasks,
|
||||
|
||||
+23
-23
@@ -1,35 +1,35 @@
|
||||
import base64
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Optional, Tuple, Union, Dict
|
||||
from typing import Any, Callable, Dict, Optional, Tuple, Union
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from lxml import etree
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.chain._interaction import InteractionChainMixin
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.application.chain.data import get_chain_site_port
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.adapters.external.cookiecloud import CookieCloudHelper
|
||||
from app.adapters.network.browser import PlaywrightHelper
|
||||
from app.adapters.network.cloudflare import under_challenge
|
||||
from app.application.security.cookie import CookieHelper
|
||||
from app.adapters.external.cookiecloud import CookieCloudHelper
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.application.chain.data import get_chain_site_port
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.messaging.site import SiteInteractionHandler
|
||||
from app.application.rss import RssHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.notification import NotificationChannel
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.site import SiteUserData
|
||||
from app.schemas.types import EventType, MessageType
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.domain.site import SiteUtils
|
||||
from app.application.security.cookie import CookieHelper
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from app.chain import ChainBase
|
||||
from app.chain._interaction import InteractionChainMixin
|
||||
from app.domain import site as site_rules
|
||||
from app.domain.site import SiteUtils
|
||||
from app.foundation import size as size_tools
|
||||
from app.foundation import url as url_tools
|
||||
from app.foundation.dom import DomUtils
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.notification import NotificationChannel
|
||||
from app.schemas.site import SiteUserData
|
||||
from app.schemas.types import EventType, MessageType
|
||||
|
||||
Site = Any
|
||||
|
||||
@@ -83,7 +83,7 @@ class SiteChain(InteractionChainMixin, ChainBase):
|
||||
re.search(r"(贵宾|VIP?)", userdata.user_level or "", re.IGNORECASE)):
|
||||
self.post_message(Message(
|
||||
mtype=MessageType.SiteMessage,
|
||||
title=f"【站点分享率低预警】",
|
||||
title="【站点分享率低预警】",
|
||||
text=f"站点 {site.get('name')} 分享率 {userdata.ratio},请注意!"
|
||||
))
|
||||
return userdata
|
||||
@@ -140,7 +140,7 @@ class SiteChain(InteractionChainMixin, ChainBase):
|
||||
data={"total": total_num, "finished": 0},
|
||||
)
|
||||
for index, site in enumerate(sites, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
return None
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
@@ -429,7 +429,7 @@ class SiteChain(InteractionChainMixin, ChainBase):
|
||||
update_count = add_count = fail_count = 0
|
||||
for index, (domain, cookie) in enumerate(cookies.items(), start=1):
|
||||
# 检查系统是否停止
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
logger.info("系统正在停止,中断CookieCloud同步")
|
||||
return False, "系统正在停止,同步被中断"
|
||||
if progress_callback:
|
||||
@@ -708,8 +708,8 @@ class SiteChain(InteractionChainMixin, ChainBase):
|
||||
timeout=timeout)
|
||||
if not public and not SiteUtils.is_logged_in(page_source):
|
||||
if under_challenge(page_source):
|
||||
return False, f"无法通过Cloudflare!"
|
||||
return False, f"仿真登录失败,Cookie已失效!"
|
||||
return False, "无法通过Cloudflare!"
|
||||
return False, "仿真登录失败,Cookie已失效!"
|
||||
else:
|
||||
res = RequestUtils(cookies=site_cookie,
|
||||
ua=ua,
|
||||
@@ -731,7 +731,7 @@ class SiteChain(InteractionChainMixin, ChainBase):
|
||||
elif res is not None:
|
||||
return False, f"错误:{res.status_code} {res.reason}!"
|
||||
else:
|
||||
return False, f"无法打开网站!"
|
||||
return False, "无法打开网站!"
|
||||
return True, "连接成功"
|
||||
|
||||
def _interaction_handler(self) -> "SiteInteractionHandler":
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, List, Dict
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
from app.chain import ChainBase
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.workflow import FileItem as _SchemaFileItem
|
||||
|
||||
|
||||
class StorageChain(ChainBase):
|
||||
|
||||
+87
-55
@@ -5,35 +5,9 @@ import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from app.schemas.mediaserver import NotExistMediaInfo as _SchemaNotExistMediaInfo
|
||||
from app.schemas.message import Message as _SchemaMessage
|
||||
from app.schemas.subscribe import SubscrbieInfo as _SchemaSubscrbieInfo
|
||||
from app.schemas.subscribe import SubscribeDownloadFileInfo as _SchemaSubscribeDownloadFileInfo
|
||||
from app.schemas.subscribe import SubscribeEpisodeInfo as _SchemaSubscribeEpisodeInfo
|
||||
from app.schemas.subscribe import SubscribeLibraryFileInfo as _SchemaSubscribeLibraryFileInfo
|
||||
from app.schemas.workflow import Subscribe as _SchemaSubscribe
|
||||
from app.chain import ChainBase
|
||||
from app.chain._interaction import InteractionChainMixin
|
||||
from app.chain._music import MusicSubscribeMixin
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.chain.torrents import TorrentsChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
TorrentInfo,
|
||||
)
|
||||
from app.runtime.events import eventmanager, Event
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.meta.words import WordsMatcher
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_history_port,
|
||||
get_chain_site_port,
|
||||
@@ -43,30 +17,66 @@ from app.application.configuration import (
|
||||
get_chain_runtime_config_snapshot,
|
||||
get_configured_system_config,
|
||||
)
|
||||
from app.application.messaging.subscribe import SubscribeInteractionHandler
|
||||
from app.application.messaging.message import MessageTemplateHelper
|
||||
from app.application.mediaserver import MediaServerHelper
|
||||
from app.application.subscription.write import add_subscribe, async_add_subscribe
|
||||
from app.application.subscription.complete import get_subscription_completion_scope
|
||||
from app.application.messaging.message import MessageTemplateHelper
|
||||
from app.application.messaging.subscribe import SubscribeInteractionHandler
|
||||
from app.application.subscription import priority as _priority
|
||||
from app.application.subscription.complete import get_subscription_completion_scope
|
||||
from app.application.subscription.contract import (
|
||||
build_subscribe_meta as _build_subscribe_meta,
|
||||
)
|
||||
from app.application.subscription.contract import (
|
||||
subscribe_media_key,
|
||||
subscribe_media_keys,
|
||||
)
|
||||
from app.application.subscription.delete import (
|
||||
SubscribeDeletionActor,
|
||||
get_sync_delete_subscribe_scope,
|
||||
)
|
||||
from app.application.subscription.contract import (
|
||||
build_subscribe_meta as _build_subscribe_meta,
|
||||
subscribe_media_key,
|
||||
subscribe_media_keys,
|
||||
)
|
||||
from app.application.subscription.query import SubscriptionQueryService
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.application.subscription.write import add_subscribe, async_add_subscribe
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.chain import ChainBase
|
||||
from app.chain._interaction import InteractionChainMixin
|
||||
from app.chain._music import MusicSubscribeMixin
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.chain.torrents import TorrentsChain
|
||||
from app.domain.context import (
|
||||
Context,
|
||||
MediaInfo,
|
||||
TorrentInfo,
|
||||
)
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.meta.words import WordsMatcher
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import SubscribeEpisodesRefreshEventData
|
||||
from app.schemas.event import SubscribeCompletionCheckEventData
|
||||
from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType, SystemConfigKey, NotificationChannel, MessageType, EventType, ChainEventType, \
|
||||
ContentType
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.event import SubscribeCompletionCheckEventData, SubscribeEpisodesRefreshEventData
|
||||
from app.schemas.media import normalize_media_source, resolve_media_identity
|
||||
from app.schemas.mediaserver import NotExistMediaInfo as _SchemaNotExistMediaInfo
|
||||
from app.schemas.message import Message as _SchemaMessage
|
||||
from app.schemas.subscribe import SubscrbieInfo as _SchemaSubscrbieInfo
|
||||
from app.schemas.subscribe import SubscribeDownloadFileInfo as _SchemaSubscribeDownloadFileInfo
|
||||
from app.schemas.subscribe import SubscribeEpisodeInfo as _SchemaSubscribeEpisodeInfo
|
||||
from app.schemas.subscribe import SubscribeLibraryFileInfo as _SchemaSubscribeLibraryFileInfo
|
||||
from app.schemas.types import (
|
||||
MUSIC_ENTITY_ALBUM,
|
||||
ChainEventType,
|
||||
ContentType,
|
||||
EventType,
|
||||
MediaSource,
|
||||
MediaType,
|
||||
MessageType,
|
||||
NotificationChannel,
|
||||
SystemConfigKey,
|
||||
)
|
||||
from app.schemas.workflow import Subscribe as _SchemaSubscribe
|
||||
|
||||
if hasattr(_SchemaSubscribe, "model_fields"):
|
||||
Subscribe = _SchemaSubscribe
|
||||
@@ -191,6 +201,28 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
电影下载优先级 writer 单独维护。
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _music_media_chain(cls):
|
||||
"""为音乐 mixin 提供可替换的媒体识别构造点。"""
|
||||
from app.chain import _music as _music_mixin
|
||||
return (_music_mixin.MediaChain or MediaChain)()
|
||||
|
||||
def _music_download_chain(self):
|
||||
"""为音乐 mixin 提供可替换的下载构造点。"""
|
||||
from app.chain import _music as _music_mixin
|
||||
return (_music_mixin.DownloadChain or DownloadChain)()
|
||||
|
||||
def _music_search_chain(self):
|
||||
"""为音乐 mixin 提供可替换的搜索构造点。"""
|
||||
from app.chain import _music as _music_mixin
|
||||
return (_music_mixin.SearchChain or SearchChain)()
|
||||
|
||||
def _music_site_keywords(self, mediainfo):
|
||||
return SearchChain.music_site_keywords(mediainfo)
|
||||
|
||||
def _matches_music_resource(self, mediainfo, *texts):
|
||||
return SearchChain.matches_music_resource(mediainfo, *texts)
|
||||
|
||||
# 交互处理器类注入,供 InteractionChainMixin 的 parse_callback 委托
|
||||
_interaction_handler_type = SubscribeInteractionHandler
|
||||
|
||||
@@ -1179,7 +1211,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
try:
|
||||
# 遍历订阅
|
||||
for index, subscribe in enumerate(subscribes, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
processed_subscribes.append(subscribe)
|
||||
if progress_callback:
|
||||
@@ -1275,7 +1307,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
matched_contexts = []
|
||||
try:
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
torrent_meta = context.meta_info
|
||||
torrent_info = context.torrent_info
|
||||
@@ -1592,11 +1624,11 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
"""预识别待匹配资源,并保留原上下文供后续订阅复用。"""
|
||||
processed_torrents: Dict[str, List[Context]] = {}
|
||||
for domain, contexts in torrents.items():
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
processed_torrents[domain] = []
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if context.torrent_info and getattr(context.torrent_info, "category", None) in (
|
||||
MediaType.MUSIC,
|
||||
@@ -1699,7 +1731,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
)
|
||||
try:
|
||||
for index, subscribe in enumerate(subscribes, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
@@ -1772,13 +1804,13 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
systemconfig = _system_config()
|
||||
wordsmatcher = WordsMatcher()
|
||||
for domain, contexts in processed_torrents.items():
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if domains and domain not in domains:
|
||||
continue
|
||||
logger.debug(f'开始匹配站点:{domain},共缓存了 {len(contexts)} 个种子...')
|
||||
for context in contexts:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
# 提取信息
|
||||
_context = copy.copy(context)
|
||||
@@ -2046,7 +2078,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
)
|
||||
# 遍历订阅
|
||||
for index, subscribe in enumerate(subscribes, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
logger.info(f'开始更新订阅元数据:{subscribe.name} ...')
|
||||
if progress_callback:
|
||||
@@ -2174,7 +2206,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
if progress_callback:
|
||||
progress_callback(value=100, text="未配置 Follow 订阅用户,跳过刷新")
|
||||
return
|
||||
logger.info(f'开始刷新follow用户分享订阅 ...')
|
||||
logger.info('开始刷新follow用户分享订阅 ...')
|
||||
success_count = 0
|
||||
subscribeoper = get_chain_subscribe_port()
|
||||
share_subscribes = MoviePilotServerHelper.get_subscribe_shares() or []
|
||||
@@ -2186,7 +2218,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
data={"total": total_num, "finished": 0},
|
||||
)
|
||||
for index, share_sub in enumerate(share_subscribes, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
@@ -2276,7 +2308,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
|
||||
:param progress_callback: 定时服务进度更新回调
|
||||
"""
|
||||
logger.info(f'开始预缓存订阅日历 ...')
|
||||
logger.info('开始预缓存订阅日历 ...')
|
||||
subscribes = await get_chain_subscribe_port().async_list()
|
||||
total_num = len(subscribes)
|
||||
if progress_callback:
|
||||
@@ -2286,7 +2318,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
data={"total": total_num, "finished": 0},
|
||||
)
|
||||
for index, subscribe in enumerate(subscribes, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
@@ -2336,7 +2368,7 @@ class SubscribeChain(MusicSubscribeMixin, InteractionChainMixin, ChainBase):
|
||||
text=f"订阅日历({index}/{total_num})预缓存完成",
|
||||
data={"total": total_num, "finished": index},
|
||||
)
|
||||
logger.info(f'订阅日历预缓存完成')
|
||||
logger.info('订阅日历预缓存完成')
|
||||
if progress_callback:
|
||||
progress_callback(value=100, text="订阅日历预缓存完成")
|
||||
|
||||
|
||||
+8
-8
@@ -4,17 +4,17 @@ import re
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.runtime.state import SystemHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.notification import NotificationChannel
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.application.configuration import get_chain_runtime_config_snapshot
|
||||
from app.chain import ChainBase
|
||||
from app.runtime import version as runtime_version
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.state import SystemHelper
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.notification import NotificationChannel
|
||||
|
||||
|
||||
class SystemChain(ChainBase):
|
||||
@@ -33,7 +33,7 @@ class SystemChain(ChainBase):
|
||||
self.post_message(Message(
|
||||
channel=channel,
|
||||
source=source,
|
||||
title=f"缓存清理完成!",
|
||||
title="缓存清理完成!",
|
||||
userid=userid,
|
||||
save_history=False))
|
||||
|
||||
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
import random
|
||||
from typing import Optional, List
|
||||
from typing import List, Optional
|
||||
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.schemas.tmdb import TmdbSeason as _SchemaTmdbSeason
|
||||
from app.schemas.tmdb import TmdbEpisode as _SchemaTmdbEpisode
|
||||
from app.chain import ChainBase
|
||||
from app.domain.context import MediaInfo
|
||||
from app.schemas.context import MediaPerson as _SchemaMediaPerson
|
||||
from app.schemas.tmdb import TmdbEpisode as _SchemaTmdbEpisode
|
||||
from app.schemas.tmdb import TmdbSeason as _SchemaTmdbSeason
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
|
||||
+20
-22
@@ -1,27 +1,25 @@
|
||||
import copy
|
||||
import re
|
||||
import traceback
|
||||
from typing import Callable, Dict, List, Union, Optional
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import TorrentInfo, Context, MediaInfo
|
||||
from app.domain.context import MusicInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.application.chain.data import get_chain_site_port
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.rss import RssHelper
|
||||
from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module
|
||||
from app.application.torrent import TorrentHelper
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.types import SystemConfigKey, NotificationChannel, MessageType, MediaType
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.domain import site as site_rules
|
||||
from app.domain.context import Context, MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.foundation import text as text_tools
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.types import MediaType, MessageType, NotificationChannel, SystemConfigKey
|
||||
|
||||
|
||||
class TorrentsChain(ChainBase):
|
||||
@@ -50,13 +48,13 @@ class TorrentsChain(ChainBase):
|
||||
"""
|
||||
self.post_message(Message(
|
||||
channel=channel,
|
||||
title=f"开始刷新种子 ...",
|
||||
title="开始刷新种子 ...",
|
||||
userid=userid,
|
||||
save_history=False))
|
||||
self.refresh()
|
||||
self.post_message(Message(
|
||||
channel=channel,
|
||||
title=f"种子刷新完成!",
|
||||
title="种子刷新完成!",
|
||||
userid=userid,
|
||||
save_history=False))
|
||||
|
||||
@@ -362,23 +360,23 @@ class TorrentsChain(ChainBase):
|
||||
"""
|
||||
清理种子缓存数据,包含音乐独立缓存
|
||||
"""
|
||||
logger.info(f'开始清理种子缓存数据 ...')
|
||||
logger.info('开始清理种子缓存数据 ...')
|
||||
self.remove_cache(self._spider_file)
|
||||
self.remove_cache(self._rss_file)
|
||||
self.remove_cache(self._music_spider_file)
|
||||
self.remove_cache(self._music_rss_file)
|
||||
logger.info(f'种子缓存数据清理完成')
|
||||
logger.info('种子缓存数据清理完成')
|
||||
|
||||
async def async_clear_torrents(self):
|
||||
"""
|
||||
异步清理种子缓存数据,包含音乐独立缓存
|
||||
"""
|
||||
logger.info(f'开始异步清理种子缓存数据 ...')
|
||||
logger.info('开始异步清理种子缓存数据 ...')
|
||||
await self.async_remove_cache(self._spider_file)
|
||||
await self.async_remove_cache(self._rss_file)
|
||||
await self.async_remove_cache(self._music_spider_file)
|
||||
await self.async_remove_cache(self._music_rss_file)
|
||||
logger.info(f'异步种子缓存数据清理完成')
|
||||
logger.info('异步种子缓存数据清理完成')
|
||||
|
||||
def browse(self, domain: str, keyword: Optional[str] = None, cat: Optional[str] = None,
|
||||
page: Optional[int] = 0,
|
||||
@@ -585,7 +583,7 @@ class TorrentsChain(ChainBase):
|
||||
return domain
|
||||
logger.info(f'{indexer.get("name")} 有 {len(torrents) + len(music_torrents)} 个新种子')
|
||||
for torrent in torrents + music_torrents:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if not torrent.enclosure:
|
||||
logger.warning(f"缺少种子链接,忽略处理: {torrent.title}")
|
||||
@@ -692,7 +690,7 @@ class TorrentsChain(ChainBase):
|
||||
)
|
||||
# 遍历站点缓存资源
|
||||
for index, indexer in enumerate(indexers, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
|
||||
+79
-51
@@ -10,55 +10,38 @@ from concurrent.futures import CancelledError as FutureCancelledError
|
||||
from concurrent.futures import Future
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple, Union, Dict, Callable, Any
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.runtime.config import global_vars
|
||||
from app.domain.context import MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfoPath
|
||||
from app.application.chain.data import (
|
||||
get_chain_download_history_port,
|
||||
get_chain_transfer_history_port,
|
||||
get_chain_transfer_pending_port,
|
||||
)
|
||||
from app.chain import ChainBase
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.domain.context import MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfoPath
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
|
||||
DownloadHistory = Any
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.application.directory import DirectoryHelper
|
||||
from app.application.formatting import FormatParser
|
||||
from app.runtime.progress import ProgressHelper
|
||||
from app.application.history import (add_transfer_fail, add_transfer_success,
|
||||
clear_transfer_failures, describe_history_gate,
|
||||
evaluate_history_gate, is_skip_action,
|
||||
record_transfer_failure)
|
||||
from app.application.outbox import TRANSFER_COMPLETED_TOPIC, TRANSFER_FAILED_TOPIC
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import StorageOperSelectionEventData
|
||||
from app.schemas.transfer import TransferInfo
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.transfer import EpisodeFormat
|
||||
from app.schemas.workflow import FileItem
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.transfer import TransferJob
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.exception import OperationInterrupted
|
||||
from app.schemas.types import (
|
||||
TorrentStatus,
|
||||
EventType,
|
||||
MediaType,
|
||||
ProgressKey,
|
||||
MessageType,
|
||||
NotificationChannel,
|
||||
SystemConfigKey,
|
||||
ChainEventType,
|
||||
ContentType,
|
||||
MediaSource,
|
||||
from app.application.history import (
|
||||
add_transfer_fail,
|
||||
add_transfer_success,
|
||||
clear_transfer_failures,
|
||||
describe_history_gate,
|
||||
evaluate_history_gate,
|
||||
is_skip_action,
|
||||
record_transfer_failure,
|
||||
)
|
||||
from app.runtime.reload import ConfigReloadMixin
|
||||
from app.application.outbox import TRANSFER_COMPLETED_TOPIC, TRANSFER_FAILED_TOPIC
|
||||
from app.application.transfer import (
|
||||
FailedRetryScheduler,
|
||||
JobManager,
|
||||
@@ -70,13 +53,40 @@ from app.application.transfer import (
|
||||
build_transfer_failure_group_key,
|
||||
job_lock,
|
||||
)
|
||||
from app.chain._transfer import (EpisodeFormatMixin, FailedRetryMixin,
|
||||
FileFilterMixin, FileKeyMixin,
|
||||
HistoryMatchMixin, ManualHistoryMixin,
|
||||
ScrapeBatchMixin)
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.chain._transfer import (
|
||||
EpisodeFormatMixin,
|
||||
FailedRetryMixin,
|
||||
FileFilterMixin,
|
||||
FileKeyMixin,
|
||||
HistoryMatchMixin,
|
||||
ManualHistoryMixin,
|
||||
ScrapeBatchMixin,
|
||||
)
|
||||
from app.domain import episode as episode_rules
|
||||
from app.foundation.singleton import Singleton
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.progress import ProgressHelper
|
||||
from app.runtime.reload import ConfigReloadMixin
|
||||
from app.schemas.event import StorageOperSelectionEventData
|
||||
from app.schemas.exception import OperationInterrupted
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.system import TransferDirectoryConf
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
from app.schemas.transfer import EpisodeFormat, TransferInfo, TransferJob
|
||||
from app.schemas.types import (
|
||||
ChainEventType,
|
||||
ContentType,
|
||||
EventType,
|
||||
MediaSource,
|
||||
MediaType,
|
||||
MessageType,
|
||||
NotificationChannel,
|
||||
ProgressKey,
|
||||
SystemConfigKey,
|
||||
TorrentStatus,
|
||||
)
|
||||
from app.schemas.workflow import FileItem
|
||||
|
||||
# 下载器锁
|
||||
downloader_lock = threading.Lock()
|
||||
@@ -90,6 +100,24 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
文件整理处理链
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _transfer_media_chain(cls):
|
||||
"""为整理 mixin 提供可替换的媒体识别构造点。"""
|
||||
from app.chain import _transfer as _transfer_mixin
|
||||
return (_transfer_mixin.MediaChain or MediaChain)()
|
||||
|
||||
@classmethod
|
||||
def _transfer_storage_chain(cls):
|
||||
"""为整理 mixin 提供可替换的存储构造点。"""
|
||||
from app.chain import _transfer as _transfer_mixin
|
||||
return (_transfer_mixin.StorageChain or StorageChain)()
|
||||
|
||||
@classmethod
|
||||
def _transfer_subscribe_chain(cls):
|
||||
"""为整理 mixin 提供可替换的订阅构造点。"""
|
||||
from app.chain.subscribe import SubscribeChain as _SubscribeChain
|
||||
return _SubscribeChain()
|
||||
|
||||
# worker 在构造期启动;若中途失败,单例仍需先发布给 lifespan 清理入口。
|
||||
_retain_failed_singleton = True
|
||||
|
||||
@@ -1148,7 +1176,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
|
||||
:param stop_event: 当前 worker 代专属停止信号,热更新后不会被重新清除
|
||||
"""
|
||||
while not global_vars.is_system_stopped and not stop_event.is_set():
|
||||
while not runtime_stop_state.is_system_stopped and not stop_event.is_set():
|
||||
try:
|
||||
item: TransferQueue = self._queue.get(
|
||||
block=True, timeout=self._transfer_interval
|
||||
@@ -1156,10 +1184,10 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
if item is self._QUEUE_STOP_SENTINEL:
|
||||
self._queue.task_done()
|
||||
self.__settle_transfer_progress_if_idle()
|
||||
if stop_event.is_set() or global_vars.is_system_stopped:
|
||||
if stop_event.is_set() or runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
continue
|
||||
if stop_event.is_set() or global_vars.is_system_stopped:
|
||||
if stop_event.is_set() or runtime_stop_state.is_system_stopped:
|
||||
# 关闭信号与 queue.get 竞态时,把尚未处理的任务放回队列;其
|
||||
# TransferPending 登记保持不变,供同进程重启 worker 或下次启动回放。
|
||||
self._queue.put(item)
|
||||
@@ -1606,7 +1634,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
try:
|
||||
total_num = len(torrents)
|
||||
for index, torrent in enumerate(torrents, start=1):
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if progress_callback:
|
||||
torrent_name = (
|
||||
@@ -1732,7 +1760,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
若 `predicate` 为 `None`,则默认保留所有项
|
||||
:param verify_file_exists: 验证目录或文件是否存在,默认值为 `True`
|
||||
"""
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
raise OperationInterrupted()
|
||||
|
||||
storagechain = StorageChain()
|
||||
@@ -2536,7 +2564,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
skipped_torrents = set()
|
||||
try:
|
||||
for file_item, bluray_dir in file_items:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
raise OperationInterrupted()
|
||||
if continue_callback and not continue_callback():
|
||||
raise OperationInterrupted()
|
||||
@@ -2762,7 +2790,7 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo
|
||||
progress.update(value=0, text=__process_msg)
|
||||
try:
|
||||
for transfer_task in transfer_tasks:
|
||||
if global_vars.is_system_stopped:
|
||||
if runtime_stop_state.is_system_stopped:
|
||||
break
|
||||
if continue_callback and not continue_callback():
|
||||
break
|
||||
|
||||
+5
-6
@@ -2,14 +2,13 @@ import secrets
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal, Optional, Tuple, Union
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.application.security.token import get_password_hash, verify_password
|
||||
from app.application.chain.data import get_chain_user_port
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import AuthCredentials
|
||||
from app.schemas.event import AuthInterceptCredentials
|
||||
from app.schemas.types import ChainEventType
|
||||
from app.application.security.otp import OtpUtils
|
||||
from app.application.security.token import get_password_hash, verify_password
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.event import AuthCredentials, AuthInterceptCredentials
|
||||
from app.schemas.types import ChainEventType
|
||||
|
||||
PASSWORD_INVALID_CREDENTIALS_MESSAGE = "用户名、密码或验证码错误"
|
||||
User = Any
|
||||
|
||||
+8
-12
@@ -13,19 +13,15 @@ from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.config import global_vars
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.application.workflow import get_workflow_manager
|
||||
from app.application.chain.data import get_chain_workflow_port
|
||||
from app.application.workflow import get_workflow_manager
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.events import Event, eventmanager
|
||||
from app.runtime.execution import OwnedThreadPoolExecutor
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.workflow import ActionContext
|
||||
from app.schemas.workflow import ActionFlow
|
||||
from app.schemas.workflow import Action
|
||||
from app.schemas.workflow import ActionExecution
|
||||
from app.schemas.workflow import ActionResult
|
||||
from app.runtime.stop import runtime_stop_state
|
||||
from app.schemas.types import EventType
|
||||
from app.schemas.workflow import Action, ActionContext, ActionExecution, ActionFlow, ActionResult
|
||||
|
||||
ARTIFACT_FIELDS = {"torrents", "medias", "fileitems", "downloads", "sites", "subscribes"}
|
||||
DEFAULT_WORKFLOW_MAX_WORKERS = 4
|
||||
@@ -113,7 +109,7 @@ class WorkflowCancelToken:
|
||||
"""
|
||||
return bool(
|
||||
(self.stop_event and self.stop_event.is_set())
|
||||
or global_vars.is_workflow_stopped(self.workflow_id)
|
||||
or runtime_stop_state.is_workflow_stopped(self.workflow_id)
|
||||
)
|
||||
|
||||
|
||||
@@ -236,7 +232,7 @@ class WorkflowExecutor:
|
||||
self._registered_execution = callable(register)
|
||||
self._admission_state = "admitted"
|
||||
# 只有获得执行准入后才能清除历史单工作流停止标记。
|
||||
global_vars.workflow_resume(self.workflow.id)
|
||||
runtime_stop_state.resume_workflow(self.workflow.id)
|
||||
return True
|
||||
|
||||
def request_stop(self) -> None:
|
||||
@@ -285,7 +281,7 @@ class WorkflowExecutor:
|
||||
"""判断本次执行或全局工作流是否已收到停止请求。"""
|
||||
return bool(
|
||||
self._stop_event.is_set()
|
||||
or global_vars.is_workflow_stopped(self.workflow.id)
|
||||
or runtime_stop_state.is_workflow_stopped(self.workflow.id)
|
||||
)
|
||||
|
||||
def get_workflow_max_workers(self) -> int:
|
||||
|
||||
Reference in New Issue
Block a user